@dfosco/tiny-canvas 1.1.0

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/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Tiny Canvas
2
+
3
+ A lightweight React canvas with draggable elements and persistent positions.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install tiny-canvas
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```jsx
14
+ import { Canvas } from 'tiny-canvas'
15
+ import 'tiny-canvas/style.css'
16
+
17
+ function App() {
18
+ return (
19
+ <Canvas grid>
20
+ <div id="card-1">Hello</div>
21
+ <div id="card-2">World</div>
22
+ </Canvas>
23
+ )
24
+ }
25
+ ```
26
+
27
+ Give each child an `id` prop — this is how positions are persisted in localStorage. Children without an `id` are still draggable but won't remember their position.
28
+
29
+ ## Components
30
+
31
+ ### `<Canvas>`
32
+
33
+ Wraps children in draggable containers.
34
+
35
+ | Prop | Type | Default | Description |
36
+ |------|------|---------|-------------|
37
+ | `centered` | `boolean` | `true` | Center children with flexbox |
38
+ | `grid` | `boolean` | `false` | Show dot-grid background and enable snap-to-grid |
39
+ | `gridSize` | `number` | `18` | Grid snap size in pixels (when `grid` is `true`) |
40
+
41
+ ### `<Draggable>`
42
+
43
+ Standalone draggable wrapper — use directly without `Canvas` if needed.
44
+
45
+ | Prop | Type | Default | Description |
46
+ |------|------|---------|-------------|
47
+ | `gridSize` | `number` | — | Grid snap size in pixels. No snapping when omitted. |
48
+
49
+ ## Hooks
50
+
51
+ ### `useResetCanvas(options?)`
52
+
53
+ Returns a function that clears all saved positions from localStorage.
54
+
55
+ | Option | Type | Default | Description |
56
+ |--------|------|---------|-------------|
57
+ | `reload` | `boolean` | `false` | Reload the page after clearing |
58
+
59
+ ## Utilities
60
+
61
+ ```js
62
+ import { saveDrag, getQueue, refreshStorage, findDragId } from 'tiny-canvas'
63
+ ```
64
+
65
+ - `saveDrag(id, x, y)` — persist a position
66
+ - `getQueue(id)` — retrieve `{ x, y }` for an element
67
+ - `refreshStorage()` — initialize localStorage if empty
68
+ - `findDragId(children)` — extract the first `id` prop from React children
69
+
70
+ ## CSS customization
71
+
72
+ Override `--tc-*` custom properties to theme the canvas:
73
+
74
+ ```css
75
+ :root {
76
+ --tc-border-radius: 12px;
77
+ --tc-shadow-rest: 0 1px 3px rgba(0, 0, 0, 0.12);
78
+ --tc-shadow-float: 0 4px 12px rgba(0, 0, 0, 0.15);
79
+ --tc-border-color: rgba(0, 0, 0, 0.15);
80
+ --tc-bg-muted: #f6f8fa;
81
+ --tc-dot-color: rgba(0, 0, 0, 0.08);
82
+ --tc-grid-size: 36px;
83
+ }
84
+ ```
85
+
86
+ If you use [Primer Primitives](https://github.com/primer/primitives), the variables automatically pick up your theme tokens.
87
+
88
+ ## Peer dependencies
89
+
90
+ - `react` >= 18
91
+ - `react-dom` >= 18
92
+
93
+ ---
94
+
95
+ MIT License
@@ -0,0 +1 @@
1
+ :root{--tc-border-radius: var(--borderRadius-large, 12px);--tc-shadow-rest: var(--shadow-resting-small, 0 1px 3px rgba(0, 0, 0, .12));--tc-shadow-float: var(--shadow-floating-small, 0 4px 12px rgba(0, 0, 0, .15));--tc-border-color: var(--borderColor-muted, rgba(0, 0, 0, .15));--tc-bg-muted: var(--bgColor-muted, #f6f8fa);--tc-dot-color: var(--overlay-backdrop-bgColor, rgba(0, 0, 0, .08));--tc-grid-size: var(--golden-number, 36px);--tc-grid-offset: calc(var(--tc-grid-size) / -2);--tc-font-stack: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif}.tc-canvas{min-width:100vw;min-height:100vh;margin:0;font-family:var(--tc-font-stack);position:relative;background-color:var(--tc-bg-muted);width:100vw;max-width:100vw;color-scheme:light dark}.tc-canvas[data-color-mode=light]{color-scheme:light}.tc-canvas[data-color-mode=dark]{color-scheme:dark}.tc-canvas[data-dotted]{background-image:radial-gradient(var(--tc-dot-color) 2px,transparent 2px);background-size:var(--tc-grid-size) var(--tc-grid-size);background-position:var(--tc-grid-offset) var(--tc-grid-offset)}@media(prefers-color-scheme:dark){.tc-canvas:not([data-color-mode=light]){--tc-bg-muted: var(--bgColor-muted, #161b22);--tc-dot-color: var(--overlay-backdrop-bgColor, rgba(255, 255, 255, .1));--tc-border-color: var(--borderColor-muted, rgba(255, 255, 255, .15));--tc-shadow-rest: var(--shadow-resting-small, 0 1px 3px rgba(0, 0, 0, .3));--tc-shadow-float: var(--shadow-floating-small, 0 4px 12px rgba(0, 0, 0, .4))}}.tc-canvas[data-color-mode=dark]{--tc-bg-muted: var(--bgColor-muted, #161b22);--tc-dot-color: var(--overlay-backdrop-bgColor, rgba(255, 255, 255, .1));--tc-border-color: var(--borderColor-muted, rgba(255, 255, 255, .15));--tc-shadow-rest: var(--shadow-resting-small, 0 1px 3px rgba(0, 0, 0, .3));--tc-shadow-float: var(--shadow-floating-small, 0 4px 12px rgba(0, 0, 0, .4))}body .tc-draggable-inner:not(:active){touch-action:none}.tc-on-translation{transition:transform calc(var(--tc-translation-ms, .25s) * 2) cubic-bezier(.78,.1,.51,.78) var(--tc-translation-ms, .25s)}.tc-drag{position:absolute;width:fit-content}.tc-draggable-inner{position:relative;width:fit-content;height:min-content}.tc-draggable-inner:after{content:"";position:absolute;z-index:-1;width:100%;height:100%;top:0;border-radius:var(--tc-border-radius);box-shadow:var(--tc-shadow-float);opacity:0;transition:opacity .3s ease-in-out}.tc-on-translation .tc-draggable-inner:after,.tc-draggable-inner:active:after,.tc-draggable-inner:hover:after{opacity:0}.tc-on-translation .tc-draggable-inner,.tc-draggable-inner:active,.tc-draggable-inner:hover{outline:none}
@@ -0,0 +1,289 @@
1
+ import { jsx as Q } from "react/jsx-runtime";
2
+ import me, { useRef as le, useState as Y, useEffect as P, Children as Ee, useCallback as Ce } from "react";
3
+ var ae = { dragStart: !0 }, se = { delay: 0, distance: 3 };
4
+ function Ae(e, r = {}) {
5
+ let n, o, { bounds: a, axis: c = "both", gpuAcceleration: l = !0, legacyTranslate: d = !1, transform: m, applyUserSelectHack: w = !0, disabled: N = !1, ignoreMultitouch: v = !1, recomputeBounds: D = ae, grid: f, threshold: y = se, position: b, cancel: q, handle: M, defaultClass: u = "neodrag", defaultClassDragging: x = "neodrag-dragging", defaultClassDragged: I = "neodrag-dragged", defaultPosition: be = { x: 0, y: 0 }, onDragStart: Se, onDrag: we, onDragEnd: De } = r, H = !1, L = !1, de = 0, O = !1, J = !1, R = 0, T = 0, k = 0, B = 0, _ = 0, G = 0, { x: j, y: z } = b ? { x: (b == null ? void 0 : b.x) ?? 0, y: (b == null ? void 0 : b.y) ?? 0 } : be;
6
+ te(j, z);
7
+ let E, $, X, K, Z, fe = "", xe = !!b;
8
+ D = { ...ae, ...D }, y = { ...se, ...y ?? {} };
9
+ let U = /* @__PURE__ */ new Set();
10
+ function ge(t) {
11
+ H && !L && J && O && Z && (L = !0, (function(s) {
12
+ re("neodrag:start", Se, s);
13
+ })(t), A.add(x), w && (fe = ee.userSelect, ee.userSelect = "none"));
14
+ }
15
+ const ee = document.body.style, A = e.classList;
16
+ function te(t = R, s = T) {
17
+ if (!m) {
18
+ if (d) {
19
+ let p = `${+t}px, ${+s}px`;
20
+ return W(e, "transform", l ? `translate3d(${p}, 0)` : `translate(${p})`);
21
+ }
22
+ return W(e, "translate", `${+t}px ${+s}px`);
23
+ }
24
+ const g = m({ offsetX: t, offsetY: s, rootNode: e });
25
+ ie(g) && W(e, "transform", g);
26
+ }
27
+ function re(t, s, g) {
28
+ const p = /* @__PURE__ */ (function(h) {
29
+ return { offsetX: R, offsetY: T, rootNode: e, currentNode: Z, event: h };
30
+ })(g);
31
+ e.dispatchEvent(new CustomEvent(t, { detail: p })), s == null || s(p);
32
+ }
33
+ const V = addEventListener, ne = new AbortController(), oe = { signal: ne.signal, capture: !1 };
34
+ function pe() {
35
+ let t = e.offsetWidth / $.width;
36
+ return isNaN(t) && (t = 1), t;
37
+ }
38
+ return W(e, "touch-action", "none"), V("pointerdown", ((t) => {
39
+ if (N || t.button === 2) return;
40
+ if (U.add(t.pointerId), v && U.size > 1) return t.preventDefault();
41
+ if (D.dragStart && (E = ce(a, e)), ie(M) && ie(q) && M === q) throw new Error("`handle` selector can't be same as `cancel` selector");
42
+ if (A.add(u), X = (function(i, S) {
43
+ if (!i) return [S];
44
+ if (ue(i)) return [i];
45
+ if (Array.isArray(i)) return i;
46
+ const C = S.querySelectorAll(i);
47
+ if (C === null) throw new Error("Selector passed for `handle` option should be child of the element on which the action is applied");
48
+ return Array.from(C.values());
49
+ })(M, e), K = (function(i, S) {
50
+ if (!i) return [];
51
+ if (ue(i)) return [i];
52
+ if (Array.isArray(i)) return i;
53
+ const C = S.querySelectorAll(i);
54
+ if (C === null) throw new Error("Selector passed for `cancel` option should be child of the element on which the action is applied");
55
+ return Array.from(C.values());
56
+ })(q, e), n = /(both|x)/.test(c), o = /(both|y)/.test(c), ye(K, X)) throw new Error("Element being dragged can't be a child of the element on which `cancel` is applied");
57
+ const s = t.composedPath()[0];
58
+ if (!X.some(((i) => {
59
+ var S;
60
+ return i.contains(s) || ((S = i.shadowRoot) == null ? void 0 : S.contains(s));
61
+ })) || ye(K, [s])) return;
62
+ Z = X.length === 1 ? e : X.find(((i) => i.contains(s))), H = !0, de = Date.now(), y.delay || (O = !0), $ = e.getBoundingClientRect();
63
+ const { clientX: g, clientY: p } = t, h = pe();
64
+ n && (k = g - j / h), o && (B = p - z / h), E && (_ = g - $.left, G = p - $.top);
65
+ }), oe), V("pointermove", ((t) => {
66
+ if (!H || v && U.size > 1) return;
67
+ if (!L) {
68
+ if (O || Date.now() - de >= y.delay && (O = !0, ge(t)), !J) {
69
+ const h = t.clientX - k, i = t.clientY - B;
70
+ Math.sqrt(h ** 2 + i ** 2) >= y.distance && (J = !0, ge(t));
71
+ }
72
+ if (!L) return;
73
+ }
74
+ D.drag && (E = ce(a, e)), t.preventDefault(), $ = e.getBoundingClientRect();
75
+ let s = t.clientX, g = t.clientY;
76
+ const p = pe();
77
+ if (E) {
78
+ const h = { left: E.left + _, top: E.top + G, right: E.right + _ - $.width, bottom: E.bottom + G - $.height };
79
+ s = he(s, h.left, h.right), g = he(g, h.top, h.bottom);
80
+ }
81
+ if (Array.isArray(f)) {
82
+ let [h, i] = f;
83
+ if (isNaN(+h) || h < 0) throw new Error("1st argument of `grid` must be a valid positive number");
84
+ if (isNaN(+i) || i < 0) throw new Error("2nd argument of `grid` must be a valid positive number");
85
+ let S = s - k, C = g - B;
86
+ [S, C] = Ne([h / p, i / p], S, C), s = k + S, g = B + C;
87
+ }
88
+ n && (R = Math.round((s - k) * p)), o && (T = Math.round((g - B) * p)), j = R, z = T, re("neodrag", we, t), te();
89
+ }), oe), V("pointerup", ((t) => {
90
+ U.delete(t.pointerId), H && (L && (V("click", ((s) => s.stopPropagation()), { once: !0, signal: ne.signal, capture: !0 }), D.dragEnd && (E = ce(a, e)), A.remove(x), A.add(I), w && (ee.userSelect = fe), re("neodrag:end", De, t), n && (k = R), o && (B = T)), H = !1, L = !1, O = !1, J = !1);
91
+ }), oe), { destroy: () => ne.abort(), update: (t) => {
92
+ var g, p;
93
+ c = t.axis || "both", N = t.disabled ?? !1, v = t.ignoreMultitouch ?? !1, M = t.handle, a = t.bounds, D = t.recomputeBounds ?? ae, q = t.cancel, w = t.applyUserSelectHack ?? !0, f = t.grid, l = t.gpuAcceleration ?? !0, d = t.legacyTranslate ?? !1, m = t.transform, y = { ...se, ...t.threshold ?? {} };
94
+ const s = A.contains(I);
95
+ A.remove(u, I), u = t.defaultClass ?? "neodrag", x = t.defaultClassDragging ?? "neodrag-dragging", I = t.defaultClassDragged ?? "neodrag-dragged", A.add(u), s && A.add(I), xe && (j = R = ((g = t.position) == null ? void 0 : g.x) ?? R, z = T = ((p = t.position) == null ? void 0 : p.y) ?? T, te());
96
+ } };
97
+ }
98
+ var he = (e, r, n) => Math.min(Math.max(e, r), n), ie = (e) => typeof e == "string", Ne = ([e, r], n, o) => {
99
+ const a = (c, l) => l === 0 ? 0 : Math.ceil(c / l) * l;
100
+ return [a(n, e), a(o, r)];
101
+ }, ye = (e, r) => e.some(((n) => r.some(((o) => n.contains(o)))));
102
+ function ce(e, r) {
103
+ if (e === void 0) return;
104
+ if (ue(e)) return e.getBoundingClientRect();
105
+ if (typeof e == "object") {
106
+ const { top: o = 0, left: a = 0, right: c = 0, bottom: l = 0 } = e;
107
+ return { top: o, right: window.innerWidth - c, bottom: window.innerHeight - l, left: a };
108
+ }
109
+ if (e === "parent") return r.parentNode.getBoundingClientRect();
110
+ const n = document.querySelector(e);
111
+ if (n === null) throw new Error("The selector provided for bound doesn't exists in the document.");
112
+ return n.getBoundingClientRect();
113
+ }
114
+ var W = (e, r, n) => e.style.setProperty(r, n), ue = (e) => e instanceof HTMLElement;
115
+ function F(e) {
116
+ return e == null || typeof e == "string" || e instanceof HTMLElement ? e : "current" in e ? e.current : Array.isArray(e) ? e.map(((r) => r instanceof HTMLElement ? r : r.current)) : void 0;
117
+ }
118
+ function qe(e, r = {}) {
119
+ const n = le(), [o, a] = Y(!1), [c, l] = Y();
120
+ let { onDragStart: d, onDrag: m, onDragEnd: w, handle: N, cancel: v } = r, D = F(N), f = F(v);
121
+ function y(u, x) {
122
+ l(u), x == null || x(u);
123
+ }
124
+ function b(u) {
125
+ a(!0), y(u, d);
126
+ }
127
+ function q(u) {
128
+ y(u, m);
129
+ }
130
+ function M(u) {
131
+ a(!1), y(u, w);
132
+ }
133
+ return P((() => {
134
+ if (typeof window > "u") return;
135
+ const u = e.current;
136
+ if (!u) return;
137
+ ({ onDragStart: d, onDrag: m, onDragEnd: w } = r);
138
+ const { update: x, destroy: I } = Ae(u, { ...r, handle: D, cancel: f, onDragStart: b, onDrag: q, onDragEnd: M });
139
+ return n.current = x, I;
140
+ }), []), P((() => {
141
+ var u;
142
+ (u = n.current) == null || u.call(n, { ...r, handle: F(N), cancel: F(v), onDragStart: b, onDrag: q, onDragEnd: M });
143
+ }), [r]), { isDragging: o, dragState: c };
144
+ }
145
+ const Me = (e) => {
146
+ if (!e) return null;
147
+ let r = null;
148
+ const n = (o) => {
149
+ if (o.props && o.props.id) {
150
+ r = o.props.id;
151
+ return;
152
+ }
153
+ o.props && o.props.children && me.Children.forEach(o.props.children, n);
154
+ };
155
+ return n(e), r;
156
+ };
157
+ function Ie(e) {
158
+ let r = 5381;
159
+ for (let n = 0; n < e.length; n++)
160
+ r = (r << 5) + r + e.charCodeAt(n) >>> 0;
161
+ return r.toString(16).padStart(8, "0");
162
+ }
163
+ function ve(e) {
164
+ var c;
165
+ if (e == null || typeof e == "boolean") return "";
166
+ if (typeof e == "string" || typeof e == "number") return "#text";
167
+ const r = e.type, n = typeof r == "function" ? r.displayName || r.name || "Anonymous" : typeof r == "string" ? r : "Fragment", o = (c = e.props) == null ? void 0 : c.children;
168
+ if (o == null) return n;
169
+ const a = [];
170
+ return me.Children.forEach(o, (l) => {
171
+ const d = ve(l);
172
+ d && a.push(d);
173
+ }), a.length ? `${n}(${a.join(",")})` : n;
174
+ }
175
+ const Re = (e, r) => {
176
+ const n = ve(e);
177
+ return `tc-${Ie(n)}-${r}`;
178
+ }, Te = (e) => {
179
+ try {
180
+ return (JSON.parse(localStorage.getItem("tiny-canvas-queue")) || []).reduce((o, a) => (o[a.id] = { id: a.id, x: a.x, y: a.y }, o), {})[e] || { x: 0, y: 0 };
181
+ } catch (r) {
182
+ return console.error("Error getting saved coordinates:", r), { x: 0, y: 0 };
183
+ }
184
+ }, $e = () => {
185
+ try {
186
+ localStorage.getItem("tiny-canvas-queue") || localStorage.setItem("tiny-canvas-queue", JSON.stringify([]));
187
+ } catch (e) {
188
+ console.error("LocalStorage is not available:", e);
189
+ }
190
+ }, Le = (e, r, n) => {
191
+ try {
192
+ const o = JSON.parse(localStorage.getItem("tiny-canvas-queue")) || [], a = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"), c = { id: e, x: r, y: n, time: a }, l = o.findIndex((d) => d.id === e);
193
+ l >= 0 ? o[l] = c : o.push(c), localStorage.setItem("tiny-canvas-queue", JSON.stringify(o));
194
+ } catch (o) {
195
+ console.error("Error saving drag position:", o);
196
+ }
197
+ }, ke = 250;
198
+ function Be({ children: e, gridSize: r, dragId: n }) {
199
+ const o = le(null), a = le(Te(n)).current, [c, l] = Y(!1), [d, m] = Y({ x: 0, y: 0 }), [w, N] = Y(
200
+ Math.random() < 0.5 ? -0.5 : 0.5
201
+ );
202
+ P(() => {
203
+ const f = o.current;
204
+ if (f && n && a && (a.x !== 0 || a.y !== 0)) {
205
+ f.classList.add("tc-on-translation"), l(!0);
206
+ const y = setTimeout(() => {
207
+ f.classList.remove("tc-on-translation"), l(!1);
208
+ }, ke * 4);
209
+ return () => clearTimeout(y);
210
+ }
211
+ }, []), P(() => {
212
+ $e(), o.current && a && m({ x: a.x, y: a.y });
213
+ }, [a]);
214
+ const { isDragging: v } = qe(o, {
215
+ axis: "both",
216
+ bounds: "body",
217
+ threshold: { delay: 50, distance: 30 },
218
+ defaultClass: "tc-drag",
219
+ defaultClassDragging: "tc-on",
220
+ defaultClassDragged: "tc-off",
221
+ applyUserSelectHack: !0,
222
+ position: { x: d.x, y: d.y },
223
+ onDrag: ({ offsetX: f, offsetY: y }) => m({ x: f, y }),
224
+ onDragEnd: (f) => {
225
+ m({ x: f.offsetX, y: f.offsetY }), n !== null && Le(n, f.offsetX, f.offsetY);
226
+ }
227
+ }), D = v || c ? `${w}deg` : "0deg";
228
+ return P(() => {
229
+ N(Math.random() < 0.5 ? -0.5 : 0.5);
230
+ }, [v]), /* @__PURE__ */ Q(
231
+ "article",
232
+ {
233
+ ref: o,
234
+ style: { cursor: v ? "grabbing" : "grab" },
235
+ children: /* @__PURE__ */ Q(
236
+ "div",
237
+ {
238
+ className: "tc-draggable-inner",
239
+ style: {
240
+ transform: v || c ? `rotate(${D})` : void 0,
241
+ transition: "transform ease-in-out 150ms"
242
+ },
243
+ children: e
244
+ }
245
+ )
246
+ }
247
+ );
248
+ }
249
+ function Xe({
250
+ children: e,
251
+ centered: r = !0,
252
+ dotted: n = !1,
253
+ grid: o = !1,
254
+ gridSize: a,
255
+ colorMode: c = "auto"
256
+ }) {
257
+ return /* @__PURE__ */ Q(
258
+ "main",
259
+ {
260
+ className: "tc-canvas",
261
+ "data-dotted": n || o || void 0,
262
+ "data-color-mode": c !== "auto" ? c : void 0,
263
+ children: Ee.map(e, (d, m) => {
264
+ const w = Me(d) ?? Re(d, m);
265
+ return /* @__PURE__ */ Q(Be, { gridSize: a, dragId: w, children: d }, m);
266
+ })
267
+ }
268
+ );
269
+ }
270
+ function Ye({ reload: e = !1 } = {}) {
271
+ return Ce(() => {
272
+ try {
273
+ localStorage.removeItem("tiny-canvas-queue");
274
+ } catch (r) {
275
+ console.error("Error clearing canvas state:", r);
276
+ }
277
+ e && window.location.reload();
278
+ }, [e]);
279
+ }
280
+ export {
281
+ Xe as Canvas,
282
+ Be as Draggable,
283
+ Me as findDragId,
284
+ Re as generateDragId,
285
+ Te as getQueue,
286
+ $e as refreshStorage,
287
+ Le as saveDrag,
288
+ Ye as useResetCanvas
289
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@dfosco/tiny-canvas",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "A lightweight React canvas with draggable elements and persistent positions",
7
+ "main": "./src/index.js",
8
+ "module": "./src/index.js",
9
+ "types": "./src/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./src/index.js",
13
+ "types": "./src/index.d.ts"
14
+ },
15
+ "./style.css": "./src/style.css"
16
+ },
17
+ "publishConfig": {
18
+ "main": "./dist/tiny-canvas.js",
19
+ "module": "./dist/tiny-canvas.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/tiny-canvas.js",
23
+ "types": "./src/index.d.ts"
24
+ },
25
+ "./style.css": "./dist/tiny-canvas.css"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "src/index.d.ts"
31
+ ],
32
+ "scripts": {
33
+ "build": "vite build"
34
+ },
35
+ "peerDependencies": {
36
+ "react": "^18.0.0 || ^19.0.0",
37
+ "react-dom": "^18.0.0 || ^19.0.0"
38
+ },
39
+ "dependencies": {
40
+ "@neodrag/react": "^2.3.0"
41
+ },
42
+ "devDependencies": {
43
+ "@vitejs/plugin-react": "^4.3.4",
44
+ "react": "^18.3.1",
45
+ "react-dom": "^18.3.1",
46
+ "vite": "^6.0.5"
47
+ }
48
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ export interface CanvasProps {
4
+ children?: ReactNode;
5
+ /** Enable centered layout. Default: true */
6
+ centered?: boolean;
7
+ /** Show dot background. Default: false */
8
+ dotted?: boolean;
9
+ /** Enable grid snapping (also shows dots). Default: false */
10
+ grid?: boolean;
11
+ /** Grid snap size in pixels when grid is enabled. Default: 18 */
12
+ gridSize?: number;
13
+ /** Color mode: 'auto' follows system preference, 'light' or 'dark' to override. Default: 'auto' */
14
+ colorMode?: 'auto' | 'light' | 'dark';
15
+ }
16
+
17
+ export declare function Canvas(props: CanvasProps): JSX.Element;
18
+
19
+ export interface DraggableProps {
20
+ children?: ReactNode;
21
+ /** Grid snap size in pixels. When undefined, no snapping is applied. */
22
+ gridSize?: number;
23
+ /** Stable identifier for persisting drag position. Auto-generated by Canvas if not provided. */
24
+ dragId?: string;
25
+ }
26
+
27
+ export declare function Draggable(props: DraggableProps): JSX.Element;
28
+
29
+ export interface UseResetCanvasOptions {
30
+ /** Reload the page after clearing state. Default: false */
31
+ reload?: boolean;
32
+ }
33
+
34
+ /** Returns a function that clears all saved canvas positions from localStorage. */
35
+ export declare function useResetCanvas(options?: UseResetCanvasOptions): () => void;
36
+
37
+ export declare function findDragId(children: ReactNode): string | null;
38
+ export declare function generateDragId(element: ReactNode, index: number): string;
39
+ export declare function getQueue(dragId: string): { x: number; y: number };
40
+ export declare function refreshStorage(): void;
41
+ export declare function saveDrag(dragId: string, x: number, y: number): void;