diff --git a/packages/dflex-core-instance/src/Element/DFlexCoreElement.ts b/packages/dflex-core-instance/src/Element/DFlexCoreElement.ts index 2061cb653..35fd28c34 100644 --- a/packages/dflex-core-instance/src/Element/DFlexCoreElement.ts +++ b/packages/dflex-core-instance/src/Element/DFlexCoreElement.ts @@ -56,6 +56,14 @@ export interface DFlexElementInput { readonly: boolean; } +function resetDOMStyle(DOM: HTMLElement): void { + DOM.style.removeProperty("transform"); + + if (!DOM.getAttribute("style")) { + DOM.removeAttribute("style"); + } +} + class DFlexCoreElement extends DFlexBaseElement { private _initialPosition: PointNum; @@ -127,23 +135,6 @@ class DFlexCoreElement extends DFlexBaseElement { this.rect.setByPointAndDimensions(top, left, height, width); } - private _updateCurrentIndicators(newPos: AxesPoint): void { - this.translate!.increase(newPos); - - /** - * This offset related directly to translate Y and Y. It's isolated from - * element current offset and effects only top and left. - */ - this.rect.setAxes( - this._initialPosition.x + this.translate!.x, - this._initialPosition.y + this.translate!.y - ); - - if (!this.isVisible) { - this.hasPendingTransform = true; - } - } - getDimensions(DOM: HTMLElement): PointNum { if (this._computedDimensions) { return this._computedDimensions; @@ -185,23 +176,27 @@ class DFlexCoreElement extends DFlexBaseElement { this.isVisible = isVisible; if (this.hasPendingTransform && this.isVisible) { - this.transform(DOM); + this._transform(DOM); this.hasPendingTransform = false; } } - transform(DOM: HTMLElement): void { + private _transform(DOM: HTMLElement, cb?: () => void): void { if (this.animatedFrame !== null) { cancelAnimationFrame(this.animatedFrame); } this.animatedFrame = requestAnimationFrame(() => { - DFlexCoreElement.transform(DOM, this.translate!.x, this.translate!.y); + DFlexCoreElement.transform(DOM, this.translate.x, this.translate.y); if (this.hasPendingTransform) { this.hasPendingTransform = false; } + if (cb) { + cb(); + } + this.animatedFrame = null; }); } @@ -211,17 +206,7 @@ class DFlexCoreElement extends DFlexBaseElement { this.VDOMOrder.self = i; } - private _updateOrderIndexing(DOM: HTMLElement, i: number) { - const { self: oldIndex } = this.VDOMOrder; - - const newIndex = oldIndex + i; - - this.updateIndex(DOM, newIndex); - - return { oldIndex, newIndex }; - } - - assignNewPosition(branchIDsOrder: string[], newIndex: number): void { + assignNewIndex(branchIDsOrder: string[], newIndex: number): void { if (newIndex < 0 || newIndex > branchIDsOrder.length - 1) { if (__DEV__) { // eslint-disable-next-line no-console @@ -252,7 +237,7 @@ class DFlexCoreElement extends DFlexBaseElement { branchIDsOrder[newIndex] = this.id; } - private _leaveToNewPosition( + private _leaveToNewIndex( branchIDsOrder: string[], newIndex: number, oldIndex: number @@ -262,32 +247,26 @@ class DFlexCoreElement extends DFlexBaseElement { branchIDsOrder[newIndex] = this.id; } - /** - * Set a new translate position and store the old one. - */ - private _seTranslate( - axis: Axes, - DOM: HTMLElement, - elmPos: AxesPoint, - operationID?: string, - hasToFlushTransform = false - ): void { - if (operationID) { - const elmAxesHistory: TransitionHistory = { - ID: operationID, - axis, - translate: { x: this.translate!.x, y: this.translate!.y }, - }; - - if (Array.isArray(this._translateHistory)) { - this._translateHistory.push(elmAxesHistory); - } else { - this._translateHistory = [elmAxesHistory]; - } + private _pushToTranslateHistory(axis: Axes, operationID: string) { + const translate = this.translate.getInstance(); + + const elmAxesHistory: TransitionHistory = { + ID: operationID, + axis, + translate, + }; + + if (!Array.isArray(this._translateHistory)) { + this._translateHistory = []; } - this._updateCurrentIndicators(elmPos); + this._translateHistory.push(elmAxesHistory); + } + private _transformOrPend( + DOM: HTMLElement, + hasToFlushTransform: boolean + ): void { if (hasToFlushTransform) { if (!this.isVisible && this.hasPendingTransform) { this.hasPendingTransform = false; @@ -295,7 +274,7 @@ class DFlexCoreElement extends DFlexBaseElement { return; } - this.transform(DOM); + this._transform(DOM); return; } @@ -306,7 +285,35 @@ class DFlexCoreElement extends DFlexBaseElement { return; } - this.transform(DOM); + this._transform(DOM); + } + + private _transformationProcess( + DOM: HTMLElement, + newPos: AxesPoint, + hasToFlushTransform: boolean, + increment: number + ) { + this.translate.increase(newPos); + + /** + * This offset related directly to translate Y and Y. It's isolated from + * element current offset and effects only top and left. + */ + this.rect.setAxes( + this._initialPosition.x + this.translate.x, + this._initialPosition.y + this.translate.y + ); + + this._transformOrPend(DOM, hasToFlushTransform); + + const { self: oldIndex } = this.VDOMOrder; + + const newIndex = oldIndex + increment; + + this.updateIndex(DOM, newIndex); + + return { oldIndex, newIndex }; } /** @@ -338,10 +345,12 @@ class DFlexCoreElement extends DFlexBaseElement { elmPos[axis] *= direction; } - this._seTranslate(axis, DOM, elmPos, operationID); + this._pushToTranslateHistory(axis, operationID); - const { oldIndex, newIndex } = this._updateOrderIndexing( + const { oldIndex, newIndex } = this._transformationProcess( DOM, + elmPos, + false, direction * numberOfPassedElm ); @@ -352,7 +361,7 @@ class DFlexCoreElement extends DFlexBaseElement { this.DOMGrid[axis] += direction * numberOfPassedElm; } - this._leaveToNewPosition(siblings, newIndex, oldIndex); + this._leaveToNewIndex(siblings, newIndex, oldIndex); if (__DEV__) { if (featureFlags.enablePositionAssertion) { @@ -365,18 +374,15 @@ class DFlexCoreElement extends DFlexBaseElement { } } - hasTransformed(): boolean { - return ( - Array.isArray(this._translateHistory) && this._translateHistory.length > 0 - ); - } + restorePosition(DOM: HTMLElement): void { + this._transform(DOM); - hasTransformedFromOrigin(): boolean { - return this._initialPosition.isNotEqual(this.rect.left, this.rect.top); + this.setAttribute(DOM, "INDEX", this.VDOMOrder.self); } - needDOMReconciliation(): boolean { - return this.VDOMOrder.self !== this.DOMOrder.self; + assignNewPosition(DOM: HTMLElement, t: PointNum): void { + this.translate.clone(t); + this._transform(DOM); } /** @@ -384,21 +390,24 @@ class DFlexCoreElement extends DFlexBaseElement { * * @param cycleID */ - rollBack(DOM: HTMLElement, cycleID: string): void { - if ( - !this.hasTransformed() || - this._translateHistory![this._translateHistory!.length - 1].ID !== cycleID - ) { + rollBackPosition(DOM: HTMLElement, cycleID: string): void { + if (!Array.isArray(this._translateHistory)) { return; } - const lastMovement = this._translateHistory!.pop()!; + const { length } = this._translateHistory; + + const stillInSameCycle = this._translateHistory[length - 1].ID === cycleID; + + if (!stillInSameCycle) { + return; + } - const { translate: preTranslate, axis } = lastMovement; + const { translate: preTranslate, axis } = this._translateHistory.pop()!; const elmPos = { - x: preTranslate.x - this.translate!.x, - y: preTranslate.y - this.translate!.y, + x: preTranslate.x - this.translate.x, + y: preTranslate.y - this.translate.y, }; let increment = 0; @@ -413,12 +422,22 @@ class DFlexCoreElement extends DFlexBaseElement { this.DOMGrid[axis] += increment; } - // Don't update UI if it's zero and wasn't transformed. - this._seTranslate(axis, DOM, elmPos, undefined, true); + this._transformationProcess(DOM, elmPos, true, increment); - this._updateOrderIndexing(DOM, increment); + if (this._translateHistory.length === 0) { + this._translateHistory = undefined; + return; + } - this.rollBack(DOM, cycleID); + this.rollBackPosition(DOM, cycleID); + } + + hasTransformedFromOrigin(): boolean { + return this._initialPosition.isNotEqual(this.rect.left, this.rect.top); + } + + needDOMReconciliation(): boolean { + return this.VDOMOrder.self !== this.DOMOrder.self; } refreshIndicators(DOM: HTMLElement): void { @@ -430,11 +449,7 @@ class DFlexCoreElement extends DFlexBaseElement { this.DOMOrder.self = this.VDOMOrder.self; - DOM.style.removeProperty("transform"); - - if (!DOM.getAttribute("style")) { - DOM.removeAttribute("style"); - } + resetDOMStyle(DOM); this.initElmRect(DOM); diff --git a/packages/dflex-dnd/src/Draggable/DraggableInteractive.ts b/packages/dflex-dnd/src/Draggable/DraggableInteractive.ts index 0c8400350..beba0b6ad 100644 --- a/packages/dflex-dnd/src/Draggable/DraggableInteractive.ts +++ b/packages/dflex-dnd/src/Draggable/DraggableInteractive.ts @@ -136,7 +136,7 @@ class DraggableInteractive extends DraggableAxes { * don't like it but it is what it is. */ if (siblings[VDOMOrder.self] !== id) { - this.draggedElm.assignNewPosition(siblings, VDOMOrder.self); + this.draggedElm.assignNewIndex(siblings, VDOMOrder.self); } // If it didn't move, then do nothing. @@ -144,8 +144,7 @@ class DraggableInteractive extends DraggableAxes { return; } - this.draggedElm.transform(draggedDOM); - this.draggedElm.setAttribute(draggedDOM, "INDEX", VDOMOrder.self); + this.draggedElm.restorePosition(draggedDOM); return; } @@ -167,13 +166,12 @@ class DraggableInteractive extends DraggableAxes { VDOMOrder.self = index; - this.draggedElm.assignNewPosition(siblings, index); + this.draggedElm.assignNewIndex(siblings, index); // If it's going to reconcile to the DOM then there's no need to update the // transformation here. if (!willReconcile) { - translate.clone(this.occupiedTranslate); - this.draggedElm.transform(draggedDOM); + this.draggedElm.assignNewPosition(draggedDOM, this.occupiedTranslate); } } diff --git a/packages/dflex-dnd/src/Mechanism/DFlexMechanismController.ts b/packages/dflex-dnd/src/Mechanism/DFlexMechanismController.ts index 8bf85824a..16afaea7b 100644 --- a/packages/dflex-dnd/src/Mechanism/DFlexMechanismController.ts +++ b/packages/dflex-dnd/src/Mechanism/DFlexMechanismController.ts @@ -21,6 +21,86 @@ export function isIDEligible(elmID: string, draggedID: string): boolean { ); } +// eslint-disable-next-line no-unused-vars +type ForEachCB = (id: string, index: number) => boolean | void; + +export function forEachSiblings( + at: number, + incremental: boolean, + siblings: string[], + draggedID: string, + cb: ForEachCB +) { + const filter = (id: string, i: number) => { + if (isIDEligible(id, draggedID)) { + return cb(id, i); + } + + return false; + }; + + if (incremental) { + for (let i = at; i < siblings.length; i += 1) { + if (filter(siblings[i], i)) { + break; + } + } + + return; + } + + for (let i = siblings.length - 1; i >= at; i -= 1) { + if (filter(siblings[i], i)) { + break; + } + } +} + +function forEachDraggedSiblings( + at: number, + incremental: boolean, + dispatch: boolean, + draggable: DraggableInteractive, + cb: ForEachCB +) { + const { + events, + draggedElm: { id }, + } = draggable; + + const { migration } = store; + + const { SK } = migration.latest(); + + const siblings = store.getElmSiblingsByKey(SK); + + const { length } = siblings; + + if (incremental) { + forEachSiblings(at, incremental, siblings, id, cb); + + if (dispatch) { + events.dispatch(DFLEX_EVENTS.ON_LIFT_UP, { + siblings, + from: at, + to: length, + }); + } + + return; + } + + forEachSiblings(at, incremental, siblings, id, cb); + + if (dispatch) { + events.dispatch(DFLEX_EVENTS.ON_MOVE_DOWN, { + siblings, + from: length - 1, + to: at, + }); + } +} + class DFlexMechanismController extends DFlexScrollableElement { private isOnDragOutThresholdEvtEmitted: boolean; @@ -69,29 +149,23 @@ class DFlexMechanismController extends DFlexScrollableElement { private _detectDroppableIndex(): number | null { let droppableIndex = null; - const { draggedElm } = this.draggable; - - const { SK } = store.migration.latest(); + const cb = (id: string, i: number) => { + const element = store.registry.get(id)!; - const siblings = store.getElmSiblingsByKey(SK); - - for (let i = 0; i < siblings.length; i += 1) { - const id = siblings[i]; + const isQualified = element.rect.isIntersect( + this.draggable.getAbsoluteCurrentPosition() + ); - if (isIDEligible(id, draggedElm.id)) { - const element = store.registry.get(id)!; + if (isQualified) { + droppableIndex = i; - const isQualified = element.rect.isIntersect( - this.draggable.getAbsoluteCurrentPosition() - ); + return true; + } - if (isQualified) { - droppableIndex = i; + return false; + }; - break; - } - } - } + forEachDraggedSiblings(0, true, false, this.draggable, cb); return droppableIndex; } @@ -302,7 +376,7 @@ class DFlexMechanismController extends DFlexScrollableElement { * Filling the space when the head of the list is leaving the list. */ private _fillHeadUp(): void { - const { occupiedPosition, draggedElm, events } = this.draggable; + const { occupiedPosition, draggedElm } = this.draggable; const { migration } = store; const { SK, index, cycleID } = migration.latest(); @@ -345,27 +419,13 @@ class DFlexMechanismController extends DFlexScrollableElement { nextElm.rect.top - (occupiedPosition.y + draggedElm.rect.height) ); - events.dispatch(DFLEX_EVENTS.ON_LIFT_UP, { - siblings, - from, - to: siblings.length, - }); - this.draggable.setDraggedTempIndex( DFlexMechanismController.INDEX_OUT_CONTAINER ); - for (let i = from; i < siblings.length; i += 1) { - /** - * Don't update translate because it's not permanent. Releasing dragged - * means undoing last position. - */ - const id = siblings[i]; + const cb = (id: string) => this.updateElement(id, siblings, cycleID, true); - if (isIDEligible(id, draggedElm.id)) { - this.updateElement(id, siblings, cycleID, true); - } - } + forEachDraggedSiblings(from, true, true, this.draggable, cb); } /** @@ -373,26 +433,15 @@ class DFlexMechanismController extends DFlexScrollableElement { * @param to - index */ private _moveDown(to: number): void { - const { events, draggedElm } = this.draggable; const { migration } = store; const { SK, cycleID } = migration.latest(); const siblings = store.getElmSiblingsByKey(SK); - events.dispatch(DFLEX_EVENTS.ON_MOVE_DOWN, { - siblings, - from: siblings!.length - 1, - to: siblings.length, - }); + const cb = (id: string) => this.updateElement(id, siblings, cycleID, false); - for (let i = siblings.length - 1; i >= to; i -= 1) { - const id = siblings[i]; - - if (isIDEligible(id, draggedElm.id)) { - this.updateElement(id, siblings, cycleID, false); - } - } + forEachDraggedSiblings(to, false, true, this.draggable, cb); } private _draggedOutPositionNotifier(): void { diff --git a/packages/dflex-dnd/src/Mechanism/DFlexPositionUpdater.ts b/packages/dflex-dnd/src/Mechanism/DFlexPositionUpdater.ts index ef9539eba..be185abf8 100644 --- a/packages/dflex-dnd/src/Mechanism/DFlexPositionUpdater.ts +++ b/packages/dflex-dnd/src/Mechanism/DFlexPositionUpdater.ts @@ -206,10 +206,10 @@ class DFlexPositionUpdater { this.isParentLocked = false; } - private setDistanceBtwPositions( - element: DFlexElement, + private _setDistanceBtwPositions( axis: Axis, - elmDirection: Direction + elmDirection: Direction, + element: DFlexElement ) { const { occupiedPosition, draggedElm } = this.draggable; @@ -249,16 +249,16 @@ class DFlexPositionUpdater { this.draggable.gridPlaceholder.clone(grid); } - private updateIndicators( - element: DFlexElement, + private _updateIndicators( axis: Axis, - elmDirection: Direction + elmDirection: Direction, + element: DFlexElement ) { this.elmTransition.setAxes(0, 0); this.draggedTransition.setAxes(0, 0); this.draggedPositionOffset.setAxes(0, 0); - this.setDistanceBtwPositions(element, axis, elmDirection); + this._setDistanceBtwPositions(axis, elmDirection, element); this.updateDraggable(element, elmDirection); } @@ -482,7 +482,7 @@ class DFlexPositionUpdater { const elmDirection: Direction = isIncrease ? -1 : 1; - this.updateIndicators(element, axis, elmDirection); + this._updateIndicators(axis, elmDirection, element); // TODO: always true for the first element if (!this.isParentLocked) { diff --git a/packages/dflex-dnd/src/Mechanism/EndCycle.ts b/packages/dflex-dnd/src/Mechanism/EndCycle.ts index d54d36b41..74ba5725a 100644 --- a/packages/dflex-dnd/src/Mechanism/EndCycle.ts +++ b/packages/dflex-dnd/src/Mechanism/EndCycle.ts @@ -2,7 +2,7 @@ import { AbstractDFlexCycle, featureFlags } from "@dflex/utils"; import { scheduler, store } from "../LayoutManager"; import DFlexMechanismController, { - isIDEligible, + forEachSiblings, } from "./DFlexMechanismController"; class EndCycle extends DFlexMechanismController { @@ -32,19 +32,17 @@ class EndCycle extends DFlexMechanismController { } } - for (let i = siblings.length - 1; i >= 0; i -= 1) { - const elmID = siblings[i]; + const cb = (elmID: string) => { + const [dflexElm, DOM] = store.getElmWithDOM(elmID); - if (isIDEligible(elmID, draggedID)) { - const [dflexElm, DOM] = store.getElmWithDOM(elmID); + /** + * Note: rolling back won't affect order array. It only deals with element + * itself and totally ignore any instance related to store. + */ + dflexElm.rollBackPosition(DOM, cycleID); + }; - /** - * Note: rolling back won't affect order array. It only deals with element - * itself and totally ignore any instance related to store. - */ - dflexElm.rollBack(DOM, cycleID); - } - } + forEachSiblings(0, false, siblings, draggedID, cb); const spliceAt = this.isParentLocked || threshold.isOut[draggedID].bottom diff --git a/packages/dflex-utils/src/Box/Box.ts b/packages/dflex-utils/src/Box/Box.ts index e67138422..3dd374998 100644 --- a/packages/dflex-utils/src/Box/Box.ts +++ b/packages/dflex-utils/src/Box/Box.ts @@ -1,5 +1,6 @@ +import { AXIS, Axis } from "../constants"; import type { AxesPoint } from "../Point"; -import type { Axis, Direction } from "../types"; +import type { Direction } from "../types"; import AbstractBox from "./AbstractBox"; /** Four direction instance - clockwise */ @@ -44,7 +45,7 @@ class Box extends AbstractBox { */ setByAxis(axis: Axis, x: T, y: T): void { switch (axis) { - case "x": { + case AXIS.X: { this.left = x; this.right = y; break; @@ -65,7 +66,7 @@ class Box extends AbstractBox { */ setOne(axis: Axis, direction: Direction, value: T): void { switch (axis) { - case "x": { + case AXIS.X: { if (direction === -1) { this.left = value; } else { @@ -92,7 +93,7 @@ class Box extends AbstractBox { */ getOne(axis: Axis, direction: Direction): T { switch (axis) { - case "x": + case AXIS.X: return direction === -1 ? this.left : this.right; default: return direction === -1 ? this.top : this.bottom; diff --git a/packages/dflex-utils/src/Box/BoxBool.ts b/packages/dflex-utils/src/Box/BoxBool.ts index afd4fbaa4..dd1ada20a 100644 --- a/packages/dflex-utils/src/Box/BoxBool.ts +++ b/packages/dflex-utils/src/Box/BoxBool.ts @@ -1,4 +1,4 @@ -import type { Axis } from "../types"; +import { AXIS, Axis } from "../constants"; import Box from "./Box"; class BoxBool extends Box { @@ -26,7 +26,7 @@ class BoxBool extends Box { */ isOneTruthyByAxis(axis: Axis): boolean { switch (axis) { - case "x": + case AXIS.X: return this.left || this.right; default: return this.top || this.bottom; diff --git a/packages/dflex-utils/src/Box/BoxRect.ts b/packages/dflex-utils/src/Box/BoxRect.ts index 9d6d3b09c..dffafb019 100644 --- a/packages/dflex-utils/src/Box/BoxRect.ts +++ b/packages/dflex-utils/src/Box/BoxRect.ts @@ -1,4 +1,4 @@ -import type { Axis } from "../types"; +import { AXIS, Axis } from "../constants"; import BoxNum from "./BoxNum"; export type BoxRectAbstract = { @@ -63,7 +63,7 @@ class BoxRect extends BoxNum { */ setAxis(axis: Axis, value: number): this { switch (axis) { - case "x": { + case AXIS.X: { this.left = value; this.right = this.width + value; break; diff --git a/packages/dflex-utils/src/Threshold/Threshold.ts b/packages/dflex-utils/src/Threshold/Threshold.ts index 1f368c365..74fe5963e 100644 --- a/packages/dflex-utils/src/Threshold/Threshold.ts +++ b/packages/dflex-utils/src/Threshold/Threshold.ts @@ -1,11 +1,12 @@ /* eslint-disable max-classes-per-file */ import { PointNum } from "../Point"; -import type { Dimensions, Axis, Direction } from "../types"; +import type { Dimensions, Direction } from "../types"; import { AbstractBox, BoxBool, BoxRectAbstract } from "../Box"; import { dirtyAssignBiggestRect } from "../collections"; +import { AXIS, Axis } from "../constants"; export interface ThresholdPercentages { /** vertical threshold in percentage from 0-100 */ @@ -198,11 +199,11 @@ class DFlexThreshold { ): boolean { const { left, right, top, bottom } = this.thresholds[key]; - if (axis === "x") { + if (axis === AXIS.X) { this.isOut[key].setByAxis(axis, startingPos < left, endingPos > right); } - if (axis === "y") { + if (axis === AXIS.Y) { this.isOut[key].setByAxis(axis, startingPos < top, endingPos > bottom); } @@ -232,7 +233,7 @@ class DFlexThreshold { const { left, right, top, bottom } = this.thresholds[key]; const is = - axis === "x" + axis === AXIS.X ? direction === -1 ? startingPos < left : endingPos > right diff --git a/packages/dflex-utils/src/constants.ts b/packages/dflex-utils/src/constants.ts new file mode 100644 index 000000000..f38214320 --- /dev/null +++ b/packages/dflex-utils/src/constants.ts @@ -0,0 +1,9 @@ +export const AXIS = Object.freeze({ + X: "x", + Y: "y", +}); + +export type Axis = (typeof AXIS)[keyof typeof AXIS]; + +/** Bi-directional Axis. */ +export type Axes = Axis | "z"; diff --git a/packages/dflex-utils/src/index.ts b/packages/dflex-utils/src/index.ts index 67fc9a88f..58811f1cc 100644 --- a/packages/dflex-utils/src/index.ts +++ b/packages/dflex-utils/src/index.ts @@ -15,8 +15,6 @@ export type { Dimensions, RectDimensions, RectBoundaries, - Axes, - Axis, Direction, } from "./types"; @@ -35,3 +33,7 @@ export { export { canUseDOM, getSelection, getParentElm } from "./dom"; export * as featureFlags from "./FeatureFlags"; + +export { AXIS } from "./constants"; + +export type { Axes, Axis } from "./constants"; diff --git a/packages/dflex-utils/src/types.ts b/packages/dflex-utils/src/types.ts index e7b3117f2..362d57024 100644 --- a/packages/dflex-utils/src/types.ts +++ b/packages/dflex-utils/src/types.ts @@ -16,9 +16,3 @@ export interface RectBoundaries { } export type Direction = 1 | -1; - -/** Single Axis. */ -export type Axis = "x" | "y"; - -/** Bi-directional Axis. */ -export type Axes = Axis | "z";