@spark-ui/hooks 17.2.1-beta.0 → 17.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/use-combined-state/index.d.ts +1 -12
- package/dist/use-combined-state/index.js +670 -47
- package/dist/use-combined-state/index.js.map +1 -1
- package/dist/use-combined-state/index.mjs +664 -4
- package/dist/use-combined-state/index.mjs.map +1 -1
- package/dist/use-merge-refs/index.d.ts +1 -7
- package/dist/use-merge-refs/index.js +7 -37
- package/dist/use-merge-refs/index.js.map +1 -1
- package/dist/use-merge-refs/index.mjs +1 -2
- package/dist/use-merge-refs/index.mjs.map +1 -1
- package/dist/use-mounted-state/index.d.ts +1 -3
- package/dist/use-mounted-state/index.js +7 -35
- package/dist/use-mounted-state/index.js.map +1 -1
- package/dist/use-mounted-state/index.mjs +2 -3
- package/dist/use-mounted-state/index.mjs.map +1 -1
- package/dist/use-scroll-overflow/index.d.ts +2 -22
- package/dist/use-scroll-overflow/index.js +8 -36
- package/dist/use-scroll-overflow/index.js.map +1 -1
- package/dist/use-scroll-overflow/index.mjs +2 -3
- package/dist/use-scroll-overflow/index.mjs.map +1 -1
- package/dist/use-sortable-list/index.d.ts +2 -98
- package/dist/use-sortable-list/index.js +6 -36
- package/dist/use-sortable-list/index.js.map +1 -1
- package/dist/use-sortable-list/index.mjs +2 -5
- package/dist/use-sortable-list/index.mjs.map +1 -1
- package/package.json +2 -2
- package/vite.config.ts +43 -0
- package/.turbo/turbo-build.log +0 -35
- package/.turbo/turbo-lint.log +0 -327
- package/dist/use-combined-state/index.d.mts +0 -12
- package/dist/use-merge-refs/index.d.mts +0 -7
- package/dist/use-mounted-state/index.d.mts +0 -3
- package/dist/use-scroll-overflow/index.d.mts +0 -22
- package/dist/use-sortable-list/index.d.mts +0 -98
- package/tsup.config.ts +0 -11
|
@@ -1,37 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/use-sortable-list/index.ts
|
|
21
|
-
var use_sortable_list_exports = {};
|
|
22
|
-
__export(use_sortable_list_exports, {
|
|
23
|
-
useSortableList: () => useSortableList
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(use_sortable_list_exports);
|
|
26
|
-
|
|
27
|
-
// src/use-sortable-list/useSortableList.tsx
|
|
28
|
-
var import_react = require("react");
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const react = require("react");
|
|
29
4
|
function useSortableList({
|
|
30
5
|
items,
|
|
31
6
|
onReorder,
|
|
32
7
|
getItemKey
|
|
33
8
|
}) {
|
|
34
|
-
const itemRefs =
|
|
9
|
+
const itemRefs = react.useRef(/* @__PURE__ */ new Map());
|
|
35
10
|
const handleDragStart = (e, index) => {
|
|
36
11
|
e.dataTransfer.effectAllowed = "move";
|
|
37
12
|
e.dataTransfer.setData("text/plain", index.toString());
|
|
@@ -53,7 +28,6 @@ function useSortableList({
|
|
|
53
28
|
}
|
|
54
29
|
};
|
|
55
30
|
const handleDragEnd = (e) => {
|
|
56
|
-
;
|
|
57
31
|
e.currentTarget.style.opacity = "";
|
|
58
32
|
e.currentTarget.removeAttribute("data-drag-over");
|
|
59
33
|
};
|
|
@@ -86,13 +60,12 @@ function useSortableList({
|
|
|
86
60
|
const currentItem = newItems[index];
|
|
87
61
|
const targetItem = newItems[targetIndex];
|
|
88
62
|
if (currentItem && targetItem) {
|
|
89
|
-
;
|
|
90
63
|
[newItems[index], newItems[targetIndex]] = [targetItem, currentItem];
|
|
91
64
|
onReorder(newItems);
|
|
92
65
|
requestAnimationFrame(() => {
|
|
93
66
|
const itemKey = getItemKey(currentItem);
|
|
94
67
|
const element = itemRefs.current.get(itemKey);
|
|
95
|
-
element
|
|
68
|
+
element == null ? void 0 : element.focus();
|
|
96
69
|
});
|
|
97
70
|
}
|
|
98
71
|
};
|
|
@@ -121,8 +94,5 @@ function useSortableList({
|
|
|
121
94
|
getItemProps
|
|
122
95
|
};
|
|
123
96
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
useSortableList
|
|
127
|
-
});
|
|
128
|
-
//# sourceMappingURL=index.js.map
|
|
97
|
+
exports.useSortableList = useSortableList;
|
|
98
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/use-sortable-list/useSortableList.tsx"],"sourcesContent":["/* eslint-disable max-lines-per-function */\nimport { Ref, useRef } from 'react'\n\nexport interface UseSortableListOptions<T> {\n /**\n * The list of items to be sortable\n */\n items: T[]\n /**\n * Callback called when items are reordered\n * @param newItems - The reordered items array\n */\n onReorder: (newItems: T[]) => void\n /**\n * Function to generate a unique key for each item\n * @param item - The item to generate a key for\n * @returns A unique string key for the item\n */\n getItemKey: (item: T) => string\n}\n\nexport interface SortableItemProps<TElement extends HTMLElement = HTMLElement> {\n /**\n * Whether the item is draggable\n */\n draggable: boolean\n /**\n * Handler for drag start event\n */\n onDragStart: (e: React.DragEvent) => void\n /**\n * Handler for drag enter event\n */\n onDragEnter: (e: React.DragEvent) => void\n /**\n * Handler for drag over event\n */\n onDragOver: (e: React.DragEvent) => void\n /**\n * Handler for drag leave event\n */\n onDragLeave: (e: React.DragEvent) => void\n /**\n * Handler for drag end event\n */\n onDragEnd: (e: React.DragEvent) => void\n /**\n * Handler for drop event\n */\n onDrop: (e: React.DragEvent) => void\n /**\n * Handler for keyboard navigation\n */\n onKeyDown: (e: React.KeyboardEvent) => void\n /**\n * Tab index for keyboard navigation\n */\n tabIndex: number\n /**\n * Ref callback to attach to the item element\n */\n ref: Ref<TElement>\n}\n\nexport interface UseSortableListReturn<T> {\n /**\n * Get props to spread on a sortable item element (includes ref)\n * @param item - The item to get props for\n * @param index - The current index of the item in the list\n * @returns Props object to spread on the element\n */\n getItemProps: <TElement extends HTMLElement = HTMLElement>(\n item: T,\n index: number\n ) => SortableItemProps<TElement>\n}\n\n/**\n * Hook to make a list of items sortable via drag and drop and keyboard navigation\n *\n * @example\n * ```tsx\n * const { getItemProps } = useSortableList({\n * items: myItems,\n * onReorder: setMyItems,\n * getItemKey: (item) => item.id\n * })\n *\n * return (\n * <ul>\n * {myItems.map((item, index) => (\n * <li\n * key={getItemKey(item)}\n * {...getItemProps(item, index)}\n * >\n * {item.name}\n * </li>\n * ))}\n * </ul>\n * )\n * ```\n */\nexport function useSortableList<T>({\n items,\n onReorder,\n getItemKey,\n}: UseSortableListOptions<T>): UseSortableListReturn<T> {\n // Refs to maintain focus after keyboard reordering\n // Uses a key based on the item rather than index\n const itemRefs = useRef<Map<string, HTMLElement>>(new Map())\n\n const handleDragStart = (e: React.DragEvent, index: number) => {\n e.dataTransfer.effectAllowed = 'move'\n e.dataTransfer.setData('text/plain', index.toString())\n // Apply inline style for opacity during drag\n ;(e.currentTarget as HTMLElement).style.opacity = 'var(--opacity-dim-3)'\n }\n\n const handleDragEnter = (e: React.DragEvent) => {\n e.preventDefault()\n e.currentTarget.setAttribute('data-drag-over', 'true')\n }\n\n const handleDragOver = (e: React.DragEvent) => {\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n }\n\n const handleDragLeave = (e: React.DragEvent) => {\n // Only remove the attribute if we're actually leaving the element\n // (not just moving to a child element)\n const relatedTarget = e.relatedTarget as Node | null\n const currentTarget = e.currentTarget as Node\n\n if (!relatedTarget || !currentTarget.contains(relatedTarget)) {\n e.currentTarget.removeAttribute('data-drag-over')\n }\n }\n\n const handleDragEnd = (e: React.DragEvent) => {\n // Remove inline style for opacity\n ;(e.currentTarget as HTMLElement).style.opacity = ''\n e.currentTarget.removeAttribute('data-drag-over')\n }\n\n const handleDrop = (e: React.DragEvent, dropIndex: number) => {\n e.preventDefault()\n e.currentTarget.removeAttribute('data-drag-over')\n\n const dragIndex = parseInt(e.dataTransfer.getData('text/plain'), 10)\n\n if (\n !isNaN(dragIndex) &&\n dragIndex !== dropIndex &&\n dragIndex >= 0 &&\n dragIndex < items.length\n ) {\n const newItems = [...items]\n const [removed] = newItems.splice(dragIndex, 1)\n if (removed) {\n newItems.splice(dropIndex, 0, removed)\n onReorder(newItems)\n }\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent, _item: T, index: number) => {\n // Determine direction\n let direction = 0\n if (e.key === 'ArrowUp') {\n direction = -1\n } else if (e.key === 'ArrowDown') {\n direction = 1\n } else {\n return\n }\n\n const targetIndex = index + direction\n\n // Validate move is within bounds\n if (targetIndex < 0 || targetIndex >= items.length) return\n\n e.preventDefault()\n\n // Create new array and swap items\n const newItems = [...items]\n const currentItem = newItems[index]\n const targetItem = newItems[targetIndex]\n\n if (currentItem && targetItem) {\n ;[newItems[index], newItems[targetIndex]] = [targetItem, currentItem]\n onReorder(newItems)\n\n // Maintain focus on the moved item\n requestAnimationFrame(() => {\n const itemKey = getItemKey(currentItem)\n const element = itemRefs.current.get(itemKey)\n element?.focus()\n })\n }\n }\n\n const getItemProps = <TElement extends HTMLElement = HTMLElement>(\n item: T,\n index: number\n ): SortableItemProps<TElement> => {\n const itemKey = getItemKey(item)\n\n return {\n draggable: true,\n onDragStart: e => handleDragStart(e, index),\n onDragEnter: handleDragEnter,\n onDragOver: handleDragOver,\n onDragLeave: handleDragLeave,\n onDragEnd: handleDragEnd,\n onDrop: e => handleDrop(e, index),\n onKeyDown: e => handleKeyDown(e, item, index),\n tabIndex: 0,\n ref: (node: TElement | null) => {\n if (node) {\n itemRefs.current.set(itemKey, node as HTMLElement)\n } else {\n itemRefs.current.delete(itemKey)\n }\n },\n }\n }\n\n return {\n getItemProps,\n }\n}\n"],"names":["useRef"],"mappings":"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"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// src/use-sortable-list/useSortableList.tsx
|
|
2
1
|
import { useRef } from "react";
|
|
3
2
|
function useSortableList({
|
|
4
3
|
items,
|
|
@@ -27,7 +26,6 @@ function useSortableList({
|
|
|
27
26
|
}
|
|
28
27
|
};
|
|
29
28
|
const handleDragEnd = (e) => {
|
|
30
|
-
;
|
|
31
29
|
e.currentTarget.style.opacity = "";
|
|
32
30
|
e.currentTarget.removeAttribute("data-drag-over");
|
|
33
31
|
};
|
|
@@ -60,13 +58,12 @@ function useSortableList({
|
|
|
60
58
|
const currentItem = newItems[index];
|
|
61
59
|
const targetItem = newItems[targetIndex];
|
|
62
60
|
if (currentItem && targetItem) {
|
|
63
|
-
;
|
|
64
61
|
[newItems[index], newItems[targetIndex]] = [targetItem, currentItem];
|
|
65
62
|
onReorder(newItems);
|
|
66
63
|
requestAnimationFrame(() => {
|
|
67
64
|
const itemKey = getItemKey(currentItem);
|
|
68
65
|
const element = itemRefs.current.get(itemKey);
|
|
69
|
-
element
|
|
66
|
+
element == null ? void 0 : element.focus();
|
|
70
67
|
});
|
|
71
68
|
}
|
|
72
69
|
};
|
|
@@ -98,4 +95,4 @@ function useSortableList({
|
|
|
98
95
|
export {
|
|
99
96
|
useSortableList
|
|
100
97
|
};
|
|
101
|
-
//# sourceMappingURL=index.mjs.map
|
|
98
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/use-sortable-list/useSortableList.tsx"],"sourcesContent":["/* eslint-disable max-lines-per-function */\nimport { Ref, useRef } from 'react'\n\nexport interface UseSortableListOptions<T> {\n /**\n * The list of items to be sortable\n */\n items: T[]\n /**\n * Callback called when items are reordered\n * @param newItems - The reordered items array\n */\n onReorder: (newItems: T[]) => void\n /**\n * Function to generate a unique key for each item\n * @param item - The item to generate a key for\n * @returns A unique string key for the item\n */\n getItemKey: (item: T) => string\n}\n\nexport interface SortableItemProps<TElement extends HTMLElement = HTMLElement> {\n /**\n * Whether the item is draggable\n */\n draggable: boolean\n /**\n * Handler for drag start event\n */\n onDragStart: (e: React.DragEvent) => void\n /**\n * Handler for drag enter event\n */\n onDragEnter: (e: React.DragEvent) => void\n /**\n * Handler for drag over event\n */\n onDragOver: (e: React.DragEvent) => void\n /**\n * Handler for drag leave event\n */\n onDragLeave: (e: React.DragEvent) => void\n /**\n * Handler for drag end event\n */\n onDragEnd: (e: React.DragEvent) => void\n /**\n * Handler for drop event\n */\n onDrop: (e: React.DragEvent) => void\n /**\n * Handler for keyboard navigation\n */\n onKeyDown: (e: React.KeyboardEvent) => void\n /**\n * Tab index for keyboard navigation\n */\n tabIndex: number\n /**\n * Ref callback to attach to the item element\n */\n ref: Ref<TElement>\n}\n\nexport interface UseSortableListReturn<T> {\n /**\n * Get props to spread on a sortable item element (includes ref)\n * @param item - The item to get props for\n * @param index - The current index of the item in the list\n * @returns Props object to spread on the element\n */\n getItemProps: <TElement extends HTMLElement = HTMLElement>(\n item: T,\n index: number\n ) => SortableItemProps<TElement>\n}\n\n/**\n * Hook to make a list of items sortable via drag and drop and keyboard navigation\n *\n * @example\n * ```tsx\n * const { getItemProps } = useSortableList({\n * items: myItems,\n * onReorder: setMyItems,\n * getItemKey: (item) => item.id\n * })\n *\n * return (\n * <ul>\n * {myItems.map((item, index) => (\n * <li\n * key={getItemKey(item)}\n * {...getItemProps(item, index)}\n * >\n * {item.name}\n * </li>\n * ))}\n * </ul>\n * )\n * ```\n */\nexport function useSortableList<T>({\n items,\n onReorder,\n getItemKey,\n}: UseSortableListOptions<T>): UseSortableListReturn<T> {\n // Refs to maintain focus after keyboard reordering\n // Uses a key based on the item rather than index\n const itemRefs = useRef<Map<string, HTMLElement>>(new Map())\n\n const handleDragStart = (e: React.DragEvent, index: number) => {\n e.dataTransfer.effectAllowed = 'move'\n e.dataTransfer.setData('text/plain', index.toString())\n // Apply inline style for opacity during drag\n ;(e.currentTarget as HTMLElement).style.opacity = 'var(--opacity-dim-3)'\n }\n\n const handleDragEnter = (e: React.DragEvent) => {\n e.preventDefault()\n e.currentTarget.setAttribute('data-drag-over', 'true')\n }\n\n const handleDragOver = (e: React.DragEvent) => {\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n }\n\n const handleDragLeave = (e: React.DragEvent) => {\n // Only remove the attribute if we're actually leaving the element\n // (not just moving to a child element)\n const relatedTarget = e.relatedTarget as Node | null\n const currentTarget = e.currentTarget as Node\n\n if (!relatedTarget || !currentTarget.contains(relatedTarget)) {\n e.currentTarget.removeAttribute('data-drag-over')\n }\n }\n\n const handleDragEnd = (e: React.DragEvent) => {\n // Remove inline style for opacity\n ;(e.currentTarget as HTMLElement).style.opacity = ''\n e.currentTarget.removeAttribute('data-drag-over')\n }\n\n const handleDrop = (e: React.DragEvent, dropIndex: number) => {\n e.preventDefault()\n e.currentTarget.removeAttribute('data-drag-over')\n\n const dragIndex = parseInt(e.dataTransfer.getData('text/plain'), 10)\n\n if (\n !isNaN(dragIndex) &&\n dragIndex !== dropIndex &&\n dragIndex >= 0 &&\n dragIndex < items.length\n ) {\n const newItems = [...items]\n const [removed] = newItems.splice(dragIndex, 1)\n if (removed) {\n newItems.splice(dropIndex, 0, removed)\n onReorder(newItems)\n }\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent, _item: T, index: number) => {\n // Determine direction\n let direction = 0\n if (e.key === 'ArrowUp') {\n direction = -1\n } else if (e.key === 'ArrowDown') {\n direction = 1\n } else {\n return\n }\n\n const targetIndex = index + direction\n\n // Validate move is within bounds\n if (targetIndex < 0 || targetIndex >= items.length) return\n\n e.preventDefault()\n\n // Create new array and swap items\n const newItems = [...items]\n const currentItem = newItems[index]\n const targetItem = newItems[targetIndex]\n\n if (currentItem && targetItem) {\n ;[newItems[index], newItems[targetIndex]] = [targetItem, currentItem]\n onReorder(newItems)\n\n // Maintain focus on the moved item\n requestAnimationFrame(() => {\n const itemKey = getItemKey(currentItem)\n const element = itemRefs.current.get(itemKey)\n element?.focus()\n })\n }\n }\n\n const getItemProps = <TElement extends HTMLElement = HTMLElement>(\n item: T,\n index: number\n ): SortableItemProps<TElement> => {\n const itemKey = getItemKey(item)\n\n return {\n draggable: true,\n onDragStart: e => handleDragStart(e, index),\n onDragEnter: handleDragEnter,\n onDragOver: handleDragOver,\n onDragLeave: handleDragLeave,\n onDragEnd: handleDragEnd,\n onDrop: e => handleDrop(e, index),\n onKeyDown: e => handleKeyDown(e, item, index),\n tabIndex: 0,\n ref: (node: TElement | null) => {\n if (node) {\n itemRefs.current.set(itemKey, node as HTMLElement)\n } else {\n itemRefs.current.delete(itemKey)\n }\n },\n }\n }\n\n return {\n getItemProps,\n }\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/use-sortable-list/useSortableList.tsx"],"sourcesContent":["/* eslint-disable max-lines-per-function */\nimport { Ref, useRef } from 'react'\n\nexport interface UseSortableListOptions<T> {\n /**\n * The list of items to be sortable\n */\n items: T[]\n /**\n * Callback called when items are reordered\n * @param newItems - The reordered items array\n */\n onReorder: (newItems: T[]) => void\n /**\n * Function to generate a unique key for each item\n * @param item - The item to generate a key for\n * @returns A unique string key for the item\n */\n getItemKey: (item: T) => string\n}\n\nexport interface SortableItemProps<TElement extends HTMLElement = HTMLElement> {\n /**\n * Whether the item is draggable\n */\n draggable: boolean\n /**\n * Handler for drag start event\n */\n onDragStart: (e: React.DragEvent) => void\n /**\n * Handler for drag enter event\n */\n onDragEnter: (e: React.DragEvent) => void\n /**\n * Handler for drag over event\n */\n onDragOver: (e: React.DragEvent) => void\n /**\n * Handler for drag leave event\n */\n onDragLeave: (e: React.DragEvent) => void\n /**\n * Handler for drag end event\n */\n onDragEnd: (e: React.DragEvent) => void\n /**\n * Handler for drop event\n */\n onDrop: (e: React.DragEvent) => void\n /**\n * Handler for keyboard navigation\n */\n onKeyDown: (e: React.KeyboardEvent) => void\n /**\n * Tab index for keyboard navigation\n */\n tabIndex: number\n /**\n * Ref callback to attach to the item element\n */\n ref: Ref<TElement>\n}\n\nexport interface UseSortableListReturn<T> {\n /**\n * Get props to spread on a sortable item element (includes ref)\n * @param item - The item to get props for\n * @param index - The current index of the item in the list\n * @returns Props object to spread on the element\n */\n getItemProps: <TElement extends HTMLElement = HTMLElement>(\n item: T,\n index: number\n ) => SortableItemProps<TElement>\n}\n\n/**\n * Hook to make a list of items sortable via drag and drop and keyboard navigation\n *\n * @example\n * ```tsx\n * const { getItemProps } = useSortableList({\n * items: myItems,\n * onReorder: setMyItems,\n * getItemKey: (item) => item.id\n * })\n *\n * return (\n * <ul>\n * {myItems.map((item, index) => (\n * <li\n * key={getItemKey(item)}\n * {...getItemProps(item, index)}\n * >\n * {item.name}\n * </li>\n * ))}\n * </ul>\n * )\n * ```\n */\nexport function useSortableList<T>({\n items,\n onReorder,\n getItemKey,\n}: UseSortableListOptions<T>): UseSortableListReturn<T> {\n // Refs to maintain focus after keyboard reordering\n // Uses a key based on the item rather than index\n const itemRefs = useRef<Map<string, HTMLElement>>(new Map())\n\n const handleDragStart = (e: React.DragEvent, index: number) => {\n e.dataTransfer.effectAllowed = 'move'\n e.dataTransfer.setData('text/plain', index.toString())\n // Apply inline style for opacity during drag\n ;(e.currentTarget as HTMLElement).style.opacity = 'var(--opacity-dim-3)'\n }\n\n const handleDragEnter = (e: React.DragEvent) => {\n e.preventDefault()\n e.currentTarget.setAttribute('data-drag-over', 'true')\n }\n\n const handleDragOver = (e: React.DragEvent) => {\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n }\n\n const handleDragLeave = (e: React.DragEvent) => {\n // Only remove the attribute if we're actually leaving the element\n // (not just moving to a child element)\n const relatedTarget = e.relatedTarget as Node | null\n const currentTarget = e.currentTarget as Node\n\n if (!relatedTarget || !currentTarget.contains(relatedTarget)) {\n e.currentTarget.removeAttribute('data-drag-over')\n }\n }\n\n const handleDragEnd = (e: React.DragEvent) => {\n // Remove inline style for opacity\n ;(e.currentTarget as HTMLElement).style.opacity = ''\n e.currentTarget.removeAttribute('data-drag-over')\n }\n\n const handleDrop = (e: React.DragEvent, dropIndex: number) => {\n e.preventDefault()\n e.currentTarget.removeAttribute('data-drag-over')\n\n const dragIndex = parseInt(e.dataTransfer.getData('text/plain'), 10)\n\n if (\n !isNaN(dragIndex) &&\n dragIndex !== dropIndex &&\n dragIndex >= 0 &&\n dragIndex < items.length\n ) {\n const newItems = [...items]\n const [removed] = newItems.splice(dragIndex, 1)\n if (removed) {\n newItems.splice(dropIndex, 0, removed)\n onReorder(newItems)\n }\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent, _item: T, index: number) => {\n // Determine direction\n let direction = 0\n if (e.key === 'ArrowUp') {\n direction = -1\n } else if (e.key === 'ArrowDown') {\n direction = 1\n } else {\n return\n }\n\n const targetIndex = index + direction\n\n // Validate move is within bounds\n if (targetIndex < 0 || targetIndex >= items.length) return\n\n e.preventDefault()\n\n // Create new array and swap items\n const newItems = [...items]\n const currentItem = newItems[index]\n const targetItem = newItems[targetIndex]\n\n if (currentItem && targetItem) {\n ;[newItems[index], newItems[targetIndex]] = [targetItem, currentItem]\n onReorder(newItems)\n\n // Maintain focus on the moved item\n requestAnimationFrame(() => {\n const itemKey = getItemKey(currentItem)\n const element = itemRefs.current.get(itemKey)\n element?.focus()\n })\n }\n }\n\n const getItemProps = <TElement extends HTMLElement = HTMLElement>(\n item: T,\n index: number\n ): SortableItemProps<TElement> => {\n const itemKey = getItemKey(item)\n\n return {\n draggable: true,\n onDragStart: e => handleDragStart(e, index),\n onDragEnter: handleDragEnter,\n onDragOver: handleDragOver,\n onDragLeave: handleDragLeave,\n onDragEnd: handleDragEnd,\n onDrop: e => handleDrop(e, index),\n onKeyDown: e => handleKeyDown(e, item, index),\n tabIndex: 0,\n ref: (node: TElement | null) => {\n if (node) {\n itemRefs.current.set(itemKey, node as HTMLElement)\n } else {\n itemRefs.current.delete(itemKey)\n }\n },\n }\n }\n\n return {\n getItemProps,\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spark-ui/hooks",
|
|
3
|
-
"version": "17.2.1
|
|
3
|
+
"version": "17.2.1",
|
|
4
4
|
"description": "Common hooks for Spark UI",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./*": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"hook"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"build": "
|
|
28
|
+
"build": "vite build"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@types/lodash.isequal": "4.5.8",
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { defineConfig } from 'vite'
|
|
5
|
+
import dts from 'vite-plugin-dts'
|
|
6
|
+
|
|
7
|
+
const srcDir = join(__dirname, 'src')
|
|
8
|
+
const hookDirectories = readdirSync(srcDir, { withFileTypes: true })
|
|
9
|
+
.filter(entry => entry.isDirectory())
|
|
10
|
+
.map(entry => entry.name)
|
|
11
|
+
|
|
12
|
+
const input = Object.fromEntries(
|
|
13
|
+
hookDirectories.map(directory => [directory, join(srcDir, directory, 'index.ts')])
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
export default defineConfig({
|
|
17
|
+
build: {
|
|
18
|
+
sourcemap: true,
|
|
19
|
+
minify: false,
|
|
20
|
+
emptyOutDir: true,
|
|
21
|
+
lib: {
|
|
22
|
+
entry: input,
|
|
23
|
+
formats: ['es', 'cjs'],
|
|
24
|
+
fileName: (format, entryName) => `${entryName}/index.${format === 'es' ? 'mjs' : 'js'}`,
|
|
25
|
+
},
|
|
26
|
+
rollupOptions: {
|
|
27
|
+
external: ['react'],
|
|
28
|
+
output: {
|
|
29
|
+
exports: 'named',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
plugins: [
|
|
34
|
+
dts({
|
|
35
|
+
include: ['src/*/index.ts'],
|
|
36
|
+
entryRoot: 'src',
|
|
37
|
+
outDir: 'dist',
|
|
38
|
+
rollupTypes: false,
|
|
39
|
+
insertTypesEntry: false,
|
|
40
|
+
copyDtsFiles: false,
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
})
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> @spark-ui/hooks@10.6.1 build
|
|
4
|
-
> tsup
|
|
5
|
-
|
|
6
|
-
[1G[0K[34mCLI[39m Building entry: src/use-combined-state/index.ts, src/use-merge-refs/index.ts, src/use-mounted-state/index.ts
|
|
7
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
8
|
-
[34mCLI[39m tsup v8.5.0
|
|
9
|
-
[34mCLI[39m Using tsup config: /Users/julien.bonnin/projects/personal/spark/packages/hooks/tsup.config.ts
|
|
10
|
-
[34mCLI[39m Target: esnext
|
|
11
|
-
[34mCJS[39m Build start
|
|
12
|
-
[34mESM[39m Build start
|
|
13
|
-
[32mCJS[39m [1mdist/use-combined-state/index.js [22m[32m2.73 KB[39m
|
|
14
|
-
[32mCJS[39m [1mdist/use-merge-refs/index.js [22m[32m1.73 KB[39m
|
|
15
|
-
[32mCJS[39m [1mdist/use-mounted-state/index.js [22m[32m1.51 KB[39m
|
|
16
|
-
[32mCJS[39m [1mdist/use-combined-state/index.js.map [22m[32m2.18 KB[39m
|
|
17
|
-
[32mCJS[39m [1mdist/use-mounted-state/index.js.map [22m[32m833.00 B[39m
|
|
18
|
-
[32mCJS[39m [1mdist/use-merge-refs/index.js.map [22m[32m1.41 KB[39m
|
|
19
|
-
[32mCJS[39m ⚡️ Build success in 24ms
|
|
20
|
-
[32mESM[39m [1mdist/use-combined-state/index.mjs [22m[32m1.00 KB[39m
|
|
21
|
-
[32mESM[39m [1mdist/use-mounted-state/index.mjs [22m[32m424.00 B[39m
|
|
22
|
-
[32mESM[39m [1mdist/use-merge-refs/index.mjs [22m[32m618.00 B[39m
|
|
23
|
-
[32mESM[39m [1mdist/use-combined-state/index.mjs.map [22m[32m2.03 KB[39m
|
|
24
|
-
[32mESM[39m [1mdist/use-mounted-state/index.mjs.map [22m[32m701.00 B[39m
|
|
25
|
-
[32mESM[39m [1mdist/use-merge-refs/index.mjs.map [22m[32m1.25 KB[39m
|
|
26
|
-
[32mESM[39m ⚡️ Build success in 25ms
|
|
27
|
-
DTS Build start
|
|
28
|
-
DTS ⚡️ Build success in 3151ms
|
|
29
|
-
DTS dist/use-combined-state/index.d.ts 454.00 B
|
|
30
|
-
DTS dist/use-merge-refs/index.d.ts 333.00 B
|
|
31
|
-
DTS dist/use-mounted-state/index.d.ts 80.00 B
|
|
32
|
-
DTS dist/use-combined-state/index.d.mts 454.00 B
|
|
33
|
-
DTS dist/use-merge-refs/index.d.mts 333.00 B
|
|
34
|
-
DTS dist/use-mounted-state/index.d.mts 80.00 B
|
|
35
|
-
[1G[0K⠙[1G[0K
|