Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add element visibility to calculations #293

Merged
merged 19 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 29 additions & 59 deletions packages/core-instance/src/CoreInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,6 @@ import type {
TransitionHistory,
} from "./types";

/**
* Why storing index here? when it's already sorted in order?
*
* Each element has index elements to element's position in list. It allows us
* to avoid looping in idsTreeOrder to know each element position.
*
* elem-id = id1
* iDsInOrder[id3, id1, id2]
* How do we get elem-id from iDsInOrder without loop? We don't want to loop
* twice once to know the qualified element to transform and the second to
* figure out element position.
*
* So, iDsInOrder[index] is our position in list. And, to update to
* new position: iDsInOrder[new-index] = elem-id.
*
*
* Why storing prev-index?
*
* Currently, there's no undo history. But, still need one-step back. Why?
* Because if dragged is out and release, it should go back to its
* prevIndex aka selfIndex before transformation.
*
* why Storing parent-index?
*
* To connect element with parents by knowing their locations.
*/

class CoreInstance
extends AbstractCoreInstance
implements CoreInstanceInterface
Expand All @@ -54,15 +27,17 @@ class CoreInstance
/** Store history of Y-transition according to unique ID. */
prevTranslateY: TransitionHistory;

currentTop: number;
currentTop!: number;

currentLeft: number;
currentLeft!: number;

order: Order;

keys: Keys;

