From 0594d7650bfdb60b3ff3ed5fd5cee6c1e98b7361 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Mon, 4 Jan 2016 22:46:50 -0700 Subject: [PATCH 01/59] Fix the event propagation when there are nested draggable items. For instance, ionSlideBox inside ionSideMenu. If developer still wants the propagation, simply pass stopPropagation=false to ionSlideBox. --- components/ionSlideBox/ionSlideBox.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/ionSlideBox/ionSlideBox.js b/components/ionSlideBox/ionSlideBox.js index 7b7ca82..7768da5 100644 --- a/components/ionSlideBox/ionSlideBox.js +++ b/components/ionSlideBox/ionSlideBox.js @@ -5,6 +5,7 @@ Template.ionSlideBox.created = function () { this.slideInterval = this.data.slideInterval || 4000; this.showPager = typeof this.data.showPager != 'undefined' ? this.data.showPager : true; this.initialSlide = this.data.initialSlide || Session.get('ion-slide-initial-slide') || 0; + this.preventPropagation = this.data.preventPropagation || true; }; Template.ionSlideBox.rendered = function () { @@ -29,3 +30,11 @@ Template.ionSlideBox.destroyed = function () { var $slideBox = this.$('.ion-slide-box'); if ($slideBox.hasClass('slick-initialized')) $slideBox.slick('unslick'); }; + +Template.ionSlideBox.events({ + 'touchmove .ion-slide-box': function(event, template) { + if (template.preventPropagation) { + event.stopPropagation(); + } + } +}); \ No newline at end of file From e80e6af0d29bf2c5d29e805bbd6d1097d10b7d58 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Sat, 9 Jan 2016 06:51:49 -0700 Subject: [PATCH 02/59] ionRadio updated to match 1.1.1 --- components/ionRadio/ionRadio.html | 8 +++++--- package.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/ionRadio/ionRadio.html b/components/ionRadio/ionRadio.html index 0a31b66..9fa2631 100644 --- a/components/ionRadio/ionRadio.html +++ b/components/ionRadio/ionRadio.html @@ -1,9 +1,11 @@ diff --git a/package.js b/package.js index 866fdc4..ae61e17 100644 --- a/package.js +++ b/package.js @@ -1,7 +1,7 @@ Package.describe({ - name: "meteoric:ionic", + name: "jandres:ionic", summary: "Ionic components for Meteor. No Angular!", - version: "0.1.19", + version: "0.1.20", git: "https://github.com/meteoric/meteor-ionic.git" }); From 1e3c63ecef58be31fe2da5ae1da0c5d9b3229c13 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Sat, 9 Jan 2016 06:51:49 -0700 Subject: [PATCH 03/59] Complex state for ionItem. Complex state occurs when ionItem contains at least one of ionOptions or ionDelete or ionReorder children. - That being said, one can now have ionOptions and swipe to left to show them. - One can now display ionDelete by setting ionItem's showDelete=true and giving ionDelete as its children. - Reorder also works by setting showReorder in ionItem=true and ionReorder is given as ionItem's children. Misc: - I forked Snap.js and integrated PEP (pointer event polyfill) that is supposed to abstract away from mouse and touch event. Making the interface much more consistent. --- .gitignore | 2 + .versions | 49 -- README.md | 48 +- .../ionDeleteButton/ionDeleteButton.html | 8 + components/ionDeleteButton/ionDeleteButton.js | 13 + components/ionHeaderBar/ionHeaderBar.js | 8 +- components/ionItem/ionItem.html | 17 +- components/ionItem/ionItem.js | 121 ++-- components/ionItemContent/ionItemContent.html | 15 + components/ionItemContent/ionItemContent.js | 13 + components/ionItemOptions/ionItemOptions.html | 10 + components/ionItemOptions/ionItemOptions.js | 7 + components/ionList/ionList.html | 5 +- components/ionList/ionList.js | 108 ++-- .../ionOptionButton/ionOptionButton.html | 5 + components/ionOptionButton/ionOptionButton.js | 6 + components/ionPopup/ionPopup.js | 10 +- .../ionReorderButton/ionReorderButton.html | 8 + .../ionReorderButton/ionReorderButton.js | 13 + .../ionSideMenuContainer.js | 23 +- package.js | 38 +- styles/main.scss | 7 + vendor/slip.js | 395 +++++++----- vendor/snap.css | 64 -- vendor/snap.js | 568 ------------------ 25 files changed, 561 insertions(+), 1000 deletions(-) delete mode 100644 .versions create mode 100644 components/ionDeleteButton/ionDeleteButton.html create mode 100644 components/ionDeleteButton/ionDeleteButton.js create mode 100644 components/ionItemContent/ionItemContent.html create mode 100644 components/ionItemContent/ionItemContent.js create mode 100644 components/ionItemOptions/ionItemOptions.html create mode 100644 components/ionItemOptions/ionItemOptions.js create mode 100644 components/ionOptionButton/ionOptionButton.html create mode 100644 components/ionOptionButton/ionOptionButton.js create mode 100644 components/ionReorderButton/ionReorderButton.html create mode 100644 components/ionReorderButton/ionReorderButton.js create mode 100644 styles/main.scss delete mode 100644 vendor/snap.css delete mode 100644 vendor/snap.js diff --git a/.gitignore b/.gitignore index 964bc06..2d06007 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +/.idea +.versions .build* .DS_Store diff --git a/.versions b/.versions deleted file mode 100644 index ba3a556..0000000 --- a/.versions +++ /dev/null @@ -1,49 +0,0 @@ -application-configuration@1.0.4 -base64@1.0.2 -binary-heap@1.0.2 -blaze@2.0.4 -blaze-tools@1.0.2 -boilerplate-generator@1.0.2 -callback-hook@1.0.2 -check@1.0.4 -ddp@1.0.14 -deps@1.0.6 -ejson@1.0.5 -fastclick@1.0.2 -follower-livedata@1.0.3 -geojson-utils@1.0.2 -html-tools@1.0.3 -htmljs@1.0.3 -id-map@1.0.2 -iron:controller@1.0.7 -iron:core@1.0.7 -iron:dynamic-template@1.0.7 -iron:layout@1.0.7 -iron:location@1.0.7 -iron:middleware-stack@1.0.7 -iron:router@1.0.7 -iron:url@1.0.7 -jquery@1.11.3 -json@1.0.2 -logging@1.0.6 -meteor@1.1.4 -meteoric:ionic@0.1.17 -minifiers@1.1.3 -minimongo@1.0.6 -mongo@1.0.11 -observe-sequence@1.0.4 -ordered-dict@1.0.2 -random@1.0.2 -reactive-dict@1.0.5 -reactive-var@1.0.4 -retry@1.0.2 -routepolicy@1.0.4 -session@1.0.5 -spacebars@1.0.5 -spacebars-compiler@1.0.4 -templating@1.0.11 -tracker@1.0.5 -ui@1.0.5 -underscore@1.0.2 -webapp@1.1.6 -webapp-hashing@1.0.2 diff --git a/README.md b/README.md index 8409241..72ce3c9 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,12 @@ ### Build [Ionic](http://ionicframework.com/) apps in [Meteor](https://www.meteor.com/)! -## NOTE: This package is no longer being actively maintained. - -If you are interested in maintaining it, contact me through [my Github profile](https://github.com/nickw). Now that Meteor is officially supporting (and actively recommending) React and Angular, I suggest using [Ionic](https://github.com/driftyco/ionic), [Ionic 2](https://github.com/driftyco/ionic2) or [Reapp](https://github.com/reapp) as alternatives for building hybrid mobile apps with Meteor. - ## Overview This is an attempt at **real Ionic and Meteor integration**. This is not just Ionic's CSS framework wrapped in a Meteor package. It aims to be a complete port of [Ionic’s Angular directives](http://ionicframework.com/docs/api/) to [Meteor Blaze](https://www.meteor.com/blaze) templates. +Note: This is a forked of meteoric:ionic, which is now not maintained. In response, I decided to fork it, and maintain this package for my own. I have written acceptance test on [JoeyAndres/ionic-demo](https://github.com/JoeyAndres/ionic-demo), which is a fork of meteoric/demo. + ## Why? [Ionic](http://ionicframework.com/) is arguably the most comprehensive, polished, cross-platform mobile framework available. But unfortunately a large portion of its functionality comes from Angular directives. [I'm not a fan of trying to force-fit Angular into Meteor](https://medium.com/space-camp/your-meteor-app-probably-doesnt-need-angular-13986a0323f6), so I wanted to see if I could rewrite Ionic specifically for Meteor. @@ -22,14 +20,27 @@ Check out the [GUIDE.md](GUIDE.md) for a guide on how to get started. **Beta** See the TODO section below to see which Angular Directives have been ported to Blaze. +## Install + +```bash +meteor add jandres:ionic +``` + ## Dependencies -Rather than include compiled or CDN versions of Ionic's CSS Framework we’ve extraced it into two separate packages: -- [meteoric:ionicons-sass](http://github.com/meteoric/ionicons-sass) Ionic’s Ionicons set wrapped for Meteor. -- [meteoric:ionic-sass](http://github.com/meteoric/ionic-sass) The base Ionic CSS Framework wrapped for Meteor. +- [seba:ionic-sass](https://github.com/sebakerckhof/meteor-ionic-sass/) Ionic's scss only package and currently up to date with ionic v1.1.0 ## Examples +### Demo of all components +The demo app of various ionic components + +[Demo](http://jandres-meteor-ionic.meteor.com/) | [Code](https://github.com/JoeyAndres/ionic-demo) + +## Pre-fork Examples + +The following are examples prior to this fork. + ### Contacts App A simple CRUD app to manage contacts. @@ -40,13 +51,6 @@ A [Product Hunt](http://producthunt.com) clone built in Meteor Ionic. (In Progre [Demo](http://meteorhunt.meteor.com/) | [Code](https://github.com/meteoric/meteorhunt) -### Demo of all components -The demo app of various meteoric components - -[Demo](http://meteor-ionic.meteor.com/) | [Code](https://github.com/meteoric/demo) - -You can also keep track of the various other repos from the [Meteoric team](https://github.com/meteoric) - ## TODO ### Angular Directives to convert to Blaze: @@ -67,15 +71,15 @@ You can also keep track of the various other repos from the [Meteoric team](http * [x] ion-footer-bar * [x] Keyboard (requires [cordova](http://cordova.apache.org/) integration) * [ ] Lists (needs edit/remove/sort functionality) - * [ ] ion-list - * [ ] ion-item - * [ ] ion-delete-button - * [ ] ion-reorder-button - * [ ] ion-option-button + * [x] ion-list + * [x] ion-item + * [x] ion-delete-button + * [x] ion-reorder-button + * [x] ion-option-button * [ ] collection-repeat * [x] Loading * [x] Modal -* [x] Navigation (requires [iron:router](https://github.com/EventedMind/iron-router) integration) +* [x] Navigation (requires [iron:router](https://github.com/EventedMind/iron-router) integration)a * [x] ion-nav-view * [x] ion-view * [x] ion-nav-bar @@ -101,6 +105,10 @@ You can also keep track of the various other repos from the [Meteoric team](http * [x] Tabs (requires [iron:router](https://github.com/EventedMind/iron-router) integration) * [x] ion-tabs * [x] ion-tab + +### Code Style Change: +These are code styles that I want to impose on this forked repo. +* Get rid of all session variables [ ] ## License [MIT License](https://github.com/meteoric/meteor-ionic/blob/master/LICENSE) diff --git a/components/ionDeleteButton/ionDeleteButton.html b/components/ionDeleteButton/ionDeleteButton.html new file mode 100644 index 0000000..8ccd42c --- /dev/null +++ b/components/ionDeleteButton/ionDeleteButton.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/components/ionDeleteButton/ionDeleteButton.js b/components/ionDeleteButton/ionDeleteButton.js new file mode 100644 index 0000000..190f75d --- /dev/null +++ b/components/ionDeleteButton/ionDeleteButton.js @@ -0,0 +1,13 @@ +Template.ionDeleteButton.onCreated(function() { + let parent = this.parent((t) => t.view.name === "Template.ionItem", true); + if (!parent) { throw "Template.ionDeleteButton must be a descendant of Template.ionItem."; } + _.extend(this, { + showDelete: parent.showDelete + }); +}); + +Template.ionDeleteButton.helpers({ + showDelete: function() { + return Template.instance().showDelete.get(); + } +}); \ No newline at end of file diff --git a/components/ionHeaderBar/ionHeaderBar.js b/components/ionHeaderBar/ionHeaderBar.js index 96d830e..adad638 100644 --- a/components/ionHeaderBar/ionHeaderBar.js +++ b/components/ionHeaderBar/ionHeaderBar.js @@ -1,6 +1,6 @@ IonHeaderBar = { alignTitle: function () { - var align = this.data.alignTitle || 'center'; + var align = this.alignTitle; var $title = this.$('.title'); if (Platform.isAndroid() && !this.alignTitle) { @@ -49,9 +49,9 @@ IonHeaderBar = { } }; -Template.ionHeaderBar.created = function () { - this.data = this.data || {}; -}; +Template.ionHeaderBar.onCreated(function() { + this.alignTitle = this.data? (this.data.alignTitle || 'center') : 'center'; +}); Template.ionHeaderBar.rendered = function () { Session.set('hasHeader', true); diff --git a/components/ionItem/ionItem.html b/components/ionItem/ionItem.html index e1788ab..baea793 100644 --- a/components/ionItem/ionItem.html +++ b/components/ionItem/ionItem.html @@ -1,11 +1,12 @@ + \ No newline at end of file diff --git a/components/ionItem/ionItem.js b/components/ionItem/ionItem.js index 811345d..b68e245 100644 --- a/components/ionItem/ionItem.js +++ b/components/ionItem/ionItem.js @@ -1,48 +1,97 @@ -Template.ionItem.helpers({ - idAttribute: function () { - if (this.id) { - return this.id; - } - }, - itemClasses: function () { - var classes = ['item']; - - if (this.class) { - var customClasses = this.class.split(' '); - _(customClasses).each(function (customClass) { - classes.push(customClass); - }); +Template.ionItem.onCreated(function() { + this.snapper = null; + this.itemComplex = new ReactiveVar(false); + + let parent = this.parent((template) => template.view.name === "Template.ionList", true); + if (!parent) { throw "Template.ionItem must be a descendant of Template.ionList."; } + + _.extend(this, { + // Props. + showDelete: parent.showDelete, + showReorder: parent.showReorder, + canSwipe: parent.canSwipe, + + // Methods. + closeIonItemSiblings: () => { + let ionItemSiblings = this.getSiblings().filter(sibling => sibling.view.name === "Template.ionItem"); + _.each(ionItemSiblings, sibling => !!sibling.snapper && sibling.snapper.close()); + }, + dragSetTransitionNone: () => this.$('.item-content').css({ transition: "none" }), + dragEndSetTransitionToInitial: () => this.$('.item-content').css({ transition: "initial"}), + initDragTransitionHandler: () => { + this.snapper.on('drag', this.dragSetTransitionNone); + this.snapper.on('end', this.dragEndSetTransitionToInitial); + }, + destroyDragTransitionHandler: () => { + this.snapper.off('drag', this.dragSetTransitionNone); + this.snapper.off('end', this.dragEndSetTransitionToInitial); + }, + isitemComplex: () => { + let complex = !!_.find(this.children(1), + elem => + elem.view.name === 'Template.ionItemOptions' || + elem.view.name === 'Template.ionDeleteButton' || + elem.view.name === 'Template.ionReorderButton'); + return complex; } + }); +}); - if (this.avatar) { - classes.push('item-avatar' + (this.avatar === 'right' ? '-right' : '')); - } +Template.ionItem.onRendered(function() { + this.autorun(() => { + if (this.canSwipe.get() && !this.showDelete.get() && !this.showReorder.get()) { + let ionOptions = this.getChildren() + .filter(child => child.view && child.view.name === 'Template.ionItemOptions'); + let ionOptionsWidth = ionOptions.reduce((width, child) => width + child.width(), 0); + if (!this.snapper) { + this.snapper = new Snap({ + element: this.$('.item-content').get(0), + disable: 'left', + minPosition: -ionOptionsWidth + }); + } - if (this.iconLeft) { - classes.push('item-icon-left'); - } + this.snapper.settings({ + element: this.$('.item-content').get(0), // In case the child template ionItemContent got changed. + minPosition: -ionOptionsWidth + }); + this.snapper.enable(); - if (this.iconRight) { - classes.push('item-icon-right'); - } + this.snapper.on('start', () => { + this.closeIonItemSiblings(); + }); - if (this.buttonLeft) { - classes.push('item-button-left'); + this.initDragTransitionHandler(); + } else { + if (this.snapper) { + this.destroyDragTransitionHandler(); + this.snapper.disable(); + this.snapper.close(); + } } + }); - if(Session.get('ionSortable')){ - classes.push('item-complex', 'item-left-editable'); - } + this.autorun(() => { + this.itemComplex.set(this.isitemComplex()); + }); +}); - if (this.buttonRight) { - classes.push('item-button-right'); - } +Template.ionItem.onDestroyed(function() { + if (!!this.snapper) { + this.destroyDragTransitionHandler(); + this.snapper.disable(); + this.snapper.close(); + } +}); - if (this.textWrap) { - classes.push('item-text-wrap'); +Template.ionItem.helpers({ + idAttribute: function () { + if (this.id) { + return this.id; } - - return classes.join(' '); + }, + itemComplex: function() { + return Template.instance().itemComplex.get(); }, isAnchor: function () { @@ -85,4 +134,4 @@ Template.ionItem.helpers({ } } } -}); +}); \ No newline at end of file diff --git a/components/ionItemContent/ionItemContent.html b/components/ionItemContent/ionItemContent.html new file mode 100644 index 0000000..239aceb --- /dev/null +++ b/components/ionItemContent/ionItemContent.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/components/ionItemContent/ionItemContent.js b/components/ionItemContent/ionItemContent.js new file mode 100644 index 0000000..640bb15 --- /dev/null +++ b/components/ionItemContent/ionItemContent.js @@ -0,0 +1,13 @@ +Template.ionItemContent.onCreated(function() { + let parent = this.parent((t) => t.view.name === "Template.ionItem", true); + if (!parent) { throw "Template.ionItemContent must be a descendant of Template.ionItem."; } + _.extend(this, { + itemComplex: parent.itemComplex + }); +}); + +Template.ionItemContent.helpers({ + itemComplex: function() { + return Template.instance().itemComplex.get(); + } +}); \ No newline at end of file diff --git a/components/ionItemOptions/ionItemOptions.html b/components/ionItemOptions/ionItemOptions.html new file mode 100644 index 0000000..1415299 --- /dev/null +++ b/components/ionItemOptions/ionItemOptions.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/components/ionItemOptions/ionItemOptions.js b/components/ionItemOptions/ionItemOptions.js new file mode 100644 index 0000000..9bd3d00 --- /dev/null +++ b/components/ionItemOptions/ionItemOptions.js @@ -0,0 +1,7 @@ +Template.ionItemOptions.onCreated(function() { + // todo: create package that gets the width of current element, if first = last. + // otherwise, nearest parent (which is just the parent). + this.width = function() { + return this.$('.item-options').width(); + }; +}); \ No newline at end of file diff --git a/components/ionList/ionList.html b/components/ionList/ionList.html index 6331f63..dba2a27 100644 --- a/components/ionList/ionList.html +++ b/components/ionList/ionList.html @@ -1,5 +1,8 @@ \ No newline at end of file diff --git a/components/ionList/ionList.js b/components/ionList/ionList.js index 529972d..4f2a135 100644 --- a/components/ionList/ionList.js +++ b/components/ionList/ionList.js @@ -1,94 +1,56 @@ -Template.ionList.helpers({ - classes: function () { - var classes = ['list']; +Template.ionList.onCreated(function() { + this.showDelete = new ReactiveVar(false); + this.showReorder = new ReactiveVar(false); + this.canSwipe = new ReactiveVar(false); +}); - if (this.class) { - var customClasses = this.class.split(' '); - _(customClasses).each(function (customClass) { - classes.push(customClass); - }); +Template.ionList.onRendered(function() { + this.autorun(() => { + if (Template.instance().showReorder.get()) { + IonSideMenu.disable(); + if (!this.slip) { + var list = this.$('.list')[0]; + this.slip = new Slip(list); + } + } else { + IonSideMenu.enable(); } + }); - return classes.join(' '); - } + this.autorun(() => { + if (!!Template.currentData()) { + this.canSwipe.set(!!Template.currentData().canSwipe); + this.showReorder.set(!!Template.currentData().showReorder); + this.showDelete.set(!!Template.currentData().showDelete); + } + }); }); - -Template.ionList.rendered = function() { - - if (this.data && this.data.ionSortable){ - Session.set("ionSortable", true ); - var list = this.$('.list')[0]; - new Slip(list); -} - -}; - - Template.ionList.events({ 'click .item-delete' : function(e, template){ e.preventDefault(); - - var target = $(e.target).closest('.item').get(0); - var targetData = Blaze.getData(target.getElementsByClassName('item-content')[0])._id || undefined; - - template.data.ionSortable.find({}).forEach(function(item, i) { - if (item._id === targetData) { - template.data.ionSortable._collection.remove({ - _id: item._id - }, function(error, result) { }); - } - }); }, 'slip:swipe .list, slip:beforeswipe .list, slip:beforewait .list, slip:afterswipe .list': function(e, template) { e.preventDefault(); }, 'slip:beforereorder .list': function(e, template) { - if (e.originalEvent.target.className.indexOf('instant') == -1) { + // Two case to consider: + // 1. instant class is in ionItem. In which case, we allow reorder, but we don't show the dragging animation. + // 2. This thing still shows the animation ven when reorder is disabled. Element goes back to orig spot, but could + // easily mislead the user. + if (e.originalEvent.target.className.indexOf('instant') !== -1 || + !template.showReorder.get()) { e.preventDefault(); } }, 'slip:reorder .list': function(e, template) { - spliceIndex = e.originalEvent.detail.spliceIndex - originalIndex = e.originalEvent.detail.originalIndex - - if (spliceIndex != originalIndex) { - - template.data.ionSortable.find({}, { - sort: { - order: 1 - } - }).forEach(function(item, i) { - template.data.ionSortable._collection.pauseObservers() - if (item._id == Blaze.getData(e.target.getElementsByClassName('item-content')[0])._id) { - temp = template.data.ionSortable.update({ - _id: item._id - }, { - $set: { - order: spliceIndex - } - }) - } else { - if (spliceIndex > originalIndex) { - newOrder = ((spliceIndex >= i) && (originalIndex < i)) ? (i - 1) : i - } else if (spliceIndex == '0') { - newOrder = (originalIndex > i) ? (i + 1) : i - } else { - newOrder = ((spliceIndex <= i) && (originalIndex > i)) ? (i + 1) : i - } - - temp = template.data.ionSortable.update({ - _id: item._id - }, { - $set: { - order: newOrder - } - }) - } - template.data.ionSortable._collection.resumeObservers() - }) + let toIndex = e.originalEvent.detail.spliceIndex; + let fromIndex = e.originalEvent.detail.originalIndex; + let index_change = toIndex !== fromIndex; + let sortable = index_change && template.showReorder.get() && !!template.data.onReorder; + if (sortable) { + template.data.onReorder(Template.instance().children()[fromIndex], fromIndex, toIndex); } } - }); \ No newline at end of file diff --git a/components/ionOptionButton/ionOptionButton.html b/components/ionOptionButton/ionOptionButton.html new file mode 100644 index 0000000..8517d0c --- /dev/null +++ b/components/ionOptionButton/ionOptionButton.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/components/ionOptionButton/ionOptionButton.js b/components/ionOptionButton/ionOptionButton.js new file mode 100644 index 0000000..64319b3 --- /dev/null +++ b/components/ionOptionButton/ionOptionButton.js @@ -0,0 +1,6 @@ +Template.ionOptionButton.events({ + 'click .button': function(e, template) { + e.stopPropagation(); + if (_.isFunction(template.data.onClick)) template.data.onClick(e); + } +}); \ No newline at end of file diff --git a/components/ionPopup/ionPopup.js b/components/ionPopup/ionPopup.js index 6fb683f..bc4e46e 100644 --- a/components/ionPopup/ionPopup.js +++ b/components/ionPopup/ionPopup.js @@ -144,17 +144,17 @@ IonPopup = { } }; -Template.ionPopup.rendered = function () { +Template.ionPopup.onRendered(function() { $(window).on('keyup.ionPopup', function(event) { - if (event.which == 27) { + if (event.which == KeyboardEvent.code["Escape"]) { IonPopup.close(); } }); -}; +}); -Template.ionPopup.destroyed = function () { +Template.ionPopup.onDestroyed(function() { $(window).off('keyup.ionPopup'); -}; +}); Template.ionPopup.events({ // Handle clicking the backdrop diff --git a/components/ionReorderButton/ionReorderButton.html b/components/ionReorderButton/ionReorderButton.html new file mode 100644 index 0000000..a882773 --- /dev/null +++ b/components/ionReorderButton/ionReorderButton.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/components/ionReorderButton/ionReorderButton.js b/components/ionReorderButton/ionReorderButton.js new file mode 100644 index 0000000..a7234a3 --- /dev/null +++ b/components/ionReorderButton/ionReorderButton.js @@ -0,0 +1,13 @@ +Template.ionReorderButton.onCreated(function() { + let parent = this.parent((t) => t.view.name === "Template.ionItem", true); + if (!parent) { throw "Template.ionReorderButton must be a descendant of Template.ionItem."; } + _.extend(this, { + showReorder: parent.showReorder + }); +}); + +Template.ionReorderButton.helpers({ + showReorder: function() { + return Template.instance().showReorder.get(); + } +}); \ No newline at end of file diff --git a/components/ionSideMenuContainer/ionSideMenuContainer.js b/components/ionSideMenuContainer/ionSideMenuContainer.js index 618d801..35beab2 100644 --- a/components/ionSideMenuContainer/ionSideMenuContainer.js +++ b/components/ionSideMenuContainer/ionSideMenuContainer.js @@ -1,17 +1,23 @@ IonSideMenu = { - snapper: null + snapper: null, // Make this private in the future. + + // Note that meteor renders most deeply nested template first before the outer most. + // Thus, if the child happens toc all any of these, before we are rendered, then + // error occurs. To solve that, dummy functions are provided. + enable: () => {}, + disable: () => {} }; -Template.ionSideMenuContainer.created = function () { +Template.ionSideMenuContainer.onCreated(function () { this.data = this.data || {}; this.side = this.data.side || 'both'; this.dragContent = true; if (typeof this.data.dragContent != 'undefined') { this.dragContent = this.data.dragContent } -}; +}); -Template.ionSideMenuContainer.rendered = function () { +Template.ionSideMenuContainer.onRendered(function () { $snapperEl = this.$('.snap-content'); if (!$snapperEl) { return; @@ -33,8 +39,11 @@ Template.ionSideMenuContainer.rendered = function () { disable: disable, touchToDrag: this.dragContent }); -}; -Template.ionSideMenuContainer.destroyed = function () { + IonSideMenu.enable = () => IonSideMenu.snapper.enable(); + IonSideMenu.disable = () => IonSideMenu.snapper.disable(); +}); + +Template.ionSideMenuContainer.onDestroyed(function () { IonSideMenu.snapper = null; -}; +}); diff --git a/package.js b/package.js index ae61e17..7acd1f5 100644 --- a/package.js +++ b/package.js @@ -1,10 +1,12 @@ Package.describe({ name: "jandres:ionic", summary: "Ionic components for Meteor. No Angular!", - version: "0.1.20", - git: "https://github.com/meteoric/meteor-ionic.git" + version: "0.1.39", + git: "https://github.com/JoeyAndres/meteor-ionic.git" }); + + Cordova.depends({ 'ionic-plugin-keyboard': '1.0.8' }); @@ -12,23 +14,34 @@ Cordova.depends({ Package.onUse(function(api) { api.versionsFrom("1.0"); api.use([ + "fourseven:scss@3.3.3" + ]); + + api.use([ + "jandres:template-extension@4.0.4", + "ecmascript@0.1.6", "templating", "underscore", + "reactive-var", "fastclick", "iron:router@1.0.0", "tracker", "session", - "jquery" + "jquery", + "jandres:snapjs@2.0.1", + "fourseven:scss@3.3.3" ], "client"); api.addFiles([ - "vendor/snap.js", - "vendor/snap.css", "vendor/slick.js", "vendor/slick.css", "vendor/slip.js" ], "client"); + api.addFiles([ + "styles/main.scss" + ], "client"); + api.addFiles([ "components/ionActionSheet/ionActionSheet.html", "components/ionActionSheet/ionActionSheet.js", @@ -42,6 +55,9 @@ Package.onUse(function(api) { "components/ionContent/ionContent.html", "components/ionContent/ionContent.js", + "components/ionDeleteButton/ionDeleteButton.html", + "components/ionDeleteButton/ionDeleteButton.js", + "components/ionFooterBar/ionFooterBar.html", "components/ionFooterBar/ionFooterBar.js", @@ -54,6 +70,12 @@ Package.onUse(function(api) { "components/ionItem/ionItem.html", "components/ionItem/ionItem.js", + "components/ionItemOptions/ionItemOptions.html", + "components/ionItemOptions/ionItemOptions.js", + + "components/ionItemContent/ionItemContent.html", + "components/ionItemContent/ionItemContent.js", + "components/ionKeyboard/ionKeyboard.js", "components/ionKeyboard/ionInputFocus.js", @@ -72,6 +94,9 @@ Package.onUse(function(api) { "components/ionNavBar/ionNavBar.html", "components/ionNavBar/ionNavBar.js", + "components/ionOptionButton/ionOptionButton.html", + "components/ionOptionButton/ionOptionButton.js", + "components/ionNavBackButton/ionNavBackButton.html", "components/ionNavBackButton/ionNavBackButton.js", @@ -90,6 +115,9 @@ Package.onUse(function(api) { "components/ionRadio/ionRadio.html", "components/ionRadio/ionRadio.js", + "components/ionReorderButton/ionReorderButton.html", + "components/ionReorderButton/ionReorderButton.js", + "components/ionSideMenu/ionSideMenu.html", "components/ionSideMenu/ionSideMenu.js", diff --git a/styles/main.scss b/styles/main.scss new file mode 100644 index 0000000..42398f1 --- /dev/null +++ b/styles/main.scss @@ -0,0 +1,7 @@ +.snap-drawers { + .snap-drawer-left {} + .item-options.snap-drawer.snap-drawer-right { + width: initial; + overflow: initial; + } +} \ No newline at end of file diff --git a/vendor/slip.js b/vendor/slip.js index 30ab0a8..aa1af09 100644 --- a/vendor/slip.js +++ b/vendor/slip.js @@ -1,115 +1,129 @@ /* - Slip - swiping and reordering in lists of elements on touch screens, no fuss. + Slip - swiping and reordering in lists of elements on touch screens, no fuss. - Fires these events on list elements: + Fires these events on list elements: - • slip:swipe - When swipe has been done and user has lifted finger off the screen. - If you execute event.preventDefault() the element will be animated back to original position. - Otherwise it will be animated off the list and set to display:none. + • slip:swipe + When swipe has been done and user has lifted finger off the screen. + If you execute event.preventDefault() the element will be animated back to original position. + Otherwise it will be animated off the list and set to display:none. - • slip:beforeswipe - Fired before first swipe movement starts. - If you execute event.preventDefault() then element will not move at all. + • slip:beforeswipe + Fired before first swipe movement starts. + If you execute event.preventDefault() then element will not move at all. - • slip:reorder - Element has been dropped in new location. event.detail contains the location: - • insertBefore: DOM node before which element has been dropped (null is the end of the list). Use with node.insertBefore(). - • spliceIndex: Index of element before which current element has been dropped, not counting the element iself. - For use with Array.splice() if the list is reflecting objects in some array. + • slip:reorder + Element has been dropped in new location. event.detail contains the location: + • insertBefore: DOM node before which element has been dropped (null is the end of the list). Use with node.insertBefore(). + • spliceIndex: Index of element before which current element has been dropped, not counting the element iself. + For use with Array.splice() if the list is reflecting objects in some array. - • slip:beforereorder - When reordering movement starts. - Element being reordered gets class `slip-reordering`. - If you execute event.preventDefault() then element will not move at all. + • slip:beforereorder + When reordering movement starts. + Element being reordered gets class `slip-reordering`. + If you execute event.preventDefault() then element will not move at all. - • slip:beforewait - If you execute event.preventDefault() then reordering will begin immediately, blocking ability to scroll the page. + • slip:beforewait + If you execute event.preventDefault() then reordering will begin immediately, blocking ability to scroll the page. - • slip:tap - When element was tapped without being swiped/reordered. + • slip:tap + When element was tapped without being swiped/reordered. - • slip:cancelswipe - Fired when the user stops dragging and the element returns to its original position. + • slip:cancelswipe + Fired when the user stops dragging and the element returns to its original position. - Usage: + Usage: - CSS: - You should set `user-select:none` (and WebKit prefixes, sigh) on list elements, - otherwise unstoppable and glitchy text selection in iOS will get in the way. + CSS: + You should set `user-select:none` (and WebKit prefixes, sigh) on list elements, + otherwise unstoppable and glitchy text selection in iOS will get in the way. - You should set `overflow-x: hidden` on the container or body to prevent horizontal scrollbar - appearing when elements are swiped off the list. + You should set `overflow-x: hidden` on the container or body to prevent horizontal scrollbar + appearing when elements are swiped off the list. - var list = document.querySelector('ul#slippylist'); - new Slip(list); + var list = document.querySelector('ul#slippylist'); + new Slip(list); - list.addEventListener('slip:beforeswipe', function(e) { - if (shouldNotSwipe(e.target)) e.preventDefault(); - }); + list.addEventListener('slip:beforeswipe', function(e) { + if (shouldNotSwipe(e.target)) e.preventDefault(); + }); - list.addEventListener('slip:swipe', function(e) { - // e.target swiped - if (thatWasSwipeToRemove) { - e.target.parentNode.removeChild(e.target); - } else { - e.preventDefault(); // will animate back to original position - } - }); + list.addEventListener('slip:swipe', function(e) { + // e.target swiped + if (thatWasSwipeToRemove) { + e.target.parentNode.removeChild(e.target); + } else { + e.preventDefault(); // will animate back to original position + } + }); - list.addEventListener('slip:beforereorder', function(e) { - if (shouldNotReorder(e.target)) e.preventDefault(); - }); + list.addEventListener('slip:beforereorder', function(e) { + if (shouldNotReorder(e.target)) e.preventDefault(); + }); - list.addEventListener('slip:reorder', function(e) { - // e.target reordered. - if (reorderedOK) { - e.target.parentNode.insertBefore(e.target, e.detail.insertBefore); - } else { - e.preventDefault(); - } - }); + list.addEventListener('slip:reorder', function(e) { + // e.target reordered. + if (reorderedOK) { + e.target.parentNode.insertBefore(e.target, e.detail.insertBefore); + } else { + e.preventDefault(); + } + }); - Requires: - • Touch events - • CSS transforms - • Function.bind() + Requires: + • Touch events + • CSS transforms + • Function.bind() - Caveats: - • Elements must not change size while reordering or swiping takes place (otherwise it will be visually out of sync) -*/ + Caveats: + • Elements must not change size while reordering or swiping takes place (otherwise it will be visually out of sync) + */ /*! @license - Slip.js 1.2.0 + Slip.js 1.2.0 - © 2014 Kornel Lesiński . All rights reserved. + © 2014 Kornel Lesiński . All rights reserved. - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - the following disclaimer in the documentation and/or other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials provided with the distribution. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -window['Slip'] = (function(){ +window['Slip'] = (function() { 'use strict'; + var accessibility = { + // Set values to false if you don't want Slip to manage them + container: { + ariaRole: "listbox", + tabIndex: 0, + focus: true, // focuses after drop + }, + items: { + ariaRole: "option", // If "option" flattens items, try "group": https://www.marcozehe.de/2013/03/08/sometimes-you-have-to-use-illegal-wai-aria-to-make-stuff-work/ + tabIndex: -1, // 0 will make every item tabbable, which isn't always useful + focus: true, // focuses when dragging + }, + }; + var damnYouChrome = /Chrome\/[34]/.test(navigator.userAgent); // For bugs that can't be programmatically detected :( Intended to catch all versions of Chrome 30-40 var needsBodyHandlerHack = damnYouChrome; // Otherwise I _sometimes_ don't get any touchstart events and only clicks instead. /* When dragging elements down in Chrome (tested 34-37) dragged element may appear below stationary elements. - Looks like WebKit bug #61824, but iOS Safari doesn't have that problem. */ + Looks like WebKit bug #61824, but iOS Safari doesn't have that problem. */ var compositorDoesNotOrderLayers = damnYouChrome; // -webkit-mess @@ -127,7 +141,7 @@ window['Slip'] = (function(){ var globalInstances = 0; var attachedBodyHandlerHack = false; - var nullHandler = function(){}; + var nullHandler = function() {}; function Slip(container, options) { if ('string' === typeof container) container = document.querySelector(container); @@ -135,7 +149,10 @@ window['Slip'] = (function(){ if (!this || this === window) return new Slip(container, options); - this.options = options; + this.options = options = options || {}; + this.options.keepSwipingPercent = options.keepSwipingPercent || 0; + this.options.minimumSwipeVelocity = options.minimumSwipeVelocity || 1; + this.options.minimumSwipeTime = options.minimumSwipeTime || 110; // Functions used for as event handlers need usable `this` and must not change to be removable this.cancel = this.setState.bind(this, this.states.idle); @@ -147,6 +164,7 @@ window['Slip'] = (function(){ this.onMouseUp = this.onMouseUp.bind(this); this.onMouseLeave = this.onMouseLeave.bind(this); this.onSelection = this.onSelection.bind(this); + this.onContainerFocus = this.onContainerFocus.bind(this); this.setState(this.states.idle); this.attach(container); @@ -156,32 +174,38 @@ window['Slip'] = (function(){ var transform = node.style[transformPrefix]; if (transform) { return { - value:transform, - original:transform, + value: transform, + original: transform, }; } if (window.getComputedStyle) { var style = window.getComputedStyle(node).getPropertyValue(transformProperty); - if (style && style !== 'none') return {value:style, original:''}; + if (style && style !== 'none') return { + value: style, + original: '' + }; } - return {value:'', original:''}; + return { + value: '', + original: '' + }; } function findIndex(target, nodes) { - var originalIndex = 0; - var listCount = 0; - - for (var i=0; i < nodes.length; i++) { - if (nodes[i].nodeType === 1) { - listCount++; - if (nodes[i] === target.node) { - originalIndex = listCount-1; - } + var originalIndex = 0; + var listCount = 0; + + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType === 1) { + listCount++; + if (nodes[i] === target.node) { + originalIndex = listCount - 1; + } + } } - } - return originalIndex; + return originalIndex; } // All functions in states are going to be executed in context of Slip object @@ -204,9 +228,12 @@ window['Slip'] = (function(){ states: { idle: function idleStateInit() { - this.target = null; - this.usingTouch = false; this.removeMouseHandlers(); + if (this.target) { + this.target.node.style.willChange = ''; + this.target = null; + } + this.usingTouch = false; return { allowTextSelection: true, @@ -215,14 +242,15 @@ window['Slip'] = (function(){ undecided: function undecidedStateInit() { this.target.height = this.target.node.offsetHeight; + this.target.node.style.willChange = transformProperty; this.target.node.style[transitionPrefix] = ''; if (!this.dispatch(this.target.originalTarget, 'beforewait')) { - if (this.dispatch(this.target.originalTarget, 'beforereorder')) { - this.setState(this.states.reorder); - } + if (this.dispatch(this.target.originalTarget, 'beforereorder')) { + this.setState(this.states.reorder); + } } else { - var holdTimer = setTimeout(function(){ + var holdTimer = setTimeout(function() { var move = this.getAbsoluteMovement(); if (this.canPreventScrolling && move.x < 15 && move.y < 25) { if (this.dispatch(this.target.originalTarget, 'beforereorder')) { @@ -241,7 +269,10 @@ window['Slip'] = (function(){ var move = this.getAbsoluteMovement(); if (move.x > 20 && move.y < Math.max(100, this.target.height)) { - if (this.dispatch(this.target.originalTarget, 'beforeswipe')) { + if (this.dispatch(this.target.originalTarget, 'beforeswipe', { + directionX: move.directionX, + directionY: move.directionY + })) { this.setState(this.states.swipe); return false; } else { @@ -253,7 +284,7 @@ window['Slip'] = (function(){ } // Chrome likes sideways scrolling :( - if (move.x > move.y*1.2) return false; + if (move.x > move.y * 1.2) return false; }, onLeave: function() { @@ -275,8 +306,9 @@ window['Slip'] = (function(){ var originalIndex = findIndex(this.target, this.container.childNodes); container.className += ' slip-swiping-container'; + function removeClass() { - container.className = container.className.replace(/(?:^| )slip-swiping-container/,''); + container.className = container.className.replace(/(?:^| )slip-swiping-container/, ''); } this.target.height = this.target.node.offsetHeight; @@ -284,7 +316,7 @@ window['Slip'] = (function(){ return { leaveState: function() { if (swipeSuccess) { - this.animateSwipe(function(target){ + this.animateSwipe(function(target) { target.node.style[transformPrefix] = target.baseTransform.original; target.node.style[transitionPrefix] = ''; if (this.dispatch(target.node, 'afterswipe')) { @@ -303,7 +335,7 @@ window['Slip'] = (function(){ onMove: function() { var move = this.getTotalMovement(); - if (Math.abs(move.y) < this.target.height+20) { + if (Math.abs(move.y) < this.target.height + 20) { this.target.node.style[transformPrefix] = 'translate(' + move.x + 'px,0) ' + hwLayerMagic + this.target.baseTransform.value; return false; } else { @@ -316,22 +348,19 @@ window['Slip'] = (function(){ }, onEnd: function() { - var dx = this.latestPosition.x - this.previousPosition.x; - var dy = this.latestPosition.y - this.previousPosition.y; - var velocity = Math.sqrt(dx*dx + dy*dy) / (this.latestPosition.time - this.previousPosition.time + 1); - var move = this.getAbsoluteMovement(); - var swiped = velocity > 0.6 && move.time > 110; + var velocity = move.x / move.time; - var direction; - if (dx > 0) { - direction = "right"; - } else { - direction = "left"; - } + // How far out has the item been swiped? + var swipedPercent = Math.abs((this.startPosition.x - this.previousPosition.x) / this.container.clientWidth) * 100; + + var swiped = (velocity > this.options.minimumSwipeVelocity && move.time > this.options.minimumSwipeTime) || (this.options.keepSwipingPercent && swipedPercent > this.options.keepSwipingPercent); if (swiped) { - if (this.dispatch(this.target.node, 'swipe', {direction: direction, originalIndex: originalIndex})) { + if (this.dispatch(this.target.node, 'swipe', { + direction: move.directionX, + originalIndex: originalIndex + })) { swipeSuccess = true; // can't animate here, leaveState overrides anim } } @@ -342,14 +371,18 @@ window['Slip'] = (function(){ }, reorder: function reorderStateInit() { + if (this.target.node.focus && accessibility.items.focus) { + this.target.node.focus(); + } + this.target.height = this.target.node.offsetHeight; var nodes = this.container.childNodes; var originalIndex = findIndex(this.target, nodes); var mouseOutsideTimer; - var zero = this.target.node.offsetTop + this.target.height/2; + var zero = this.target.node.offsetTop + this.target.height / 2; var otherNodes = []; - for(var i=0; i < nodes.length; i++) { + for (var i = 0; i < nodes.length; i++) { if (nodes[i].nodeType != 1 || nodes[i] === this.target.node) continue; var t = nodes[i].offsetTop; nodes[i].style[transitionPrefix] = transformProperty + ' 0.2s ease-in-out'; @@ -373,23 +406,23 @@ window['Slip'] = (function(){ if (mouseOutsideTimer) { // don't care where the mouse is as long as it moves - clearTimeout(mouseOutsideTimer); mouseOutsideTimer = null; + clearTimeout(mouseOutsideTimer); + mouseOutsideTimer = null; } var move = this.getTotalMovement(); this.target.node.style[transformPrefix] = 'translate(0,' + move.y + 'px) ' + hwTopLayerMagic + this.target.baseTransform.value; var height = this.target.height; - otherNodes.forEach(function(o){ + otherNodes.forEach(function(o) { var off = 0; if (o.pos < 0 && move.y < 0 && o.pos > move.y) { off = height; - } - else if (o.pos > 0 && move.y > 0 && o.pos < move.y) { + } else if (o.pos > 0 && move.y > 0 && o.pos < move.y) { off = -height; } // FIXME: should change accelerated/non-accelerated state lazily - o.node.style[transformPrefix] = off ? 'translate(0,'+off+'px) ' + hwLayerMagic + o.baseTransform.value : o.baseTransform.original; + o.node.style[transformPrefix] = off ? 'translate(0,' + off + 'px) ' + hwLayerMagic + o.baseTransform.value : o.baseTransform.original; }); return false; } @@ -404,13 +437,17 @@ window['Slip'] = (function(){ this.container.style.webkitTransformStyle = ''; } - this.target.node.className = this.target.node.className.replace(/(?:^| )slip-reordering/,''); + if (this.container.focus && accessibility.container.focus) { + this.container.focus(); + } + + this.target.node.className = this.target.node.className.replace(/(?:^| )slip-reordering/, ''); this.target.node.style[userSelectPrefix] = ''; - this.animateToZero(function(target){ + this.animateToZero(function(target) { target.node.style.zIndex = ''; }); - otherNodes.forEach(function(o){ + otherNodes.forEach(function(o) { o.node.style[transformPrefix] = o.baseTransform.original; o.node.style[transitionPrefix] = ''; // FIXME: animate to new position }); @@ -422,7 +459,7 @@ window['Slip'] = (function(){ // don't let element get stuck if mouse left the window // but don't cancel immediately as it'd be annoying near window edges if (mouseOutsideTimer) clearTimeout(mouseOutsideTimer); - mouseOutsideTimer = setTimeout(function(){ + mouseOutsideTimer = setTimeout(function() { mouseOutsideTimer = null; this.cancel(); }.bind(this), 700); @@ -431,16 +468,24 @@ window['Slip'] = (function(){ onEnd: function() { var move = this.getTotalMovement(); if (move.y < 0) { - for(var i=0; i < otherNodes.length; i++) { + for (var i = 0; i < otherNodes.length; i++) { if (otherNodes[i].pos > move.y) { - this.dispatch(this.target.node, 'reorder', {spliceIndex:i, insertBefore:otherNodes[i].node, originalIndex: originalIndex}); + this.dispatch(this.target.node, 'reorder', { + spliceIndex: i, + insertBefore: otherNodes[i].node, + originalIndex: originalIndex + }); break; } } } else { - for(var i=otherNodes.length-1; i >= 0; i--) { + for (var i = otherNodes.length - 1; i >= 0; i--) { if (otherNodes[i].pos < move.y) { - this.dispatch(this.target.node, 'reorder', {spliceIndex:i+1, insertBefore:otherNodes[i+1] ? otherNodes[i+1].node : null, originalIndex: originalIndex}); + this.dispatch(this.target.node, 'reorder', { + spliceIndex: i + 1, + insertBefore: otherNodes[i + 1] ? otherNodes[i + 1].node : null, + originalIndex: originalIndex + }); break; } } @@ -464,6 +509,17 @@ window['Slip'] = (function(){ } this.container = container; + + // Accessibility + if (false !== accessibility.container.tabIndex) { + this.container.tabIndex = accessibility.container.tabIndex; + } + if (accessibility.container.ariaRole) { + this.container.setAttribute('aria-role', accessibility.container.ariaRole); + } + this.setChildNodesAriaRoles(); + this.container.addEventListener('focus', this.onContainerFocus, false); + this.otherNodes = []; // selection on iOS interferes with reordering @@ -496,7 +552,7 @@ window['Slip'] = (function(){ } }, - setState: function(newStateCtor){ + setState: function(newStateCtor) { if (this.state) { if (this.state.ctor === newStateCtor) return; if (this.state.leaveState) this.state.leaveState.call(this); @@ -512,12 +568,29 @@ window['Slip'] = (function(){ }, findTargetNode: function(targetNode) { - while(targetNode && targetNode.parentNode !== this.container) { + while (targetNode && targetNode.parentNode !== this.container) { targetNode = targetNode.parentNode; } return targetNode; }, + onContainerFocus: function(e) { + this.setChildNodesAriaRoles(); + }, + + setChildNodesAriaRoles: function() { + var nodes = this.container.childNodes; + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType != 1) continue; + if (accessibility.items.ariaRole) { + nodes[i].setAttribute('aria-role', accessibility.items.ariaRole); + } + if (false !== accessibility.items.tabIndex) { + nodes[i].tabIndex = accessibility.items.tabIndex; + } + } + }, + onSelection: function(e) { var isRelated = e.target === document || this.findTargetNode(e); if (!isRelated) return; @@ -606,9 +679,9 @@ window['Slip'] = (function(){ //check for a scrollable parent var scrollContainer = targetNode.parentNode; - while (scrollContainer){ - if (scrollContainer.scrollHeight > scrollContainer.clientHeight && window.getComputedStyle(scrollContainer)['overflow-y'] != 'visible') break; - else scrollContainer = scrollContainer.parentNode; + while (scrollContainer) { + if (scrollContainer.scrollHeight > scrollContainer.clientHeight && window.getComputedStyle(scrollContainer)['overflow-y'] != 'visible') break; + else scrollContainer = scrollContainer.parentNode; } this.target = { @@ -626,8 +699,9 @@ window['Slip'] = (function(){ }, updatePosition: function(e, pos) { - if(this.target == null) + if (this.target == null) { return; + } this.latestPosition = pos; var triggerOffset = 40, @@ -639,16 +713,15 @@ window['Slip'] = (function(){ bottomOffset = Math.min(containerRect.bottom, window.innerHeight) - targetRect.bottom, topOffset = targetRect.top - Math.max(containerRect.top, 0); - if (bottomOffset < triggerOffset){ - offset = triggerOffset - bottomOffset; - } - else if (topOffset < triggerOffset){ - offset = topOffset - triggerOffset; + if (bottomOffset < triggerOffset) { + offset = triggerOffset - bottomOffset; + } else if (topOffset < triggerOffset) { + offset = topOffset - triggerOffset; } var prevScrollTop = scrollable.scrollTop; scrollable.scrollTop += offset; - if (prevScrollTop != scrollable.scrollTop) this.startPosition.y += prevScrollTop-scrollable.scrollTop; + if (prevScrollTop != scrollable.scrollTop) this.startPosition.y += prevScrollTop - scrollable.scrollTop; if (this.state.onMove) { if (this.state.onMove.call(this) === false) { @@ -699,8 +772,8 @@ window['Slip'] = (function(){ getTotalMovement: function() { return { - x:this.latestPosition.x - this.startPosition.x, - y:this.latestPosition.y - this.startPosition.y, + x: this.latestPosition.x - this.startPosition.x, + y: this.latestPosition.y - this.startPosition.y, }; }, @@ -708,7 +781,9 @@ window['Slip'] = (function(){ return { x: Math.abs(this.latestPosition.x - this.startPosition.x), y: Math.abs(this.latestPosition.y - this.startPosition.y), - time:this.latestPosition.time - this.startPosition.time, + time: this.latestPosition.time - this.startPosition.time, + directionX: this.latestPosition.x - this.startPosition.x < 0 ? 'left' : 'right', + directionY: this.latestPosition.y - this.startPosition.y < 0 ? 'up' : 'down', }; }, @@ -727,7 +802,7 @@ window['Slip'] = (function(){ getSiblings: function(target) { var siblings = []; var tmp = target.node.nextSibling; - while(tmp) { + while (tmp) { if (tmp.nodeType == 1) siblings.push({ node: tmp, baseTransform: getTransform(tmp), @@ -741,9 +816,9 @@ window['Slip'] = (function(){ // save, because this.target/container could change during animation target = target || this.target; - // target.node.style[transitionPrefix] = transformProperty + ' 5s ease-out'; + target.node.style[transitionPrefix] = transformProperty + ' 0.1s ease-out'; target.node.style[transformPrefix] = 'translate(0,0) ' + hwLayerMagic + target.baseTransform.value; - setTimeout(function(){ + setTimeout(function() { target.node.style[transitionPrefix] = ''; target.node.style[transformPrefix] = target.baseTransform.original; if (callback) callback.call(this, target); @@ -759,23 +834,23 @@ window['Slip'] = (function(){ target.node.style[transitionPrefix] = 'all 0.1s linear'; target.node.style[transformPrefix] = ' translate(' + (this.getTotalMovement().x > 0 ? '' : '-') + '100%,0) ' + hwLayerMagic + target.baseTransform.value; - setTimeout(function(){ + setTimeout(function() { if (callback.call(this, target)) { - siblings.forEach(function(o){ + siblings.forEach(function(o) { o.node.style[transitionPrefix] = ''; o.node.style[transformPrefix] = emptySpaceTransform + o.baseTransform.value; }); - setTimeout(function(){ - siblings.forEach(function(o){ + setTimeout(function() { + siblings.forEach(function(o) { o.node.style[transitionPrefix] = transformProperty + ' 0.1s ease-in-out'; o.node.style[transformPrefix] = 'translate(0,0) ' + hwLayerMagic + o.baseTransform.value; }); - setTimeout(function(){ - siblings.forEach(function(o){ + setTimeout(function() { + siblings.forEach(function(o) { o.node.style[transitionPrefix] = ''; o.node.style[transformPrefix] = o.baseTransform.original; }); - },101); + }, 101); }, 1); } }.bind(this), 101); @@ -784,9 +859,9 @@ window['Slip'] = (function(){ // AMD if ('function' === typeof define && define.amd) { - define(function(){ + define(function() { return Slip; }); } return Slip; -})(); +})(); \ No newline at end of file diff --git a/vendor/snap.css b/vendor/snap.css deleted file mode 100644 index b09c3a6..0000000 --- a/vendor/snap.css +++ /dev/null @@ -1,64 +0,0 @@ -.snap-content { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: auto; - height: auto; - z-index: 2; - overflow: auto; - -webkit-overflow-scrolling: touch; - -webkit-transform: translate3d(0, 0, 0); - -moz-transform: translate3d(0, 0, 0); - -ms-transform: translate3d(0, 0, 0); - -o-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} - -.snap-drawers { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: auto; - height: auto; -} - -.snap-drawer { - position: absolute; - top: 0; - right: auto; - bottom: 0; - left: auto; - width: 265px; - height: auto; - overflow: auto; - -webkit-overflow-scrolling: touch; - -webkit-transition: width 0.3s ease; - -moz-transition: width 0.3s ease; - -ms-transition: width 0.3s ease; - -o-transition: width 0.3s ease; - transition: width 0.3s ease; -} - -.snap-drawer-left { - left: 0; - z-index: 1; -} - -.snap-drawer-right { - right: 0; - z-index: 1; -} - -.snapjs-left .snap-drawer-right, -.snapjs-right .snap-drawer-left { - display: none; -} - -.snapjs-expand-left .snap-drawer-left, -.snapjs-expand-right .snap-drawer-right { - width: 100%; -} diff --git a/vendor/snap.js b/vendor/snap.js deleted file mode 100644 index 07b5a7f..0000000 --- a/vendor/snap.js +++ /dev/null @@ -1,568 +0,0 @@ -/* -* Snap.js -* -* Copyright 2013, Jacob Kelley - http://jakiestfu.com/ -* Released under the MIT Licence -* http://opensource.org/licenses/MIT -* -* Github: http://github.com/jakiestfu/Snap.js/ -* Version: 1.9.3 -*/ -/*jslint browser: true*/ -/*global define, module, ender*/ -(function(win, doc) { - 'use strict'; - var Snap = Snap || function(userOpts) { - var settings = { - element: null, - dragger: null, - disable: 'none', - addBodyClasses: true, - hyperextensible: true, - resistance: 0.5, - flickThreshold: 50, - transitionSpeed: 0.3, - easing: 'ease', - maxPosition: 266, - minPosition: -266, - tapToClose: true, - touchToDrag: true, - slideIntent: 40, // degrees - minDragDistance: 5 - }, - cache = { - simpleStates: { - opening: null, - towards: null, - hyperExtending: null, - halfway: null, - flick: null, - translation: { - absolute: 0, - relative: 0, - sinceDirectionChange: 0, - percentage: 0 - } - } - }, - eventList = {}, - utils = { - hasTouch: ('ontouchstart' in doc.documentElement || win.navigator.msPointerEnabled), - eventType: function(action) { - var eventTypes = { - down: (utils.hasTouch ? 'touchstart' : 'mousedown'), - move: (utils.hasTouch ? 'touchmove' : 'mousemove'), - up: (utils.hasTouch ? 'touchend' : 'mouseup'), - out: (utils.hasTouch ? 'touchcancel' : 'mouseout') - }; - return eventTypes[action]; - }, - page: function(t, e){ - return (utils.hasTouch && e.touches.length && e.touches[0]) ? e.touches[0]['page'+t] : e['page'+t]; - }, - klass: { - has: function(el, name){ - return (el.className).indexOf(name) !== -1; - }, - add: function(el, name){ - if(!utils.klass.has(el, name) && settings.addBodyClasses){ - el.className += " "+name; - } - }, - remove: function(el, name){ - if(settings.addBodyClasses){ - el.className = (el.className).replace(name, "").replace(/^\s+|\s+$/g, ''); - } - } - }, - dispatchEvent: function(type) { - if (typeof eventList[type] === 'function') { - return eventList[type].call(); - } - }, - vendor: function(){ - var tmp = doc.createElement("div"), - prefixes = 'webkit Moz O ms'.split(' '), - i; - for (i in prefixes) { - if (typeof tmp.style[prefixes[i] + 'Transition'] !== 'undefined') { - return prefixes[i]; - } - } - }, - transitionCallback: function(){ - return (cache.vendor==='Moz' || cache.vendor==='ms') ? 'transitionend' : cache.vendor+'TransitionEnd'; - }, - canTransform: function(){ - return typeof settings.element.style[cache.vendor+'Transform'] !== 'undefined'; - }, - deepExtend: function(destination, source) { - var property; - for (property in source) { - if (source[property] && source[property].constructor && source[property].constructor === Object) { - destination[property] = destination[property] || {}; - utils.deepExtend(destination[property], source[property]); - } else { - destination[property] = source[property]; - } - } - return destination; - }, - angleOfDrag: function(x, y) { - var degrees, theta; - // Calc Theta - theta = Math.atan2(-(cache.startDragY - y), (cache.startDragX - x)); - if (theta < 0) { - theta += 2 * Math.PI; - } - // Calc Degrees - degrees = Math.floor(theta * (180 / Math.PI) - 180); - if (degrees < 0 && degrees > -180) { - degrees = 360 - Math.abs(degrees); - } - return Math.abs(degrees); - }, - events: { - addEvent: function addEvent(element, eventName, func) { - if (element.addEventListener) { - return element.addEventListener(eventName, func, false); - } else if (element.attachEvent) { - return element.attachEvent("on" + eventName, func); - } - }, - removeEvent: function addEvent(element, eventName, func) { - if (element.addEventListener) { - return element.removeEventListener(eventName, func, false); - } else if (element.attachEvent) { - return element.detachEvent("on" + eventName, func); - } - }, - prevent: function(e) { - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } - } - }, - parentUntil: function(el, attr) { - var isStr = typeof attr === 'string'; - while (el.parentNode) { - if (isStr && el.getAttribute && el.getAttribute(attr)){ - return el; - } else if(!isStr && el === attr){ - return el; - } - el = el.parentNode; - } - return null; - } - }, - action = { - translate: { - get: { - matrix: function(index) { - - if( !utils.canTransform() ){ - return parseInt(settings.element.style.left, 10); - } else { - var matrix = win.getComputedStyle(settings.element)[cache.vendor+'Transform'].match(/\((.*)\)/), - ieOffset = 8; - if (matrix) { - matrix = matrix[1].split(','); - if(matrix.length===16){ - index+=ieOffset; - } - return parseInt(matrix[index], 10); - } - return 0; - } - } - }, - easeCallback: function(){ - settings.element.style[cache.vendor+'Transition'] = ''; - cache.translation = action.translate.get.matrix(4); - cache.easing = false; - clearInterval(cache.animatingInterval); - - if(cache.easingTo===0){ - utils.klass.remove(doc.body, 'snapjs-right'); - utils.klass.remove(doc.body, 'snapjs-left'); - } - - utils.dispatchEvent('animated'); - utils.events.removeEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); - }, - easeTo: function(n) { - - if( !utils.canTransform() ){ - cache.translation = n; - action.translate.x(n); - } else { - cache.easing = true; - cache.easingTo = n; - - settings.element.style[cache.vendor+'Transition'] = 'all ' + settings.transitionSpeed + 's ' + settings.easing; - - cache.animatingInterval = setInterval(function() { - utils.dispatchEvent('animating'); - }, 1); - - utils.events.addEvent(settings.element, utils.transitionCallback(), action.translate.easeCallback); - action.translate.x(n); - } - if(n===0){ - settings.element.style[cache.vendor+'Transform'] = ''; - } - }, - x: function(n) { - if( (settings.disable==='left' && n>0) || - (settings.disable==='right' && n<0) - ){ return; } - - if( !settings.hyperextensible ){ - if( n===settings.maxPosition || n>settings.maxPosition ){ - n=settings.maxPosition; - } else if( n===settings.minPosition || n 0, - translateTo = whileDragX, - diff; - - // Shown no intent already - if((cache.intentChecked && !cache.hasIntent)){ - return; - } - - if(settings.addBodyClasses){ - if((absoluteTranslation)>0){ - utils.klass.add(doc.body, 'snapjs-left'); - utils.klass.remove(doc.body, 'snapjs-right'); - } else if((absoluteTranslation)<0){ - utils.klass.add(doc.body, 'snapjs-right'); - utils.klass.remove(doc.body, 'snapjs-left'); - } - } - - if (cache.hasIntent === false || cache.hasIntent === null) { - var deg = utils.angleOfDrag(thePageX, thePageY), - inRightRange = (deg >= 0 && deg <= settings.slideIntent) || (deg <= 360 && deg > (360 - settings.slideIntent)), - inLeftRange = (deg >= 180 && deg <= (180 + settings.slideIntent)) || (deg <= 180 && deg >= (180 - settings.slideIntent)); - if (!inLeftRange && !inRightRange) { - cache.hasIntent = false; - } else { - cache.hasIntent = true; - } - cache.intentChecked = true; - } - - if ( - (settings.minDragDistance>=Math.abs(thePageX-cache.startDragX)) || // Has user met minimum drag distance? - (cache.hasIntent === false) - ) { - return; - } - - utils.events.prevent(e); - utils.dispatchEvent('drag'); - - cache.dragWatchers.current = thePageX; - // Determine which direction we are going - if (cache.dragWatchers.last > thePageX) { - if (cache.dragWatchers.state !== 'left') { - cache.dragWatchers.state = 'left'; - cache.dragWatchers.hold = thePageX; - } - cache.dragWatchers.last = thePageX; - } else if (cache.dragWatchers.last < thePageX) { - if (cache.dragWatchers.state !== 'right') { - cache.dragWatchers.state = 'right'; - cache.dragWatchers.hold = thePageX; - } - cache.dragWatchers.last = thePageX; - } - if (openingLeft) { - // Pulling too far to the right - if (settings.maxPosition < absoluteTranslation) { - diff = (absoluteTranslation - settings.maxPosition) * settings.resistance; - translateTo = whileDragX - diff; - } - cache.simpleStates = { - opening: 'left', - towards: cache.dragWatchers.state, - hyperExtending: settings.maxPosition < absoluteTranslation, - halfway: absoluteTranslation > (settings.maxPosition / 2), - flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold, - translation: { - absolute: absoluteTranslation, - relative: whileDragX, - sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold), - percentage: (absoluteTranslation/settings.maxPosition)*100 - } - }; - } else { - // Pulling too far to the left - if (settings.minPosition > absoluteTranslation) { - diff = (absoluteTranslation - settings.minPosition) * settings.resistance; - translateTo = whileDragX - diff; - } - cache.simpleStates = { - opening: 'right', - towards: cache.dragWatchers.state, - hyperExtending: settings.minPosition > absoluteTranslation, - halfway: absoluteTranslation < (settings.minPosition / 2), - flick: Math.abs(cache.dragWatchers.current - cache.dragWatchers.hold) > settings.flickThreshold, - translation: { - absolute: absoluteTranslation, - relative: whileDragX, - sinceDirectionChange: (cache.dragWatchers.current - cache.dragWatchers.hold), - percentage: (absoluteTranslation/settings.minPosition)*100 - } - }; - } - action.translate.x(translateTo + translated); - } - }, - endDrag: function(e) { - if (cache.isDragging) { - utils.dispatchEvent('end'); - var translated = action.translate.get.matrix(4); - - // Tap Close - if (cache.dragWatchers.current === 0 && translated !== 0 && settings.tapToClose) { - utils.dispatchEvent('close'); - utils.events.prevent(e); - action.translate.easeTo(0); - cache.isDragging = false; - cache.startDragX = 0; - return; - } - - // Revealing Left - if (cache.simpleStates.opening === 'left') { - // Halfway, Flicking, or Too Far Out - if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) { - if (cache.simpleStates.flick && cache.simpleStates.towards === 'left') { // Flicking Closed - action.translate.easeTo(0); - } else if ( - (cache.simpleStates.flick && cache.simpleStates.towards === 'right') || // Flicking Open OR - (cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending - ) { - action.translate.easeTo(settings.maxPosition); // Open Left - } - } else { - action.translate.easeTo(0); // Close Left - } - // Revealing Right - } else if (cache.simpleStates.opening === 'right') { - // Halfway, Flicking, or Too Far Out - if ((cache.simpleStates.halfway || cache.simpleStates.hyperExtending || cache.simpleStates.flick)) { - if (cache.simpleStates.flick && cache.simpleStates.towards === 'right') { // Flicking Closed - action.translate.easeTo(0); - } else if ( - (cache.simpleStates.flick && cache.simpleStates.towards === 'left') || // Flicking Open OR - (cache.simpleStates.halfway || cache.simpleStates.hyperExtending) // At least halfway open OR hyperextending - ) { - action.translate.easeTo(settings.minPosition); // Open Right - } - } else { - action.translate.easeTo(0); // Close Right - } - } - cache.isDragging = false; - cache.startDragX = utils.page('X', e); - } - } - } - }, - init = function(opts) { - if (opts.element) { - utils.deepExtend(settings, opts); - cache.vendor = utils.vendor(); - action.drag.listen(); - } - }; - /* - * Public - */ - this.open = function(side) { - utils.dispatchEvent('open'); - utils.klass.remove(doc.body, 'snapjs-expand-left'); - utils.klass.remove(doc.body, 'snapjs-expand-right'); - - if (side === 'left') { - cache.simpleStates.opening = 'left'; - cache.simpleStates.towards = 'right'; - utils.klass.add(doc.body, 'snapjs-left'); - utils.klass.remove(doc.body, 'snapjs-right'); - action.translate.easeTo(settings.maxPosition); - } else if (side === 'right') { - cache.simpleStates.opening = 'right'; - cache.simpleStates.towards = 'left'; - utils.klass.remove(doc.body, 'snapjs-left'); - utils.klass.add(doc.body, 'snapjs-right'); - action.translate.easeTo(settings.minPosition); - } - }; - this.close = function() { - utils.dispatchEvent('close'); - action.translate.easeTo(0); - }; - this.expand = function(side){ - var to = win.innerWidth || doc.documentElement.clientWidth; - - if(side==='left'){ - utils.dispatchEvent('expandLeft'); - utils.klass.add(doc.body, 'snapjs-expand-left'); - utils.klass.remove(doc.body, 'snapjs-expand-right'); - } else { - utils.dispatchEvent('expandRight'); - utils.klass.add(doc.body, 'snapjs-expand-right'); - utils.klass.remove(doc.body, 'snapjs-expand-left'); - to *= -1; - } - action.translate.easeTo(to); - }; - - this.on = function(evt, fn) { - eventList[evt] = fn; - return this; - }; - this.off = function(evt) { - if (eventList[evt]) { - eventList[evt] = false; - } - }; - - this.enable = function() { - utils.dispatchEvent('enable'); - action.drag.listen(); - }; - this.disable = function() { - utils.dispatchEvent('disable'); - action.drag.stopListening(); - }; - - this.settings = function(opts){ - utils.deepExtend(settings, opts); - }; - - this.state = function() { - var state, - fromLeft = action.translate.get.matrix(4); - if (fromLeft === settings.maxPosition) { - state = 'left'; - } else if (fromLeft === settings.minPosition) { - state = 'right'; - } else { - state = 'closed'; - } - return { - state: state, - info: cache.simpleStates - }; - }; - init(userOpts); - }; - if ((typeof module !== 'undefined') && module.exports) { - module.exports = Snap; - } - if (typeof ender === 'undefined') { - this.Snap = Snap; - } - if ((typeof define === "function") && define.amd) { - define("snap", [], function() { - return Snap; - }); - } - }).call(this, window, document); From b7ba546bee4962cf881029598f5de1684de0fcc4 Mon Sep 17 00:00:00 2001 From: Joey Andres Date: Sat, 30 Jan 2016 22:22:26 -0700 Subject: [PATCH 04/59] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72ce3c9..5e4bbd9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ meteor add jandres:ionic ### Demo of all components The demo app of various ionic components -[Demo](http://jandres-meteor-ionic.meteor.com/) | [Code](https://github.com/JoeyAndres/ionic-demo) +[Demo](http://jandres-ionic.meteor.com/) | [Code](https://github.com/JoeyAndres/ionic-demo) ## Pre-fork Examples From 5c94fe8d852a20d2e8a65d6df5842b9e770d1312 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Mon, 1 Feb 2016 18:58:30 -0700 Subject: [PATCH 05/59] ionContent hasBounce attribute. --- components/ionContent/ionContent.js | 73 ++++++++++------- .../ionSideMenuContainer.js | 80 ++++++++++--------- package.js | 2 +- 3 files changed, 89 insertions(+), 66 deletions(-) diff --git a/components/ionContent/ionContent.js b/components/ionContent/ionContent.js index 5ec59b4..51badc2 100644 --- a/components/ionContent/ionContent.js +++ b/components/ionContent/ionContent.js @@ -1,39 +1,56 @@ +Template.ionContent.onCreated(function() { + this.ionSideMenuContainerParent = this.parent((t) => t.view.name === "Template.ionSideMenuContainer", true); + + _.extend(this, { + hasBouncing: (!!this.ionSideMenuContainerParent && this.ionSideMenuContainerParent.hasBouncing) || new ReactiveVar(null), + itemComplex: parent.itemComplex + }); + + this.autorun(() => { + if (!Template.currentData()) return; + let hasBouncing = Template.currentData().hasBouncing; + if (!!this.ionSideMenuContainerParent) { + this.ionSideMenuContainerParent.hasBouncing.set(hasBouncing); + } + }); +}); + Template.ionContent.helpers({ - classes: function () { - var classes = ['content']; + classes: function () { + var classes = ['content']; - if (this.class) { - classes.push(this.class); - } + if (this.class) { + classes.push(this.class); + } - if (this.scroll !== false) { - classes.push('overflow-scroll'); - } + if (this.scroll !== false) { + classes.push('overflow-scroll'); + } - if (Session.get('hasHeader')) { - classes.push('has-header'); - } + if (Session.get('hasHeader')) { + classes.push('has-header'); + } - if (Session.get('hasSubheader')) { - classes.push('has-subheader'); - } + if (Session.get('hasSubheader')) { + classes.push('has-subheader'); + } - if (Session.get('hasTabs')) { - classes.push('has-tabs'); - } + if (Session.get('hasTabs')) { + classes.push('has-tabs'); + } - if (Session.get('hasTabsTop')) { - classes.push('has-tabs-top'); - } + if (Session.get('hasTabsTop')) { + classes.push('has-tabs-top'); + } - if (Session.get('hasFooter')) { - classes.push('has-footer'); - } + if (Session.get('hasFooter')) { + classes.push('has-footer'); + } - if (Session.get('hasSubfooter')) { - classes.push('has-subfooter'); - } + if (Session.get('hasSubfooter')) { + classes.push('has-subfooter'); + } - return classes.join(' '); - } + return classes.join(' '); + } }); diff --git a/components/ionSideMenuContainer/ionSideMenuContainer.js b/components/ionSideMenuContainer/ionSideMenuContainer.js index 35beab2..08f4cb7 100644 --- a/components/ionSideMenuContainer/ionSideMenuContainer.js +++ b/components/ionSideMenuContainer/ionSideMenuContainer.js @@ -1,49 +1,55 @@ IonSideMenu = { - snapper: null, // Make this private in the future. + snapper: null, // Make this private in the future. - // Note that meteor renders most deeply nested template first before the outer most. - // Thus, if the child happens toc all any of these, before we are rendered, then - // error occurs. To solve that, dummy functions are provided. - enable: () => {}, - disable: () => {} + // Note that meteor renders most deeply nested template first before the outer most. + // Thus, if the child happens toc all any of these, before we are rendered, then + // error occurs. To solve that, dummy functions are provided. + enable: () => {}, + disable: () => {} }; Template.ionSideMenuContainer.onCreated(function () { - this.data = this.data || {}; - this.side = this.data.side || 'both'; - this.dragContent = true; - if (typeof this.data.dragContent != 'undefined') { - this.dragContent = this.data.dragContent - } + this.data = this.data || {}; + this.side = this.data.side || 'both'; + this.dragContent = true; + + this.hasBouncing = new ReactiveVar(true); + + if (typeof this.data.dragContent != 'undefined') { + this.dragContent = this.data.dragContent + } }); Template.ionSideMenuContainer.onRendered(function () { - $snapperEl = this.$('.snap-content'); - if (!$snapperEl) { - return; - } - - var disable; - if (this.side == 'both') { - disable = 'none'; - } - if (this.side == 'left') { - disable = 'right'; - } - if (this.side == 'right') { - disable = 'left'; - } - - IonSideMenu.snapper = new Snap({ - element: $snapperEl.get(0), - disable: disable, - touchToDrag: this.dragContent - }); - - IonSideMenu.enable = () => IonSideMenu.snapper.enable(); - IonSideMenu.disable = () => IonSideMenu.snapper.disable(); + $snapperEl = this.$('.snap-content'); + if (!$snapperEl) { + return; + } + + var disable; + if (this.side == 'both') { + disable = 'none'; + } + if (this.side == 'left') { + disable = 'right'; + } + if (this.side == 'right') { + disable = 'left'; + } + + IonSideMenu.snapper = new Snap({ + element: $snapperEl.get(0), + disable: disable, + touchToDrag: this.dragContent + }); + + IonSideMenu.enable = () => IonSideMenu.snapper.enable(); + IonSideMenu.disable = () => IonSideMenu.snapper.disable(); + + // Watch hasBounce attribute. + this.autorun(() => { IonSideMenu.snapper.settings({ hyperextensible: this.hasBouncing.get() }); }); }); Template.ionSideMenuContainer.onDestroyed(function () { - IonSideMenu.snapper = null; + IonSideMenu.snapper = null; }); diff --git a/package.js b/package.js index 7acd1f5..300ace6 100644 --- a/package.js +++ b/package.js @@ -1,7 +1,7 @@ Package.describe({ name: "jandres:ionic", summary: "Ionic components for Meteor. No Angular!", - version: "0.1.39", + version: "0.1.40", git: "https://github.com/JoeyAndres/meteor-ionic.git" }); From 1a484adaf8ebe3cce7c77c18dcee58a0f65b18d1 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Mon, 1 Feb 2016 19:11:37 -0700 Subject: [PATCH 06/59] Some copy/paste error from last commit --- components/ionContent/ionContent.js | 3 +-- package.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/components/ionContent/ionContent.js b/components/ionContent/ionContent.js index 51badc2..02ccddc 100644 --- a/components/ionContent/ionContent.js +++ b/components/ionContent/ionContent.js @@ -2,8 +2,7 @@ Template.ionContent.onCreated(function() { this.ionSideMenuContainerParent = this.parent((t) => t.view.name === "Template.ionSideMenuContainer", true); _.extend(this, { - hasBouncing: (!!this.ionSideMenuContainerParent && this.ionSideMenuContainerParent.hasBouncing) || new ReactiveVar(null), - itemComplex: parent.itemComplex + hasBouncing: (!!this.ionSideMenuContainerParent && this.ionSideMenuContainerParent.hasBouncing) || new ReactiveVar(null) }); this.autorun(() => { diff --git a/package.js b/package.js index 300ace6..9eeece8 100644 --- a/package.js +++ b/package.js @@ -1,7 +1,7 @@ Package.describe({ name: "jandres:ionic", summary: "Ionic components for Meteor. No Angular!", - version: "0.1.40", + version: "0.1.41", git: "https://github.com/JoeyAndres/meteor-ionic.git" }); @@ -28,7 +28,7 @@ Package.onUse(function(api) { "tracker", "session", "jquery", - "jandres:snapjs@2.0.1", + "jandres:snapjs@2.0.2", "fourseven:scss@3.3.3" ], "client"); From 06818b14010473fed608374055c6239f644f8a39 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Wed, 3 Feb 2016 23:29:57 -0700 Subject: [PATCH 07/59] ionScroll ionScroll can now be used with almost all the ionic 1.2 camelCase version of paremeters (except events like onScroll/onZooming). To be able to be fully featured, iscroll would need to be modified heavily. --- README.md | 4 +- components/ionScroll/ionScroll.html | 9 +++ components/ionScroll/ionScroll.js | 87 +++++++++++++++++++++++++++++ package.js | 9 +-- 4 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 components/ionScroll/ionScroll.html create mode 100644 components/ionScroll/ionScroll.js diff --git a/README.md b/README.md index 5e4bbd9..38d065b 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ A [Product Hunt](http://producthunt.com) clone built in Meteor Ionic. (In Progre * [x] Popover * [x] Popup * [ ] Scroll - * [ ] ion-scroll + * [x] ion-scroll: Need to modify iscroll (massive re-engineering and cleanup) to add features. * [ ] ion-infinite-scroll * [x] Side Menus * [x] ion-side-menus @@ -108,7 +108,7 @@ A [Product Hunt](http://producthunt.com) clone built in Meteor Ionic. (In Progre ### Code Style Change: These are code styles that I want to impose on this forked repo. -* Get rid of all session variables [ ] +* [ ] Get rid of all session variables ## License [MIT License](https://github.com/meteoric/meteor-ionic/blob/master/LICENSE) diff --git a/components/ionScroll/ionScroll.html b/components/ionScroll/ionScroll.html new file mode 100644 index 0000000..2d08b13 --- /dev/null +++ b/components/ionScroll/ionScroll.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/components/ionScroll/ionScroll.js b/components/ionScroll/ionScroll.js new file mode 100644 index 0000000..6af1c15 --- /dev/null +++ b/components/ionScroll/ionScroll.js @@ -0,0 +1,87 @@ +Template.ionScroll.onCreated(function() { + this.nativeScrolling = new ReactiveVar(false); + this.direction = new ReactiveVar('y'); + this.locking = new ReactiveVar(true); + this.paging = new ReactiveVar(true); + this.onRefresh = new ReactiveVar(true); + this.onScroll = new ReactiveVar(() => {}); + this.scrollBarX = new ReactiveVar(true); + this.scrollBarY = new ReactiveVar(true); + this.zooming = new ReactiveVar(false); + this.minZoom = new ReactiveVar(0.5); + this.maxZoom = new ReactiveVar(3); + this.hasBouncing = new ReactiveVar(true); + + this.scroller = null; + + _.extend(this, { + set_direction: direction => this.direction.set(_.isUndefined(direction) ? 'y' : direction), + set_locking: locking => this.locking.set(_.isUndefined(locking) ? true : locking), + set_paging: paging => this.paging.set(!!paging), + set_onRefresh: onRefresh => this.onRefresh.set(onRefresh), + set_onScroll: onScroll => { + if (_.isFunction(onScroll)) { + if (!!this.scroller) this.scroller.off(this.onScroll.get(onScroll)); + this.onScroll.set(onScroll); + } + }, + set_scrollBarX: scrollBarX => this.scrollBarX.set(_.isUndefined(scrollBarX) ? true : scrollBarX), + set_scrollBarY: scrollBarY => this.scrollBarY.set(_.isUndefined(scrollBarY) ? true : scrollBarY), + set_zooming: zooming => this.zooming.set(!!zooming), + set_minZoom: minZoom => this.minZoom.set(_.isUndefined(minZoom) ? minZoom : 0.5), + set_maxZoom: maxZoom => this.maxZoom.set(_.isUndefined(maxZoom) ? maxZoom : 3), + set_hasBouncing: hasBouncing => this.hasBouncing.set(_.isUndefined(hasBouncing) ? true: hasBouncing) // todo: Make this platform dependent. + }); + + this.autorun(() => { + if (!Template.currentData()) return; + this.nativeScrolling.set(!!Template.currentData().overflowScroll); + + this.set_direction(Template.currentData().direction); + this.set_locking(Template.currentData().locking); + this.set_paging(Template.currentData().paging); + this.set_onRefresh(Template.currentData().onRefresh); + this.set_onScroll(Template.currentData().onScroll); + this.set_scrollBarX(Template.currentData().scrollBarX); + this.set_scrollBarY(Template.currentData().scrollBarY); + this.set_zooming(Template.currentData().zooming); + this.set_minZoom(Template.currentData().minZoom); + this.set_maxZoom(Template.currentData().maxZoom); + this.set_hasBouncing(Template.currentData().hasBouncing); + }); +}); + +Template.ionScroll.onRendered(function() { + if (!this.nativeScrolling.get()) { // todo: make this reactive? Is there a use case? + this.scroller = new IScroll(this.$(".scroller-wrapper").get(0)); + + if (!!this.onScroll.get()) { this.scroller.on('scroll', this.onScroll.get()); } + + this.autorun(() => { + this.scroller.options = _.extend(this.scroller.options, { + scrollX: this.direction.get().indexOf('x') !== -1, + scrollY: this.direction.get().indexOf('y') !== -1, + freeScroll: !this.locking.get(), + zoom: this.zooming.get(), + zoomMin: this.minZoom.get(), + zoomMax: this.maxZoom.get(), + bounce: this.hasBouncing.get() + }); + + this.scroller.refresh(); + }); + + this.autorun(() => { + // only available in ionscroll-probe. + //if (_.isFunction(this.onScroll.get())) { console.log(this.onScroll.get()); this.scroller.on('onScroll', this.onScroll.get()); } + }); + + this.$(".scroller-wrapper").children().load(() => this.scroller.refresh()); + } +}); + +Template.ionScroll.helpers({ + nativeScrollingClass: function() { + return Template.instance().nativeScrolling.get() ? 'overflow-scroll' : ''; + } +}); \ No newline at end of file diff --git a/package.js b/package.js index 9eeece8..c745900 100644 --- a/package.js +++ b/package.js @@ -1,7 +1,7 @@ Package.describe({ name: "jandres:ionic", summary: "Ionic components for Meteor. No Angular!", - version: "0.1.41", + version: "0.1.43", git: "https://github.com/JoeyAndres/meteor-ionic.git" }); @@ -13,9 +13,6 @@ Cordova.depends({ Package.onUse(function(api) { api.versionsFrom("1.0"); - api.use([ - "fourseven:scss@3.3.3" - ]); api.use([ "jandres:template-extension@4.0.4", @@ -28,6 +25,7 @@ Package.onUse(function(api) { "tracker", "session", "jquery", + "jandres:iscroll-zoom@5.1.4", "jandres:snapjs@2.0.2", "fourseven:scss@3.3.3" ], "client"); @@ -118,6 +116,9 @@ Package.onUse(function(api) { "components/ionReorderButton/ionReorderButton.html", "components/ionReorderButton/ionReorderButton.js", + "components/ionScroll/ionScroll.html", + "components/ionScroll/ionScroll.js", + "components/ionSideMenu/ionSideMenu.html", "components/ionSideMenu/ionSideMenu.js", From f8548bfd37f3512d45a86a6d429b92de532fd7c3 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Fri, 5 Feb 2016 06:38:56 -0700 Subject: [PATCH 08/59] Upgraded jandres:iscroll-zoom to 5.1.6 to stopPropagation --- README.md | 2 +- components/ionScroll/ionScroll.js | 3 ++- package.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 38d065b..5f4dd47 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ A [Product Hunt](http://producthunt.com) clone built in Meteor Ionic. (In Progre * [x] Popover * [x] Popup * [ ] Scroll - * [x] ion-scroll: Need to modify iscroll (massive re-engineering and cleanup) to add features. + * [x] ion-scroll * [ ] ion-infinite-scroll * [x] Side Menus * [x] ion-side-menus diff --git a/components/ionScroll/ionScroll.js b/components/ionScroll/ionScroll.js index 6af1c15..93ceae7 100644 --- a/components/ionScroll/ionScroll.js +++ b/components/ionScroll/ionScroll.js @@ -65,7 +65,8 @@ Template.ionScroll.onRendered(function() { zoom: this.zooming.get(), zoomMin: this.minZoom.get(), zoomMax: this.maxZoom.get(), - bounce: this.hasBouncing.get() + bounce: this.hasBouncing.get(), + eventPassthrough: true }); this.scroller.refresh(); diff --git a/package.js b/package.js index c745900..cc5c40d 100644 --- a/package.js +++ b/package.js @@ -25,7 +25,7 @@ Package.onUse(function(api) { "tracker", "session", "jquery", - "jandres:iscroll-zoom@5.1.4", + "jandres:iscroll-zoom@5.1.6", "jandres:snapjs@2.0.2", "fourseven:scss@3.3.3" ], "client"); From c685732c8f823b332e307f201cbadbccb5031711 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Sat, 6 Feb 2016 11:28:48 -0700 Subject: [PATCH 09/59] Some fixes, to ionScroll --- components/ionScroll/ionScroll.html | 5 ++- components/ionScroll/ionScroll.js | 12 ++++--- package.js | 6 ++-- styles/main.scss | 52 ++++++++++++++++++++++++++++- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/components/ionScroll/ionScroll.html b/components/ionScroll/ionScroll.html index 2d08b13..6d74feb 100644 --- a/components/ionScroll/ionScroll.html +++ b/components/ionScroll/ionScroll.html @@ -1,9 +1,8 @@ \ No newline at end of file diff --git a/components/ionScroll/ionScroll.js b/components/ionScroll/ionScroll.js index 93ceae7..c1f3f3f 100644 --- a/components/ionScroll/ionScroll.js +++ b/components/ionScroll/ionScroll.js @@ -28,8 +28,8 @@ Template.ionScroll.onCreated(function() { set_scrollBarX: scrollBarX => this.scrollBarX.set(_.isUndefined(scrollBarX) ? true : scrollBarX), set_scrollBarY: scrollBarY => this.scrollBarY.set(_.isUndefined(scrollBarY) ? true : scrollBarY), set_zooming: zooming => this.zooming.set(!!zooming), - set_minZoom: minZoom => this.minZoom.set(_.isUndefined(minZoom) ? minZoom : 0.5), - set_maxZoom: maxZoom => this.maxZoom.set(_.isUndefined(maxZoom) ? maxZoom : 3), + set_minZoom: minZoom => this.minZoom.set(_.isUndefined(minZoom) ? 0.5 : minZoom), + set_maxZoom: maxZoom => this.maxZoom.set(_.isUndefined(maxZoom) ? 3 : maxZoom), set_hasBouncing: hasBouncing => this.hasBouncing.set(_.isUndefined(hasBouncing) ? true: hasBouncing) // todo: Make this platform dependent. }); @@ -53,7 +53,10 @@ Template.ionScroll.onCreated(function() { Template.ionScroll.onRendered(function() { if (!this.nativeScrolling.get()) { // todo: make this reactive? Is there a use case? - this.scroller = new IScroll(this.$(".scroller-wrapper").get(0)); + this.scroller = new IScroll(this.$(".scroller-wrapper").get(0), { + mouseWheel: true, + wheelAction: 'zoom' + }); if (!!this.onScroll.get()) { this.scroller.on('scroll', this.onScroll.get()); } @@ -65,8 +68,7 @@ Template.ionScroll.onRendered(function() { zoom: this.zooming.get(), zoomMin: this.minZoom.get(), zoomMax: this.maxZoom.get(), - bounce: this.hasBouncing.get(), - eventPassthrough: true + bounce: this.hasBouncing.get() }); this.scroller.refresh(); diff --git a/package.js b/package.js index cc5c40d..ccd96e9 100644 --- a/package.js +++ b/package.js @@ -1,7 +1,7 @@ Package.describe({ name: "jandres:ionic", summary: "Ionic components for Meteor. No Angular!", - version: "0.1.43", + version: "0.1.44", git: "https://github.com/JoeyAndres/meteor-ionic.git" }); @@ -25,8 +25,8 @@ Package.onUse(function(api) { "tracker", "session", "jquery", - "jandres:iscroll-zoom@5.1.6", - "jandres:snapjs@2.0.2", + "jandres:iscroll-zoom@5.1.9", + "jandres:snapjs@2.0.6", "fourseven:scss@3.3.3" ], "client"); diff --git a/styles/main.scss b/styles/main.scss index 42398f1..acfa269 100644 --- a/styles/main.scss +++ b/styles/main.scss @@ -4,4 +4,54 @@ width: initial; overflow: initial; } -} \ No newline at end of file +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + -ms-touch-action: none; +} + +body,ul,li { + padding: 0; + margin: 0; + border: 0; +} + +body { + font-size: 12px; + font-family: ubuntu, helvetica, arial; + overflow: hidden; /* this is important to prevent the whole page to bounce */ +} + +.scroller-wrapper { + position: absolute; + z-index: 1; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #ccc; + overflow: hidden; +} + +.scroller-wrapper > * { + -webkit-transform-origin: 0 0; + -moz-transform-origin: 0 0; + -ms-transform-origin: 0 0; + -o-transform-origin: 0 0; + transform-origin: 0 0; + + position: absolute; + z-index: 1; + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); +} From 9552e379d8062c1d8384b6c4e9a4bca312e63cf7 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Sun, 7 Feb 2016 02:18:35 -0700 Subject: [PATCH 10/59] README logo --- README.md | 2 +- meteoric-logo.png | Bin 0 -> 7056 bytes package.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 meteoric-logo.png diff --git a/README.md b/README.md index 5f4dd47..cbc1c14 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![](http://f.cl.ly/items/391y4708420P0H001k1G/meteoric.png) +![Meteoric Logo](meteoric-logo.png) # meteor-ionic diff --git a/meteoric-logo.png b/meteoric-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ddc64627559e5a0c3f044a3e2f0fef2c0935d79d GIT binary patch literal 7056 zcmV;B8*k)^P)`4s4megJI$Gt6I zHQHKBcWXJHk3QeM-kAxiI6RdH}#-KWYMD=Lap#l`zuYe%V2sEAZ5 zWmBmZ20()`(-fQqeA)jSHNe&H23`Q30Dh-M zUIs|annifksu2JT6s?{$iy5m{(O}Fz3cd=|02RG>)gIu_z+ZtDAmsn;1AGoB>BT!& z081QruvDaF!+GaX{?J1s0O%_vj3F!nfNRV}z(U}Q-8_~8{s62862Ko7ywRLYZk@Jl zSxZmq-C&HP;0Ogr0+aozWEyZ{52le9fL~~lUjif|5yJ8K2mrETd^Acp7Q;2B9Jmf| z#CJyT0?U1oyjqLA+xDJ*=zO=T3LQ`;8;lw4z!?^1`V#2yE*}3S@FOj<$*rtJFIzSO zfF5`?*1Cu(r<$2)Oz$04Z1D`RJ`O;#bX~VpElrLSHZtG}Z9&kQzT4!ntaJ?2; z@uzT@LlcP+0JQ7SxpOIh^if=6&IfJ>KHtG@O~9QF++8ZtQb>T^Xbh7?Xoy6Llu`?T z%YbkPw{8Qj(jrTmA`vDRz|^4fJkrr3xW>!@{tGyygWK*_a7$QZ(_qrBU1N>{z7Ndr z;PP9*d@b@+P|wK+Ie8f|kx2UEIM`spv{L07b0=_l2e(|OMQ&yYl55O&ftxz$_nWlH^|{nz zaxMYZEnMi==uCHw+1UZ}|5b~qp~`#!E%H+>;sCd0g5EXeJ>^+1v5OWBEdaR2Fn#f2 zTw`tnUIGpbTz&>PSc_cqNKFkxof0)QXp!##2m7m;G>rvbb&a`6En0-TcbXg$5@3vL z%qHNfKv^p=SBuP0upQvsB};}p0K)!nRMu|SB45I@Xa@CzYh7cSaGd>Ls;C(7_NW|; z{?mb{GKyCgLv*4@Gb4mMdv*~eC98ll1H~5jk`{Sk&`E&CxpULZzorA`|GgHeXtj2P z`DrGUl(cG*nZU&vgY$XUn9BhY10)UlT>=Emxc31U1TO7Bsi`^^+xn?zDk=!CSdsn6 zsje=z$z)4e0o&SpAu-4KdeWFc@%>ul!hx_E^s5ADFec6XCo`D;`=X+vF|92vTRWT= zSFFH|ME1|NzEm=~A4-i$M59AT8YuOq7C8uSLn}=exW+sY_KaqQ34n%Zlrpb@F9T-; zF5Rs~PHJgsVe0bb9ZwmRm6SNn$8Iz_HJduiU+-bB(Fj@i=ZjNxeSn0B$sj_L#p8&lM}E z?EP1^QX~mQMb>^ti`<;3t|nYt+fxwE29jFj@t)jYKYKP)R<0Z@dcgwVu0YW%TI4u4 z5<&M-|LKzi*cgeVng4hO^DoMl`Dx@AS=$QS<{I0b8;_#taYsY z5IC(B-VVW-`kt;1M6L&RxyCFnt*h%)Yt|x5ff>DRW3K5HZ!>8=uqIIaOp`IA8+u_Q z$O-^<(IUbkD0ngO?2h7=mJ=GHQ6^@b#3`$*L#0y9!1}auwkFFRztVV|mZG9}fa8GpV5I*dbUF zghZD8o3#??8gn!7-9XWJl}f!4?%)_!hEg{QxKfM!q&^a1N-q>I*O)S3129#K)c0hx z9J^!*t}#o2$y%heD|4!A%+GvPV&Xy8CvA&Ztt(IMtjT2$MGCB#{;`FR~2^A zBGjTqxW@bnNNACp)-7Df^u>z{ePwW=g?j@<$1BHK*Hin6%Chsfv)5zyDJ^nlFP3)) z;%#nZwYq&@VcGYs`-V%ufLo*=|hu%>FYl-!*2979r6K#YKxe zf#-|=NP4@p%ll(7+(-m1auVL5&v#TZc|_)Y)0Zrvsk$01Vt}w~%x_AUE$jCUn0{tH zKqY&fN=CcJ+!5}fb4|ArpurfFwTGhAJAvXFE%MvmHo7qqA*Iw8fTt~#DroJcB)AYy z{QX3We6Ksy2i(w`d`VEQMH7?5l$Apu7$6oXIs(|* zUB9XxzWfCsy-fPOKa$$$&PkIv>*%9ZOL4L7saNZUXcWiW#d{GL7lMx`i`ZWL@NeTC zCb>t8%x{cDD2>NiQ&Ypq0Wi4H=oG9i119)-K-y&dh(CIE;2qr9tgPJ-j>li~-(L?{ zEiyG9Ub|WL#cI40(g5qU$nkxW0B%(kIL>K!j+<#3YaQqPvJ54|HRfPoC(xYDXm*VW z11|$ds#NN|u2vIWyyJ(!jX<3iiTGv5$S*D*Am#&%20nC+`8A#n)2VSO zAN2H$8EBDf@El43zR=z7Xr};};6>p0w?NUgGh;D3=y0Gz&hb^7Bqf#O@U)&Vv~qqJIE z1*BzMN{fWD6h%J+ZY@qGw@%Dr9qk(P72sjuJT3C;j+Do8Z%uzO?OmqwMhY$ti`<(w zIu*!N(25uN1YmA)GFjUZA3Br(lf2RY!$8rE*;KyX^J%*RxUD4=Iws3C{%?E<@{nuH z6=^G7TG4T=Eq0B0wosUlCvWd@jd{8`nJj8794C`SfzLneY!H4*0&Fk_$J2HclT8q4rC4#TQJ45$VEpn}2HXdjI_AStr*e(lG%S7JD`>J5x zJV&**CWB)Or4l+8>wXGwjd=k0y8rbafBP|O3Y-yefy?}PzBkLedw^f~HiLpE1z!q_ zJYN9Qz}@UUZ1SKMIlo6bfNM<9Aak^}_RZc}!2)*pt-z&PB%bAu_&4B61|L=Ueu{aa zL?nV$>S*Bg!1SuR1i+S>8g?}|e;s(hFKW^vrTu_Je1+sg;0i5re-_Uk=Nn`OBhAnv z&*WJOZ1fFS02dX(eUk!)taeJ^k)G-Fn}MRA^+zLo%UKdQ6u7G=erye%9pcDA%zS{= zt})}AI&n$M5e0cTe^Q1O)5%cW_5s@L{3)e+;B4yvEiwOgHz$8o$M%%#8r*O;Z@ zSd5?qagF&p?a#^&cKV`g%s=F@{$B+?&z%t1a8sRW{}x7FlOPL=? zv77r(^wIg07CF<6L~x3VN>a(>9)=*bDz#rxGWoIY#g1p66v1fpP^^8Yqt$>DU`Fsz z=R4Ul-+%EF7EV*Gt&?rf?tc+D8#u!?W}S}5NhOnu8KP8V?E|@HK7fwJ{zPD#*fnNq zM@rz?fzQ3(@b-fwn?!p+b#?#az}JA|e1p*i3|*S*8Z#kJXA*-EIL_<{0ACJ#eyT4) z7yQ#J-{|Ry6)o~GUf8%WL!a)*G{+7!ZSZ*cWT0qfM*x`7R%q=r;XX9-5%2WMmB6Jv zIc#AUEy7Xieuf8CxW@B+5x~d=5xSj{IWfz@%a0Fsqow-OQ`p} zYrg_q)tysiWPkf65cb9@_?h$$^{d`}4efIeXzR7qmI? z6^0+xOwVCtRDb0WJk~dP<}0tX0boMl!t3SzY_?3F0J2^Vp0B&cJXpSTY0#mvVi*D- zr@z@>c?84mPY6x{N43ojFd*J6{;~2_ynTRb*O=u2*YRVAApj=lxw&N1f&lme zdy%kZ05wrUp4-$>z}c=bFC-$7aST(+HZEdcDU?iZ3w%8+2!J+^-_Q2qcvd^3Lv?kR zZqk!QDE7>fe+!&!m1-ES0Ki(E%jzaAUmlFlTms-^Fsx6TFdHKgikq8T-AH6&mkkj? zi@b%GAmOtNUz(7II@>@{AB~0_CzT5Bx$o=~!s2mS9VeA4Dtgm3rduUPi);Yy9L50n zLLTb;OWTxS?f#Bx?_&K)U*BFTt5nb_q~G!fq@#gY&;Rd1|9k)= zly_Vi|iJ_A{^p8@M1K7t(~7&UGEdVtr4F#tB_ za|$Sr#af+A&Cso^EFNGGEC;SgdZK?c);0`d0Oa(yzczPn&;zU$_L_pX1=fa#4|GHP zvw=4NAL2M43|H>^hOnH`o@DZ_ zt+#>z2+qTkaX+4 z^3ut4Xp1uvIo`O@BD;X4!+`nA zwaEJua+tKT!PE0*1d3L-t@=AE1ZIYF91z)$pz-Zt0DvnSa(p=`={8hXqf)6+ zDnHw#15Tl$q63mGExU3ZF2*%x3|_eV^mMB_k^t?D6Aql4>kcZft))!lufQckkpGuW zUb=Kwo?-WBuOAGVkx6V z{8}yY(?S|@O~&k(g!^b8;`VPaIhAcV;yHxT4yR0ny_*u%GzLtkhio* ztgEg4E~kL?RaF!_P6h3qHV?M2y%4i#zd2(D%K^61{iAa26jAbZ4*Y|gr> zDyG*Jo=L*@CcB zIbc8k^2c}wHQ&Z_mriJrwxEg|BM~MQV4~;___P-IqW|?dEz)hU?w({1N5MGTK73rY z7Kt%Jg~a@MKrm1Ip}@P{?Lnw4D)<@jk7@l2*vCR^Ax;IuiTq5jpYf$wI{3qnwMdU_ zH%?D(sz2+jbS~gO`$Zw3ZiM*-U>osxuqwd(_j}6xtRz6gym_eB*6F}Yf#L;POy7-K(-X$f*F(HcUmF*W$8o*%aMKB7rSc`gB7nZ4bXczQ z{d`KUF*jr||5YtA-R%eF_lpDw8YarTfY6ze;C?M~VWO&va9!O9XM56@#!%*k)V&wD zFldU=BFCrux<5$uXT#lS6zzdv744Hzznt2-^?2v7!>k?z+p77bCSyo}vap7|Te|>H zYLS_ZkqD*z*oRJkHeSbKG*blBTMR-oFv)Wy-_Ea5@zc7$>I|KdW_C4T$ zHP*`uoCp-3-jYh~a*e4;>kh+^>FNJ-@v^lAnZL<~LZb(o`8ff=_+R}B1s`b<4crvC z6apS`jai$rb}V>q%|o4@-kUgktZU3Oc#+Y9e7Hl4OjNC{IY}m=@__uwcu}f8pKVlg|E0bp;ukwks{ExT}^J7E4DT4Z!6nOq0tjQM$y02v)}2wq&GFLiMFhNMzAPl?4^(}t13 zMD>vfQ{r*#rI)J2wr$tr?cil5<9Hc3M~l3buk!{@K0C>JZp)|n0C+3o^1pUyx@NF&f&>w4Rm{{KCj;H$G|Gj-)k z{Jg{eO#3~BAgu)+aI9TY8jpVnP;U%Vax_ge8bhgHmmh?=2KX9qN@tG%-`66wg$7zx zh$CrJRTan7)!`a*4DdbL$GOX-KLK&zDMzW*rLoxdju_SJQSEPcw|zI}5DTXRGx5$H zc9reqA>d{$(okPj#gqcv3@bURZh3@)n4Y1fxj76aQ&?hC<{^ghs^n{`8&Gf&xAgb}kyjdv#0000 Date: Sun, 7 Feb 2016 12:36:57 -0700 Subject: [PATCH 11/59] Upgraded snap.js to pull more touch fixes --- package.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.js b/package.js index 6d353ac..374f982 100644 --- a/package.js +++ b/package.js @@ -1,7 +1,7 @@ Package.describe({ name: "jandres:ionic", summary: "Ionic components for Meteor. No Angular!", - version: "0.1.45", + version: "0.1.46", git: "https://github.com/JoeyAndres/meteor-ionic.git" }); @@ -26,7 +26,7 @@ Package.onUse(function(api) { "session", "jquery", "jandres:iscroll-zoom@5.1.9", - "jandres:snapjs@2.0.6", + "jandres:snapjs@2.0.8", "fourseven:scss@3.3.3" ], "client"); From 69a85179c852686ff977802a1b3c361b7c916ef3 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Sun, 7 Feb 2016 23:59:13 -0700 Subject: [PATCH 12/59] page transitions more robust --- .../ionNavBackButton/ionNavBackButton.js | 16 ++--- components/ionNavBar/ionNavBar.html | 4 +- components/ionNavBar/ionNavBar.js | 4 +- components/ionNavView/ionNavView.html | 3 +- components/ionNavView/ionNavView.js | 41 +----------- components/ionView/ionView.html | 2 +- components/ionView/ionView.js | 42 ++++++++++++- lib/utility.js | 38 +++++++++++ package.js | 9 ++- styles/_transitions.scss | 63 +++++++++++++++++++ styles/main.scss | 7 ++- 11 files changed, 175 insertions(+), 54 deletions(-) create mode 100644 lib/utility.js create mode 100644 styles/_transitions.scss diff --git a/components/ionNavBackButton/ionNavBackButton.js b/components/ionNavBackButton/ionNavBackButton.js index 086b1a1..6619d70 100644 --- a/components/ionNavBackButton/ionNavBackButton.js +++ b/components/ionNavBackButton/ionNavBackButton.js @@ -6,11 +6,11 @@ Router.onStop(function () { Template.ionNavBackButton.events({ 'click': function (event, template) { - $('[data-nav-container]').addClass('nav-view-direction-back'); - $('[data-navbar-container]').addClass('nav-bar-direction-back'); + $('[data-nav-container]').attr('nav-view-direction', 'back'); + $('[data-navbar-container]').attr('nav-bar-direction', 'back'); //get most up-to-date url, if it exists - backUrl = template.getBackUrl() + backUrl = template.getBackUrl(); if (backUrl) { Router.go(backUrl); } else { @@ -19,11 +19,11 @@ Template.ionNavBackButton.events({ } }); -Template.ionNavBackButton.created = function () { +Template.ionNavBackButton.onCreated(function () { this.data = this.data || {}; -}; +}); -Template.ionNavBackButton.rendered = function () { +Template.ionNavBackButton.onRendered(function () { var self = this; this.getBackUrl = function () { var backUrl = null; @@ -44,7 +44,9 @@ Template.ionNavBackButton.rendered = function () { } return backUrl; }; -}; + + +}); Template.ionNavBackButton.helpers({ classes: function () { diff --git a/components/ionNavBar/ionNavBar.html b/components/ionNavBar/ionNavBar.html index 220edaf..fae9642 100644 --- a/components/ionNavBar/ionNavBar.html +++ b/components/ionNavBar/ionNavBar.html @@ -1,5 +1,7 @@ diff --git a/components/ionView/ionView.js b/components/ionView/ionView.js index 0466a7a..d7d7672 100644 --- a/components/ionView/ionView.js +++ b/components/ionView/ionView.js @@ -1,4 +1,4 @@ -Template.ionView.rendered = function () { +Template.ionView.onRendered(function () { // Reset our transition preference IonNavigation.skipTransitions = false; @@ -8,11 +8,31 @@ Template.ionView.rendered = function () { $('.overflow-scroll').not('.nav-view-leaving .overflow-scroll').scrollTop(IonScrollPositions[routePath]); delete IonScrollPositions[routePath]; } -}; + + let $view = this.$('.view'); + $view.attr('nav-view', 'entering'); + + addPrefixedEvent($view.get(0), 'animationend', e => { + $view.attr('nav-view', 'active'); + $('[data-nav-container]').attr('nav-view-direction', 'forward'); + $('[data-navbar-container]').attr('nav-bar-direction', 'forward'); + }); +}); + +Template.ionView.onDestroyed(function () { + // Get this now, so that when Meteor.setTimeout is called, Template.instance() retrieves the correct element. + let $view = this.$('.view'); + $view.attr('nav-view', 'leaving'); + + addPrefixedEvent($view.get(0), 'animationend', e => { + removePrefixedEvent($view.get(0), 'animationend'); + $view.remove(); + }); +}); Template.ionView.helpers({ classes: function () { - var classes = ['view']; + var classes = []; if (this.class) { classes.push(this.class); @@ -26,3 +46,19 @@ Template.ionView.helpers({ } } }); + +// For some reason jQuery is not calling these callbacks. +// TODO: Investigate jQuery. +var pfx = ["webkit", "moz", "MS", "o", ""]; +function addPrefixedEvent(element, type, callback) { + for (var p = 0; p < pfx.length; p++) { + if (!pfx[p]) type = type.toLowerCase(); + element.addEventListener(pfx[p]+type, callback, false); + } +} +function removePrefixedEvent(element, type, callback) { + for (var p = 0; p < pfx.length; p++) { + if (!pfx[p]) type = type.toLowerCase(); + element.removeEventListener(pfx[p]+type, callback, false); + } +} diff --git a/lib/utility.js b/lib/utility.js new file mode 100644 index 0000000..ae5782c --- /dev/null +++ b/lib/utility.js @@ -0,0 +1,38 @@ +METEORIC = {}; + +METEORIC.UTILITY = { + transitionend_events: [ + 'otransitionend', // oTransitionEnd in very old Opera + 'transitionend', + 'webkittransitionend', + 'transitionend' + ], + animationend_events: [ + 'webkitanimationend' , + 'mozanimationend', + 'oanimationend', + 'MSanimationEnd', + 'animationend' + ], + transitionend_event_name: (function() { + var i, + undefined, + el = document.createElement('div'), + transitions = { + 'transition': 'transitionend', + 'OTransition': 'otransitionend', // oTransitionEnd in very old Opera + 'MozTransition': 'transitionend', + 'WebkitTransition': 'webkitTransitionEnd' + }; + + for (i in transitions) { + if (transitions.hasOwnProperty(i) && el.style[i] !== undefined) { + return transitions[i]; + } + } + })() +}; + +Meteor.startup(function() { + +}); \ No newline at end of file diff --git a/package.js b/package.js index 374f982..bde5c8f 100644 --- a/package.js +++ b/package.js @@ -27,7 +27,11 @@ Package.onUse(function(api) { "jquery", "jandres:iscroll-zoom@5.1.9", "jandres:snapjs@2.0.8", - "fourseven:scss@3.3.3" + "fourseven:scss@3.3.3", + + "jandres:meteoric-sass", + + "cwaring:modernizr" ], "client"); api.addFiles([ @@ -37,10 +41,13 @@ Package.onUse(function(api) { ], "client"); api.addFiles([ + "styles/_transitions.scss", "styles/main.scss" ], "client"); api.addFiles([ + "lib/utility.js", + "components/ionActionSheet/ionActionSheet.html", "components/ionActionSheet/ionActionSheet.js", diff --git a/styles/_transitions.scss b/styles/_transitions.scss new file mode 100644 index 0000000..2281492 --- /dev/null +++ b/styles/_transitions.scss @@ -0,0 +1,63 @@ +// For some reason, animation works on more complex content i.e. ionList with ionItems in complex state. Transition +// don't work in such cases. +@mixin nav-view-transitions($transition-duration) { + &[nav-view-direction="forward"] [nav-view="entering"] { + @include animation(nav-view-direction-entering-forward $transition-duration); + } + + @-webkit-keyframes nav-view-direction-entering-forward { + from { -webkit-transform: translate3d(100%, 0px, 0px); } + to { -webkit-transform: translate3d(0%, 0px, 0px); } + } + @keyframes nav-view-direction-entering-forward { + from { transform: translate3d(100%, 0px, 0px); } + to { transform: translate3d(0%, 0px, 0px); } + } + + &[nav-view-direction="back"] [nav-view="entering"] { + @include animation(nav-view-direction-entering-backward $transition-duration); + } + + @-webkit-keyframes nav-view-direction-entering-backward { + from { -webkit-transform: translate3d(-100%, 0px, 0px); } + to { -webkit-transform: translate3d(0%, 0px, 0px); } + } + @keyframes nav-view-direction-entering-backward { + from { transform: translate3d(-100%, 0px, 0px); } + to { transform: translate3d(0%, 0px, 0px); } + } + + &[nav-view-direction="forward"] [nav-view="leaving"] { + @include animation(nav-view-direction-leaving-forward $transition-duration); + } + + @-webkit-keyframes nav-view-direction-leaving-forward { + from { -webkit-transform: translate3d(0%, 0px, 0px); } + to { -webkit-transform: translate3d(-100%, 0px, 0px); } + } + @keyframes nav-view-direction-leaving-forward { + from { transform: translate3d(0%, 0px, 0px); } + to { transform: translate3d(-100%, 0px, 0px); } + } + + &[nav-view-direction="back"] [nav-view="leaving"] { + @include animation(nav-view-direction-leaving-backward $transition-duration); + } + + @-webkit-keyframes nav-view-direction-leaving-backward { + from { -webkit-transform: translate3d(0%, 0px, 0px); } + to { -webkit-transform: translate3d(100%, 0px, 0px); } + } + @keyframes nav-view-direction-leaving-backward { + from { transform: translate3d(0%, 0px, 0px); } + to { transform: translate3d(100%, 0px, 0px); } + } +} + +[nav-view-transition="ios"] { + @include nav-view-transitions($ios-transition-duration); +} + +[nav-view-transition="android"] { + @include nav-view-transitions($android-transition-duration); +} \ No newline at end of file diff --git a/styles/main.scss b/styles/main.scss index acfa269..3a5cd03 100644 --- a/styles/main.scss +++ b/styles/main.scss @@ -1,3 +1,8 @@ +@import '{jandres:meteoric-sass}/scss/ionic.scss'; +@import '{jandres:meteoric-sass}/scss/ionicons/ionicons.scss'; + +@import "_transitions.scss"; + .snap-drawers { .snap-drawer-left {} .item-options.snap-drawer.snap-drawer-right { @@ -54,4 +59,4 @@ body { -ms-transform: translateZ(0); -o-transform: translateZ(0); transform: translateZ(0); -} +} \ No newline at end of file From 823eed6a8c6dad502234f163ba09d472297e6a48 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Wed, 10 Feb 2016 06:10:23 -0700 Subject: [PATCH 13/59] nav-view and header transitions. --- components/ionContent/ionContent.js | 2 +- components/ionHeaderBar/ionHeaderBar.html | 6 +- components/ionHeaderBar/ionHeaderBar.js | 120 ++++----- .../ionNavBackButton/ionNavBackButton.js | 10 +- components/ionNavBar/ionNavBar.html | 21 +- components/ionNavBar/ionNavBar.js | 238 ++++++++++-------- components/ionNavView/ionNavView.js | 15 +- components/ionView/ionView.js | 141 +++++++---- lib/utility.js | 52 ++-- styles/_transitions.scss | 116 +++++---- styles/main.scss | 3 - 11 files changed, 404 insertions(+), 320 deletions(-) diff --git a/components/ionContent/ionContent.js b/components/ionContent/ionContent.js index 02ccddc..004e5f3 100644 --- a/components/ionContent/ionContent.js +++ b/components/ionContent/ionContent.js @@ -26,7 +26,7 @@ Template.ionContent.helpers({ classes.push('overflow-scroll'); } - if (Session.get('hasHeader')) { + if (METEORIC.has_header.get()) { classes.push('has-header'); } diff --git a/components/ionHeaderBar/ionHeaderBar.html b/components/ionHeaderBar/ionHeaderBar.html index 9e25640..ecf80a0 100644 --- a/components/ionHeaderBar/ionHeaderBar.html +++ b/components/ionHeaderBar/ionHeaderBar.html @@ -1,5 +1,7 @@ diff --git a/components/ionHeaderBar/ionHeaderBar.js b/components/ionHeaderBar/ionHeaderBar.js index adad638..c46891e 100644 --- a/components/ionHeaderBar/ionHeaderBar.js +++ b/components/ionHeaderBar/ionHeaderBar.js @@ -1,78 +1,78 @@ IonHeaderBar = { - alignTitle: function () { - var align = this.alignTitle; - var $title = this.$('.title'); + alignTitle: function () { + var align = this.alignTitle; + var $title = this.$('.title'); - if (Platform.isAndroid() && !this.alignTitle) { - $title.addClass('title-left'); - return; - } + if (Platform.isAndroid() && !this.alignTitle) { + $title.addClass('title-left'); + return; + } - if (align === 'center') { - $title.addClass('title-center'); - } else if (align === 'left') { - $title.addClass('title-left'); - } else if (align === 'right') { - $title.addClass('title-right'); - } - }, + if (align === 'center') { + $title.addClass('title-center'); + } else if (align === 'left') { + $title.addClass('title-left'); + } else if (align === 'right') { + $title.addClass('title-right'); + } + }, - positionTitle: function () { - var $title = this.$('.title'); - var $leftButton = $('.button.pull-left'); - var $rightButton = $('.button.pull-right'); + positionTitle: function () { + var $title = this.$('.title'); + var $leftButton = $('.button.pull-left'); + var $rightButton = $('.button.pull-right'); - // Find out which button is wider, - // use that to offset the title on both sides - var leftButtonWidth = 0; - var rightButtonWidth = 0; - if ($leftButton.length) { - $leftButton.each(function(index, element){ - leftButtonWidth += $(element).outerWidth(); - }); - } - if ($rightButton.length) { - $rightButton.each(function(index, element){ - rightButtonWidth += $(element).outerWidth(); - }); - } + // Find out which button is wider, + // use that to offset the title on both sides + var leftButtonWidth = 0; + var rightButtonWidth = 0; + if ($leftButton.length) { + $leftButton.each(function(index, element){ + leftButtonWidth += $(element).outerWidth(); + }); + } + if ($rightButton.length) { + $rightButton.each(function(index, element){ + rightButtonWidth += $(element).outerWidth(); + }); + } - // If we're on Android, we only care about the left button - var margin; - if (Platform.isAndroid()) { - margin = leftButtonWidth; - } else { - margin = Math.max(leftButtonWidth, rightButtonWidth); + // If we're on Android, we only care about the left button + var margin; + if (Platform.isAndroid()) { + margin = leftButtonWidth; + } else { + margin = Math.max(leftButtonWidth, rightButtonWidth); + } + $title.css('left', margin); + $title.css('right', margin); } - $title.css('left', margin); - $title.css('right', margin); - } }; Template.ionHeaderBar.onCreated(function() { - this.alignTitle = this.data? (this.data.alignTitle || 'center') : 'center'; + this.alignTitle = this.data? (this.data.alignTitle || 'center') : 'center'; }); -Template.ionHeaderBar.rendered = function () { - Session.set('hasHeader', true); - IonHeaderBar.alignTitle.call(this); - IonHeaderBar.positionTitle.call(this); -}; +Template.ionHeaderBar.onRendered(function () { + METEORIC.has_header.set(true); + IonHeaderBar.alignTitle.call(this); + IonHeaderBar.positionTitle.call(this); +}); -Template.ionHeaderBar.destroyed = function () { - Session.set('hasHeader', false); -}; +Template.ionHeaderBar.onDestroyed(function () { + METEORIC.has_header.set(false); +}); Template.ionHeaderBar.helpers({ - classes: function () { - var classes = ['bar', 'bar-header']; + classes: function () { + var classes = ['bar', 'bar-header']; - if (this.class) { - classes.push(this.class); - } else { - classes.push('bar-stable'); - } + if (this.class) { + classes.push(this.class); + } else { + classes.push('bar-stable'); + } - return classes.join(' '); - } + return classes.join(' '); + } }); diff --git a/components/ionNavBackButton/ionNavBackButton.js b/components/ionNavBackButton/ionNavBackButton.js index 6619d70..1af11ec 100644 --- a/components/ionNavBackButton/ionNavBackButton.js +++ b/components/ionNavBackButton/ionNavBackButton.js @@ -8,7 +8,7 @@ Template.ionNavBackButton.events({ 'click': function (event, template) { $('[data-nav-container]').attr('nav-view-direction', 'back'); $('[data-navbar-container]').attr('nav-bar-direction', 'back'); - + //get most up-to-date url, if it exists backUrl = template.getBackUrl(); if (backUrl) { @@ -29,13 +29,13 @@ Template.ionNavBackButton.onRendered(function () { var backUrl = null; self.data = self.data || {}; - + if (self.data.href) { backUrl = self.data.href; } - + if (self.data.path) { - backRoute = Router.routes[self.data.path] + backRoute = Router.routes[self.data.path]; if (!backRoute) { console.warn("back to nonexistent route: ", self.data.path); return; @@ -44,8 +44,6 @@ Template.ionNavBackButton.onRendered(function () { } return backUrl; }; - - }); Template.ionNavBackButton.helpers({ diff --git a/components/ionNavBar/ionNavBar.html b/components/ionNavBar/ionNavBar.html index fae9642..8c29bbf 100644 --- a/components/ionNavBar/ionNavBar.html +++ b/components/ionNavBar/ionNavBar.html @@ -1,9 +1,16 @@ + + \ No newline at end of file diff --git a/components/ionNavBar/ionNavBar.js b/components/ionNavBar/ionNavBar.js index dce36bf..2b83d72 100644 --- a/components/ionNavBar/ionNavBar.js +++ b/components/ionNavBar/ionNavBar.js @@ -1,119 +1,139 @@ -Template.ionNavBar.created = function () { - this.data = this.data || {}; - - if (Platform.isAndroid()) { - this.transition = 'android'; - } else { - this.transition = 'ios'; - } - - // Allow overriding the transition - if (this.data.transition) { - this.transition = this.data.transition; - } - - if (this.transition === 'ios') { - this.transitionDuration = 450; - } else { - this.transitionDuration = 200; - } -}; - -Template.ionNavBar.rendered = function () { - Session.set('hasHeader', true); - - IonHeaderBar.alignTitle.call(this); - IonHeaderBar.positionTitle.call(this); - - var template = this; - var container = this.find('[data-navbar-container]'); - container._uihooks = { - insertElement: function(node, next) { - var $node = $(node); - - if (!$node.hasClass('title') && !$node.hasClass('button') || IonNavigation.skipTransitions) { - container.insertBefore(node, next); - // Changing tabs skips transition animations, but we still want to update the position of the title - IonHeaderBar.alignTitle.call(template); - IonHeaderBar.positionTitle.call(template); - return; - } - - $node.attr('nav-bar', 'entering'); - if ($node.hasClass('title')) { - container.insertBefore(node, next); - $node.addClass('title-entering title-stage'); - - IonHeaderBar.alignTitle.call(template); - IonHeaderBar.positionTitle.call(template); - - Meteor.setTimeout(function() { - $node.removeClass('title-stage').addClass('title-active'); - }, 16); - - Meteor.setTimeout(function () { - $(this).removeClass('title-entering'); - $('[data-navbar-container]').attr('nav-bar-direction', 'forward'); - }, template.transitionDuration + 16); - } - - if ($node.hasClass('button')) { - container.insertBefore(node, next); - $node.addClass('button-entering button-stage'); - Meteor.setTimeout(function() { - $node.removeClass('button-stage').addClass('button-active'); - }, 16); - - Meteor.setTimeout(function () { - $(this).removeClass('button-entering'); - }, template.transitionDuration + 16); - } - }, +let _ionNavBar_Destroyed = new ReactiveVar(false); + +Template.ionNavBar.onCreated(function() { + this.data = this.data || {}; - removeElement: function(node) { - var $node = $(node); - if (!$node.hasClass('title') && !$node.hasClass('button') || IonNavigation.skipTransitions) { - $node.remove(); - return; - } - - $node.attr('nav-bar', 'leaving'); - if ($node.hasClass('title')) { - $node.addClass('title-leaving title-stage'); - Meteor.setTimeout(function() { - $node.removeClass('title-stage').addClass('title-active'); - }, 16); - - Meteor.setTimeout(function () { - $node.remove(); - }, template.transitionDuration + 16); - } - - if ($node.hasClass('button')) { - $node.remove(); - } + if (Platform.isAndroid()) { + this.transition = 'android'; + } else { + this.transition = 'ios'; } - }; -}; -Template.ionNavBar.destroyed = function () { - Session.set('hasHeader', false); -}; + this.ionNavBarTemplate = new ReactiveVar('_ionNavBar'); + + $(window).on('statechange', e => { + this.ionNavBarTemplate.set(''); + }); + + this.autorun(() => { + if (_ionNavBar_Destroyed.get()) { + this.ionNavBarTemplate.set('_ionNavBar'); + } + }); +}); + +Template.ionNavBar.onRendered(function() { + IonHeaderBar.alignTitle.call(this); + IonHeaderBar.positionTitle.call(this); + + let container = this.$('.nav-bar-container'); + container.attr('nav-bar-direction', 'forward'); + + let navBarContainer = this.find('.nav-bar-container'); + navBarContainer._uihooks = { + // Override onDestroyed so that's children won't remove themselves immediately. + removeElement: function(node) { + // Worst case scenario. Remove if exceeded maximum transition duration. + Meteor.setTimeout(() => { + node.remove(); + }, METEORIC.maximum_transition_duration); + } + }; +}); Template.ionNavBar.helpers({ - classes: function () { - var classes = ['bar', 'bar-header']; + ionNavBarTemplate: function() { + return Template.instance().ionNavBarTemplate.get(); + }, - if (this.class) { - classes.push(this.class); - } else { - classes.push('bar-stable'); + transition: function () { + return Template.instance().transition; } +}); + +Template._ionNavBar.onCreated(function () { + this.entering = false; + this.leaving = false; - return classes.join(' '); - }, + this.activate_view_timeout_id = null; + this.deactivate_view_timeout_id = null; + + _ionNavBar_Destroyed.set(false); +}); + +Template._ionNavBar.onRendered(function () { + // Reset nav-bar-direction. + $('[data-navbar-container]').attr('nav-bar-direction', 'forward'); + + let navBarBlock = this.find('.nav-bar-block'); + navBarBlock._uihooks = { + // Override onDestroyed so that's children won't remove themselves immediately. + removeElement: function(node) { + // Worst case scenario. Remove if exceeded maximum transition duration. + Meteor.setTimeout(() => { + node.remove(); + }, METEORIC.maximum_transition_duration); + } + }; + + let $navBarBlock = this.$('.nav-bar-block').first(); + let activate_view = () => { + this.entering = false; + + let activate_timer_active = !!this.activate_view_timeout_id; + if (activate_timer_active) { + Meteor.clearTimeout(this.activate_view_timeout_id); + this.activate_view_timeout_id = null; + } + + $navBarBlock.attr('nav-bar', 'active'); + $('[data-navbar-container]').attr('nav-bar-direction', 'forward'); + }; + + $navBarBlock.attr('nav-bar', 'stage'); + let $headerBar = this.$('.nav-bar-block *'); + Meteor.setTimeout(() => { + this.entering = true; + $navBarBlock.attr('nav-bar', 'entering'); + $headerBar.one(METEORIC.UTILITY.transitionend_events.join(' '), activate_view); + }, 0); + + // Worst case scenario, transitionend did not occur. Just place view in. + this.activate_view_timeout_id = Meteor.setTimeout(activate_view, METEORIC.maximum_transition_duration); +}); - transition: function () { - return Template.instance().transition; - } +Template._ionNavBar.onDestroyed(function () { + _ionNavBar_Destroyed.set(true); + + let $navBarBlock = this.$('.nav-bar-block'); + let deactivate_view = () => { + this.leaving = false; + + // If the user have trigger fingers, in which he/she can click back buttons + // really fast, activate view timer might still be going. Kill it. + let activate_timer_active = !!this.activate_view_timeout_id; + if (activate_timer_active) { + Meteor.clearTimeout(this.activate_view_timeout_id); + this.activate_view_timeout_id = null; + } + + let deactivate_timer_active = !!this.deactivate_view_timeout_id; + if (deactivate_timer_active) { + Meteor.clearTimeout(this.deactivate_view_timeout_id); + this.deactivate_view_timeout_id = null; + } + + this.deactivate_view_timeout_id = null; + $navBarBlock.remove(); + }; + + let $headerBar = this.$('.nav-bar-block *'); + Meteor.setTimeout(() => { + this.leaving = true; + $navBarBlock.attr('nav-bar', 'leaving'); + $headerBar.one(METEORIC.UTILITY.transitionend_events.join(' '), deactivate_view); + }, 0); + + // Worst case scenario, transitionend did not occur. Just remove the view. + this.deactivate_view_timeout_id = Meteor.setTimeout(deactivate_view, METEORIC.maximum_transition_duration); }); diff --git a/components/ionNavView/ionNavView.js b/components/ionNavView/ionNavView.js index 3d062ee..9fa86d4 100644 --- a/components/ionNavView/ionNavView.js +++ b/components/ionNavView/ionNavView.js @@ -16,20 +16,19 @@ Template.ionNavView.created = function () { if (this.data && this.data.transition) { this.transition = this.data.transition; } - - if (this.transition === 'ios') { - this.transitionDuration = 450; - } else { - this.transitionDuration = 320; - } }; Template.ionNavView.rendered = function () { var container = this.find('[data-nav-container]'); $('[data-nav-container]').attr('nav-view-direction', 'forward'); container._uihooks = { - // Override onDestroyed so that it won't remove itself immediately. - removeElement: function(node) {} + // Override onDestroyed so that's children won't remove themselves immediately. + removeElement: function(node) { + // Worst case scenario. Remove if exceeded maximum transition duration. + Meteor.setTimeout(() => { + node.remove(); + }, METEORIC.maximum_transition_duration); + } }; }; diff --git a/components/ionView/ionView.js b/components/ionView/ionView.js index d7d7672..e127747 100644 --- a/components/ionView/ionView.js +++ b/components/ionView/ionView.js @@ -1,64 +1,97 @@ +Template.ionView.onCreated(function() { + this.entering = false; + this.leaving = false; + + this.activate_view_timeout_id = null; + this.deactivate_view_timeout_id = null; +}); + Template.ionView.onRendered(function () { - // Reset our transition preference - IonNavigation.skipTransitions = false; - - // Reset our scroll position - var routePath = Router.current().route.path(Router.current().params); - if(IonScrollPositions[routePath]) { - $('.overflow-scroll').not('.nav-view-leaving .overflow-scroll').scrollTop(IonScrollPositions[routePath]); - delete IonScrollPositions[routePath]; - } - - let $view = this.$('.view'); - $view.attr('nav-view', 'entering'); - - addPrefixedEvent($view.get(0), 'animationend', e => { - $view.attr('nav-view', 'active'); - $('[data-nav-container]').attr('nav-view-direction', 'forward'); - $('[data-navbar-container]').attr('nav-bar-direction', 'forward'); - }); + // Reset our transition preference + IonNavigation.skipTransitions = false; + + // Reset our scroll position + var routePath = Router.current().route.path(Router.current().params); + if(IonScrollPositions[routePath]) { + $('.overflow-scroll').not('.nav-view-leaving .overflow-scroll').scrollTop(IonScrollPositions[routePath]); + delete IonScrollPositions[routePath]; + } + + // Get this now, so that when Meteor.setTimeout is called, Template.instance() retrieves the correct element. + let $view = this.$('.view'); + + let activate_view = () => { + this.entering = false; + + let activate_timer_active = !!this.activate_view_timeout_id; + if (activate_timer_active) { + Meteor.clearTimeout(this.activate_view_timeout_id); + this.activate_view_timeout_id = null; + } + + $view.attr('nav-view', 'active'); + $('[data-nav-container]').attr('nav-view-direction', 'forward'); + }; + + $view.attr('nav-view', 'stage'); + Meteor.setTimeout(() => { + this.entering = true; + $view.attr('nav-view', 'entering'); + $view.one(METEORIC.UTILITY.transitionend_events.join(' '), activate_view); + }, 0); + + // Worst case scenario, transitionend did not occur. Just place view in. + this.activate_view_timeout_id = Meteor.setTimeout(activate_view, METEORIC.maximum_transition_duration); }); Template.ionView.onDestroyed(function () { - // Get this now, so that when Meteor.setTimeout is called, Template.instance() retrieves the correct element. - let $view = this.$('.view'); - $view.attr('nav-view', 'leaving'); - - addPrefixedEvent($view.get(0), 'animationend', e => { - removePrefixedEvent($view.get(0), 'animationend'); - $view.remove(); - }); + // Get this now, so that when Meteor.setTimeout is called, Template.instance() retrieves the correct element. + let $view = this.$('.view'); + + let deactivate_view = () => { + this.leaving = false; + + // If the user have trigger fingers, in which he/she can click back buttons + // really fast, activate view timer might still be going. Kill it. + let activate_timer_active = !!this.activate_view_timeout_id; + if (activate_timer_active) { + Meteor.clearTimeout(this.activate_view_timeout_id); + this.activate_view_timeout_id = null; + } + + let deactivate_timer_active = !!this.deactivate_view_timeout_id; + if (deactivate_timer_active) { + Meteor.clearTimeout(this.deactivate_view_timeout_id); + this.deactivate_view_timeout_id = null; + } + + this.deactivate_view_timeout_id = null; + $view.remove(); + }; + + Meteor.setTimeout(() => { + this.leaving = true; + $view.attr('nav-view', 'leaving'); + $view.one(METEORIC.UTILITY.transitionend_events.join(' '), deactivate_view); + }, 0); + + // Worst case scenario, transitionend did not occur. Just remove the view. + this.deactivate_view_timeout_id = Meteor.setTimeout(deactivate_view, METEORIC.maximum_transition_duration); }); Template.ionView.helpers({ - classes: function () { - var classes = []; + classes: function () { + var classes = []; - if (this.class) { - classes.push(this.class); - } + if (this.class) { + classes.push(this.class); + } - return classes.join(' '); - }, - title: function () { - if ( Template.instance().data && Template.instance().data.title ) { - return Template.instance().data.title; + return classes.join(' '); + }, + title: function () { + if ( Template.instance().data && Template.instance().data.title ) { + return Template.instance().data.title; + } } - } -}); - -// For some reason jQuery is not calling these callbacks. -// TODO: Investigate jQuery. -var pfx = ["webkit", "moz", "MS", "o", ""]; -function addPrefixedEvent(element, type, callback) { - for (var p = 0; p < pfx.length; p++) { - if (!pfx[p]) type = type.toLowerCase(); - element.addEventListener(pfx[p]+type, callback, false); - } -} -function removePrefixedEvent(element, type, callback) { - for (var p = 0; p < pfx.length; p++) { - if (!pfx[p]) type = type.toLowerCase(); - element.removeEventListener(pfx[p]+type, callback, false); - } -} +}); \ No newline at end of file diff --git a/lib/utility.js b/lib/utility.js index ae5782c..b3a6884 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -1,11 +1,31 @@ -METEORIC = {}; +METEORIC = { + PLATFORM: { + orientation() { + // 0 = Portrait + // 90 = Landscape + // not using window.orientation because each device has a different implementation + return (window.innerWidth > window.innerHeight ? 90 : 0); + } + + // TODO: Move the Platform namespace here. And create platform.js + }, + + has_header: new ReactiveVar(false), + + /** + * In a worst case scenario, such that "transitionend" event is not called, + * for any reason. This will be the maximum alloted duration. This will + * prevent memory leaks during transition out events, in which some views + * are still in the DOM tree, even though they should've been removed. + */ + maximum_transition_duration: 1100 // todo: change this to 1.1s +}; METEORIC.UTILITY = { transitionend_events: [ - 'otransitionend', // oTransitionEnd in very old Opera 'transitionend', - 'webkittransitionend', - 'transitionend' + 'webkitTransitionEnd', + 'oTransitionEnd' // oTransitionEnd in very old Opera ], animationend_events: [ 'webkitanimationend' , @@ -13,26 +33,10 @@ METEORIC.UTILITY = { 'oanimationend', 'MSanimationEnd', 'animationend' - ], - transitionend_event_name: (function() { - var i, - undefined, - el = document.createElement('div'), - transitions = { - 'transition': 'transitionend', - 'OTransition': 'otransitionend', // oTransitionEnd in very old Opera - 'MozTransition': 'transitionend', - 'WebkitTransition': 'webkitTransitionEnd' - }; - - for (i in transitions) { - if (transitions.hasOwnProperty(i) && el.style[i] !== undefined) { - return transitions[i]; - } - } - })() + ] }; -Meteor.startup(function() { - +Router.onBeforeAction(function() { + $(window).trigger('statechange'); + this.next(); }); \ No newline at end of file diff --git a/styles/_transitions.scss b/styles/_transitions.scss index 2281492..9f9ffd8 100644 --- a/styles/_transitions.scss +++ b/styles/_transitions.scss @@ -1,63 +1,87 @@ -// For some reason, animation works on more complex content i.e. ionList with ionItems in complex state. Transition -// don't work in such cases. -@mixin nav-view-transitions($transition-duration) { - &[nav-view-direction="forward"] [nav-view="entering"] { - @include animation(nav-view-direction-entering-forward $transition-duration); - } +@import '{jandres:meteoric-sass}/scss/_mixins.scss'; +@import '{jandres:meteoric-sass}/scss/_variables.scss'; + +[nav-view-transition] { + /** + * Transition states: + * 1. stage: Set the initial position of the not yet existent view. + * 2. entering: Triggers animation toward translate3d(0, 0, 0), centering the view. + * 3. active: Keeps the translate3d(0, 0, 0) for the rest of the states, until leaving. + * 4. leaving: Transitions out. + */ - @-webkit-keyframes nav-view-direction-entering-forward { - from { -webkit-transform: translate3d(100%, 0px, 0px); } - to { -webkit-transform: translate3d(0%, 0px, 0px); } + [nav-view="stage"] { opacity: 0; } + &[nav-view-direction="back"] [nav-view="stage"] { + @include translate3d(-100%, 0px, 0px); } - @keyframes nav-view-direction-entering-forward { - from { transform: translate3d(100%, 0px, 0px); } - to { transform: translate3d(0%, 0px, 0px); } + &[nav-view-direction="forward"] [nav-view="stage"] { + @include translate3d(100%, 0px, 0px); } - &[nav-view-direction="back"] [nav-view="entering"] { - @include animation(nav-view-direction-entering-backward $transition-duration); + [nav-view="entering"] { opacity: 1; } + &[nav-view-direction="back"] [nav-view="entering"], + &[nav-view-direction="forward"] [nav-view="entering"]{ + @include translate3d(0px, 0px, 0px); } - @-webkit-keyframes nav-view-direction-entering-backward { - from { -webkit-transform: translate3d(-100%, 0px, 0px); } - to { -webkit-transform: translate3d(0%, 0px, 0px); } - } - @keyframes nav-view-direction-entering-backward { - from { transform: translate3d(-100%, 0px, 0px); } - to { transform: translate3d(0%, 0px, 0px); } + [nav-view="leaving"] { opacity: 0; } + &[nav-view-direction="back"] [nav-view="leaving"] { + @include translate3d(100%, 0px, 0px); } - &[nav-view-direction="forward"] [nav-view="leaving"] { - @include animation(nav-view-direction-leaving-forward $transition-duration); + @include translate3d(-100%, 0px, 0px); } - @-webkit-keyframes nav-view-direction-leaving-forward { - from { -webkit-transform: translate3d(0%, 0px, 0px); } - to { -webkit-transform: translate3d(-100%, 0px, 0px); } - } - @keyframes nav-view-direction-leaving-forward { - from { transform: translate3d(0%, 0px, 0px); } - to { transform: translate3d(-100%, 0px, 0px); } + &[nav-view-direction="back"], + &[nav-view-direction="forward"] { + [nav-view="active"] { + @include translate3d(0px, 0px, 0px); + } } +} - &[nav-view-direction="back"] [nav-view="leaving"] { - @include animation(nav-view-direction-leaving-backward $transition-duration); +[nav-bar-transition] { + [nav-bar="leaving"] { + z-index: 11; + .bar.bar-header { background: transparent; } } - @-webkit-keyframes nav-view-direction-leaving-backward { - from { -webkit-transform: translate3d(0%, 0px, 0px); } - to { -webkit-transform: translate3d(100%, 0px, 0px); } + [nav-bar="leaving"], [nav-bar="stage"] { + .buttons, .title { + opacity: 0.0; + } } - @keyframes nav-view-direction-leaving-backward { - from { transform: translate3d(0%, 0px, 0px); } - to { transform: translate3d(100%, 0px, 0px); } + [nav-bar="entering"], [nav-bar="active"] { + .buttons, .title { + opacity: 1.0; + } + } + &[nav-bar-direction="back"] { + [nav-bar="stage"] .back-text, .title { + @include translate3d(-50%, 0px, 0px); + } + [nav-bar="entering"], + [nav-bar="active"] { + .back-text, .title { + @include translate3d(0px, 0px, 0px); + } + } + [nav-bar="leaving"] .back-text, .title { + @include translate3d(50%, 0px, 0px); + } + } + &[nav-bar-direction="forward"] { + [nav-bar="stage"] .back-text, .title { + @include translate3d(50%, 0px, 0px); + } + [nav-bar="entering"], + [nav-bar="active"] { + .back-text, .title { + @include translate3d(0px, 0px, 0px); + } + } + [nav-bar="leaving"] .back-text, .title { + @include translate3d(-50%, 0px, 0px); + } } -} - -[nav-view-transition="ios"] { - @include nav-view-transitions($ios-transition-duration); -} - -[nav-view-transition="android"] { - @include nav-view-transitions($android-transition-duration); } \ No newline at end of file diff --git a/styles/main.scss b/styles/main.scss index 3a5cd03..ee01b6f 100644 --- a/styles/main.scss +++ b/styles/main.scss @@ -1,6 +1,3 @@ -@import '{jandres:meteoric-sass}/scss/ionic.scss'; -@import '{jandres:meteoric-sass}/scss/ionicons/ionicons.scss'; - @import "_transitions.scss"; .snap-drawers { From 9f938d3719a60026e7880b0327f2b048fdcacd10 Mon Sep 17 00:00:00 2001 From: Joey Arnold Date: Wed, 10 Feb 2016 22:10:02 -0700 Subject: [PATCH 14/59] ionNavBar documentation and bumped to 0.1.47-alpha1 --- components/ionContent/ionContent.html | 5 ++++- components/ionContent/ionContent.js | 6 ++---- components/ionNavBar/ionNavBar.js | 16 ++++++++++------ package.js | 6 ++---- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/components/ionContent/ionContent.html b/components/ionContent/ionContent.html index f848164..166eae8 100644 --- a/components/ionContent/ionContent.html +++ b/components/ionContent/ionContent.html @@ -1,5 +1,8 @@