diff --git a/packages/dflex-core-instance/src/Node/DFlexBaseNode.ts b/packages/dflex-core-instance/src/Node/DFlexBaseNode.ts
index 062cf89b5..c1e5aedab 100644
--- a/packages/dflex-core-instance/src/Node/DFlexBaseNode.ts
+++ b/packages/dflex-core-instance/src/Node/DFlexBaseNode.ts
@@ -1,4 +1,4 @@
-import { PointNum } from "@dflex/utils";
+import { PointNum, DFlexElmType } from "@dflex/utils";
import { DFLEX_ATTRIBUTES } from "./constants";
import type { AllowedAttributes } from "./constants";
@@ -14,19 +14,31 @@ class DFlexBaseNode {
private _hasAttribute?: AttributeSet;
- static getType(): string {
- return "base:node";
- }
+ private _type: DFlexElmType;
static transform(DOM: HTMLElement, x: number, y: number): void {
DOM.style.transform = `translate3d(${x}px,${y}px, 0)`;
}
- constructor(id: string) {
+ constructor(id: string, type: DFlexElmType) {
this.id = id;
+ this._type = type;
this.isPaused = true;
}
+ getType(): DFlexElmType {
+ return this._type;
+ }
+
+ /**
+ * This only happens during the registration.
+ *
+ * @param type
+ */
+ setType(type: DFlexElmType): void {
+ this._type = type;
+ }
+
/**
* Initialize the translate AxesCoordinates as part of abstract instance and
* necessary for darg only movement.
@@ -42,7 +54,7 @@ class DFlexBaseNode {
setAttribute(
DOM: HTMLElement,
key: AllowedAttributes,
- value: string | number
+ value: "true" | "false" | DFlexElmType | number
): void {
if (key === "INDEX") {
DOM.setAttribute(DFLEX_ATTRIBUTES[key], `${value}`);
@@ -50,6 +62,16 @@ class DFlexBaseNode {
return;
}
+ if (__DEV__) {
+ if (this._hasAttribute === undefined) {
+ throw new Error(`setAttribute: Attribute set is not initialized`);
+ }
+
+ if (!DFLEX_ATTRIBUTES[key]) {
+ throw new Error(`setAttribute: Invalid attribute key: ${key}`);
+ }
+ }
+
if (this._hasAttribute!.has(key)) return;
DOM.setAttribute(DFLEX_ATTRIBUTES[key], `${value}`);
this._hasAttribute!.add(key);
diff --git a/packages/dflex-core-instance/src/Node/DFlexCoreNode.ts b/packages/dflex-core-instance/src/Node/DFlexCoreNode.ts
index a8f9c3555..47551526d 100644
--- a/packages/dflex-core-instance/src/Node/DFlexCoreNode.ts
+++ b/packages/dflex-core-instance/src/Node/DFlexCoreNode.ts
@@ -1,10 +1,16 @@
import { PointNum } from "@dflex/utils";
-import type { RectDimensions, Direction, Axes, AxesPoint } from "@dflex/utils";
+import type {
+ RectDimensions,
+ Direction,
+ Axes,
+ AxesPoint,
+ DFlexElmType,
+} from "@dflex/utils";
import DFlexBaseNode from "./DFlexBaseNode";
export type SerializedDFlexCoreNode = {
- type: string;
+ type: DFlexElmType;
version: 3;
id: string;
translate: PointNum | null;
@@ -45,7 +51,7 @@ export interface DFlexNodeInput {
order: DOMGenOrder;
keys: Keys;
depth: number;
- readonly: boolean;
+ type: DFlexElmType;
}
class DFlexCoreNode extends DFlexBaseNode {
@@ -65,27 +71,20 @@ class DFlexCoreNode extends DFlexBaseNode {
hasPendingTransform!: boolean;
- readonly: boolean;
-
animatedFrame: number | null;
private _translateHistory?: TransitionHistory[];
- static getType(): string {
- return "core:node";
- }
-
static transform = DFlexBaseNode.transform;
constructor(eleWithPointer: DFlexNodeInput) {
- const { order, keys, depth, readonly, id } = eleWithPointer;
+ const { order, keys, depth, id, type } = eleWithPointer;
- super(id);
+ super(id, type);
this.order = order;
this.keys = keys;
this.depth = depth;
- this.readonly = readonly;
this.isPaused = false;
this.isVisible = !this.isPaused;
this.animatedFrame = null;
@@ -382,7 +381,7 @@ class DFlexCoreNode extends DFlexBaseNode {
getSerializedInstance(): SerializedDFlexCoreNode {
return {
- type: DFlexCoreNode.getType(),
+ type: this.getType(),
version: 3,
id: this.id,
grid: this.grid,
diff --git a/packages/dflex-core-instance/src/Node/constants.ts b/packages/dflex-core-instance/src/Node/constants.ts
index eb9171c02..ec79b007c 100644
--- a/packages/dflex-core-instance/src/Node/constants.ts
+++ b/packages/dflex-core-instance/src/Node/constants.ts
@@ -2,6 +2,8 @@ const OUT_POS = "data-dragged-out-position";
const OUT_CONTAINER = "data-dragged-out-container";
const INDEX = "data-index";
const DRAGGED = "dragged";
+const ELM_TYPE = "data-element-type";
+
// const GRID_X = "data-grid-x";
// const GRID_Y = "data-grid-y";
@@ -12,6 +14,7 @@ export const DFLEX_ATTRIBUTES = Object.freeze({
INDEX,
OUT_POS,
OUT_CONTAINER,
+ ELM_TYPE,
});
export type AllowedAttributes = keyof typeof DFLEX_ATTRIBUTES;
diff --git a/packages/dflex-dnd-playground/src/App.tsx b/packages/dflex-dnd-playground/src/App.tsx
index b83434ded..3fb13bcbd 100644
--- a/packages/dflex-dnd-playground/src/App.tsx
+++ b/packages/dflex-dnd-playground/src/App.tsx
@@ -15,6 +15,7 @@ import {
ContainerBasedEvent,
ScrollMultiLists,
ListMigration,
+ LayoutWithDroppable,
} from "./components";
function App() {
@@ -61,6 +62,7 @@ function App() {
} />
} />
} />
+ } />
{
const taskRef = React.useRef() as React.MutableRefObject;
- const { id, depth, readonly } = registerInput;
+ const { id, depth, type } = registerInput;
React.useEffect(() => {
if (taskRef.current) {
- store.register({ id, depth, readonly });
+ store.register({ id, depth, type });
}
return () => {
diff --git a/packages/dflex-dnd-playground/src/components/droppable/LayoutWithDroppable.tsx b/packages/dflex-dnd-playground/src/components/droppable/LayoutWithDroppable.tsx
new file mode 100644
index 000000000..455b0be02
--- /dev/null
+++ b/packages/dflex-dnd-playground/src/components/droppable/LayoutWithDroppable.tsx
@@ -0,0 +1,86 @@
+import type { DFlexElmType } from "@dflex/dnd";
+import React from "react";
+
+import DFlexDnDComponent from "../DFlexDnDComponent";
+
+type Container = {
+ id: string;
+ content: string;
+ type: DFlexElmType;
+ style: React.CSSProperties;
+};
+
+const container1: Container[] = [
+ { id: "inter-elm-1", type: "interactive", content: "Inter-1", style: {} },
+ {
+ id: "inter-elm-2",
+ type: "interactive",
+ content: "Inter-2",
+ style: {},
+ },
+ {
+ id: "inter-elm-3",
+ type: "interactive",
+ content: "Inter-3",
+ style: {},
+ },
+ { id: "inter-elm-4", type: "interactive", content: "Inter-4", style: {} },
+ { id: "inter-elm-5", type: "interactive", content: "Inter-5", style: {} },
+ { id: "inter-elm-6", type: "interactive", content: "Inter-6", style: {} },
+ { id: "inter-elm-7", type: "interactive", content: "Inter-7", style: {} },
+ { id: "inter-elm-8", type: "interactive", content: "Inter-8", style: {} },
+ { id: "inter-elm-9", type: "interactive", content: "Inter-9", style: {} },
+ { id: "inter-elm-10", type: "interactive", content: "Inter-10", style: {} },
+];
+
+const container2: Container = {
+ id: "drop-elm-1",
+ type: "droppable",
+ content: "Droppable area 1",
+ style: {},
+};
+
+const container3: Container = {
+ id: "drop-elm-2",
+ type: "droppable",
+ content: "Droppable area 2",
+ style: {},
+};
+
+const DFlexInteractive = ({ container }: { container: Container[] }) => (
+
+ {container.map(({ content, id, type, style }) => (
+
+ {content}
+
+ ))}
+
+);
+
+const DFlexDroppable = ({ content, id, style, type }: Container) => (
+
+ {content}
+
+);
+
+const LayoutWithDroppable = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default LayoutWithDroppable;
diff --git a/packages/dflex-dnd-playground/src/components/droppable/index.ts b/packages/dflex-dnd-playground/src/components/droppable/index.ts
new file mode 100644
index 000000000..3a14cb7e2
--- /dev/null
+++ b/packages/dflex-dnd-playground/src/components/droppable/index.ts
@@ -0,0 +1,3 @@
+import LayoutWithDroppable from "./LayoutWithDroppable";
+
+export default LayoutWithDroppable;
diff --git a/packages/dflex-dnd-playground/src/components/essential/List.css b/packages/dflex-dnd-playground/src/components/essential/List.css
index 2502014ad..ac168957b 100644
--- a/packages/dflex-dnd-playground/src/components/essential/List.css
+++ b/packages/dflex-dnd-playground/src/components/essential/List.css
@@ -117,6 +117,7 @@
padding: 8px;
border: 12px #ffee93 solid;
border-radius: 18px;
+ overflow: auto;
}
.list-migration li {
diff --git a/packages/dflex-dnd-playground/src/components/index.ts b/packages/dflex-dnd-playground/src/components/index.ts
index 29ce94e40..09d0824fb 100644
--- a/packages/dflex-dnd-playground/src/components/index.ts
+++ b/packages/dflex-dnd-playground/src/components/index.ts
@@ -1,5 +1,6 @@
export { default as Depth1 } from "./depth";
export { default as ExtendedList } from "./extended";
+export { default as LayoutWithDroppable } from "./droppable";
export {
AllRestrictedContainer,
diff --git a/packages/dflex-dnd-playground/src/components/todo/TodoListWithReadonly.tsx b/packages/dflex-dnd-playground/src/components/todo/TodoListWithReadonly.tsx
index 1bd9ff21c..63021b7cc 100644
--- a/packages/dflex-dnd-playground/src/components/todo/TodoListWithReadonly.tsx
+++ b/packages/dflex-dnd-playground/src/components/todo/TodoListWithReadonly.tsx
@@ -1,29 +1,37 @@
+import type { DFlexElmType } from "@dflex/dnd";
import React from "react";
import DFlexDnDComponent from "../DFlexDnDComponent";
+type Task = {
+ id: string;
+ type: DFlexElmType;
+ msg: string;
+ style: React.CSSProperties;
+};
+
const TodoListWithReadonly = () => {
- const tasks = [
+ const tasks: Task[] = [
{
- readonly: false,
+ type: "interactive",
id: "interactive-1",
msg: "Interactive task 1",
style: { height: "4.5rem" },
},
{
- readonly: true,
+ type: "draggable",
id: "readonly-1",
msg: "Readonly task 1",
style: { height: "4.5rem" },
},
{
- readonly: false,
+ type: "interactive",
id: "interactive-2",
msg: "Interactive task 2",
style: { height: "4.5rem" },
},
{
- readonly: true,
+ type: "draggable",
id: "readonly-2",
msg: "Readonly task 2",
style: { height: "4.5rem" },
@@ -34,10 +42,10 @@ const TodoListWithReadonly = () => {
- {tasks.map(({ msg, id, readonly, style }) => (
+ {tasks.map(({ msg, id, type, style }) => (
diff --git a/packages/dflex-dnd/src/Droppable/DFlexMechanismController.ts b/packages/dflex-dnd/src/Droppable/DFlexMechanismController.ts
index 43a8053e5..2d2bd0b67 100644
--- a/packages/dflex-dnd/src/Droppable/DFlexMechanismController.ts
+++ b/packages/dflex-dnd/src/Droppable/DFlexMechanismController.ts
@@ -16,7 +16,7 @@ export function isIDEligible(elmID: string, draggedID: string): boolean {
elmID.length > 0 &&
elmID !== draggedID &&
store.has(elmID) &&
- !store.registry.get(elmID)!.readonly
+ store.registry.get(elmID)!.getType() === "interactive"
);
}
diff --git a/packages/dflex-dnd/src/LayoutManager/DFlexDnDStore.ts b/packages/dflex-dnd/src/LayoutManager/DFlexDnDStore.ts
index 404037a07..cf0865f27 100644
--- a/packages/dflex-dnd/src/LayoutManager/DFlexDnDStore.ts
+++ b/packages/dflex-dnd/src/LayoutManager/DFlexDnDStore.ts
@@ -36,6 +36,8 @@ type UpdatesQueue = Array<
type Deferred = Array<() => void>;
+const INTERACTIVE_ELM = "interactive";
+
class DFlexDnDStore extends DFlexBaseStore {
containers: Containers;
@@ -89,24 +91,27 @@ class DFlexDnDStore extends DFlexBaseStore {
container: DFlexParentContainer,
id: string
) {
- const [dflexNode, DOM] = this.getElmWithDOM(id);
+ const [dflexElm, DOM] = this.getElmWithDOM(id);
const { scrollRect } = scroll;
- dflexNode.resume(DOM, scrollRect.left, scrollRect.top);
+ dflexElm.resume(DOM, scrollRect.left, scrollRect.top);
// Using element grid zero to know if the element has been initiated inside
// container or not.
- if (dflexNode.grid.x === 0) {
- const { initialOffset } = dflexNode;
+ if (dflexElm.grid.x === 0) {
+ const { initialOffset } = dflexElm;
container.registerNewElm(
initialOffset,
- this.unifiedContainerDimensions[dflexNode.depth]
+ this.unifiedContainerDimensions[dflexElm.depth]
);
- dflexNode.grid.clone(container.grid);
+ dflexElm.grid.clone(container.grid);
}
+
+ dflexElm.setAttribute(DOM, "INDEX", dflexElm.order.self);
+ dflexElm.setAttribute(DOM, "ELM_TYPE", dflexElm.getType());
}
private _initBranch(SK: string, depth: number, DOM: HTMLElement) {
@@ -196,8 +201,8 @@ class DFlexDnDStore extends DFlexBaseStore {
() => {
const coreInput = {
id,
- readonly: !!element.readonly,
depth: element.depth || 0,
+ type: element.type || INTERACTIVE_ELM,
};
// Create an instance of DFlexCoreNode and gets the DOM element into the store.
diff --git a/packages/dflex-dnd/src/index.ts b/packages/dflex-dnd/src/index.ts
index a4985e555..f10898f06 100644
--- a/packages/dflex-dnd/src/index.ts
+++ b/packages/dflex-dnd/src/index.ts
@@ -11,3 +11,5 @@ export type {
DFlexEventsTypes,
DFlexListenerEvents,
} from "./LayoutManager";
+
+export type { DFlexElmType } from "@dflex/utils";
diff --git a/packages/dflex-draggable/src/DFlexDraggable.ts b/packages/dflex-draggable/src/DFlexDraggable.ts
index 3de18594d..bb1ec027e 100644
--- a/packages/dflex-draggable/src/DFlexDraggable.ts
+++ b/packages/dflex-draggable/src/DFlexDraggable.ts
@@ -18,6 +18,15 @@ class DFlexDraggable extends DFlexBaseDraggable {
}
dragAt(x: number, y: number) {
+ if (this.draggedElm.getType() === "droppable") {
+ if (__DEV__) {
+ // eslint-disable-next-line no-console
+ console.warn("Droppable element can't be dragged");
+ }
+
+ return;
+ }
+
this.translate(x, y);
this.draggedElm.translate.clone(this.translatePlaceholder);
diff --git a/packages/dflex-draggable/src/DFlexDraggableStore.ts b/packages/dflex-draggable/src/DFlexDraggableStore.ts
index 05b19a116..03f3d0b6c 100644
--- a/packages/dflex-draggable/src/DFlexDraggableStore.ts
+++ b/packages/dflex-draggable/src/DFlexDraggableStore.ts
@@ -6,6 +6,8 @@ declare global {
var $DFlex_Draggable: DFlexDraggableStore;
}
+const DRAGGABLE_ELM = "draggable";
+
class DFlexDraggableStore extends DFlexBaseStore {
constructor() {
super();
@@ -17,6 +19,7 @@ class DFlexDraggableStore extends DFlexBaseStore {
const [dflexNode, DOM] = this.getElmWithDOM(id);
dflexNode.resume(DOM, 0, 0);
+ dflexNode.setAttribute(DOM, "ELM_TYPE", DRAGGABLE_ELM);
}
private _initBranch(SK: string) {
@@ -29,11 +32,13 @@ class DFlexDraggableStore extends DFlexBaseStore {
*/
// @ts-ignore
register(id: string) {
+ if (!canUseDOM()) return;
+
super.register(
{
id,
depth: 0,
- readonly: false,
+ type: DRAGGABLE_ELM,
},
this._initBranch
);
diff --git a/packages/dflex-store/src/DFlexBaseStore.ts b/packages/dflex-store/src/DFlexBaseStore.ts
index 4efc1e1be..a120639d4 100644
--- a/packages/dflex-store/src/DFlexBaseStore.ts
+++ b/packages/dflex-store/src/DFlexBaseStore.ts
@@ -2,7 +2,7 @@
import Generator, { ELmBranch } from "@dflex/dom-gen";
import { DFlexNode, DFlexNodeInput } from "@dflex/core-instance";
-import { getParentElm, Tracker } from "@dflex/utils";
+import { getParentElm, Tracker, DFlexElmType } from "@dflex/utils";
// https://github.com/microsoft/TypeScript/issues/28374#issuecomment-536521051
type DeepNonNullable = {
@@ -19,11 +19,7 @@ export type RegisterInputOpts = {
/** The depth of targeted element starting from zero (The default value is zero). */
depth?: number;
- /**
- * True for elements that won't be transformed during DnD but belongs to the
- * same interactive container.
- * */
- readonly?: boolean;
+ type?: DFlexElmType;
};
export type RegisterInputBase = DeepNonNullable;
@@ -110,7 +106,7 @@ class DFlexBaseStore {
elm: RegisterInputBase,
branchComposedCallBack: BranchComposedCallBackFunction | null
): void {
- const { id, depth, readonly } = elm;
+ const { id, depth, type } = elm;
if (!this.interactiveDOM.has(id)) {
this.interactiveDOM.set(id, DOM);
@@ -122,7 +118,7 @@ class DFlexBaseStore {
// This is the only difference between register by default and register
// with a user only. In the future if there's new options then this should
// be updated.
- elmInRegistry!.readonly = readonly;
+ elmInRegistry!.setType(type);
if (__DEV__) {
// eslint-disable-next-line no-console
@@ -139,15 +135,13 @@ class DFlexBaseStore {
order,
keys,
depth,
- readonly,
+ type,
};
const dflexElm = new DFlexNode(coreElement);
this.registry.set(id, dflexElm);
- dflexElm.setAttribute(DOM, "INDEX", dflexElm.order.self);
-
if (depth >= 1) {
if (keys.CHK === null) {
if (__DEV__) {
@@ -176,7 +170,7 @@ class DFlexBaseStore {
*/
register(
element: RegisterInputBase,
- branchComposedCallBack?: BranchComposedCallBackFunction
+ branchComposedCallBack: BranchComposedCallBackFunction | null = null
): void {
const { id, depth } = element;
@@ -215,10 +209,10 @@ class DFlexBaseStore {
{
id: parentID,
depth: parentDepth,
- // Default value for inserted parent element.
- readonly: true,
+ // Dropped elements are not interactive.
+ type: "droppable",
},
- branchComposedCallBack || null
+ branchComposedCallBack
);
});
diff --git a/packages/dflex-utils/src/dom/getParentElm.ts b/packages/dflex-utils/src/dom/getParentElm.ts
index 3bba3af7b..714452ed9 100644
--- a/packages/dflex-utils/src/dom/getParentElm.ts
+++ b/packages/dflex-utils/src/dom/getParentElm.ts
@@ -9,29 +9,38 @@ function getParentElm(
let current: HTMLElement | null = baseElement;
- do {
- iterationCounter += 1;
-
- if (__DEV__) {
- if (iterationCounter > MAX_LOOP_ELEMENTS_TO_WARN) {
- throw new Error(
- `getParentElm: DFlex detects performance issues during iterating for nearest parent element.` +
- `Please check your registered interactive element at id:${baseElement.id}.`
- );
+ try {
+ do {
+ iterationCounter += 1;
+
+ if (__DEV__) {
+ if (iterationCounter > MAX_LOOP_ELEMENTS_TO_WARN) {
+ throw new Error(
+ `getParentElm: DFlex detects performance issues during iterating for nearest parent element.` +
+ `Please check your registered interactive element at id:${baseElement.id}.`
+ );
+ }
}
- }
- // Skip the same element `baseElement`.
- if (iterationCounter > 1) {
- // If the callback returns true, then we have found the parent element.
- if (cb(current)) {
- iterationCounter = 0;
- return current;
+ // Skip the same element `baseElement`.
+ if (iterationCounter > 1) {
+ // If the callback returns true, then we have found the parent element.
+ if (cb(current)) {
+ iterationCounter = 0;
+ return current;
+ }
}
- }
- current = current.parentElement;
- } while (current !== null && !current.isSameNode(document.body));
+ current = current.parentElement;
+ } while (current !== null && !current.isSameNode(document.body));
+ } catch (e) {
+ if (__DEV__) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ }
+ } finally {
+ iterationCounter = 0;
+ }
return null;
}
diff --git a/packages/dflex-utils/src/index.ts b/packages/dflex-utils/src/index.ts
index 740e4fef9..bed6bff04 100644
--- a/packages/dflex-utils/src/index.ts
+++ b/packages/dflex-utils/src/index.ts
@@ -21,6 +21,7 @@ export type {
Axes,
Axis,
Direction,
+ DFlexElmType,
} from "./types";
export { combineKeys, dirtyAssignBiggestRect } from "./collections";
diff --git a/packages/dflex-utils/src/types.ts b/packages/dflex-utils/src/types.ts
index e7b3117f2..fe610753a 100644
--- a/packages/dflex-utils/src/types.ts
+++ b/packages/dflex-utils/src/types.ts
@@ -22,3 +22,13 @@ export type Axis = "x" | "y";
/** Bi-directional Axis. */
export type Axes = Axis | "z";
+
+export type DFlexElmType =
+ /** Interactive element can be dragged and switched its position. */
+ | "interactive"
+
+ /** To define droppable area. */
+ | "droppable"
+
+ /** For elements that won't interact with active dragging element. */
+ | "draggable";