constructor(elementWithPointer: ElmWIthPointer) {
isVisible: boolean;

constructor(elementWithPointer: ElmWIthPointer, isPause = false) {
const { order, keys, ...element } = elementWithPointer;

super(element);
Expand All @@ -77,19 +52,15 @@ class CoreInstance
top: 0,
};

/**
* Used for dragged, storing temporary top, left new positions during the transition.
*/
this.currentTop = 0;
this.order = order;
this.keys = keys;

this.currentLeft = 0;
this.isVisible = !isPause;

if (this.ref) {
if (this.ref && this.isVisible) {
this.initIndicators();
this.ref.dataset.index = `${this.order.self}`;
}

this.order = order;
this.keys = keys;
}

/**
Expand All @@ -99,7 +70,7 @@ class CoreInstance
*
* So, basically any working element in DnD should be initiated first.
*/
private initIndicators() {
initIndicators() {
const { height, width, left, top } = this.ref.getBoundingClientRect();

/**
Expand All @@ -111,22 +82,22 @@ class CoreInstance
height,
width,

left,
top,
left: Math.abs(left),
top: Math.abs(top),
};

this.currentTop = top;
this.currentLeft = left;
this.currentTop = this.offset.top;
this.currentLeft = this.offset.left;
}

getOffset() {
return {
height: this.offset.height,
width: this.offset.width,
visibilityHasChanged(isVisible: boolean) {
if (isVisible === this.isVisible) return;

left: this.currentLeft,
top: this.currentTop,
};
if (isVisible && !this.isVisible) {
this.transformElm();
}

this.isVisible = isVisible;
}

private updateCurrentIndicators(topSpace: number, leftSpace: number) {
Expand Down Expand Up @@ -186,8 +157,7 @@ class CoreInstance
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.error(
"Illegal Attempt: More than one element have left the siblings list",
branchIDsOrder
"Illegal Attempt: More than one element have left the siblings list"
);
}

Expand All @@ -198,17 +168,16 @@ class CoreInstance
} else if (branchIDsOrder[newIndex].length > 0) {
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.error(
"Illegal Attempt: Colliding in positions",
branchIDsOrder
);
console.error("Illegal Attempt: Colliding in positions");
}

return siblingsEmptyElmIndex;
}

branchIDsOrder[newIndex] = this.id;

this.ref.dataset.index = `${newIndex}`;

return oldIndex;
}

Expand All @@ -227,7 +196,8 @@ class CoreInstance
}

this.updateCurrentIndicators(topSpace, 0);
this.transformElm();

if (this.isVisible) this.transformElm();
}

/**
Expand All @@ -252,7 +222,7 @@ class CoreInstance
*/
setYPosition(
iDsInOrder: string[],
sign: number,
sign: 1 | -1,
topSpace: number,
operationID: string,
siblingsEmptyElmIndex = -1,
Expand Down
5 changes: 3 additions & 2 deletions packages/core-instance/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface CoreInstanceInterface extends AbstractCoreInterface {
keys: Keys;
setYPosition(
iDsInOrder: ELmBranch,
sign: number,
sign: 1 | -1,
topSpace: number,
operationID: string,
siblingsHasEmptyElm?: number,
Expand All @@ -88,5 +88,6 @@ export interface CoreInstanceInterface extends AbstractCoreInterface {
oldIndex?: number,
siblingsHasEmptyElm?: number
): number;
getOffset(): Offset;
initIndicators(): void;
visibilityHasChanged(isVisible: boolean): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const TodoItem = ({
}, []);

const onMouseMove = (e: MouseEvent) => {
e.stopPropagation();
// e.stopPropagation();

if (dndEvent) {
const { clientX, clientY } = e;
Expand Down Expand Up @@ -66,6 +66,7 @@ export const TodoItem = ({
if (id) {
document.addEventListener("mouseup", onMouseUp);
document.addEventListener("mousemove", onMouseMove);
// document.addEventListener("scroll", onMouseScroll);

dndEvent = new DnD(id, { x: clientX, y: clientY });
}
Expand Down
150 changes: 148 additions & 2 deletions packages/dnd/src/DnDStore/DnDStoreImp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { ElmInstance } from "@dflex/store";
import type { ElmTree, BoundariesOffset, DnDStoreInterface } from "./types";

import Tracker from "./Tracker";
// import Environment from "../Environment";

// function noop() {}

Expand All @@ -23,11 +24,119 @@ class DnDStoreImp extends Store<CoreInstance> implements DnDStoreInterface {

siblingsBoundaries: { [k: string]: BoundariesOffset };

viewportHeight!: number;

viewportWidth!: number;

scrollY!: number;

scrollX!: number;

private throttle: boolean;

private elmIndicator!: {
currentKy: string;
prevKy: string;
exceptionToNextElm: boolean;
};

constructor() {
super();

this.siblingsBoundaries = {};
this.tracker = new Tracker();

this.initELmIndicator();

this.setViewport();
this.setScrollXY();

this.throttle = false;

window.addEventListener("scroll", this.animatedScroll.bind(this));
}

private setViewport() {
this.viewportHeight = Math.max(
document.documentElement.clientHeight || 0,
window.innerHeight || 0
);

this.viewportWidth = Math.max(
document.documentElement.clientWidth || 0,
window.innerWidth || 0
);
}

private setScrollXY() {
this.scrollY = Math.round(
document.documentElement.scrollTop || window.pageYOffset
);
this.scrollX = Math.round(
document.documentElement.scrollLeft || window.pageXOffset
);
}

private initELmIndicator() {
this.elmIndicator = {
currentKy: "",

prevKy: "",

exceptionToNextElm: false,
};
}

private isElementHiddenInViewport(
currentTop: number,
currentLeft: number
): boolean {
return (
currentTop < this.scrollY ||
currentTop >= this.viewportHeight + this.scrollY ||
currentLeft < this.scrollX ||
currentLeft >= this.viewportWidth + this.scrollX
);
}

private updateRegisteredLayoutIndicators() {
this.initELmIndicator();

Object.keys(this.DOMGen.branches).forEach((branchKey) => {
// Ignore non array branches.
if (Array.isArray(this.DOMGen.branches[branchKey])) {
let prevIndex = 0;

(this.DOMGen.branches[branchKey] as string[]).forEach((elmID, i) => {
if (elmID.length > 0) {
const { currentTop, currentLeft } = this.registry[elmID];

let isVisible = !this.isElementHiddenInViewport(
currentTop,
currentLeft
);

if (
!isVisible &&
!this.elmIndicator.exceptionToNextElm &&
i > prevIndex
) {
this.elmIndicator.exceptionToNextElm = true;
isVisible = true;
} else if (isVisible && this.elmIndicator.exceptionToNextElm) {
// In this case, we are moving from hidden to visible.
// Eg: 1, 2 are hidden the rest of the list is visible.
// But, there's a possibility that the rest of the branch elements
// are hidden.
// Eg: 1, 2: hidden 3, 4, 5, 6, 7:visible 8, 9, 10: hidden.
this.initELmIndicator();
}
this.registry[elmID].visibilityHasChanged(isVisible);
prevIndex = i;
}
});
}
});
}

private assignSiblingsBoundaries(siblingsK: string, elemOffset: Offset) {
Expand Down Expand Up @@ -103,15 +212,35 @@ class DnDStoreImp extends Store<CoreInstance> implements DnDStoreInterface {
super.register(element, CoreInstance);

const {
currentTop,
currentLeft,
offset,
keys: { sK },
keys: { sK, pK },
} = this.registry[id];

this.assignSiblingsBoundaries(sK, offset);

let isVisible = !this.isElementHiddenInViewport(currentTop, currentLeft);

// same branch
this.elmIndicator.currentKy = `${sK}${pK}`;

if (
!isVisible &&
!this.elmIndicator.exceptionToNextElm &&
this.elmIndicator.currentKy === this.elmIndicator.prevKy
) {
this.elmIndicator.exceptionToNextElm = true;
isVisible = true;
}

this.registry[id].isVisible = isVisible;

this.elmIndicator.prevKy = this.elmIndicator.currentKy;
}

getELmOffsetById(id: string) {
return this.getElmById(id).getOffset();
return this.getElmById(id).offset;
}

getELmTranslateById(id: string) {
Expand Down Expand Up @@ -170,6 +299,23 @@ class DnDStoreImp extends Store<CoreInstance> implements DnDStoreInterface {
},
};
}

private animatedScroll() {
this.setScrollXY();

if (!this.throttle) {
window.requestAnimationFrame(() => {
this.updateRegisteredLayoutIndicators();
this.throttle = false;
});

this.throttle = true;
}
}

cleanup() {
window.removeEventListener("scroll", this.animatedScroll.bind(this));
}
}

export default (function createStoreInstance() {
Expand Down