diff --git a/build.ts b/build.ts index f06f5cec..e1c0de59 100644 --- a/build.ts +++ b/build.ts @@ -45,6 +45,19 @@ const __dirname = dirname(__filename); outExtension: ({ format }) => ({ js: format === "cjs" ? ".cjs" : ".mjs" }), }); + // Build the solid entry + await build({ + entry: ["src/solid/index.ts"], + format: ["cjs", "esm"], + external: ["solid-js", "../index", "../utils"], + splitting: false, + sourcemap: true, + clean: true, + dts: true, + outDir: "dist/solid", + outExtension: ({ format }) => ({ js: format === "cjs" ? ".cjs" : ".mjs" }), + }); + async function replaceImports(fileName) { const format = fileName.endsWith("mjs") ? "mjs" : "cjs"; const file = await readFile(resolve(__dirname, `${fileName}`), "utf8"); diff --git a/dist/index.cjs b/dist/index.cjs index 3f3561cd..1d63f129 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -18,8 +18,8 @@ var __copyProps = (to, from, except, desc) => { var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts -var src_exports = {}; -__export(src_exports, { +var index_exports = {}; +__export(index_exports, { addClass: () => addClass, addEvents: () => addEvents, addNodeClass: () => addNodeClass, @@ -87,7 +87,7 @@ __export(src_exports, { validateSort: () => validateSort, validateTransfer: () => validateTransfer }); -module.exports = __toCommonJS(src_exports); +module.exports = __toCommonJS(index_exports); // src/utils.ts function pd(e) { diff --git a/dist/react/index.cjs b/dist/react/index.cjs index fa6e862c..aeacb766 100644 --- a/dist/react/index.cjs +++ b/dist/react/index.cjs @@ -18,12 +18,12 @@ var __copyProps = (to, from, except, desc) => { var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/react/index.ts -var react_exports = {}; -__export(react_exports, { +var index_exports = {}; +__export(index_exports, { dragAndDrop: () => dragAndDrop, useDragAndDrop: () => useDragAndDrop }); -module.exports = __toCommonJS(react_exports); +module.exports = __toCommonJS(index_exports); var import_react = require("react"); var import__ = require("../index.cjs"); diff --git a/dist/solid/index.cjs b/dist/solid/index.cjs new file mode 100644 index 00000000..e3826c28 --- /dev/null +++ b/dist/solid/index.cjs @@ -0,0 +1,91 @@ +"use strict"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/solid/index.ts +var index_exports = {}; +__export(index_exports, { + dragAndDrop: () => dragAndDrop, + useDragAndDrop: () => useDragAndDrop +}); +module.exports = __toCommonJS(index_exports); +var import__ = require("../index"); +var import_solid_js2 = require("solid-js"); +var import_store = require("solid-js/store"); + +// src/solid/utils.ts +var import_solid_js = require("solid-js"); +function getEl(parent) { + if (parent instanceof HTMLElement) return parent; + else if (typeof parent !== "function") return void 0; + const p = parent(); + return p instanceof HTMLElement ? p : void 0; +} +function handleSolidElements(element, cb) { + (0, import_solid_js.createEffect)((0, import_solid_js.on)(() => getEl(element), (el) => el && cb(el))); +} + +// src/solid/index.ts +var parentValues = /* @__PURE__ */ new WeakMap(); +function getValues(parent) { + const values = parentValues.get(parent); + if (!values) { + console.warn("No values found for parent element"); + return []; + } + return values[0](); +} +function setValues(newValues, parent) { + const currentValues = parentValues.get(parent); + if (currentValues) currentValues[1](newValues); +} +function handleParent(config, values) { + return (el) => { + parentValues.set(el, values); + (0, import__.dragAndDrop)({ parent: el, getValues, setValues, config }); + }; +} +function dragAndDrop(data) { + if (!import__.isBrowser) return; + if (!Array.isArray(data)) data = [data]; + data.forEach((dnd) => { + const { parent, state, ...rest } = dnd; + handleSolidElements(parent, handleParent(rest, state)); + }); +} +function useDragAndDrop(initValues, options = {}) { + const [parent, setParent] = (0, import_solid_js2.createSignal)(null); + const [values, setValues2] = (0, import_store.createStore)(initValues); + function updateConfig(config = {}) { + dragAndDrop({ parent, state: [() => values, setValues2], ...config }); + } + (0, import_solid_js2.onMount)( + () => dragAndDrop({ parent, state: [() => values, setValues2], ...options }) + ); + (0, import_solid_js2.onCleanup)(() => { + const p = parent(); + p && (0, import__.tearDown)(p); + }); + return [setParent, () => values, setValues2, updateConfig]; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + dragAndDrop, + useDragAndDrop +}); +//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/dist/solid/index.cjs.map b/dist/solid/index.cjs.map new file mode 100644 index 00000000..442e8b62 --- /dev/null +++ b/dist/solid/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/solid/index.ts","../../src/solid/utils.ts"],"sourcesContent":["import {\n dragAndDrop as initParent,\n isBrowser,\n type ParentConfig,\n tearDown,\n} from \"../index\";\nimport {\n createSignal,\n type Accessor,\n type Setter,\n onCleanup,\n onMount,\n} from \"solid-js\";\nimport { createStore, Store } from \"solid-js/store\";\nimport type { SolidDragAndDropConfig, SolidState } from \"./types\";\nimport { handleSolidElements } from \"./utils\";\n\n/**\n * Global store for parent els to values.\n */\nconst parentValues: WeakMap> = new WeakMap();\n\n/**\n * Returns the values of the parent element.\n *\n * @param parent - The parent element.\n *\n * @returns The values of the parent element.\n */\nfunction getValues(parent: HTMLElement): Array {\n const values = parentValues.get(parent);\n\n if (!values) {\n console.warn(\"No values found for parent element\");\n\n return [];\n }\n\n return (values[0] as Accessor>)();\n}\n\n/**\n * Sets the values of the parent element.\n *\n * @param parent - The parent element.\n *\n * @param newValues - The new values for the parent element.\n *\n * @returns void\n */\nfunction setValues(newValues: Array, parent: HTMLElement): void {\n const currentValues = parentValues.get(parent);\n\n if (currentValues) currentValues[1](newValues);\n}\n\nfunction handleParent | HTMLElement, T>(\n config: Partial>,\n values: SolidState\n) {\n return (el: HTMLElement) => {\n parentValues.set(el, values);\n\n initParent({ parent: el, getValues, setValues, config });\n };\n}\n\nexport function dragAndDrop(\n data:\n | SolidDragAndDropConfig | HTMLElement, I[]>\n | Array | HTMLElement, I[]>>\n): void {\n if (!isBrowser) return;\n\n if (!Array.isArray(data)) data = [data];\n\n data.forEach((dnd) => {\n const { parent, state, ...rest } = dnd;\n\n handleSolidElements(parent, handleParent(rest, state));\n });\n}\n\n/**\n * Hook for adding drag and drop/sortable support to a list of items.\n *\n * @param initValues - Initial list of data.\n * @param options - The drag and drop configuration.\n * @returns\n */\nexport function useDragAndDrop(\n initValues: T[],\n options: Partial> = {}\n): [\n Setter,\n Accessor>,\n ReturnType>[1], // Return type of `createStore` will be changed in solid-js 2, so use `ReturnType` util here\n (config?: Partial>) => void\n] {\n const [parent, setParent] = createSignal(null);\n\n const [values, setValues] = createStore(initValues);\n\n function updateConfig(config: Partial> = {}) {\n dragAndDrop({ parent, state: [() => values, setValues], ...config });\n }\n\n onMount(() =>\n dragAndDrop({ parent, state: [() => values, setValues], ...options })\n );\n onCleanup(() => {\n const p = parent();\n p && tearDown(p);\n });\n\n return [setParent, () => values, setValues, updateConfig];\n}\n","import { createEffect, on, type Accessor } from \"solid-js\";\n\n/**\n * Checks if the given parent is an HTMLElement.\n *\n * @param dnd - The drag and drop configuration.\n */\nexport function getEl(\n parent: HTMLElement | Accessor\n): HTMLElement | void {\n if (parent instanceof HTMLElement) return parent;\n else if (typeof parent !== 'function') return undefined;\n const p = parent();\n return p instanceof HTMLElement ? p : undefined;\n}\n\nexport function handleSolidElements(\n element: HTMLElement | Accessor,\n cb: (el: HTMLElement) => void\n): void {\n createEffect(on(() => getEl(element), (el) => el && cb(el)));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAKO;AACP,IAAAA,mBAMO;AACP,mBAAmC;;;ACbnC,sBAAgD;AAOzC,SAAS,MACd,QACoB;AACpB,MAAI,kBAAkB,YAAa,QAAO;AAAA,WACjC,OAAO,WAAW,WAAY,QAAO;AAC9C,QAAM,IAAI,OAAO;AACjB,SAAO,aAAa,cAAc,IAAI;AACxC;AAEO,SAAS,oBACd,SACA,IACM;AACN,wCAAa,oBAAG,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC;AAC7D;;;ADDA,IAAM,eAAsD,oBAAI,QAAQ;AASxE,SAAS,UAAa,QAA+B;AACnD,QAAM,SAAS,aAAa,IAAI,MAAM;AAEtC,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,oCAAoC;AAEjD,WAAO,CAAC;AAAA,EACV;AAEA,SAAQ,OAAO,CAAC,EAAyB;AAC3C;AAWA,SAAS,UAAU,WAAuB,QAA2B;AACnE,QAAM,gBAAgB,aAAa,IAAI,MAAM;AAE7C,MAAI,cAAe,eAAc,CAAC,EAAE,SAAS;AAC/C;AAEA,SAAS,aACP,QACA,QACA;AACA,SAAO,CAAC,OAAoB;AAC1B,iBAAa,IAAI,IAAI,MAAM;AAE3B,iBAAAC,aAAc,EAAE,QAAQ,IAAI,WAAW,WAAW,OAAO,CAAC;AAAA,EAC5D;AACF;AAEO,SAAS,YACd,MAGM;AACN,MAAI,CAAC,mBAAW;AAEhB,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC,IAAI;AAEtC,OAAK,QAAQ,CAAC,QAAQ;AACpB,UAAM,EAAE,QAAQ,OAAO,GAAG,KAAK,IAAI;AAEnC,wBAAoB,QAAQ,aAAa,MAAM,KAAK,CAAC;AAAA,EACvD,CAAC;AACH;AASO,SAAS,eACd,YACA,UAAoC,CAAC,GAMrC;AACA,QAAM,CAAC,QAAQ,SAAS,QAAI,+BAAuB,IAAI;AAEvD,QAAM,CAAC,QAAQC,UAAS,QAAI,0BAAY,UAAU;AAElD,WAAS,aAAa,SAAmC,CAAC,GAAG;AAC3D,gBAAY,EAAE,QAAQ,OAAO,CAAC,MAAM,QAAQA,UAAS,GAAG,GAAG,OAAO,CAAC;AAAA,EACrE;AAEA;AAAA,IAAQ,MACN,YAAY,EAAE,QAAQ,OAAO,CAAC,MAAM,QAAQA,UAAS,GAAG,GAAG,QAAQ,CAAC;AAAA,EACtE;AACA,kCAAU,MAAM;AACd,UAAM,IAAI,OAAO;AACjB,aAAK,mBAAS,CAAC;AAAA,EACjB,CAAC;AAED,SAAO,CAAC,WAAW,MAAM,QAAQA,YAAW,YAAY;AAC1D;","names":["import_solid_js","initParent","setValues"]} \ No newline at end of file diff --git a/dist/solid/index.d.cts b/dist/solid/index.d.cts new file mode 100644 index 00000000..8519555f --- /dev/null +++ b/dist/solid/index.d.cts @@ -0,0 +1,796 @@ +import { ParentConfig as ParentConfig$1 } from '../index'; +import { Accessor, Setter } from 'solid-js'; +import { createStore, Store } from 'solid-js/store'; + +interface ParentDragEventData extends ParentEventData { + e: DragEvent; +} +type NativeDragEffects = "link" | "none" | "copy" | "move"; +/** + * The configuration object for a given parent. + */ +interface ParentConfig { + /** + * A function that returns whether a given parent accepts a given node. + */ + accepts?: (targetParentData: ParentRecord, initialParentData: ParentRecord, currentParentData: ParentRecord, state: BaseDragState) => boolean; + activeDescendantClass?: string; + /** + * The data transfer effect to use for the drag operation. + */ + dragEffectAllowed: NativeDragEffects; + /** + * The data transfer effect to use for the drag operation. + */ + dragDropEffect: NativeDragEffects; + /** + * A function that returns the image to use for the drag operation. This is + * invoked for native operations. The clonedNode is what will be set as the drag + * image, but this can be updated. + */ + dragImage?: (data: NodeDragEventData, draggedNodes: Array>) => HTMLElement; + /** + * A flag to disable dragability of all nodes in the parent. + */ + disabled?: boolean; + /** + * A selector for the drag handle. Will search at any depth within the + * draggable element. + */ + dragHandle?: string; + /** + * External drag handle + */ + externalDragHandle?: { + el: HTMLElement; + callback: () => HTMLElement; + }; + /** + * A function that returns whether a given node is draggable. + */ + draggable?: (child: HTMLElement) => boolean; + /** + * A function that returns whether a given value is draggable + */ + draggableValue?: (values: T) => boolean; + draggedNodes: (pointerDown: { + parent: ParentRecord; + node: NodeRecord; + }) => Array>; + /** + * The class to add to a node when it is being dragged. + */ + draggingClass?: string; + /** + * Accepts array of "dragged nodes" and applies dragstart classes to them. + */ + dragstartClasses: (node: NodeRecord, nodes: Array>, config: ParentConfig, isSynthDrag?: boolean) => void; + dragPlaceholderClass?: string; + /** + * The configuration object for the drop and swap plugin. + */ + dropSwapConfig?: DropSwapConfig; + /** + * The class to add to a node when the node is dragged over it. + */ + dropZoneClass?: string; + /** + * The class to add to a parent when it is dragged over. + */ + dropZoneParentClass?: string; + /** + * A flag to indicate whether the parent itself is a dropZone. + */ + dropZone?: boolean; + /** + * The group that the parent belongs to. This is used for allowing multiple + * parents to transfer nodes between each other. + */ + group?: string; + handleParentFocus: (data: ParentEventData, state: BaseDragState) => void; + handleNodeKeydown: (data: NodeEventData, state: DragState) => void; + handleParentKeydown: (data: ParentKeydownEventData, state: DragState) => void; + /** + * Function that is called when dragend or touchend event occurs. + */ + handleDragend: (data: NodeDragEventData, state: DragState) => void; + /** + * Function that is called when dragstart event occurs. + */ + handleDragstart: (data: NodeDragEventData, state: DragState) => void; + handleEnd: (state: DragState | SynthDragState) => void; + handleNodeDrop: (data: NodeDragEventData, state: DragState) => void; + handleNodePointerup: (data: NodePointerEventData, state: DragState) => void; + handleParentScroll: (data: ParentEventData, state: DragState | BaseDragState | SynthDragState) => void; + /** + * Function that is called when a dragenter event is triggered on the node. + */ + handleNodeDragenter: (data: NodeDragEventData, state: DragState) => void; + handleNodeBlur: (data: NodeEventData, state: DragState) => void; + handleNodeFocus: (data: NodeEventData, state: DragState) => void; + /** + * Dragleave event on node + */ + handleNodeDragleave: (data: NodeDragEventData, state: DragState) => void; + /** + * Function that is called when a dragover event is triggered on the parent. + */ + handleParentDragover: (data: ParentDragEventData, state: DragState) => void; + /** + * Drop event on parent + */ + handleParentDrop: (data: ParentDragEventData, state: DragState) => void; + /** + * Function that is called when a dragover event is triggered on a node. + */ + handleNodeDragover: (data: NodeDragEventData, state: DragState) => void; + handlePointercancel: (data: NodeDragEventData | NodePointerEventData, state: DragState | SynthDragState | BaseDragState) => void; + handleNodePointerdown: (data: NodePointerEventData, state: DragState) => void; + /** + * Function that is called when a node that is being moved by touchmove event + * is over a given node (similar to dragover). + */ + handleNodePointerover: (data: PointeroverNodeEvent, state: SynthDragState) => void; + /** + * Function that is called when a node that is being moved by touchmove event + * is over the parent (similar to dragover). + */ + handleParentPointerover: (e: PointeroverParentEvent, state: SynthDragState) => void; + /** + * Config option for insert plugin. + */ + insertConfig?: InsertConfig; + /** + * A flag to indicate whether long touch is enabled. + */ + longPress?: boolean; + /** + * The class to add to a node when a long touch action is performed. + */ + longPressClass?: string; + /** + * The time in milliseconds to wait before a long touch is performed. + */ + longPressDuration?: number; + /** + * The name of the parent (used for accepts function for increased specificity). + */ + name?: string; + multiDrag?: boolean; + /** + * If set to false, the library will not use the native drag and drop API. + */ + nativeDrag?: boolean; + /** + * Function that is called when a sort operation is to be performed. + */ + performSort: ({ parent, draggedNodes, targetNodes, }: { + parent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + }) => void; + /** + * Function that is called when a transfer operation is to be performed. + */ + performTransfer: ({ currentParent, targetParent, initialParent, draggedNodes, initialIndex, state, targetNodes, }: { + currentParent: ParentRecord; + targetParent: ParentRecord; + initialParent: ParentRecord; + draggedNodes: Array>; + initialIndex: number; + state: BaseDragState | DragState | SynthDragState; + targetNodes: Array>; + }) => void; + /** + * An array of functions to use for a given parent. + */ + plugins?: Array; + /** + * Takes a given node and reapplies the drag classes. + */ + reapplyDragClasses: (node: Node, parentData: ParentData) => void; + /** + * Invoked when the remapping of a given parent's nodes is finished. + */ + remapFinished: (data: ParentData) => void; + /** + * The root element to use for the parent. + */ + root: Document | ShadowRoot; + selectedClass?: string; + /** + * Function that is called when a node is set up. + */ + setupNode: SetupNode; + /** + * Called when the value of the parent is changed and the nodes are remapped. + */ + setupNodeRemap: SetupNode; + /** + * Flag for whether or not to allow sorting within a given parent. + */ + sortable?: boolean; + /** + * The class to add to a parent when it is dragged over. + */ + synthDropZoneParentClass?: string; + /** + * A function that returns the image to use for the drag operation. This is + * invoked for synth drag operations operations. The clonedNode is what will + * be set as the drag image, but this can be updated. + */ + synthDragImage?: (node: NodeRecord, parent: ParentRecord, e: PointerEvent, draggedNodes: Array>) => { + dragImage: HTMLElement; + offsetX?: number; + offsetY?: number; + }; + /** + * Function that is called when a node is torn down. + */ + tearDownNode: TearDownNode; + /** + * Called when the value of the parent is changed and the nodes are remapped. + */ + tearDownNodeRemap: TearDownNode; + /** + * Property to identify which group of tree descendants the current parent belongs to. + */ + /** + * The threshold for a drag to be considered a valid sort + * operation. + */ + threshold: { + horizontal: number; + vertical: number; + }; + /** + * The class to add to a node when it is being synthetically dragged. + */ + synthDraggingClass?: string; + /** + * On synth drag start, this is applied to the dragged node(s) (not their + * representations being dragged). + */ + synthDragPlaceholderClass?: string; + /** + * When hovering over a node, this class is applied to the node. + */ + synthDropZoneClass?: string; + /** + * When a node receives focus, this class is applied to the node. + */ + synthActiveDescendantClass?: string; + /** + * Callback function for when a sort operation is performed. + */ + onSort?: SortEvent; + /** + * Callback function for when a transfer operation is performed. + */ + onTransfer?: TransferEvent; + /** + * Fired when a drag is started, whether native drag or synthetic + */ + onDragstart?: DragstartEvent; + /** + * Fired when a drag is ended, whether native drag or synthetic + */ + onDragend?: DragendEvent; +} +/** + * The data assigned to a given parent in the `parents` weakmap. + */ +interface ParentData { + /** + * A function that returns the values assigned to the parent. + */ + getValues: (parent: HTMLElement) => Array; + /** + * A function that sets the values assigned to the parent. + */ + setValues: (values: Array, parent: HTMLElement) => void; + /** + * The configuration object for the parent. + */ + config: ParentConfig; + /** + * The nodes that are currently enabled for drag and drop. + */ + enabledNodes: Array>; + /** + * The abort controllers for the parent. + */ + abortControllers: Record; + /** + * The private classes of the node (used for preventing erroneous removal of + * classes). + */ + privateClasses: Array; + /** + * Set on parentData indicating that the current parent is nested beneath an ancestor. + */ + nestedParent?: ParentRecord; + emit: (event: string, data: unknown) => void; + on: (event: string, callback: (data: unknown) => void) => void; +} +/** + * The data assigned to a given node in the `nodes` weakmap. + */ +interface NodeData { + /** + * The index of the in respect to its adjacent enabled nodes. + */ + index: number; + /** + * The value of the node. + */ + value: T; + /** + * The private classes of the node (used for preventing erroneous removal of + * classes). + */ + privateClasses: Array; + /** + * The abort controllers for the node. + */ + abortControllers: Record; + /** + * Set by the insertion plugin to define the coordinates for a given node. + */ + range?: { + ascending?: { + y: number[]; + x: number[]; + vertical: boolean; + }; + descending?: { + y: number[]; + x: number[]; + vertical: boolean; + }; + }; +} +/** + * The data passed to the node event listener. + */ +interface NodeEventData { + /** + * The event that was triggered. + */ + e: Event; + /** + * The data of the target node. + */ + targetData: NodeTargetData; +} +/** + * The data passed to the node event listener when the event is a drag event. + */ +interface NodeDragEventData extends NodeEventData { + e: DragEvent; +} +/** + * The data passed to the node event listener when the event is a pointer event (not a native drag event). + */ +interface NodePointerEventData extends NodeEventData { + /** + * The event that was triggered. + */ + e: PointerEvent; + /** + * The data of the target node. + */ + targetData: NodeTargetData; +} +interface ParentKeydownEventData extends ParentEventData { + /** + * The event that was triggered. + */ + e: KeyboardEvent; + /** + * The data of the target node. + */ + targetData: ParentTargetData; +} +/** + * The data passed to the parent event listener. + * + * @param e - The event that was triggered. + * @param targetData - The data of the target parent. + */ +interface ParentEventData { + e: Event; + targetData: ParentTargetData; +} +/** + * The target data of the parent involved with the event, whether a node or + * parent event. + * + * @param param - The parent record. + */ +interface ParentTargetData { + parent: ParentRecord; +} +/** + * The target data of the node involved with the event. + * + * @param node - The node record. + * @param parent - The parent record. + */ +interface NodeTargetData { + node: NodeRecord; + parent: ParentRecord; +} +/** + * The node record, contains the el and the data in the `nodes` weakmap. + */ +interface NodeRecord { + el: Node; + data: NodeData; +} +/** + * The parent record, contains the el and the data in the `parents` weakmap. + */ +interface ParentRecord { + el: HTMLElement; + data: ParentData; +} +/** + * The interface for a node in the context of FormKit's Drag and Drop. + */ +interface Node extends HTMLElement { + parentNode: HTMLElement; +} +/** + * The payload of the custom event dispatched when a node is "touched" over a + * node. + */ +interface PointeroverNodeEvent extends Event { + detail: { + e: PointerEvent; + targetData: NodeTargetData; + state: SynthDragState; + }; +} +/** + * The payload of the custom event dispatched when a node is "touched" over a + * parent. + */ +interface PointeroverParentEvent extends Event { + detail: { + e: PointerEvent; + targetData: ParentTargetData; + state: SynthDragState; + }; +} +/** + * The interface for a drag and drop plugin. + */ +interface DNDPluginData { + /** + * The function to call when the parent is set up. + */ + setup?: () => void; + /** + * The function to call when the parent is torn down. + */ + tearDown?: () => void; + /** + * Called when entry point function is invoked on parent. + */ + setupNode?: SetupNode; + /** + * Called when entry point function is invoked on parent. + */ + tearDownNode?: TearDownNode; + /** + * Called anytime the nodes are mutated + */ + setupNodeRemap?: SetupNode; + /** + * Called when the parent is dragged over. + */ + tearDownNodeRemap?: TearDownNode; + /** + * Called when all nodes have finished remapping for a given parent + */ + remapFinished?: () => void; +} +type DNDPlugin = (parent: HTMLElement) => DNDPluginData | undefined; +type SetupNode = (data: SetupNodeData) => void; +type TearDownNode = (data: TearDownNodeData) => void; +/** + * The payload of when the setupNode function is called in a given plugin. + */ +interface SetupNodeData { + node: NodeRecord; + parent: ParentRecord; +} +/** + * The payload of when the tearDownNode function is called in a given plugin. + */ +interface TearDownNodeData { + node: { + el: Node; + data?: NodeData; + }; + parent: ParentRecord; +} +/** + * The state of the current drag. State is only created when a drag start + * event is triggered. + * + * @param activeNode - The node that was most recently clicked (used optionally). + * @param affectedNodes - The nodes that will be updated by a drag action + * (sorted). + * @param ascendingDirection - Indicates whetehr the dragged node is moving to a + * node with a higher index or not. + * @param clonedDraggedEls - The cloned elements of the dragged node. This is + * used primarily for TouchEvents or multi-drag purposes. + * @param draggedNode - The node that is being dragged. + * @param draggedNodes - The nodes that are being dragged. + * @param incomingDirection - The direction that the dragged node is moving into + * a dragover node. + * @param initialParent - The parent that the dragged node was initially in. + * @param currentParent - The parent that the dragged node was most recently in. + * @param lastValue - The last value of the dragged node. + * @param originalZIndex - The original z-index of the dragged node. + * @param preventEnter - A flag to prevent a sort operation from firing until + * the mutation observer has had a chance to update the data of the remapped + * nodes. + * @param swappedNodeValue - The value of the node that was swapped with the + * dragged node. + * @param targetIndex - The index of the node that the dragged node is moving + * into. + */ +type SynthDragState = SynthDragStateProps & DragState; +interface SynthDragStateProps { + /** + * Element + */ + clonedDraggedNode: HTMLElement; + /** + * Direction of the synthetic drag scroll + */ + synthScrollDirection: "up" | "down" | "left" | "right" | undefined; + /** + * The display of the synthetic node. + */ + draggedNodeDisplay: string; + /** + * Flag indcating whether a scrollable el is being scrolled via. + * synthetic drag. + */ + synthDragScrolling: boolean; + /** + * Pointer id of dragged el + */ + pointerId: number; + animationFrameIdX: number | undefined; + animationFrameIdY: number | undefined; + lastScrollX: HTMLElement | null; + lastScrollY: HTMLElement | null; + rootScrollWidth: number | undefined; + rootScrollHeight: number | undefined; + rootOverScrollBehavior: string | undefined; + rootTouchAction: string | undefined; +} +type DragState = DragStateProps & BaseDragState; +type BaseDragState = { + activeState?: { + node: NodeRecord; + parent: ParentRecord; + }; + /** + * The nodes that will be updated by a drag action (sorted). + */ + affectedNodes: Array>; + /** + * The last value the dragged node targeted. + */ + currentTargetValue: T | undefined; + emit: (event: string, data: unknown) => void; + on: (event: string, callback: (data: unknown) => void) => void; + newActiveDescendant?: NodeRecord; + preventSynthDrag: boolean; + longPress: boolean; + longPressTimeout: ReturnType | undefined; + pointerDown: { + parent: ParentRecord; + node: NodeRecord; + validated: boolean; + } | undefined; + /** + * The original z-index of the dragged node. + */ + originalZIndex?: string; + pointerSelection: boolean; + preventEnter: boolean; + /** + * Flag indicating that the remap just finished. + */ + remapJustFinished: boolean; + selectedState?: { + nodes: Array>; + parent: ParentRecord; + }; + scrolling: boolean; + rootUserSelect: string | undefined; +}; +interface DragStateProps { + /** + * The nodes that will be updated by a drag action (sorted). + */ + affectedNodes: Array>; + /** + * Indicates whether the dragged node is moving to a node with a higher index + * or not. + */ + ascendingDirection: boolean; + /** + * The cloned elements of the dragged node. This is used primarily for + * TouchEvents or multi-drag purposes. + */ + clonedDraggedEls: Array; + /** + * The coordinates of the dragged element itself. + */ + coordinates: { + x: number; + y: number; + }; + /** + * The parent that the dragged node was most recently in. + */ + currentParent: ParentRecord; + currentTargetValue: T | undefined; + /** + * The node that is being dragged. + */ + draggedNode: NodeRecord; + /** + * The nodes that are being dragged. + */ + draggedNodes: Array>; + /** + * The direction that the dragged node is moving into a dragover node. + */ + incomingDirection: "above" | "below" | "left" | "right" | undefined; + /** + * The index of the node that the dragged node was initially in. + */ + initialIndex: number; + /** + * The parent that the dragged node was initially in. + */ + initialParent: ParentRecord; + /** + * longPress - A flag to indicate whether a long press has occurred. + */ + longPress: boolean; + /** + * Long press timeout + */ + longPressTimeout: ReturnType | undefined; + /** + * scrollEls + */ + scrollEls: Array<[HTMLElement, AbortController]>; + /** + * The top position of pointerdown. + */ + startTop: number; + /** + * The left position of the pointerdown. + */ + startLeft: number; + /** + * The index of the node that the dragged node is moving into. + */ + targetIndex: number; + /** + * Flag indicating that the dragged node was transferred + */ + transferred: boolean; +} +type SortEvent = (data: SortEventData) => void; +type TransferEvent = (data: TransferEventData) => void; +type DragstartEvent = (data: DragstartEventData) => void; +type DragendEvent = (data: DragendEventData) => void; +interface SortEventData { + parent: ParentRecord; + previousValues: Array; + values: Array; + previousNodes: Array>; + nodes: Array>; + draggedNodes: Array>; + targetNodes: Array>; + previousPosition: number; + position: number; + state: BaseDragState | DragState | SynthDragState; +} +interface TransferEventData { + sourceParent: ParentRecord; + targetParent: ParentRecord; + initialParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + targetIndex: number; + state: BaseDragState | DragState | SynthDragState; +} +interface DragstartEventData { + parent: ParentRecord; + values: Array; + draggedNode: NodeRecord; + draggedNodes: Array>; + position: number; + state: BaseDragState | DragState | SynthDragState; +} +interface DragendEventData { + parent: ParentRecord; + values: Array; + draggedNode: NodeRecord; + draggedNodes: Array>; + state: BaseDragState | DragState | SynthDragState; +} +interface ShouldSwapData { + sourceParent: ParentRecord; + targetParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + state: BaseDragState | DragState | SynthDragState; +} +interface DropSwapConfig { + shouldSwap?: (data: ShouldSwapData) => boolean; + handleNodeDragover?: (data: NodeDragEventData, state: DragState) => void; + handleParentDragover?: (data: ParentDragEventData, state: DragState) => void; + handleParentPointerover?: (e: PointeroverParentEvent, state: DragState) => void; + handleNodePointerover?: (data: PointeroverNodeEvent, state: SynthDragState) => void; +} +interface InsertConfig { + insertPoint: (parent: ParentRecord) => HTMLElement; + insertEvent?: (data: InsertEvent) => void; + handleNodeDragover?: (data: NodeDragEventData, state: DragState) => void; + handleParentDragover?: (data: ParentDragEventData, state: DragState) => void; + handleParentPointerover?: (data: PointeroverParentEvent) => void; + handleNodePointerover?: (data: PointeroverNodeEvent) => void; + handleEnd?: (state: BaseDragState | DragState | SynthDragState) => void; + dynamicValues?: (data: DynamicValuesData) => Array; +} +type DynamicValuesData = { + sourceParent: ParentRecord; + targetParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + targetIndex?: number; +}; +interface InsertEvent { + sourceParent: ParentRecord; + targetParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + state: BaseDragState | DragState | SynthDragState; +} + +type SolidState = [Accessor, ((items: T) => void) | ReturnType[1]]; +interface SolidDragAndDropConfig | HTMLElement, ListItems extends unknown[]> extends Partial> { + parent: E; + state: SolidState; +} + +declare function dragAndDrop(data: SolidDragAndDropConfig | HTMLElement, I[]> | Array | HTMLElement, I[]>>): void; +/** + * Hook for adding drag and drop/sortable support to a list of items. + * + * @param initValues - Initial list of data. + * @param options - The drag and drop configuration. + * @returns + */ +declare function useDragAndDrop(initValues: T[], options?: Partial>): [ + Setter, + Accessor>, + ReturnType>[1], + (config?: Partial>) => void +]; + +export { dragAndDrop, useDragAndDrop }; diff --git a/dist/solid/index.d.ts b/dist/solid/index.d.ts new file mode 100644 index 00000000..8519555f --- /dev/null +++ b/dist/solid/index.d.ts @@ -0,0 +1,796 @@ +import { ParentConfig as ParentConfig$1 } from '../index'; +import { Accessor, Setter } from 'solid-js'; +import { createStore, Store } from 'solid-js/store'; + +interface ParentDragEventData extends ParentEventData { + e: DragEvent; +} +type NativeDragEffects = "link" | "none" | "copy" | "move"; +/** + * The configuration object for a given parent. + */ +interface ParentConfig { + /** + * A function that returns whether a given parent accepts a given node. + */ + accepts?: (targetParentData: ParentRecord, initialParentData: ParentRecord, currentParentData: ParentRecord, state: BaseDragState) => boolean; + activeDescendantClass?: string; + /** + * The data transfer effect to use for the drag operation. + */ + dragEffectAllowed: NativeDragEffects; + /** + * The data transfer effect to use for the drag operation. + */ + dragDropEffect: NativeDragEffects; + /** + * A function that returns the image to use for the drag operation. This is + * invoked for native operations. The clonedNode is what will be set as the drag + * image, but this can be updated. + */ + dragImage?: (data: NodeDragEventData, draggedNodes: Array>) => HTMLElement; + /** + * A flag to disable dragability of all nodes in the parent. + */ + disabled?: boolean; + /** + * A selector for the drag handle. Will search at any depth within the + * draggable element. + */ + dragHandle?: string; + /** + * External drag handle + */ + externalDragHandle?: { + el: HTMLElement; + callback: () => HTMLElement; + }; + /** + * A function that returns whether a given node is draggable. + */ + draggable?: (child: HTMLElement) => boolean; + /** + * A function that returns whether a given value is draggable + */ + draggableValue?: (values: T) => boolean; + draggedNodes: (pointerDown: { + parent: ParentRecord; + node: NodeRecord; + }) => Array>; + /** + * The class to add to a node when it is being dragged. + */ + draggingClass?: string; + /** + * Accepts array of "dragged nodes" and applies dragstart classes to them. + */ + dragstartClasses: (node: NodeRecord, nodes: Array>, config: ParentConfig, isSynthDrag?: boolean) => void; + dragPlaceholderClass?: string; + /** + * The configuration object for the drop and swap plugin. + */ + dropSwapConfig?: DropSwapConfig; + /** + * The class to add to a node when the node is dragged over it. + */ + dropZoneClass?: string; + /** + * The class to add to a parent when it is dragged over. + */ + dropZoneParentClass?: string; + /** + * A flag to indicate whether the parent itself is a dropZone. + */ + dropZone?: boolean; + /** + * The group that the parent belongs to. This is used for allowing multiple + * parents to transfer nodes between each other. + */ + group?: string; + handleParentFocus: (data: ParentEventData, state: BaseDragState) => void; + handleNodeKeydown: (data: NodeEventData, state: DragState) => void; + handleParentKeydown: (data: ParentKeydownEventData, state: DragState) => void; + /** + * Function that is called when dragend or touchend event occurs. + */ + handleDragend: (data: NodeDragEventData, state: DragState) => void; + /** + * Function that is called when dragstart event occurs. + */ + handleDragstart: (data: NodeDragEventData, state: DragState) => void; + handleEnd: (state: DragState | SynthDragState) => void; + handleNodeDrop: (data: NodeDragEventData, state: DragState) => void; + handleNodePointerup: (data: NodePointerEventData, state: DragState) => void; + handleParentScroll: (data: ParentEventData, state: DragState | BaseDragState | SynthDragState) => void; + /** + * Function that is called when a dragenter event is triggered on the node. + */ + handleNodeDragenter: (data: NodeDragEventData, state: DragState) => void; + handleNodeBlur: (data: NodeEventData, state: DragState) => void; + handleNodeFocus: (data: NodeEventData, state: DragState) => void; + /** + * Dragleave event on node + */ + handleNodeDragleave: (data: NodeDragEventData, state: DragState) => void; + /** + * Function that is called when a dragover event is triggered on the parent. + */ + handleParentDragover: (data: ParentDragEventData, state: DragState) => void; + /** + * Drop event on parent + */ + handleParentDrop: (data: ParentDragEventData, state: DragState) => void; + /** + * Function that is called when a dragover event is triggered on a node. + */ + handleNodeDragover: (data: NodeDragEventData, state: DragState) => void; + handlePointercancel: (data: NodeDragEventData | NodePointerEventData, state: DragState | SynthDragState | BaseDragState) => void; + handleNodePointerdown: (data: NodePointerEventData, state: DragState) => void; + /** + * Function that is called when a node that is being moved by touchmove event + * is over a given node (similar to dragover). + */ + handleNodePointerover: (data: PointeroverNodeEvent, state: SynthDragState) => void; + /** + * Function that is called when a node that is being moved by touchmove event + * is over the parent (similar to dragover). + */ + handleParentPointerover: (e: PointeroverParentEvent, state: SynthDragState) => void; + /** + * Config option for insert plugin. + */ + insertConfig?: InsertConfig; + /** + * A flag to indicate whether long touch is enabled. + */ + longPress?: boolean; + /** + * The class to add to a node when a long touch action is performed. + */ + longPressClass?: string; + /** + * The time in milliseconds to wait before a long touch is performed. + */ + longPressDuration?: number; + /** + * The name of the parent (used for accepts function for increased specificity). + */ + name?: string; + multiDrag?: boolean; + /** + * If set to false, the library will not use the native drag and drop API. + */ + nativeDrag?: boolean; + /** + * Function that is called when a sort operation is to be performed. + */ + performSort: ({ parent, draggedNodes, targetNodes, }: { + parent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + }) => void; + /** + * Function that is called when a transfer operation is to be performed. + */ + performTransfer: ({ currentParent, targetParent, initialParent, draggedNodes, initialIndex, state, targetNodes, }: { + currentParent: ParentRecord; + targetParent: ParentRecord; + initialParent: ParentRecord; + draggedNodes: Array>; + initialIndex: number; + state: BaseDragState | DragState | SynthDragState; + targetNodes: Array>; + }) => void; + /** + * An array of functions to use for a given parent. + */ + plugins?: Array; + /** + * Takes a given node and reapplies the drag classes. + */ + reapplyDragClasses: (node: Node, parentData: ParentData) => void; + /** + * Invoked when the remapping of a given parent's nodes is finished. + */ + remapFinished: (data: ParentData) => void; + /** + * The root element to use for the parent. + */ + root: Document | ShadowRoot; + selectedClass?: string; + /** + * Function that is called when a node is set up. + */ + setupNode: SetupNode; + /** + * Called when the value of the parent is changed and the nodes are remapped. + */ + setupNodeRemap: SetupNode; + /** + * Flag for whether or not to allow sorting within a given parent. + */ + sortable?: boolean; + /** + * The class to add to a parent when it is dragged over. + */ + synthDropZoneParentClass?: string; + /** + * A function that returns the image to use for the drag operation. This is + * invoked for synth drag operations operations. The clonedNode is what will + * be set as the drag image, but this can be updated. + */ + synthDragImage?: (node: NodeRecord, parent: ParentRecord, e: PointerEvent, draggedNodes: Array>) => { + dragImage: HTMLElement; + offsetX?: number; + offsetY?: number; + }; + /** + * Function that is called when a node is torn down. + */ + tearDownNode: TearDownNode; + /** + * Called when the value of the parent is changed and the nodes are remapped. + */ + tearDownNodeRemap: TearDownNode; + /** + * Property to identify which group of tree descendants the current parent belongs to. + */ + /** + * The threshold for a drag to be considered a valid sort + * operation. + */ + threshold: { + horizontal: number; + vertical: number; + }; + /** + * The class to add to a node when it is being synthetically dragged. + */ + synthDraggingClass?: string; + /** + * On synth drag start, this is applied to the dragged node(s) (not their + * representations being dragged). + */ + synthDragPlaceholderClass?: string; + /** + * When hovering over a node, this class is applied to the node. + */ + synthDropZoneClass?: string; + /** + * When a node receives focus, this class is applied to the node. + */ + synthActiveDescendantClass?: string; + /** + * Callback function for when a sort operation is performed. + */ + onSort?: SortEvent; + /** + * Callback function for when a transfer operation is performed. + */ + onTransfer?: TransferEvent; + /** + * Fired when a drag is started, whether native drag or synthetic + */ + onDragstart?: DragstartEvent; + /** + * Fired when a drag is ended, whether native drag or synthetic + */ + onDragend?: DragendEvent; +} +/** + * The data assigned to a given parent in the `parents` weakmap. + */ +interface ParentData { + /** + * A function that returns the values assigned to the parent. + */ + getValues: (parent: HTMLElement) => Array; + /** + * A function that sets the values assigned to the parent. + */ + setValues: (values: Array, parent: HTMLElement) => void; + /** + * The configuration object for the parent. + */ + config: ParentConfig; + /** + * The nodes that are currently enabled for drag and drop. + */ + enabledNodes: Array>; + /** + * The abort controllers for the parent. + */ + abortControllers: Record; + /** + * The private classes of the node (used for preventing erroneous removal of + * classes). + */ + privateClasses: Array; + /** + * Set on parentData indicating that the current parent is nested beneath an ancestor. + */ + nestedParent?: ParentRecord; + emit: (event: string, data: unknown) => void; + on: (event: string, callback: (data: unknown) => void) => void; +} +/** + * The data assigned to a given node in the `nodes` weakmap. + */ +interface NodeData { + /** + * The index of the in respect to its adjacent enabled nodes. + */ + index: number; + /** + * The value of the node. + */ + value: T; + /** + * The private classes of the node (used for preventing erroneous removal of + * classes). + */ + privateClasses: Array; + /** + * The abort controllers for the node. + */ + abortControllers: Record; + /** + * Set by the insertion plugin to define the coordinates for a given node. + */ + range?: { + ascending?: { + y: number[]; + x: number[]; + vertical: boolean; + }; + descending?: { + y: number[]; + x: number[]; + vertical: boolean; + }; + }; +} +/** + * The data passed to the node event listener. + */ +interface NodeEventData { + /** + * The event that was triggered. + */ + e: Event; + /** + * The data of the target node. + */ + targetData: NodeTargetData; +} +/** + * The data passed to the node event listener when the event is a drag event. + */ +interface NodeDragEventData extends NodeEventData { + e: DragEvent; +} +/** + * The data passed to the node event listener when the event is a pointer event (not a native drag event). + */ +interface NodePointerEventData extends NodeEventData { + /** + * The event that was triggered. + */ + e: PointerEvent; + /** + * The data of the target node. + */ + targetData: NodeTargetData; +} +interface ParentKeydownEventData extends ParentEventData { + /** + * The event that was triggered. + */ + e: KeyboardEvent; + /** + * The data of the target node. + */ + targetData: ParentTargetData; +} +/** + * The data passed to the parent event listener. + * + * @param e - The event that was triggered. + * @param targetData - The data of the target parent. + */ +interface ParentEventData { + e: Event; + targetData: ParentTargetData; +} +/** + * The target data of the parent involved with the event, whether a node or + * parent event. + * + * @param param - The parent record. + */ +interface ParentTargetData { + parent: ParentRecord; +} +/** + * The target data of the node involved with the event. + * + * @param node - The node record. + * @param parent - The parent record. + */ +interface NodeTargetData { + node: NodeRecord; + parent: ParentRecord; +} +/** + * The node record, contains the el and the data in the `nodes` weakmap. + */ +interface NodeRecord { + el: Node; + data: NodeData; +} +/** + * The parent record, contains the el and the data in the `parents` weakmap. + */ +interface ParentRecord { + el: HTMLElement; + data: ParentData; +} +/** + * The interface for a node in the context of FormKit's Drag and Drop. + */ +interface Node extends HTMLElement { + parentNode: HTMLElement; +} +/** + * The payload of the custom event dispatched when a node is "touched" over a + * node. + */ +interface PointeroverNodeEvent extends Event { + detail: { + e: PointerEvent; + targetData: NodeTargetData; + state: SynthDragState; + }; +} +/** + * The payload of the custom event dispatched when a node is "touched" over a + * parent. + */ +interface PointeroverParentEvent extends Event { + detail: { + e: PointerEvent; + targetData: ParentTargetData; + state: SynthDragState; + }; +} +/** + * The interface for a drag and drop plugin. + */ +interface DNDPluginData { + /** + * The function to call when the parent is set up. + */ + setup?: () => void; + /** + * The function to call when the parent is torn down. + */ + tearDown?: () => void; + /** + * Called when entry point function is invoked on parent. + */ + setupNode?: SetupNode; + /** + * Called when entry point function is invoked on parent. + */ + tearDownNode?: TearDownNode; + /** + * Called anytime the nodes are mutated + */ + setupNodeRemap?: SetupNode; + /** + * Called when the parent is dragged over. + */ + tearDownNodeRemap?: TearDownNode; + /** + * Called when all nodes have finished remapping for a given parent + */ + remapFinished?: () => void; +} +type DNDPlugin = (parent: HTMLElement) => DNDPluginData | undefined; +type SetupNode = (data: SetupNodeData) => void; +type TearDownNode = (data: TearDownNodeData) => void; +/** + * The payload of when the setupNode function is called in a given plugin. + */ +interface SetupNodeData { + node: NodeRecord; + parent: ParentRecord; +} +/** + * The payload of when the tearDownNode function is called in a given plugin. + */ +interface TearDownNodeData { + node: { + el: Node; + data?: NodeData; + }; + parent: ParentRecord; +} +/** + * The state of the current drag. State is only created when a drag start + * event is triggered. + * + * @param activeNode - The node that was most recently clicked (used optionally). + * @param affectedNodes - The nodes that will be updated by a drag action + * (sorted). + * @param ascendingDirection - Indicates whetehr the dragged node is moving to a + * node with a higher index or not. + * @param clonedDraggedEls - The cloned elements of the dragged node. This is + * used primarily for TouchEvents or multi-drag purposes. + * @param draggedNode - The node that is being dragged. + * @param draggedNodes - The nodes that are being dragged. + * @param incomingDirection - The direction that the dragged node is moving into + * a dragover node. + * @param initialParent - The parent that the dragged node was initially in. + * @param currentParent - The parent that the dragged node was most recently in. + * @param lastValue - The last value of the dragged node. + * @param originalZIndex - The original z-index of the dragged node. + * @param preventEnter - A flag to prevent a sort operation from firing until + * the mutation observer has had a chance to update the data of the remapped + * nodes. + * @param swappedNodeValue - The value of the node that was swapped with the + * dragged node. + * @param targetIndex - The index of the node that the dragged node is moving + * into. + */ +type SynthDragState = SynthDragStateProps & DragState; +interface SynthDragStateProps { + /** + * Element + */ + clonedDraggedNode: HTMLElement; + /** + * Direction of the synthetic drag scroll + */ + synthScrollDirection: "up" | "down" | "left" | "right" | undefined; + /** + * The display of the synthetic node. + */ + draggedNodeDisplay: string; + /** + * Flag indcating whether a scrollable el is being scrolled via. + * synthetic drag. + */ + synthDragScrolling: boolean; + /** + * Pointer id of dragged el + */ + pointerId: number; + animationFrameIdX: number | undefined; + animationFrameIdY: number | undefined; + lastScrollX: HTMLElement | null; + lastScrollY: HTMLElement | null; + rootScrollWidth: number | undefined; + rootScrollHeight: number | undefined; + rootOverScrollBehavior: string | undefined; + rootTouchAction: string | undefined; +} +type DragState = DragStateProps & BaseDragState; +type BaseDragState = { + activeState?: { + node: NodeRecord; + parent: ParentRecord; + }; + /** + * The nodes that will be updated by a drag action (sorted). + */ + affectedNodes: Array>; + /** + * The last value the dragged node targeted. + */ + currentTargetValue: T | undefined; + emit: (event: string, data: unknown) => void; + on: (event: string, callback: (data: unknown) => void) => void; + newActiveDescendant?: NodeRecord; + preventSynthDrag: boolean; + longPress: boolean; + longPressTimeout: ReturnType | undefined; + pointerDown: { + parent: ParentRecord; + node: NodeRecord; + validated: boolean; + } | undefined; + /** + * The original z-index of the dragged node. + */ + originalZIndex?: string; + pointerSelection: boolean; + preventEnter: boolean; + /** + * Flag indicating that the remap just finished. + */ + remapJustFinished: boolean; + selectedState?: { + nodes: Array>; + parent: ParentRecord; + }; + scrolling: boolean; + rootUserSelect: string | undefined; +}; +interface DragStateProps { + /** + * The nodes that will be updated by a drag action (sorted). + */ + affectedNodes: Array>; + /** + * Indicates whether the dragged node is moving to a node with a higher index + * or not. + */ + ascendingDirection: boolean; + /** + * The cloned elements of the dragged node. This is used primarily for + * TouchEvents or multi-drag purposes. + */ + clonedDraggedEls: Array; + /** + * The coordinates of the dragged element itself. + */ + coordinates: { + x: number; + y: number; + }; + /** + * The parent that the dragged node was most recently in. + */ + currentParent: ParentRecord; + currentTargetValue: T | undefined; + /** + * The node that is being dragged. + */ + draggedNode: NodeRecord; + /** + * The nodes that are being dragged. + */ + draggedNodes: Array>; + /** + * The direction that the dragged node is moving into a dragover node. + */ + incomingDirection: "above" | "below" | "left" | "right" | undefined; + /** + * The index of the node that the dragged node was initially in. + */ + initialIndex: number; + /** + * The parent that the dragged node was initially in. + */ + initialParent: ParentRecord; + /** + * longPress - A flag to indicate whether a long press has occurred. + */ + longPress: boolean; + /** + * Long press timeout + */ + longPressTimeout: ReturnType | undefined; + /** + * scrollEls + */ + scrollEls: Array<[HTMLElement, AbortController]>; + /** + * The top position of pointerdown. + */ + startTop: number; + /** + * The left position of the pointerdown. + */ + startLeft: number; + /** + * The index of the node that the dragged node is moving into. + */ + targetIndex: number; + /** + * Flag indicating that the dragged node was transferred + */ + transferred: boolean; +} +type SortEvent = (data: SortEventData) => void; +type TransferEvent = (data: TransferEventData) => void; +type DragstartEvent = (data: DragstartEventData) => void; +type DragendEvent = (data: DragendEventData) => void; +interface SortEventData { + parent: ParentRecord; + previousValues: Array; + values: Array; + previousNodes: Array>; + nodes: Array>; + draggedNodes: Array>; + targetNodes: Array>; + previousPosition: number; + position: number; + state: BaseDragState | DragState | SynthDragState; +} +interface TransferEventData { + sourceParent: ParentRecord; + targetParent: ParentRecord; + initialParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + targetIndex: number; + state: BaseDragState | DragState | SynthDragState; +} +interface DragstartEventData { + parent: ParentRecord; + values: Array; + draggedNode: NodeRecord; + draggedNodes: Array>; + position: number; + state: BaseDragState | DragState | SynthDragState; +} +interface DragendEventData { + parent: ParentRecord; + values: Array; + draggedNode: NodeRecord; + draggedNodes: Array>; + state: BaseDragState | DragState | SynthDragState; +} +interface ShouldSwapData { + sourceParent: ParentRecord; + targetParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + state: BaseDragState | DragState | SynthDragState; +} +interface DropSwapConfig { + shouldSwap?: (data: ShouldSwapData) => boolean; + handleNodeDragover?: (data: NodeDragEventData, state: DragState) => void; + handleParentDragover?: (data: ParentDragEventData, state: DragState) => void; + handleParentPointerover?: (e: PointeroverParentEvent, state: DragState) => void; + handleNodePointerover?: (data: PointeroverNodeEvent, state: SynthDragState) => void; +} +interface InsertConfig { + insertPoint: (parent: ParentRecord) => HTMLElement; + insertEvent?: (data: InsertEvent) => void; + handleNodeDragover?: (data: NodeDragEventData, state: DragState) => void; + handleParentDragover?: (data: ParentDragEventData, state: DragState) => void; + handleParentPointerover?: (data: PointeroverParentEvent) => void; + handleNodePointerover?: (data: PointeroverNodeEvent) => void; + handleEnd?: (state: BaseDragState | DragState | SynthDragState) => void; + dynamicValues?: (data: DynamicValuesData) => Array; +} +type DynamicValuesData = { + sourceParent: ParentRecord; + targetParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + targetIndex?: number; +}; +interface InsertEvent { + sourceParent: ParentRecord; + targetParent: ParentRecord; + draggedNodes: Array>; + targetNodes: Array>; + state: BaseDragState | DragState | SynthDragState; +} + +type SolidState = [Accessor, ((items: T) => void) | ReturnType[1]]; +interface SolidDragAndDropConfig | HTMLElement, ListItems extends unknown[]> extends Partial> { + parent: E; + state: SolidState; +} + +declare function dragAndDrop(data: SolidDragAndDropConfig | HTMLElement, I[]> | Array | HTMLElement, I[]>>): void; +/** + * Hook for adding drag and drop/sortable support to a list of items. + * + * @param initValues - Initial list of data. + * @param options - The drag and drop configuration. + * @returns + */ +declare function useDragAndDrop(initValues: T[], options?: Partial>): [ + Setter, + Accessor>, + ReturnType>[1], + (config?: Partial>) => void +]; + +export { dragAndDrop, useDragAndDrop }; diff --git a/dist/solid/index.mjs b/dist/solid/index.mjs new file mode 100644 index 00000000..f95656ce --- /dev/null +++ b/dist/solid/index.mjs @@ -0,0 +1,73 @@ +// src/solid/index.ts +import { + dragAndDrop as initParent, + isBrowser, + tearDown +} from "../index"; +import { + createSignal, + onCleanup, + onMount +} from "solid-js"; +import { createStore } from "solid-js/store"; + +// src/solid/utils.ts +import { createEffect, on } from "solid-js"; +function getEl(parent) { + if (parent instanceof HTMLElement) return parent; + else if (typeof parent !== "function") return void 0; + const p = parent(); + return p instanceof HTMLElement ? p : void 0; +} +function handleSolidElements(element, cb) { + createEffect(on(() => getEl(element), (el) => el && cb(el))); +} + +// src/solid/index.ts +var parentValues = /* @__PURE__ */ new WeakMap(); +function getValues(parent) { + const values = parentValues.get(parent); + if (!values) { + console.warn("No values found for parent element"); + return []; + } + return values[0](); +} +function setValues(newValues, parent) { + const currentValues = parentValues.get(parent); + if (currentValues) currentValues[1](newValues); +} +function handleParent(config, values) { + return (el) => { + parentValues.set(el, values); + initParent({ parent: el, getValues, setValues, config }); + }; +} +function dragAndDrop(data) { + if (!isBrowser) return; + if (!Array.isArray(data)) data = [data]; + data.forEach((dnd) => { + const { parent, state, ...rest } = dnd; + handleSolidElements(parent, handleParent(rest, state)); + }); +} +function useDragAndDrop(initValues, options = {}) { + const [parent, setParent] = createSignal(null); + const [values, setValues2] = createStore(initValues); + function updateConfig(config = {}) { + dragAndDrop({ parent, state: [() => values, setValues2], ...config }); + } + onMount( + () => dragAndDrop({ parent, state: [() => values, setValues2], ...options }) + ); + onCleanup(() => { + const p = parent(); + p && tearDown(p); + }); + return [setParent, () => values, setValues2, updateConfig]; +} +export { + dragAndDrop, + useDragAndDrop +}; +//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/dist/solid/index.mjs.map b/dist/solid/index.mjs.map new file mode 100644 index 00000000..ae7c0729 --- /dev/null +++ b/dist/solid/index.mjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/solid/index.ts","../../src/solid/utils.ts"],"sourcesContent":["import {\n dragAndDrop as initParent,\n isBrowser,\n type ParentConfig,\n tearDown,\n} from \"../index\";\nimport {\n createSignal,\n type Accessor,\n type Setter,\n onCleanup,\n onMount,\n} from \"solid-js\";\nimport { createStore, Store } from \"solid-js/store\";\nimport type { SolidDragAndDropConfig, SolidState } from \"./types\";\nimport { handleSolidElements } from \"./utils\";\n\n/**\n * Global store for parent els to values.\n */\nconst parentValues: WeakMap> = new WeakMap();\n\n/**\n * Returns the values of the parent element.\n *\n * @param parent - The parent element.\n *\n * @returns The values of the parent element.\n */\nfunction getValues(parent: HTMLElement): Array {\n const values = parentValues.get(parent);\n\n if (!values) {\n console.warn(\"No values found for parent element\");\n\n return [];\n }\n\n return (values[0] as Accessor>)();\n}\n\n/**\n * Sets the values of the parent element.\n *\n * @param parent - The parent element.\n *\n * @param newValues - The new values for the parent element.\n *\n * @returns void\n */\nfunction setValues(newValues: Array, parent: HTMLElement): void {\n const currentValues = parentValues.get(parent);\n\n if (currentValues) currentValues[1](newValues);\n}\n\nfunction handleParent | HTMLElement, T>(\n config: Partial>,\n values: SolidState\n) {\n return (el: HTMLElement) => {\n parentValues.set(el, values);\n\n initParent({ parent: el, getValues, setValues, config });\n };\n}\n\nexport function dragAndDrop(\n data:\n | SolidDragAndDropConfig | HTMLElement, I[]>\n | Array | HTMLElement, I[]>>\n): void {\n if (!isBrowser) return;\n\n if (!Array.isArray(data)) data = [data];\n\n data.forEach((dnd) => {\n const { parent, state, ...rest } = dnd;\n\n handleSolidElements(parent, handleParent(rest, state));\n });\n}\n\n/**\n * Hook for adding drag and drop/sortable support to a list of items.\n *\n * @param initValues - Initial list of data.\n * @param options - The drag and drop configuration.\n * @returns\n */\nexport function useDragAndDrop(\n initValues: T[],\n options: Partial> = {}\n): [\n Setter,\n Accessor>,\n ReturnType>[1], // Return type of `createStore` will be changed in solid-js 2, so use `ReturnType` util here\n (config?: Partial>) => void\n] {\n const [parent, setParent] = createSignal(null);\n\n const [values, setValues] = createStore(initValues);\n\n function updateConfig(config: Partial> = {}) {\n dragAndDrop({ parent, state: [() => values, setValues], ...config });\n }\n\n onMount(() =>\n dragAndDrop({ parent, state: [() => values, setValues], ...options })\n );\n onCleanup(() => {\n const p = parent();\n p && tearDown(p);\n });\n\n return [setParent, () => values, setValues, updateConfig];\n}\n","import { createEffect, on, type Accessor } from \"solid-js\";\n\n/**\n * Checks if the given parent is an HTMLElement.\n *\n * @param dnd - The drag and drop configuration.\n */\nexport function getEl(\n parent: HTMLElement | Accessor\n): HTMLElement | void {\n if (parent instanceof HTMLElement) return parent;\n else if (typeof parent !== 'function') return undefined;\n const p = parent();\n return p instanceof HTMLElement ? p : undefined;\n}\n\nexport function handleSolidElements(\n element: HTMLElement | Accessor,\n cb: (el: HTMLElement) => void\n): void {\n createEffect(on(() => getEl(element), (el) => el && cb(el)));\n}\n"],"mappings":";AAAA;AAAA,EACE,eAAe;AAAA,EACf;AAAA,EAEA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAA0B;;;ACbnC,SAAS,cAAc,UAAyB;AAOzC,SAAS,MACd,QACoB;AACpB,MAAI,kBAAkB,YAAa,QAAO;AAAA,WACjC,OAAO,WAAW,WAAY,QAAO;AAC9C,QAAM,IAAI,OAAO;AACjB,SAAO,aAAa,cAAc,IAAI;AACxC;AAEO,SAAS,oBACd,SACA,IACM;AACN,eAAa,GAAG,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC;AAC7D;;;ADDA,IAAM,eAAsD,oBAAI,QAAQ;AASxE,SAAS,UAAa,QAA+B;AACnD,QAAM,SAAS,aAAa,IAAI,MAAM;AAEtC,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,oCAAoC;AAEjD,WAAO,CAAC;AAAA,EACV;AAEA,SAAQ,OAAO,CAAC,EAAyB;AAC3C;AAWA,SAAS,UAAU,WAAuB,QAA2B;AACnE,QAAM,gBAAgB,aAAa,IAAI,MAAM;AAE7C,MAAI,cAAe,eAAc,CAAC,EAAE,SAAS;AAC/C;AAEA,SAAS,aACP,QACA,QACA;AACA,SAAO,CAAC,OAAoB;AAC1B,iBAAa,IAAI,IAAI,MAAM;AAE3B,eAAc,EAAE,QAAQ,IAAI,WAAW,WAAW,OAAO,CAAC;AAAA,EAC5D;AACF;AAEO,SAAS,YACd,MAGM;AACN,MAAI,CAAC,UAAW;AAEhB,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC,IAAI;AAEtC,OAAK,QAAQ,CAAC,QAAQ;AACpB,UAAM,EAAE,QAAQ,OAAO,GAAG,KAAK,IAAI;AAEnC,wBAAoB,QAAQ,aAAa,MAAM,KAAK,CAAC;AAAA,EACvD,CAAC;AACH;AASO,SAAS,eACd,YACA,UAAoC,CAAC,GAMrC;AACA,QAAM,CAAC,QAAQ,SAAS,IAAI,aAAuB,IAAI;AAEvD,QAAM,CAAC,QAAQA,UAAS,IAAI,YAAY,UAAU;AAElD,WAAS,aAAa,SAAmC,CAAC,GAAG;AAC3D,gBAAY,EAAE,QAAQ,OAAO,CAAC,MAAM,QAAQA,UAAS,GAAG,GAAG,OAAO,CAAC;AAAA,EACrE;AAEA;AAAA,IAAQ,MACN,YAAY,EAAE,QAAQ,OAAO,CAAC,MAAM,QAAQA,UAAS,GAAG,GAAG,QAAQ,CAAC;AAAA,EACtE;AACA,YAAU,MAAM;AACd,UAAM,IAAI,OAAO;AACjB,SAAK,SAAS,CAAC;AAAA,EACjB,CAAC;AAED,SAAO,CAAC,WAAW,MAAM,QAAQA,YAAW,YAAY;AAC1D;","names":["setValues"]} \ No newline at end of file diff --git a/dist/vue/index.cjs b/dist/vue/index.cjs index 1b859187..9eec0137 100644 --- a/dist/vue/index.cjs +++ b/dist/vue/index.cjs @@ -18,12 +18,12 @@ var __copyProps = (to, from, except, desc) => { var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/vue/index.ts -var vue_exports = {}; -__export(vue_exports, { +var index_exports = {}; +__export(index_exports, { dragAndDrop: () => dragAndDrop, useDragAndDrop: () => useDragAndDrop }); -module.exports = __toCommonJS(vue_exports); +module.exports = __toCommonJS(index_exports); var import__ = require("../index.cjs"); var import_vue2 = require("vue"); diff --git a/docs/components/CodeExample.vue b/docs/components/CodeExample.vue index c64cfbba..967ac01c 100644 --- a/docs/components/CodeExample.vue +++ b/docs/components/CodeExample.vue @@ -36,6 +36,16 @@ try { > Vue +
  • + Solid +
  • +
    + +
    diff --git a/docs/components/CodeExampleSolid.server.vue b/docs/components/CodeExampleSolid.server.vue new file mode 100644 index 00000000..8421f2f2 --- /dev/null +++ b/docs/components/CodeExampleSolid.server.vue @@ -0,0 +1,42 @@ + + + diff --git a/docs/components/FrameworkIcons.vue b/docs/components/FrameworkIcons.vue index dd55b589..beda6f0d 100644 --- a/docs/components/FrameworkIcons.vue +++ b/docs/components/FrameworkIcons.vue @@ -2,7 +2,7 @@ import IconReact from "~/components/IconReact.vue"; import IconVue from "~/components/IconVue.vue"; import IconJavaScript from "~/components/IconJavaScript.vue"; - +import IconSolid from "~/components/IconSolid.vue"; const props = defineProps({ active: String, }); @@ -11,6 +11,7 @@ const activeFramework = computed(() => { if (props.active === "react") return IconReact; if (props.active === "vue") return IconVue; if (props.active === "native") return IconJavaScript; + if (props.active === "solid") return IconSolid; }); diff --git a/docs/components/IconSolid.vue b/docs/components/IconSolid.vue new file mode 100644 index 00000000..58ca6e5c --- /dev/null +++ b/docs/components/IconSolid.vue @@ -0,0 +1,78 @@ + diff --git a/docs/components/Section/GettingStarted.vue b/docs/components/Section/GettingStarted.vue index 225f8b70..fc7027a8 100644 --- a/docs/components/Section/GettingStarted.vue +++ b/docs/components/Section/GettingStarted.vue @@ -22,7 +22,7 @@ Drag and drop ships two main functions: dragAndDrop and useDragAndDrop. These can be imported for your framework of choice by using the subpath import - @formkit/drag-and-drop/{react/vue}. A native JavaScript + @formkit/drag-and-drop/{react/vue/solid}. A native JavaScript version of the dragAndDrop function is also available at @formkit/drag-and-drop.

    @@ -34,14 +34,14 @@

    useDragAndDrop

    The useDragAndDrop hook is the most convenient way to add - Drag and Drop to your app and is available for React and Vue. Call this - function with an initial array of values and the configuration options. It - returns an array of values containing a a template ref, a reactive set of - values (and a setter in the case of React), as well as a function to - update the parent's config. The template ref should be assigned to the - parent DOM element of the items you wish to make draggable. The reactive - array of values should be used in your template to render the draggable - elements. + Drag and Drop to your app and is available for React, Vue, and Solid. Call + this function with an initial array of values and the configuration + options. It returns an array of values containing a a template ref, a + reactive set of values (and a setter in the case of React), as well as a + function to update the parent's config. The template ref should be + assigned to the parent DOM element of the items you wish to make + draggable. The reactive array of values should be used in your template to + render the draggable elements.

    diff --git a/docs/components/TheHero.vue b/docs/components/TheHero.vue index d3c2a72c..8ec92d3d 100644 --- a/docs/components/TheHero.vue +++ b/docs/components/TheHero.vue @@ -276,6 +276,13 @@ onMounted(() => { >
  • +
  • + +
  • ( + [ + "dungeon_master.exe", + "map_1.dat", + "map_2.dat", + "character1.txt", + "character2.txt", + "shell32.dll", + "README.txt", + ], + { + draggable: (el) => { + return el.id !== "no-drag"; + }, + } + ); + + const config1: Partial> = {}; + config1.accepts = (_parent, lastParent) => { + if (lastParent.el === target2()) { + return false; + } + return items2().length < 3; + }; + + const config2: Partial> = {}; + config2.accepts = (_parent, lastParent) => { + if (lastParent.el === target1()) { + return false; + } + return items3().length < 5; + }; + + const [target1, items2] = useDragAndDrop( + ["knight.bmp", "dragon.bmp"], + config1 + ); + const [target2, items3] = useDragAndDrop(["brick.bmp", "moss.bmp"], config2); + + return ( +
    +
      + {(item) =>
    • {item}
    • }
      +
    +
      + {(item) =>
    • {item}
    • }
      +
    +
      + {(item) =>
    • {item}
    • }
      +
    +
    + ); +} diff --git a/docs/examples/animations/animations-solid.tsx b/docs/examples/animations/animations-solid.tsx new file mode 100644 index 00000000..7e9e01cd --- /dev/null +++ b/docs/examples/animations/animations-solid.tsx @@ -0,0 +1,29 @@ +/** @jsxImportSource solid-js */ +import { For } from "solid-js"; +import { animations } from "@formkit/drag-and-drop"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +export function MyComponent() { + const [parent, tapes] = useDragAndDrop( + [ + "Depeche Mode", + "Duran Duran", + "Pet Shop Boys", + "Kraftwerk", + "Tears for Fears", + "Spandau Ballet", + ], + { plugins: [animations()] } + ); + return ( +
      + + {(tape) => ( +
    • + {tape} +
    • + )} +
      +
    + ); +} diff --git a/docs/examples/drag-handles/drag-handles-solid.tsx b/docs/examples/drag-handles/drag-handles-solid.tsx new file mode 100644 index 00000000..a7256332 --- /dev/null +++ b/docs/examples/drag-handles/drag-handles-solid.tsx @@ -0,0 +1,55 @@ +/** @jsxImportSource solid-js */ +import { For } from "solid-js"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +export function MyComponent() { + const todoItems = [ + "Schedule perm", + "Rewind VHS tapes", + "Make change for the arcade", + "Get disposable camera developed", + "Learn C++", + "Return Nintendo Power Glove", + ]; + const doneItems = ["Pickup new mix-tape from Beth", "Implement drag handles"]; + + const [todoList, todos] = useDragAndDrop( + todoItems, + { + group: "todoList", + dragHandle: ".kanban-handle", + } + ); + const [doneList, dones] = useDragAndDrop( + doneItems, + { + group: "todoList", + dragHandle: ".kanban-handle", + } + ); + + return ( +
    +
      + + {(todo) => ( +
    • + + {todo} +
    • + )} +
      +
    +
      + + {(done) => ( +
    • + + {done} +
    • + )} +
      +
    +
    + ); +} diff --git a/docs/examples/draggable/draggable-solid.tsx b/docs/examples/draggable/draggable-solid.tsx new file mode 100644 index 00000000..251a14cb --- /dev/null +++ b/docs/examples/draggable/draggable-solid.tsx @@ -0,0 +1,34 @@ +/** @jsxImportSource solid-js */ +import { For } from "solid-js"; +import { useDragAndDrop } from "../../../src/solid/index"; + +export function MyComponent() { + const [parent, tapes] = useDragAndDrop( + [ + "Depeche Mode", + "Duran Duran", + "Pet Shop Boys", + "Kraftwerk", + "Tears for Fears", + "Spandau Ballet", + ], + { + draggable: (el) => { + return el.id !== "no-drag"; + }, + } + ); + + return ( +
      + + {(tape) => ( +
    • + {tape} +
    • + )} +
      +
      I am NOT draggable
      +
    + ); +} diff --git a/docs/examples/drop-or-swap/drop-or-swap-solid.tsx b/docs/examples/drop-or-swap/drop-or-swap-solid.tsx new file mode 100644 index 00000000..1832223c --- /dev/null +++ b/docs/examples/drop-or-swap/drop-or-swap-solid.tsx @@ -0,0 +1,86 @@ +/** @jsxImportSource solid-js */ +import { createSignal } from "solid-js"; +import { For } from "solid-js"; +import { dropOrSwap } from "@formkit/drag-and-drop"; +import { useDragAndDrop } from "../../../src/solid/index"; + +export function MyComponent() { + const todoItems = [ + "Schedule perm", + "Rewind VHS tapes", + "Make change for the arcade", + "Get disposable camera developed", + "Learn C++", + "Return Nintendo Power Glove", + ]; + + const [todoSwap, setTodoSwap] = createSignal(false); + const [doneSwap, setDoneSwap] = createSignal(false); + + const doneItems = ["Pickup new mix-tape from Beth"]; + + const [todoList, todos] = useDragAndDrop( + todoItems, + { + group: "todoList", + plugins: [ + dropOrSwap({ + shouldSwap: () => { + return todoSwap(); + }, + }), + ], + } + ); + + const [doneList, dones] = useDragAndDrop( + doneItems, + { + group: "todoList", + plugins: [ + dropOrSwap({ + shouldSwap: () => { + return doneSwap(); + }, + }), + ], + } + ); + + function toggleTodoSwap() { + setTodoSwap(!todoSwap()); + } + + function toggleDoneSwap() { + setDoneSwap(!doneSwap()); + } + + return ( +
    +
      + + {(todo) => ( +
    • + {todo} +
    • + )} +
      +
    + +
      + + {(done) => ( +
    • + {done} +
    • + )} +
      +
    + +
    + ); +} diff --git a/docs/examples/events-global/events-global-solid.tsx b/docs/examples/events-global/events-global-solid.tsx new file mode 100644 index 00000000..0670da2b --- /dev/null +++ b/docs/examples/events-global/events-global-solid.tsx @@ -0,0 +1,33 @@ +/** @jsxImportSource solid-js */ +import { createSignal } from "solid-js"; +import { For } from "solid-js"; +import { state } from "@formkit/drag-and-drop"; +import { useDragAndDrop } from "../../../src/solid/index"; + +export function App() { + const [dragStatus, setDragStatus] = createSignal("Not dragging"); + + const [parent, items] = useDragAndDrop([ + "🍦 vanilla", + "🍫 chocolate", + "🍓 strawberry", + ]); + + const onDragStarted = () => setDragStatus("Dragging"); + const onDragEnded = () => setDragStatus("Not dragging"); + + state.on("dragStarted", onDragStarted); + state.on("dragEnded", onDragEnded); + + return ( +
    + Rank your favorite flavors +
    + {dragStatus()} +
    +
      + {(item) =>
    • {item}
    • }
      +
    +
    + ); +} diff --git a/docs/examples/events-parent/events-parents-solid.tsx b/docs/examples/events-parent/events-parents-solid.tsx new file mode 100644 index 00000000..91c6d497 --- /dev/null +++ b/docs/examples/events-parent/events-parents-solid.tsx @@ -0,0 +1,39 @@ +/** @jsxImportSource solid-js */ +// Start of Selection +import { createSignal } from "solid-js"; +import { For } from "solid-js"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +export function App() { + const [dragStatus, setDragStatus] = createSignal("Not dragging"); + const [valuesChanged, setValuesChanged] = createSignal("Not sorting"); + + const [parent, items] = useDragAndDrop( + ["🍦 vanilla", "🍫 chocolate", "🍓 strawberry"], + { + onDragstart: () => { + setDragStatus("Dragging"); + }, + onDragend: () => { + setDragStatus("Not dragging"); + setValuesChanged("Not sorting"); + }, + onSort: (event) => { + setValuesChanged(`${event.previousValues} -> ${event.values}`); + }, + } + ); + + return ( +
    + Rank your favorite flavors +
    + {dragStatus()} + {valuesChanged()} +
    +
      + {(item) =>
    • {item}
    • }
      +
    +
    + ); +} diff --git a/docs/examples/insert/insert-solid.tsx b/docs/examples/insert/insert-solid.tsx new file mode 100644 index 00000000..8efe7a33 --- /dev/null +++ b/docs/examples/insert/insert-solid.tsx @@ -0,0 +1,104 @@ +/** @jsxImportSource solid-js */ +import { createSignal } from "solid-js"; +import { For } from "solid-js"; +import { insert } from "@formkit/drag-and-drop"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +const insertPointClasses = [ + "absolute", + "bg-blue-500", + "z-[1000]", + "rounded-full", + "duration-[5ms]", + "before:block", + 'before:content-["Insert"]', + "before:whitespace-nowrap", + "before:block", + "before:bg-blue-500", + "before:py-1", + "before:px-2", + "before:rounded-full", + "before:text-xs", + "before:absolute", + "before:top-1/2", + "before:left-1/2", + "before:-translate-y-1/2", + "before:-translate-x-1/2", + "before:text-white", + "before:text-xs", +]; + +export function MyComponent() { + const todoItems = [ + "Schedule perm", + "Rewind VHS tapes", + "Make change for the arcade", + "Get disposable camera developed", + "Learn C++", + "Return Nintendo Power Glove", + ]; + + const [todoSwap, setTodoSwap] = createSignal(false); + const [doneSwap, setDoneSwap] = createSignal(false); + + const doneItems = ["Pickup new mix-tape from Beth"]; + + const [todoList, todos] = useDragAndDrop( + todoItems, + { + group: "todoList", + plugins: [ + insert({ + insertPoint: (parent) => { + const div = document.createElement("div"); + + for (const cls of insertPointClasses) div.classList.add(cls); + + return div; + }, + }), + ], + } + ); + + const [doneList, dones] = useDragAndDrop( + doneItems, + { + group: "todoList", + plugins: [ + insert({ + insertPoint: (parent) => { + const div = document.createElement("div"); + + for (const cls of insertPointClasses) div.classList.add(cls); + + return div; + }, + }), + ], + } + ); + + function toggleTodoSwap() { + setTodoSwap(!todoSwap()); + } + + function toggleDoneSwap() { + setDoneSwap(!doneSwap()); + } + + return ( +
    +
      + + {(todo) =>
    • {todo}
    • } +
      +
    +
      + + {(done) =>
    • {done}
    • } +
      +
    +
    + ); +} diff --git a/docs/examples/insert/insert.tsx b/docs/examples/insert/insert.tsx index 00505c71..58a51472 100644 --- a/docs/examples/insert/insert.tsx +++ b/docs/examples/insert/insert.tsx @@ -77,14 +77,6 @@ export function myComponent() { } ); - function toggleTodoSwap() { - setTodoSwap(!todoSwap); - } - - function toggleDoneSwap() { - setDoneSwap(!doneSwap); - } - return (
      diff --git a/docs/examples/introduction/demo.vue b/docs/examples/introduction/demo.vue deleted file mode 100644 index 77a79c82..00000000 --- a/docs/examples/introduction/demo.vue +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/docs/examples/introduction/introduction.ts b/docs/examples/introduction/introduction.ts deleted file mode 100644 index ce4ca298..00000000 --- a/docs/examples/introduction/introduction.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { reactive, html } from "@arrow-js/core"; -import { dragAndDrop } from "@formkit/drag-and-drop"; - -const state = reactive({ - items: ["🍦 vanilla", "🍫 chocolate", "🍓 strawberry"], -}); - -html` -
        - ${() => state.items.map((item) => html`
      • ${item}
      • `.key(item))} -
      -`(document.getElementById("app")!); - -dragAndDrop({ - parent: document.getElementById("list")!, - getValues: () => state.items, - setValues: (newValues) => { - state.items = reactive(newValues); - }, -}); diff --git a/docs/examples/introduction/introduction.tsx b/docs/examples/introduction/introduction.tsx deleted file mode 100644 index a5458153..00000000 --- a/docs/examples/introduction/introduction.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; -import { useDragAndDrop } from "@formkit/drag-and-drop/react"; - -export function myComponent() { - const [parent, items] = useDragAndDrop([ - "🍦 vanilla", - "🍫 chocolate", - "🍓 strawberry", - ]); - return ( -
        - {items.map((item) => ( -
      • {item}
      • - ))} -
      - ); -} diff --git a/docs/examples/introduction/introduction.vue b/docs/examples/introduction/introduction.vue deleted file mode 100644 index b5fe072b..00000000 --- a/docs/examples/introduction/introduction.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/docs/examples/multi-drag/multi-drag-solid.tsx b/docs/examples/multi-drag/multi-drag-solid.tsx new file mode 100644 index 00000000..a44f5451 --- /dev/null +++ b/docs/examples/multi-drag/multi-drag-solid.tsx @@ -0,0 +1,39 @@ +/** @jsxImportSource solid-js */ +import { For } from "solid-js"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +export function MyComponent() { + const mockFileNames = [ + "dungeon_master.exe", + "map_1.dat", + "map_2.dat", + "character1.txt", + "character2.txt", + "shell32.dll", + "README.txt", + ]; + const [parent1, files1] = useDragAndDrop( + mockFileNames, + { + group: "A", + multiDrag: true, + selectedClass: "bg-blue-500 text-white", + } + ); + const [parent2, files2] = useDragAndDrop([], { + group: "A", + multiDrag: true, + selectedClass: "bg-blue-500 text-white", + }); + + return ( +
      +
        + {(file) =>
      • {file}
      • }
        +
      +
        + {(file) =>
      • {file}
      • }
        +
      +
      + ); +} diff --git a/docs/examples/sortable/sortable-solid.tsx b/docs/examples/sortable/sortable-solid.tsx new file mode 100644 index 00000000..8ba1ecc6 --- /dev/null +++ b/docs/examples/sortable/sortable-solid.tsx @@ -0,0 +1,45 @@ +/** @jsxImportSource solid-js */ +import { For } from "solid-js"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +export function MyComponent() { + const todoItems = [ + "Schedule perm", + "Rewind VHS tapes", + "Make change for the arcade", + "Get disposable camera developed", + "Learn C++", + "Return Nintendo Power Glove", + ]; + const doneItems = ["Pickup new mix-tape from Beth"]; + + const [todoList, todos] = useDragAndDrop( + todoItems, + { + group: "todoList", + sortable: false, + } + ); + const [doneList, dones] = useDragAndDrop( + doneItems, + { + group: "todoList", + sortable: false, + } + ); + + return ( +
      +
        + + {(todo) =>
      • {todo}
      • } +
        +
      +
        + + {(done) =>
      • {done}
      • } +
        +
      +
      + ); +} diff --git a/docs/examples/sorting/sorting-solid.tsx b/docs/examples/sorting/sorting-solid.tsx new file mode 100644 index 00000000..f6675950 --- /dev/null +++ b/docs/examples/sorting/sorting-solid.tsx @@ -0,0 +1,26 @@ +/** @jsxImportSource solid-js */ +import { For, type JSX } from "solid-js"; +import { useDragAndDrop } from "../../../src/solid/index"; + +export const MyComponent = (): JSX.Element => { + const [parent, tapes] = useDragAndDrop([ + "Depeche Mode", + "Duran Duran", + "Pet Shop Boys", + "Kraftwerk", + "Tears for Fears", + "Spandau Ballet", + ]); + + return ( +
        + + {(tape) => ( +
      • + {tape} +
      • + )} +
        +
      + ); +}; diff --git a/docs/examples/sorting/sorting.tsx b/docs/examples/sorting/sorting.tsx index 6795c89f..a88afe17 100644 --- a/docs/examples/sorting/sorting.tsx +++ b/docs/examples/sorting/sorting.tsx @@ -10,6 +10,7 @@ export function myComponent() { "Tears for Fears", "Spandau Ballet", ]); + return (
        {tapes.map((tape) => ( diff --git a/docs/examples/transfer/transfer-solid.tsx b/docs/examples/transfer/transfer-solid.tsx new file mode 100644 index 00000000..fa6806ab --- /dev/null +++ b/docs/examples/transfer/transfer-solid.tsx @@ -0,0 +1,39 @@ +/** @jsxImportSource solid-js */ +import { For } from "solid-js"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +export function MyComponent() { + const todoItems = [ + "Schedule perm", + "Rewind VHS tapes", + "Make change for the arcade", + "Get disposable camera developed", + "Learn C++", + "Return Nintendo Power Glove", + ]; + const doneItems = ["Pickup new mix-tape from Beth"]; + + const [todoList, todos] = useDragAndDrop( + todoItems, + { group: "todoList" } + ); + const [doneList, dones] = useDragAndDrop( + doneItems, + { group: "todoList" } + ); + + return ( +
        +
          + + {(todo) =>
        • {todo}
        • } +
          +
        +
          + + {(done) =>
        • {done}
        • } +
          +
        +
        + ); +} diff --git a/docs/examples/update-config/update-config-solid.tsx b/docs/examples/update-config/update-config-solid.tsx new file mode 100644 index 00000000..8ff600a4 --- /dev/null +++ b/docs/examples/update-config/update-config-solid.tsx @@ -0,0 +1,42 @@ +/** @jsxImportSource solid-js */ +import { createSignal } from "solid-js"; +import { For } from "solid-js"; +import { useDragAndDrop } from "@formkit/drag-and-drop/solid"; + +export function MyComponent() { + const [parent, values, , updateConfig] = useDragAndDrop< + HTMLUListElement, + string + >([ + "Depeche Mode", + "Duran Duran", + "Pet Shop Boys", + "Kraftwerk", + "Tears for Fears", + "Spandau Ballet", + ]); + + const [disabled, setDisabled] = createSignal(false); + + const toggleDisabled = () => { + setDisabled(!disabled()); + updateConfig({ disabled: !disabled() }); + }; + + return ( +
        +
          + + {(tape) => ( +
        • + {tape} +
        • + )} +
          +
        + +
        + ); +} diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index 37a0fac7..34ba0df9 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -1,3 +1,5 @@ +import { defineNuxtConfig } from "nuxt/config"; + const title = "Drag & Drop • by FormKit"; const description = "An open-source JavaScript library for declarative data-first drag & drop."; diff --git a/docs/package.json b/docs/package.json index b1c77373..e5f1c063 100644 --- a/docs/package.json +++ b/docs/package.json @@ -19,6 +19,7 @@ "nuxt-fathom": "^0.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "solid-js": "^1.8.0", "shiki": "^1.1.6", "twoslash-vue": "^0.2.4" }, diff --git a/docs/pages/index.vue b/docs/pages/index.vue index 88091706..d00570e4 100644 --- a/docs/pages/index.vue +++ b/docs/pages/index.vue @@ -16,11 +16,11 @@ - +
    diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 75f0db32..ef12d716 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -2,7 +2,12 @@ // https://nuxt.com/docs/guide/concepts/typescript "extends": "./.nuxt/tsconfig.json", "compilerOptions": { - "jsx": "react-jsx", - "types": ["@types/react"] - } + "jsx": "preserve", + "types": ["@types/node"], + "paths": { + "~/*": ["./*"] + } + }, + "files": ["./examples/sorting/sorting-solid.tsx"], + "include": ["./examples/**/*"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c405db0f..4f283411 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: shiki: specifier: ^1.1.6 version: 1.29.2 + solid-js: + specifier: ^1.8.0 + version: 1.9.4 twoslash-vue: specifier: ^0.2.4 version: 0.2.12(typescript@5.7.3) diff --git a/src/solid/index.ts b/src/solid/index.ts index f833a991..082387c9 100644 --- a/src/solid/index.ts +++ b/src/solid/index.ts @@ -1,5 +1,16 @@ -import { dragAndDrop as initParent, isBrowser, type ParentConfig, tearDown } from "../index"; -import { createSignal, type Accessor, type Setter, onCleanup, onMount } from "solid-js"; +import { + dragAndDrop as initParent, + isBrowser, + type ParentConfig, + tearDown, +} from "../index"; +import { + createSignal, + type Accessor, + type Setter, + onCleanup, + onMount, +} from "solid-js"; import { createStore, Store } from "solid-js/store"; import type { SolidDragAndDropConfig, SolidState } from "./types"; import { handleSolidElements } from "./utils"; @@ -7,8 +18,7 @@ import { handleSolidElements } from "./utils"; /** * Global store for parent els to values. */ -const parentValues: WeakMap> = - new WeakMap(); +const parentValues: WeakMap> = new WeakMap(); /** * Returns the values of the parent element. @@ -41,10 +51,7 @@ function getValues(parent: HTMLElement): Array { function setValues(newValues: Array, parent: HTMLElement): void { const currentValues = parentValues.get(parent); - if (currentValues) - currentValues[1](newValues); - - // parentValues.set(parent, currentValues!); + if (currentValues) currentValues[1](newValues); } function handleParent | HTMLElement, T>( @@ -60,8 +67,8 @@ function handleParent | HTMLElement, T>( export function dragAndDrop( data: - | SolidDragAndDropConfig | HTMLElement, I[]> - | Array | HTMLElement, I[]>> + | SolidDragAndDropConfig | HTMLElement, I[]> + | Array | HTMLElement, I[]>> ): void { if (!isBrowser) return; @@ -98,7 +105,9 @@ export function useDragAndDrop( dragAndDrop({ parent, state: [() => values, setValues], ...config }); } - onMount(() => dragAndDrop({ parent, state: [() => values, setValues], ...options })); + onMount(() => + dragAndDrop({ parent, state: [() => values, setValues], ...options }) + ); onCleanup(() => { const p = parent(); p && tearDown(p); diff --git a/tests-frameworks/react/components/Test2.tsx b/tests-frameworks/react/components/Test2.tsx index 63b37d71..bb869916 100644 --- a/tests-frameworks/react/components/Test2.tsx +++ b/tests-frameworks/react/components/Test2.tsx @@ -2,8 +2,13 @@ import React from "react"; import { useDragAndDrop } from "../../../src/react/index"; +type PlayingCard = { + id: string; + src: string; +}; + function Test2(props: { id: string; testDescription: string }) { - const playingCardAssets = [ + const playingCardAssets: PlayingCard[] = [ { id: "10_of_clubs", src: "/cards/10_of_clubs.png", @@ -16,10 +21,10 @@ function Test2(props: { id: string; testDescription: string }) { const [parent, values, setValues, updateConfig] = useDragAndDrop< HTMLUListElement, - any + PlayingCard >(playingCardAssets); - const playingCards = values.map((card: { id: string; src: string }) => ( + const playingCards = values.map((card: PlayingCard) => (
  • @@ -50,7 +55,7 @@ function Test2(props: { id: string; testDescription: string }) { Disable - {values.map((x: { id: string; src: string }) => x.id).join(" ")} + {values.map((x: PlayingCard) => x.id).join(" ")} ); diff --git a/tests-frameworks/solid/components/Test2.tsx b/tests-frameworks/solid/components/Test2.tsx index c813689e..274faba0 100644 --- a/tests-frameworks/solid/components/Test2.tsx +++ b/tests-frameworks/solid/components/Test2.tsx @@ -2,8 +2,13 @@ import { For } from "solid-js"; import { useDragAndDrop } from "../../../src/solid/index"; import { produce } from "solid-js/store"; +type PlayingCard = { + id: string; + src: string; +}; + function Test2(props: { id: string; testDescription: string }) { - const playingCardAssets = [ + const playingCardAssets: PlayingCard[] = [ { id: "10_of_clubs", src: "/cards/10_of_clubs.png", @@ -16,7 +21,7 @@ function Test2(props: { id: string; testDescription: string }) { const [parent, values, setValues, updateConfig] = useDragAndDrop< HTMLUListElement, - any + PlayingCard >(playingCardAssets); const playingCards = ( @@ -30,7 +35,11 @@ function Test2(props: { id: string; testDescription: string }) { ); function addValue() { - setValues(produce((cards) => cards.push({ id: "queen_of_spades", src: "/cards/queen_of_spades.png" }))); + setValues( + produce((cards) => + cards.push({ id: "queen_of_spades", src: "/cards/queen_of_spades.png" }) + ) + ); } function disable() { @@ -52,7 +61,7 @@ function Test2(props: { id: string; testDescription: string }) { {values() - .map((x: { id: string; src: string }) => x.id) + .map((x: PlayingCard) => x.id) .join(" ")} diff --git a/tests/.nuxt/nuxt.d.ts b/tests/.nuxt/nuxt.d.ts index 977b7354..496db634 100644 --- a/tests/.nuxt/nuxt.d.ts +++ b/tests/.nuxt/nuxt.d.ts @@ -1,7 +1,7 @@ // Generated by nuxi -/// /// /// +/// /// /// /// diff --git a/tests/.nuxt/tsconfig.json b/tests/.nuxt/tsconfig.json index 866d44bc..28218e1f 100644 --- a/tests/.nuxt/tsconfig.json +++ b/tests/.nuxt/tsconfig.json @@ -102,7 +102,7 @@ "./imports" ], "#app-manifest": [ - "./manifest/meta/d4575c0a-3ed8-46e5-9921-ecd7a7fff86b" + "./manifest/meta/288fec24-7933-46c5-b62a-4e2d5a22dd74" ], "#components": [ "./components"