diff --git a/BREAKING.md b/BREAKING.md index 771e7b973e..3e1c997dbf 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -14,6 +14,7 @@ A list of the breaking changes introduced in Ionic Angular v4. - [Input](#Input) - [Item](#item) - [Item Divider](#item-divider) +- [Item Sliding](#item-sliding) - [List Header](#list-header) - [Menu Toggle](#menu-toggle) - [Nav](#nav) @@ -467,7 +468,48 @@ The `menuToggle` attribute should not be added to an element anymore. Elements t ``` -#### Toolbar +## Item Sliding + +### Markup Changed + +The option component should not be written as a `button` with an `ion-button` directive anymore. It should be written as an `ion-item-option`. This will render a native button element inside of it. + +**Old Usage Example:** + +```html + + + Item 1 + + + + + +``` + +**New Usage Example:** + +```html + + + Item 1 + + + + + + + +``` + +### Method Renamed + +The `getSlidingPercent` method has been renamed to `getSlidingRatio` since the function is returning a ratio of the open amount of the item compared to the width of the options. + + +## Toolbar Previously if a `menuToggle` directive was added to an Ionic `button` in a toolbar, it would be positioned outside of the `ion-buttons` element. Since menu toggle is simply a wrapper to a button now, it should be placed inside of the `ion-buttons` element. diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 2e732467e8..937a67c2ae 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -1314,6 +1314,7 @@ declare global { export interface IonItemOptionAttributes extends HTMLAttributes { color?: string; disabled?: boolean; + expandable?: boolean; href?: string; mode?: 'ios' | 'md'; } diff --git a/packages/core/src/components/card/card.ios.scss b/packages/core/src/components/card/card.ios.scss index 7497bf4826..fd84cd6c51 100755 --- a/packages/core/src/components/card/card.ios.scss +++ b/packages/core/src/components/card/card.ios.scss @@ -25,7 +25,7 @@ .card-ios > .item:last-child, .card-ios > .item:last-child .item-inner, -.card-ios > .item-wrapper:last-child .item { +.card-ios > .item-sliding:last-child .item { border-bottom: 0; } diff --git a/packages/core/src/components/card/card.md.scss b/packages/core/src/components/card/card.md.scss index d51f925c87..af51da0033 100755 --- a/packages/core/src/components/card/card.md.scss +++ b/packages/core/src/components/card/card.md.scss @@ -23,7 +23,7 @@ .card-md > .item:last-child, .card-md > .item:last-child .item-inner, -.card-md > .item-wrapper:last-child .item { +.card-md > .item-sliding:last-child .item { border-bottom: 0; } diff --git a/packages/core/src/components/item-group/item-group.ios.scss b/packages/core/src/components/item-group/item-group.ios.scss index 913a819573..6d2b2bd04d 100644 --- a/packages/core/src/components/item-group/item-group.ios.scss +++ b/packages/core/src/components/item-group/item-group.ios.scss @@ -10,6 +10,6 @@ } .item-group-ios ion-item:last-child .item-inner, -.item-group-ios .item-wrapper:last-child .item .item-inner { +.item-group-ios .item-sliding:last-child .item .item-inner { border: 0; } diff --git a/packages/core/src/components/item-group/item-group.md.scss b/packages/core/src/components/item-group/item-group.md.scss index 87e8a27bc5..d3416e2127 100644 --- a/packages/core/src/components/item-group/item-group.md.scss +++ b/packages/core/src/components/item-group/item-group.md.scss @@ -10,6 +10,6 @@ } .item-group-md ion-item:last-child .item-inner, -.item-group-md .item-wrapper:last-child .item .item-inner { +.item-group-md .item-sliding:last-child .item .item-inner { border: 0; } diff --git a/packages/core/src/components/item-option/item-option.ios.scss b/packages/core/src/components/item-option/item-option.ios.scss index 7c26d094da..1113bfa837 100644 --- a/packages/core/src/components/item-option/item-option.ios.scss +++ b/packages/core/src/components/item-option/item-option.ios.scss @@ -10,10 +10,6 @@ background-color: $item-option-button-ios-background-color; } -.item-option-ios .icon { - fill: $item-option-button-ios-icon-color; -} - .list-ios .item-options-right ion-item-option:last-child { @include safe-area-padding-horizontal(null, .7em); } @@ -35,8 +31,4 @@ color: $color-contrast; background-color: $color-base; } - - .item-option-ios-#{$color-name} .icon { - fill: $color-contrast; - } } diff --git a/packages/core/src/components/item-option/item-option.md.scss b/packages/core/src/components/item-option/item-option.md.scss index 344004e1ac..16402520c3 100644 --- a/packages/core/src/components/item-option/item-option.md.scss +++ b/packages/core/src/components/item-option/item-option.md.scss @@ -10,8 +10,9 @@ background-color: $item-option-button-md-background-color; } -.item-option-md .icon { - fill: $item-option-button-md-icon-color; +.item-option-md .item-option-button { + font-weight: 500; + text-transform: uppercase; } @@ -27,8 +28,4 @@ color: $color-contrast; background-color: $color-base; } - - .item-option-md-#{$color-name} .icon { - fill: $color-contrast; - } } diff --git a/packages/core/src/components/item-option/item-option.scss b/packages/core/src/components/item-option/item-option.scss index a2f6ab180a..a3125f6d78 100644 --- a/packages/core/src/components/item-option/item-option.scss +++ b/packages/core/src/components/item-option/item-option.scss @@ -3,33 +3,21 @@ // Item Option // -------------------------------------------------- -ion-item-option { - @include padding(0, .7em); - - position: relative; - display: flex; - - align-items: center; - - min-width: 60px; -} - .item-option-button { - @include position(0, 0, 0, 0); - @include margin(0); - @include padding(0); - @include border-radius(0); - - position: absolute; + @include padding(0, .7em); border: 0; background: none; + color: inherit; + + height: 100%; + width: 100%; + + font-size: 1em; } -ion-item-options:not([icon-start]) ion-item-option:not([icon-only]) { - .item-option-button-inner { - flex-direction: column; - } +ion-item-options .item-option-button-inner { + flex-direction: column; } .item-option-button-inner { @@ -44,11 +32,18 @@ ion-item-options:not([icon-start]) ion-item-option:not([icon-only]) { height: 100%; } +.item-option-button [slot="icon-only"] { + padding: 0; + min-width: .9em; + + font-size: 1.8em; +} + // Item Expandable Animation // -------------------------------------------------- -ion-item-option[expandable] { +.item-option-expandable { flex-shrink: 0; transition-duration: 0; @@ -56,7 +51,7 @@ ion-item-option[expandable] { transition-timing-function: cubic-bezier(.65, .05, .36, 1); } -ion-item-sliding.active-swipe-right ion-item-option[expandable] { +.item-sliding-active-swipe-right .item-options-right .item-option-expandable { transition-duration: .6s; transition-property: padding-left; @@ -74,7 +69,7 @@ ion-item-sliding.active-swipe-right ion-item-option[expandable] { } } -ion-item-sliding.active-swipe-left ion-item-option[expandable] { +.item-sliding-active-swipe-left .item-options-left .item-option-expandable { transition-duration: .6s; transition-property: padding-right; diff --git a/packages/core/src/components/item-option/item-option.tsx b/packages/core/src/components/item-option/item-option.tsx index ac219a2c66..d3af82007f 100644 --- a/packages/core/src/components/item-option/item-option.tsx +++ b/packages/core/src/components/item-option/item-option.tsx @@ -26,17 +26,22 @@ export class ItemOption { */ @Prop() mode: 'ios' | 'md'; + /** + * If true, the user cannot interact with the item option. Defaults to `false`. + */ + @Prop() disabled = false; + + /** + * If true, the option will expand to take up the available width and cover any other options. Defaults to `false`. + */ + @Prop() expandable = false; + /** * Contains a URL or a URL fragment that the hyperlink points to. * If this property is set, an anchor tag will be rendered. */ @Prop() href: string; - /** - * If true, the user cannot interact with the item option. Defaults to `false`. - */ - @Prop() disabled = false; - notCaptured() { // if (!clickedOptionButton(ev)) { // this.closeOpened(); @@ -48,21 +53,33 @@ export class ItemOption { return !!el; } - render() { + hostData() { + return { + class: { + 'item-option-expandable': this.expandable + } + }; + } + render() { const TagType = this.href ? 'a' : 'button'; - // TODO TagType should wrap button-inner - return [ + return ( , - - - - ]; + onClick={this.clickedOptionButton.bind(this)}> + + + + + + + + + + ); } } diff --git a/packages/core/src/components/item-option/readme.md b/packages/core/src/components/item-option/readme.md index 7d4bda3ab4..b2dc0b45e0 100644 --- a/packages/core/src/components/item-option/readme.md +++ b/packages/core/src/components/item-option/readme.md @@ -26,6 +26,13 @@ boolean If true, the user cannot interact with the item option. Defaults to `false`. +#### expandable + +boolean + +If true, the option will expand to take up the available width and cover any other options. Defaults to `false`. + + #### href string @@ -61,6 +68,13 @@ boolean If true, the user cannot interact with the item option. Defaults to `false`. +#### expandable + +boolean + +If true, the option will expand to take up the available width and cover any other options. Defaults to `false`. + + #### href string diff --git a/packages/core/src/components/item-options/item-options.scss b/packages/core/src/components/item-options/item-options.scss index b01fe0fbc4..aaa21d1257 100644 --- a/packages/core/src/components/item-options/item-options.scss +++ b/packages/core/src/components/item-options/item-options.scss @@ -11,7 +11,10 @@ ion-item-options { height: 100%; font-size: 14px; - visibility: hidden; + + &.hydrated { + visibility: hidden; + } @include multi-dir() { // scss-lint:disable PropertySpelling @@ -53,21 +56,21 @@ ion-item-options[side=left] { } } -ion-item-sliding.active-slide { +.item-sliding-active-slide { @include rtl() { - &.active-options-left ion-item-options:not([side=right]) { + &.item-sliding-active-options-left ion-item-options:not([side=right]) { width: 100%; visibility: visible; - } + } } ion-item-options { display: flex; } - &.active-options-left ion-item-options[side=left], - &.active-options-right ion-item-options:not([side=left]) { + &.item-sliding-active-options-left ion-item-options[side=left], + &.item-sliding-active-options-right ion-item-options:not([side=left]) { width: 100%; visibility: visible; diff --git a/packages/core/src/components/item-sliding/item-sliding.scss b/packages/core/src/components/item-sliding/item-sliding.scss index de412c2a33..8985799d8f 100644 --- a/packages/core/src/components/item-sliding/item-sliding.scss +++ b/packages/core/src/components/item-sliding/item-sliding.scss @@ -11,7 +11,7 @@ ion-item-sliding { width: 100%; } -ion-item-sliding.active-slide { +.item-sliding-active-slide { ion-item, ion-item.activated { position: relative; diff --git a/packages/core/src/components/item-sliding/item-sliding.tsx b/packages/core/src/components/item-sliding/item-sliding.tsx index 7ea31c22ca..e6fc1edb24 100644 --- a/packages/core/src/components/item-sliding/item-sliding.tsx +++ b/packages/core/src/components/item-sliding/item-sliding.tsx @@ -34,7 +34,6 @@ export const enum SlidingState { } }) export class ItemSliding { - private item: HTMLIonItemElement; private list: HTMLIonListElement; private openAmount = 0; @@ -42,55 +41,20 @@ export class ItemSliding { private optsWidthRightSide = 0; private optsWidthLeftSide = 0; private sides: ItemSide; - private tmr: any = null; + private tmr: number; private leftOptions: ItemOptions; private rightOptions: ItemOptions; private optsDirty = true; - private gestureOptions: any; @Element() private el: HTMLElement; - @State() state: SlidingState = SlidingState.Disabled; + @State() state: SlidingState = SlidingState.Disabled; /** * Emitted when the sliding position changes. - * It reports the relative position. - * - * ```ts - * onDrag(slidingItem) { - * let percent = slidingItem.getSlidingPercent(); - * if (percent > 0) { - * // positive - * console.log('right side'); - * } else { - * // negative - * console.log('left side'); - * } - * if (Math.abs(percent) > 1) { - * console.log('overscroll'); - * } - * } - * ``` - * */ @Event() ionDrag: EventEmitter; - constructor() { - this.gestureOptions = { - 'canStart': this.canStart.bind(this), - 'onStart': this.onDragStart.bind(this), - 'onMove': this.onDragMove.bind(this), - 'onEnd': this.onDragEnd.bind(this), - 'gestureName': 'item-swipe', - 'gesturePriority': -10, - 'type': 'pan', - 'direction': 'x', - 'maxAngle': 20, - 'threshold': 5, - 'attachTo': 'parent' - }; - } - componentDidLoad() { this.item = this.el.querySelector('ion-item'); this.list = this.el.closest('ion-list') as HTMLIonListElement; @@ -102,18 +66,27 @@ export class ItemSliding { this.item = this.list = this.leftOptions = this.rightOptions = null; } + /** + * Get the amount the item is open in pixels. + */ @Method() getOpenAmount(): number { return this.openAmount; } + /** + * Get the ratio of the open amount of the item compared to the width of the options. + * If the number returned is positive, then the options on the right side are open. + * If the number returned is negative, then the options on the left side are open. + * If the absolute value of the number is greater than 1, the item is open more than + * the width of the options. + */ @Method() - getSlidingPercent(): number { - const openAmount = this.openAmount; - if (openAmount > 0) { - return openAmount / this.optsWidthRightSide; - } else if (openAmount < 0) { - return openAmount / this.optsWidthLeftSide; + getSlidingRatio(): number { + if (this.openAmount > 0) { + return this.openAmount / this.optsWidthRightSide; + } else if (this.openAmount < 0) { + return this.openAmount / this.optsWidthLeftSide; } else { return 0; } @@ -122,43 +95,15 @@ export class ItemSliding { /** * Close the sliding item. Items can also be closed from the [List](../../list/List). - * - * The sliding item can be closed by grabbing a reference to `ItemSliding`. In the - * below example, the template reference variable `slidingItem` is placed on the element - * and passed to the `share` method. - * - * ```html - * - * - * - * Item - * - * - * Share - * - * - * - * ``` - * - * ```ts - * import { Component } from '@angular/core'; - * import { ItemSliding } from 'ionic-angular'; - * - * @Component({...}) - * export class MyClass { - * constructor() { } - * - * share(slidingItem: ItemSliding) { - * slidingItem.close(); - * } - * } - * ``` */ @Method() close() { this.setOpenAmount(0, true); } + /** + * Close all of the sliding items in the list. Items can also be closed from the [List](../../list/List). + */ @Method() closeOpened(): boolean { return this.list && this.list.closeSlidingItems(); @@ -286,20 +231,18 @@ export class ItemSliding { if (isFinal) { style.transition = ''; + } - } else if (openAmount > 0) { + if (openAmount > 0) { this.state = (openAmount >= (this.optsWidthRightSide + SWIPE_MARGIN)) ? SlidingState.Right | SlidingState.SwipeRight : SlidingState.Right; - } else if (openAmount < 0) { this.state = (openAmount <= (-this.optsWidthLeftSide - SWIPE_MARGIN)) ? SlidingState.Left | SlidingState.SwipeLeft : SlidingState.Left; - } - - if (openAmount === 0) { - this.tmr = setTimeout(() => { + } else { + this.tmr = window.setTimeout(() => { this.state = SlidingState.Disabled; this.tmr = null; }, 600); @@ -315,19 +258,31 @@ export class ItemSliding { hostData() { return { class: { - 'item-wrapper': true, - 'active-slide': (this.state !== SlidingState.Disabled), - 'active-options-right': !!(this.state & SlidingState.Right), - 'active-options-left': !!(this.state & SlidingState.Left), - 'active-swipe-right': !!(this.state & SlidingState.SwipeRight), - 'active-swipe-left': !!(this.state & SlidingState.SwipeLeft) + 'item-sliding': true, + 'item-sliding-active-slide': (this.state !== SlidingState.Disabled), + 'item-sliding-active-options-right': !!(this.state & SlidingState.Right), + 'item-sliding-active-options-left': !!(this.state & SlidingState.Left), + 'item-sliding-active-swipe-right': !!(this.state & SlidingState.SwipeRight), + 'item-sliding-active-swipe-left': !!(this.state & SlidingState.SwipeLeft) } }; } render() { return ( - + ); diff --git a/packages/core/src/components/item-sliding/readme.md b/packages/core/src/components/item-sliding/readme.md index 8d9ac661d6..7b0e36932c 100644 --- a/packages/core/src/components/item-sliding/readme.md +++ b/packages/core/src/components/item-sliding/readme.md @@ -1,79 +1,72 @@ # ion-item-sliding -A sliding item is a list item that can be swiped to reveal buttons. It requires -an [Item](../Item) component as a child and a [List](../../list/List) component as -a parent. All buttons to reveal can be placed in the `` element. +A Sliding item is a component that contains an item that can be dragged to reveal buttons. It requires an [Item](../Item) component as a child. All options to reveal should be placed in the item options element. ```html - + - Item + Item - Favorite - Share + Favorite + Share - Unread + Unread ``` -### Swipe Direction +### Direction -By default, the buttons are revealed when the sliding item is swiped from right to left, -so the buttons are placed in the right side. But it's also possible to reveal them -in the right side (sliding from left to right) by setting the `side` attribute -on the `ion-item-options` element. Up to 2 `ion-item-options` can used at the same time -in order to reveal two different sets of buttons depending the swipping direction. +By default, the options are revealed when the sliding item is swiped from right to left, so the buttons are placed on the right side. It's also possible to reveal them from the right side by setting the `side` attribute on the `ion-item-options` element. Up to two `ion-item-options` can be used at the same time in order to reveal two different sets of options depending on the swiping direction. ```html - + Archive - + - + Archive - + ``` -### Listening for events (ionDrag) and (ionSwipe) -It's possible to know the current relative position of the sliding item by subscribing -to the (ionDrag)` event. +### Events + +It's possible to know the current relative position of the sliding item by subscribing to the ionDrag` event. ```html - - Item + + + Item + - Favorite + Favorite ``` -### Button Layout -If an icon is placed with text in the option button, by default it will -display the icon on top of the text. This can be changed to display the icon -to the left of the text by setting `icon-start` as an attribute on the -`` element. +### Layout +By default if an icon is placed with text in the option button, it will display the icon on top of the text. This can be changed to display the icon to the left of the text by setting `icon-start` as an attribute on the `` element. ```html - + Archive - + ``` @@ -81,22 +74,20 @@ to the left of the text by setting `icon-start` as an attribute on the ### Expandable Options -Options can be expanded to take up the full width of the item if you swipe past -a certain point. This can be combined with the `ionSwipe` event to call methods -on the class. +Options can be expanded to take up the full width of the item if you swipe past a certain point. This can be combined with the `ionSwipe` event to call methods on the class. ```html - - - Item + + + Item + - Delete + Delete ``` -We can call `delete` by either clicking the button, or by doing a full swipe on the item. - +We can call `delete` by either clicking the option, or by doing a full swipe on the item. @@ -107,23 +98,6 @@ We can call `delete` by either clicking the button, or by doing a full swipe on #### ionDrag Emitted when the sliding position changes. -It reports the relative position. - -```ts -onDrag(slidingItem) { - let percent = slidingItem.getSlidingPercent(); - if (percent > 0) { - // positive - console.log('right side'); - } else { - // negative - console.log('left side'); - } - if (Math.abs(percent) > 1) { - console.log('overscroll'); - } -} -``` ## Methods @@ -132,35 +106,24 @@ onDrag(slidingItem) { Close the sliding item. Items can also be closed from the [List](../../list/List). -The sliding item can be closed by grabbing a reference to `ItemSliding`. In the -below example, the template reference variable `slidingItem` is placed on the element -and passed to the `share` method. - -```html - - - - Item - - - Share - - - -``` - -```ts -import { Component } from '@angular/core'; -import { ItemSliding } from 'ionic-angular'; - #### closeOpened() +Close all of the sliding items in the list. Items can also be closed from the [List](../../list/List). + #### getOpenAmount() +Get the amount the item is open in pixels. -#### getSlidingPercent() + +#### getSlidingRatio() + +Get the ratio of the open amount of the item compared to the width of the options. +If the number returned is positive, then the options on the right side are open. +If the number returned is negative, then the options on the left side are open. +If the absolute value of the number is greater than 1, the item is open more than +the width of the options. diff --git a/packages/core/src/components/item-sliding/test/basic/index.html b/packages/core/src/components/item-sliding/test/basic/index.html index 13e2d0deda..001f209b97 100644 --- a/packages/core/src/components/item-sliding/test/basic/index.html +++ b/packages/core/src/components/item-sliding/test/basic/index.html @@ -99,7 +99,7 @@ - + @@ -380,7 +380,24 @@ window.location.reload(); } document.addEventListener('ionSwipe', (ev)=> console.log('SWIPE!!', ev.detail)); - document.addEventListener('ionDrag', (ev)=> console.log('DRAG!!', ev.detail.getOpenAmount())); + document.addEventListener('ionDrag', (ev) => { + // console.log('DRAG!!', ev.detail); + + let slidingRatio = ev.target.getSlidingRatio(); + console.log('sliding', slidingRatio); + + if (slidingRatio > 0) { + // positive + console.log('right side'); + } else { + // negative + console.log('left side'); + } + if (Math.abs(slidingRatio) > 1) { + console.log('overscroll'); + } + }); +