diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 6aa05215c5..b2bab1566d 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@stencil/core": { - "version": "0.0.6-2", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-0.0.6-2.tgz", - "integrity": "sha512-sr6U3Hbdsk8WLn5ZKckATRWkHmCbZqsJFr8R7wFj0SCLLUtdpOKh6+p+C+taJguFx+sm0n14JnybFFrpmB0mvg==", + "version": "0.0.6-4", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-0.0.6-4.tgz", + "integrity": "sha512-QWuC6K07Y1Iv+uit040QFrpMa5dBRhDQhg3qRZ88fWMZQ1vpHnUggfavzfjNNDdfu5AhvG9BcT5jMYgG2rPTfg==", "dev": true, "requires": { "chokidar": "1.7.0", @@ -58,9 +58,9 @@ "dev": true }, "abbrev": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, "acorn": { @@ -2282,14 +2282,6 @@ } } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -2300,6 +2292,14 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -4526,7 +4526,7 @@ "integrity": "sha512-kxBL06p6iO2qPBHsqGK2b3cRwiRGpnmSuVWNhwHcMX7qJOUr1HvricYP1LZOCdkQBUp0jiWg2d6WJwR3vYgByw==", "dev": true, "requires": { - "vlq": "0.2.2" + "vlq": "0.2.3" } }, "makeerror": { @@ -4760,7 +4760,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.1.0" + "abbrev": "1.1.1" } }, "normalize-package-data": { @@ -5745,15 +5745,6 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -5798,6 +5789,15 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -6233,9 +6233,9 @@ } }, "vlq": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.2.tgz", - "integrity": "sha1-4xbVJXtAuGu0PLjV/qXX9U1rDKE=", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", "dev": true }, "walker": { diff --git a/packages/core/package.json b/packages/core/package.json index c55a13ea74..3fd6e4b096 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -10,7 +10,7 @@ "dist/" ], "devDependencies": { - "@stencil/core": "0.0.6-2", + "@stencil/core": "0.0.6-4", "@stencil/dev-server": "latest", "@stencil/utils": "latest", "@types/jest": "^21.1.0", @@ -26,7 +26,7 @@ "test": "jest --no-cache", "test.watch": "jest --watch --no-cache", "clean": "rm -rf dist", - "lint": "npm run tslint", + "lint": "npm run tslint & npm run sass-lint", "sass-lint": "sass-lint -v -q", "tslint": "tslint --project .", "tslint-fix": "tslint --project . --fix", diff --git a/packages/core/src/components/button/test/button.spec.ts b/packages/core/src/components/button/test/button.spec.ts index aebefad187..0dd4cf839f 100644 --- a/packages/core/src/components/button/test/button.spec.ts +++ b/packages/core/src/components/button/test/button.spec.ts @@ -1,4 +1,4 @@ -import { render, flush } from '@stencil/core/testing'; +import { flush, render } from '@stencil/core/testing'; import { Button } from '../button'; diff --git a/packages/core/src/components/gesture/gesture.tsx b/packages/core/src/components/gesture/gesture.tsx index 8d98ce94ec..aafd72d296 100644 --- a/packages/core/src/components/gesture/gesture.tsx +++ b/packages/core/src/components/gesture/gesture.tsx @@ -167,6 +167,9 @@ export class Gesture { this.positions.push(detail.currentX, detail.currentY, timeStamp); if (this.pan) { this.hasStartedPan = true; + if (this.threshold === 0) { + return this.tryToCapturePan(); + } this.pan.start(detail.startX, detail.startY); } return true; diff --git a/packages/core/src/components/item/item.scss b/packages/core/src/components/item/item.scss index cc7cdd18fb..702ee852cb 100644 --- a/packages/core/src/components/item/item.scss +++ b/packages/core/src/components/item/item.scss @@ -97,10 +97,3 @@ ion-input.item { background: transparent; cursor: pointer; } - -[reorderAnchor] { - display: none; - - pointer-events: all !important; - touch-action: manipulation; -} diff --git a/packages/core/src/components/reorder/reorder-group.tsx b/packages/core/src/components/reorder/reorder-group.tsx index f97c689036..24795099bb 100644 --- a/packages/core/src/components/reorder/reorder-group.tsx +++ b/packages/core/src/components/reorder/reorder-group.tsx @@ -1,11 +1,11 @@ import { Component, Element, Prop, PropDidChange, State } from '@stencil/core'; import { GestureDetail } from '../../index'; -import { reorderArray } from '../../utils/helpers'; +import { clamp, reorderArray } from '../../utils/helpers'; import { CSS_PROP } from '../animation-controller/constants'; -// const AUTO_SCROLL_MARGIN = 60; -// const SCROLL_JUMP = 10; -const ITEM_REORDER_ACTIVE = 'reorder-active'; +const AUTO_SCROLL_MARGIN = 60; +const SCROLL_JUMP = 10; +const ITEM_REORDER_SELECTED = 'reorder-selected'; export class ReorderIndexes { @@ -147,10 +147,16 @@ export class ReorderGroup { private selectedItemEle: HTMLElement = null; private selectedItemHeight: number; private lastToIndex: number; - private lastYcoord: number; - private topOfList: number; private cachedHeights: number[] = []; private containerEle: HTMLElement; + private scrollEle: HTMLElement; + + private scrollTop: number; + private scrollBottom: number; + private scrollInitial: number; + + private containerTop: number; + private containerBottom: number; @State() _enabled: boolean = false; @State() _iconVisible: boolean = false; @@ -178,6 +184,7 @@ export class ReorderGroup { ionViewDidLoad() { this.containerEle = this.ele.querySelector('ion-gesture') as HTMLElement; + this.scrollEle = this.ele.closest('ion-scroll') as HTMLElement; } ionViewDidUnload() { @@ -189,7 +196,7 @@ export class ReorderGroup { return false; } const target = ev.event.target as HTMLElement; - const reorderEle = target.closest('[reorderAnchor]') as HTMLElement; + const reorderEle = target.closest('ion-reorder') as HTMLElement; if (!reorderEle) { return false; } @@ -213,20 +220,33 @@ export class ReorderGroup { } let sum = 0; - for (let i = 0, ilen = children.length; i < ilen; i++) { + for (var i = 0, ilen = children.length; i < ilen; i++) { var child = children[i]; sum += child.offsetHeight; heights.push(sum); child.$ionIndex = i; } - this.topOfList = item.getBoundingClientRect().top; - this._actived = true; - this.lastYcoord = -100; + const box = this.containerEle.getBoundingClientRect(); + this.containerTop = box.top; + this.containerBottom = box.bottom; + + if (this.scrollEle) { + var scrollBox = this.scrollEle.getBoundingClientRect(); + this.scrollInitial = this.scrollEle.scrollTop; + this.scrollTop = scrollBox.top + AUTO_SCROLL_MARGIN; + this.scrollBottom = scrollBox.bottom - AUTO_SCROLL_MARGIN; + } else { + this.scrollInitial = 0; + this.scrollTop = 0; + this.scrollBottom = 0; + } + this.lastToIndex = indexForItem(item); this.selectedItemHeight = item.offsetHeight; + this._actived = true; - item.classList.add(ITEM_REORDER_ACTIVE); + item.classList.add(ITEM_REORDER_SELECTED); } private onDragMove(ev: GestureDetail) { @@ -234,26 +254,24 @@ export class ReorderGroup { if (!selectedItem) { return; } - // ev.event.preventDefault(); + // Scroll if we reach the scroll margins + const scroll = this.autoscroll(ev.currentY); // // Get coordinate - const posY = ev.deltaY; - - // Scroll if we reach the scroll margins - // const scrollPosition = this.scroll(posY); - // Only perform hit test if we moved at least 30px from previous position - if (Math.abs(posY - this.lastYcoord) > 30) { - let toIndex = this.itemIndexForDelta(posY); - if (toIndex !== undefined && (toIndex !== this.lastToIndex)) { - let fromIndex = indexForItem(selectedItem); - this.lastToIndex = toIndex; - this.lastYcoord = posY; - this._reorderMove(fromIndex, toIndex, this.selectedItemHeight); - } + const top = this.containerTop - scroll; + const bottom = this.containerBottom - scroll; + const currentY = clamp(top, ev.currentY, bottom); + const deltaY = scroll + currentY - ev.startY; + const normalizedY = currentY - top; + const toIndex = this.itemIndexForTop(normalizedY); + if (toIndex !== undefined && (toIndex !== this.lastToIndex)) { + let fromIndex = indexForItem(selectedItem); + this.lastToIndex = toIndex; + this._reorderMove(fromIndex, toIndex); } // Update selected item position - (selectedItem.style as any)[CSS_PROP.transformProp] = `translateY(${posY}px)`; + (selectedItem.style as any)[CSS_PROP.transformProp] = `translateY(${deltaY}px)`; } private onDragEnd() { @@ -285,7 +303,7 @@ export class ReorderGroup { const reorderInactive = () => { this.selectedItemEle.style.transition = ''; - this.selectedItemEle.classList.remove(ITEM_REORDER_ACTIVE); + this.selectedItemEle.classList.remove(ITEM_REORDER_SELECTED); this.selectedItemEle = null; }; if (toIndex === fromIndex) { @@ -296,24 +314,29 @@ export class ReorderGroup { } } - private itemIndexForDelta(deltaY: number): number { + private itemIndexForTop(deltaY: number): number { const heights = this.cachedHeights; - let sum = deltaY + this.topOfList - (this.selectedItemHeight / 2); - for (var i = 0; i < heights.length; i++) { - if (heights[i] > sum) { - return i; + let i = 0; + + // TODO: since heights is a sorted array of integers, we can do + // speed up the search using binary search. Remember that linear-search is still + // faster than binary-search for small arrays (<64) due CPU branch misprediction. + for (i = 0; i < heights.length; i++) { + if (heights[i] > deltaY) { + break; } } - return null; + return i; } - private _reorderMove(fromIndex: number, toIndex: number, itemHeight: number) { - /********* DOM WRITE ********* */ + /********* DOM WRITE ********* */ + private _reorderMove(fromIndex: number, toIndex: number) { + const itemHeight = this.selectedItemHeight; const children = this.containerEle.children; const transform = CSS_PROP.transformProp; for (var i = 0; i < children.length; i++) { - const style = (children[i] as any).style; - let value = ''; + var style = (children[i] as any).style; + var value = ''; if (i > fromIndex && i <= toIndex) { value = `translateY(${-itemHeight}px)`; } else if (i < fromIndex && i >= toIndex) { @@ -323,6 +346,23 @@ export class ReorderGroup { } } + private autoscroll(posY: number): number { + if (!this.scrollEle) { + return 0; + } + + let amount = 0; + if (posY < this.scrollTop) { + amount = -SCROLL_JUMP; + } else if (posY > this.scrollBottom) { + amount = SCROLL_JUMP; + } + if (amount !== 0) { + this.scrollEle.scrollBy(0, amount); + } + return this.scrollEle.scrollTop - this.scrollInitial; + } + hostData() { return { class: { diff --git a/packages/core/src/components/reorder/reorder.scss b/packages/core/src/components/reorder/reorder.scss index a16b01f215..8b1bfb3deb 100644 --- a/packages/core/src/components/reorder/reorder.scss +++ b/packages/core/src/components/reorder/reorder.scss @@ -4,8 +4,7 @@ $reorder-initial-transform: 160% !default; // Reorder group general // -------------------------------------------------- - -.reorder-enabled [reorderAnchor] { +ion-reorder-group > ion-gesture { display: block; } @@ -15,50 +14,68 @@ $reorder-initial-transform: 160% !default; will-change: transform; } -.reorder-list-active ion-gesture *:not([reorderAnchor]) { - pointer-events: none; -} - -.reorder-active { +.reorder-selected { position: relative; z-index: 100; box-shadow: 0 0 10px rgba(0, 0, 0, .4); opacity: .8; transition: none !important; - - pointer-events: none; } // Reorder icon // -------------------------------------------------- -ion-reorder { - @include transform(translate3d($reorder-initial-transform, 0, 0)); +ion-reorder.no-hide { + display: block; + visibility: normal; +} + +ion-reorder:not(.no-hide) { + display: none; + + pointer-events: all !important; + touch-action: manipulation; +} + +.reorder-enabled ion-reorder { + display: block; + + cursor: grab; + cursor: -webkit-grab; +} + +.reorder-selected, +.reorder-selected ion-reorder { + cursor: grabbing; + cursor: -webkit-grabbing; +} + +ion-reorder[slot] { + line-height: 0; margin-top: auto !important; margin-bottom: auto !important; +} - font-size: 1.7em; +ion-reorder[slot="start"] { + order: -1; +} + +.reorder-icon { + @include transform(translate3d($reorder-initial-transform, 0, 0)); + + font-size: 1.3em; opacity: .25; - line-height: 0; - transition: transform 140ms ease-in; } -ion-reorder ion-icon { - pointer-events: none; -} - -ion-reorder[slot="start"] { +ion-reorder[slot="start"] .reorder-icon { @include transform(translate3d(-$reorder-initial-transform, 0, 0)); - - order: -1; } -.reorder-visible ion-reorder { +.reorder-visible ion-reorder .reorder-icon { @include transform(translate3d(0, 0, 0)); } - diff --git a/packages/core/src/components/reorder/reorder.tsx b/packages/core/src/components/reorder/reorder.tsx index 8100af5c98..44aba5d37c 100644 --- a/packages/core/src/components/reorder/reorder.tsx +++ b/packages/core/src/components/reorder/reorder.tsx @@ -1,20 +1,26 @@ -import { Component } from '@stencil/core'; +import { Component, Element, State } from '@stencil/core'; @Component({ tag: 'ion-reorder', }) export class ItemReorder { - hostData()  { - return { - attrs: { - 'reorderAnchor': '', - } - }; + @State() hasContent: boolean = null; + @Element() ele: HTMLElement; + + ionViewDidLoad() { + this.hasContent = this.ele.childElementCount > 0; } render() { - return ; + // TODO: https://github.com/ionic-team/stencil/issues/171 + if (this.hasContent === true) { + return ; + } else if (this.hasContent === false) { + return ; + } else { + return undefined; + } } -} +} diff --git a/packages/core/src/components/reorder/test/basic.html b/packages/core/src/components/reorder/test/basic.html index 4d5aaf5178..5ee421291b 100644 --- a/packages/core/src/components/reorder/test/basic.html +++ b/packages/core/src/components/reorder/test/basic.html @@ -22,54 +22,49 @@ + - Item 1 + Item 1 (default ion-reorder) - Item 2 - - - Item 3 - - - Item 4 - + + Item 2 (default ion-reorder) + - Item 5 - + + Item 3 (default ion-reorder slot="start") + - Item 6 - + + Item 4 (custom ion-reorder) + - Item 7 - + + Item 5 (custom ion-reorder) + - Item 8 - + + Item 6 (custom ion-reorder slot="start") + - Item 9 - - - Item 10 - + + Item 7 (the whole item can be dragged) + - - Item 11 - + + Item 8 (the whole item can be dragged) + + + + Item 9 (the whole item can be dragged) + - - Item 12 - - - Item 13 - -