@snapgridjs/react 0.1.0 → 0.3.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 +45 -9
- package/dist/index.cjs +564 -378
- package/dist/index.d.cts +156 -95
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +156 -95
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +563 -380
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,222 @@
|
|
|
1
|
-
import { DragDropProvider, useDragDropMonitor, useDraggable, useDraggable as useDraggable$1, useDroppable, useDroppable as useDroppable$1 } from "@dnd-kit/react";
|
|
2
|
-
import { beginDrag, beginReceive, beginResize, bottom, calcGridItemPosition, calcXY, commitLayout, defaultGridConfig, dragResize, dragTo, findOrGenerateResponsiveLayout, getBreakpointFromWidth, getColsFromBreakpoint, getCompactor, horizontalCompactor, noCompactor, nudge, removeItemWithCompactor, toPositionParams, verticalCompactor, verticalCompactor as verticalCompactor$1 } from "@snapgridjs/core";
|
|
3
|
-
import { Children, createContext, isValidElement, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { DragDropProvider, DragOverlay, useDragDropManager, useDragDropMonitor, useDraggable, useDraggable as useDraggable$1, useDroppable, useDroppable as useDroppable$1, useInstance } from "@dnd-kit/react";
|
|
2
|
+
import { beginDrag, beginReceive, beginResize, bottom, calcGridColWidth, calcGridItemPosition, calcXY, commitLayout, defaultGridConfig, dragResize, dragTo, findOrGenerateResponsiveLayout, getBreakpointFromWidth, getColsFromBreakpoint, getCompactor, horizontalCompactor, noCompactor, nudge, removeItemWithCompactor, toPositionParams, verticalCompactor, verticalCompactor as verticalCompactor$1 } from "@snapgridjs/core";
|
|
3
|
+
import { Children, createContext, isValidElement, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { Modifier } from "@dnd-kit/abstract";
|
|
4
5
|
import { Feedback, Feedback as Feedback$1, KeyboardSensor, KeyboardSensor as KeyboardSensor$1, PointerActivationConstraints, PointerSensor, PointerSensor as PointerSensor$1 } from "@dnd-kit/dom";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return runtime;
|
|
6
|
+
import { isKeyboardEvent } from "@dnd-kit/dom/utilities";
|
|
7
|
+
import { useSortable } from "@dnd-kit/react/sortable";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
//#region src/controller/GridController.ts
|
|
10
|
+
function sameItem(a, b) {
|
|
11
|
+
if (a === b) return true;
|
|
12
|
+
if (!a || !b) return false;
|
|
13
|
+
return a.i === b.i && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Live per-grid drag/resize state as a plain observable: the provider writes
|
|
17
|
+
* (`setSession`/`setKeyboard`/`setCommitted`), hooks subscribe to just their own
|
|
18
|
+
* slice via `useSyncExternalStore`. Value-cached snapshots mean a drag re-renders
|
|
19
|
+
* only the tiles whose slice changed, not the whole subtree (the old
|
|
20
|
+
* context-value model re-rendered every tile every frame).
|
|
21
|
+
*/
|
|
22
|
+
var GridController = class {
|
|
23
|
+
id;
|
|
24
|
+
#committed;
|
|
25
|
+
#session = null;
|
|
26
|
+
#keyboard = false;
|
|
27
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
28
|
+
config = null;
|
|
29
|
+
#itemCache = /* @__PURE__ */ new Map();
|
|
30
|
+
#resizeCache = /* @__PURE__ */ new Map();
|
|
31
|
+
#placeholderCache = null;
|
|
32
|
+
#renderedMap = null;
|
|
33
|
+
#renderedMapSource = null;
|
|
34
|
+
#indexById = /* @__PURE__ */ new Map();
|
|
35
|
+
#nextIndex = 0;
|
|
36
|
+
/** The dnd-kit manager this grid is registered with (set by useInstance). */
|
|
37
|
+
manager;
|
|
38
|
+
constructor(id, committed = [], manager) {
|
|
39
|
+
this.id = id;
|
|
40
|
+
this.#committed = committed;
|
|
41
|
+
this.manager = manager;
|
|
42
|
+
}
|
|
43
|
+
/** Replace the per-grid config (called by the container host during render). */
|
|
44
|
+
setConfig(config) {
|
|
45
|
+
this.config = config;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Re-point this grid's id. The container host syncs it (during render, before
|
|
49
|
+
* the droppable/group read it) when the controlled `id` prop changes, so the
|
|
50
|
+
* returned `group`, the droppable id, and the registry key never drift apart.
|
|
51
|
+
*/
|
|
52
|
+
setId(id) {
|
|
53
|
+
this.id = id;
|
|
54
|
+
}
|
|
55
|
+
register = () => {};
|
|
56
|
+
subscribe = (listener) => {
|
|
57
|
+
this.#listeners.add(listener);
|
|
58
|
+
return () => {
|
|
59
|
+
this.#listeners.delete(listener);
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
#emit() {
|
|
63
|
+
for (const listener of this.#listeners) listener();
|
|
64
|
+
}
|
|
65
|
+
/** The layout currently shown: the drag preview while dragging, else committed. */
|
|
66
|
+
#rendered() {
|
|
67
|
+
return this.#session ? this.#session.preview : this.#committed;
|
|
68
|
+
}
|
|
69
|
+
#renderedById() {
|
|
70
|
+
const rendered = this.#rendered();
|
|
71
|
+
if (this.#renderedMapSource !== rendered) {
|
|
72
|
+
this.#renderedMap = new Map(rendered.map((it) => [it.i, it]));
|
|
73
|
+
this.#renderedMapSource = rendered;
|
|
74
|
+
}
|
|
75
|
+
return this.#renderedMap;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Sync the committed layout from the controlled `layout` prop. Called during
|
|
79
|
+
* the provider's render, so it must NOT notify — emitting here would update
|
|
80
|
+
* subscribed GridItems mid-render (a React "setState while rendering" error).
|
|
81
|
+
* No notify is needed: a `layout` prop change already re-renders the whole
|
|
82
|
+
* provider subtree, so every GridItem re-reads its snapshot on that pass.
|
|
83
|
+
*/
|
|
84
|
+
setCommitted(layout) {
|
|
85
|
+
if (this.#committed === layout) return;
|
|
86
|
+
this.#committed = layout;
|
|
87
|
+
const present = new Set(layout.map((it) => it.i));
|
|
88
|
+
for (const id of this.#indexById.keys()) if (!present.has(id)) this.#indexById.delete(id);
|
|
89
|
+
for (const id of this.#itemCache.keys()) if (!present.has(id)) this.#itemCache.delete(id);
|
|
90
|
+
for (const id of this.#resizeCache.keys()) if (!present.has(id)) this.#resizeCache.delete(id);
|
|
91
|
+
}
|
|
92
|
+
setSession(next) {
|
|
93
|
+
this.#session = next;
|
|
94
|
+
this.#emit();
|
|
95
|
+
}
|
|
96
|
+
getSession() {
|
|
97
|
+
return this.#session;
|
|
98
|
+
}
|
|
99
|
+
/** Record whether the active drag is keyboard-driven (drives `hidden`). */
|
|
100
|
+
setKeyboard(value) {
|
|
101
|
+
if (this.#keyboard === value) return;
|
|
102
|
+
this.#keyboard = value;
|
|
103
|
+
this.#emit();
|
|
104
|
+
}
|
|
105
|
+
itemSnapshot = (id) => {
|
|
106
|
+
const item = this.#renderedById().get(id);
|
|
107
|
+
const isDragging = this.#session?.activeId === id;
|
|
108
|
+
const hidden = isDragging && this.#session?.kind === "move" && !this.#keyboard;
|
|
109
|
+
const prev = this.#itemCache.get(id);
|
|
110
|
+
if (prev && prev.isDragging === isDragging && prev.hidden === hidden && sameItem(prev.item, item)) return prev;
|
|
111
|
+
const snap = {
|
|
112
|
+
item,
|
|
113
|
+
isDragging,
|
|
114
|
+
hidden
|
|
115
|
+
};
|
|
116
|
+
this.#itemCache.set(id, snap);
|
|
117
|
+
return snap;
|
|
118
|
+
};
|
|
119
|
+
placeholderSnapshot = () => {
|
|
120
|
+
const next = this.#session?.placeholder ?? null;
|
|
121
|
+
if (sameItem(this.#placeholderCache ?? void 0, next ?? void 0)) return this.#placeholderCache;
|
|
122
|
+
this.#placeholderCache = next;
|
|
123
|
+
return next;
|
|
124
|
+
};
|
|
125
|
+
resizeSnapshot = (itemId) => {
|
|
126
|
+
const isResizing = this.#session?.kind === "resize" && this.#session.activeId === itemId;
|
|
127
|
+
const prev = this.#resizeCache.get(itemId);
|
|
128
|
+
if (prev && prev.isResizing === isResizing) return prev;
|
|
129
|
+
const snap = { isResizing };
|
|
130
|
+
this.#resizeCache.set(itemId, snap);
|
|
131
|
+
return snap;
|
|
132
|
+
};
|
|
133
|
+
renderedSnapshot = () => this.#rendered();
|
|
134
|
+
/** A stable index for `id` (see {@link GridController.#indexById}). */
|
|
135
|
+
itemIndex(id) {
|
|
136
|
+
let i = this.#indexById.get(id);
|
|
137
|
+
if (i === void 0) {
|
|
138
|
+
i = this.#nextIndex++;
|
|
139
|
+
this.#indexById.set(id, i);
|
|
140
|
+
}
|
|
141
|
+
return i;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
15
144
|
//#endregion
|
|
16
|
-
//#region src/
|
|
145
|
+
//#region src/controller/registry.ts
|
|
17
146
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
147
|
+
* Resolves a grid's {@link GridController} by its id, scoped to the dnd-kit
|
|
148
|
+
* manager the grid is registered with. A container registers its controller
|
|
149
|
+
* here (during render, so child items resolve it on their first render); items
|
|
150
|
+
* look it up by their `group` (= the grid id). Replaces the old geometry
|
|
151
|
+
* `GridRegistry` — which grid the pointer is over now comes from the collision
|
|
152
|
+
* target, so the registry's only job is id → controller resolution.
|
|
153
|
+
*
|
|
154
|
+
* Keyed by manager so two apps (or two providers) never collide, and grids in
|
|
155
|
+
* one provider share a map (the cross-grid seam).
|
|
21
156
|
*/
|
|
157
|
+
const byManager = /* @__PURE__ */ new WeakMap();
|
|
158
|
+
const noManager = /* @__PURE__ */ new Map();
|
|
159
|
+
function mapFor(manager) {
|
|
160
|
+
if (!manager) return noManager;
|
|
161
|
+
let map = byManager.get(manager);
|
|
162
|
+
if (!map) {
|
|
163
|
+
map = /* @__PURE__ */ new Map();
|
|
164
|
+
byManager.set(manager, map);
|
|
165
|
+
}
|
|
166
|
+
return map;
|
|
167
|
+
}
|
|
168
|
+
/** Register a controller under `id` for `manager`. Returns an unregister fn. */
|
|
169
|
+
function registerController(manager, id, controller) {
|
|
170
|
+
const map = mapFor(manager);
|
|
171
|
+
map.set(id, controller);
|
|
172
|
+
return () => {
|
|
173
|
+
if (map.get(id) === controller) map.delete(id);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** The controller registered under `id` for `manager`, or undefined. */
|
|
177
|
+
function getController(manager, id) {
|
|
178
|
+
return mapFor(manager).get(id);
|
|
179
|
+
}
|
|
180
|
+
const grabOffsets = /* @__PURE__ */ new WeakMap();
|
|
181
|
+
const noManagerGrab = { current: null };
|
|
182
|
+
function setGrabOffset(manager, offset) {
|
|
183
|
+
if (!manager) {
|
|
184
|
+
noManagerGrab.current = offset;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (offset) grabOffsets.set(manager, offset);
|
|
188
|
+
else grabOffsets.delete(manager);
|
|
189
|
+
}
|
|
190
|
+
function getGrabOffset(manager) {
|
|
191
|
+
return (manager ? grabOffsets.get(manager) : noManagerGrab.current) ?? {
|
|
192
|
+
x: 0,
|
|
193
|
+
y: 0
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/dnd/dragFlow.ts
|
|
198
|
+
/**
|
|
199
|
+
* Pure decision helpers for the drag interaction so the tricky bits — grab-offset
|
|
200
|
+
* cell mapping, the cross-grid drop lifecycle, and external-drop acceptance — are
|
|
201
|
+
* unit-testable without a DOM or dnd-kit.
|
|
202
|
+
*/
|
|
203
|
+
/** Read snapgrid's payload off a dnd-kit drag source. */
|
|
204
|
+
function dragData(event) {
|
|
205
|
+
return (event.operation.source?.data)?.snapGrid;
|
|
206
|
+
}
|
|
207
|
+
/** Size/id spec for an external (non-grid) draggable the grid may accept, or null. */
|
|
208
|
+
function externalDropSpec(source, dropConfig) {
|
|
209
|
+
if (!dropConfig?.enabled || !source) return null;
|
|
210
|
+
const data = source.data;
|
|
211
|
+
if (data?.snapGrid) return null;
|
|
212
|
+
if (dropConfig.accept && !dropConfig.accept(source)) return null;
|
|
213
|
+
const spec = data?.snapGridDrop;
|
|
214
|
+
return {
|
|
215
|
+
i: spec?.i,
|
|
216
|
+
w: spec?.w ?? dropConfig.defaultItem?.w ?? 1,
|
|
217
|
+
h: spec?.h ?? dropConfig.defaultItem?.h ?? 1
|
|
218
|
+
};
|
|
219
|
+
}
|
|
22
220
|
/**
|
|
23
221
|
* Map a client-space pointer to a grid cell, accounting for where *within* the
|
|
24
222
|
* dragged tile the pointer grabbed it. Subtracting the grab offset means the
|
|
@@ -29,6 +227,29 @@ function useGridRuntime() {
|
|
|
29
227
|
function receiveCell(pointer, gridRect, grabOffset, w, h, pp) {
|
|
30
228
|
return calcXY(pp, pointer.y - grabOffset.y - gridRect.top, pointer.x - grabOffset.x - gridRect.left, w, h);
|
|
31
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Map a keyboard event key to a one-cell grid step while a keyboard drag is
|
|
232
|
+
* active, or null for keys snapgrid doesn't own — Enter/Space (drop) and Escape
|
|
233
|
+
* (cancel) fall through to dnd-kit's KeyboardSensor.
|
|
234
|
+
*/
|
|
235
|
+
function arrowStep(key) {
|
|
236
|
+
switch (key) {
|
|
237
|
+
case "ArrowLeft": return [-1, 0];
|
|
238
|
+
case "ArrowRight": return [1, 0];
|
|
239
|
+
case "ArrowUp": return [0, -1];
|
|
240
|
+
case "ArrowDown": return [0, 1];
|
|
241
|
+
default: return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Which grid a drop commits to, as fed to {@link classifyDrop} as `dest`. A
|
|
246
|
+
* keyboard drag has no pointer, so it can only ever land in its own grid; a
|
|
247
|
+
* pointer drag lands in whichever grid the collision observer resolved (or none).
|
|
248
|
+
*/
|
|
249
|
+
function dropDestination(opts) {
|
|
250
|
+
if (opts.keyboard) return opts.myId;
|
|
251
|
+
return opts.targetId != null ? String(opts.targetId) : null;
|
|
252
|
+
}
|
|
32
253
|
/** Pure classification of a drag end. See {@link DropAction}. */
|
|
33
254
|
function classifyDrop(s) {
|
|
34
255
|
if (s.canceled) {
|
|
@@ -46,37 +267,27 @@ function classifyDrop(s) {
|
|
|
46
267
|
return "noop";
|
|
47
268
|
}
|
|
48
269
|
//#endregion
|
|
49
|
-
//#region src/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
getGrabOffset() {
|
|
71
|
-
return grabOffset ?? {
|
|
72
|
-
x: 0,
|
|
73
|
-
y: 0
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/** Non-null when grids are wrapped in a `<SnapGridGroup>` (shared cross-grid registry). */
|
|
79
|
-
const SnapGridGroupContext = createContext(null);
|
|
270
|
+
//#region src/dnd/snapToGrid.ts
|
|
271
|
+
/**
|
|
272
|
+
* Quantizes the dragged item's transform to whole grid cells, so the floating
|
|
273
|
+
* <DragOverlay> clone jumps cell-to-cell in lockstep with the (always-snapped)
|
|
274
|
+
* placeholder instead of tracking the pointer smoothly. Applied on the item
|
|
275
|
+
* draggable; a no-op unless `dragConfig.snapToGrid` is set.
|
|
276
|
+
*/
|
|
277
|
+
var SnapToGrid = class extends Modifier {
|
|
278
|
+
apply({ transform }) {
|
|
279
|
+
const opts = this.options;
|
|
280
|
+
if (!opts?.isEnabled()) return transform;
|
|
281
|
+
const pp = opts.getPositionParams();
|
|
282
|
+
const colStep = calcGridColWidth(pp) + pp.margin[0];
|
|
283
|
+
const rowStep = pp.rowHeight + pp.margin[1];
|
|
284
|
+
if (colStep <= 0 || rowStep <= 0) return transform;
|
|
285
|
+
return {
|
|
286
|
+
x: Math.round(transform.x / colStep) * colStep,
|
|
287
|
+
y: Math.round(transform.y / rowStep) * rowStep
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
};
|
|
80
291
|
//#endregion
|
|
81
292
|
//#region src/hooks/dndShared.ts
|
|
82
293
|
/** Marker attribute placed on resize-handle elements. */
|
|
@@ -108,52 +319,33 @@ function buildItemSensors(threshold, getDragConfig) {
|
|
|
108
319
|
}), KeyboardSensor$1];
|
|
109
320
|
}
|
|
110
321
|
//#endregion
|
|
111
|
-
//#region src/
|
|
322
|
+
//#region src/hooks/useGridController.ts
|
|
112
323
|
const DEFAULT_HANDLES = ["se"];
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return (event.operation.source?.data)?.snapGrid;
|
|
116
|
-
}
|
|
117
|
-
/** Size/id spec for an external (non-grid) draggable the grid may accept, or null. */
|
|
118
|
-
function externalDropSpec(source, dropConfig) {
|
|
119
|
-
if (!dropConfig?.enabled || !source) return null;
|
|
120
|
-
const data = source.data;
|
|
121
|
-
if (data?.snapGrid) return null;
|
|
122
|
-
if (dropConfig.accept && !dropConfig.accept(source)) return null;
|
|
123
|
-
const spec = data?.snapGridDrop;
|
|
124
|
-
return {
|
|
125
|
-
i: spec?.i,
|
|
126
|
-
w: spec?.w ?? dropConfig.defaultItem?.w ?? 1,
|
|
127
|
-
h: spec?.h ?? dropConfig.defaultItem?.h ?? 1
|
|
128
|
-
};
|
|
324
|
+
function itemGateOpen(flag, isStatic) {
|
|
325
|
+
return isStatic ? flag === true : flag ?? true;
|
|
129
326
|
}
|
|
130
327
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
328
|
+
* The grid's brain: owns the {@link GridController}, runs the dnd-kit drag/resize
|
|
329
|
+
* monitor for this grid, and writes per-grid config to the controller each render.
|
|
330
|
+
* Created by {@link useGridContainer}; items resolve the same controller by their
|
|
331
|
+
* `group` (= this grid's id) from the per-manager registry. Consumes the ambient
|
|
332
|
+
* `DragDropProvider` — it does not mint one.
|
|
135
333
|
*/
|
|
136
|
-
function
|
|
137
|
-
const groupRegistry = useContext(SnapGridGroupContext);
|
|
138
|
-
const runtime = /* @__PURE__ */ jsx(SnapGridRuntime, {
|
|
139
|
-
groupRegistry,
|
|
140
|
-
...props
|
|
141
|
-
});
|
|
142
|
-
return groupRegistry ? runtime : /* @__PURE__ */ jsx(DragDropProvider, { children: runtime });
|
|
143
|
-
}
|
|
144
|
-
function SnapGridRuntime(props) {
|
|
334
|
+
function useGridController(opts) {
|
|
145
335
|
const autoId = useId();
|
|
146
|
-
const containerId =
|
|
336
|
+
const containerId = opts.id ?? autoId;
|
|
147
337
|
const gridConfig = useMemo(() => ({
|
|
148
338
|
...defaultGridConfig,
|
|
149
|
-
...
|
|
150
|
-
}), [
|
|
151
|
-
const positionParams = useMemo(() => toPositionParams(gridConfig,
|
|
152
|
-
const compactor =
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
339
|
+
...opts.gridConfig
|
|
340
|
+
}), [opts.gridConfig]);
|
|
341
|
+
const positionParams = useMemo(() => toPositionParams(gridConfig, opts.width), [gridConfig, opts.width]);
|
|
342
|
+
const compactor = opts.compactor ?? verticalCompactor$1;
|
|
343
|
+
const manager = useDragDropManager();
|
|
344
|
+
const controller = useInstance((m) => new GridController(containerId, opts.layout, m ?? void 0));
|
|
345
|
+
controller.setCommitted(opts.layout);
|
|
346
|
+
if (controller.id !== containerId) controller.setId(containerId);
|
|
347
|
+
const optsRef = useRef(opts);
|
|
348
|
+
optsRef.current = opts;
|
|
157
349
|
const ppRef = useRef(positionParams);
|
|
158
350
|
ppRef.current = positionParams;
|
|
159
351
|
const gridRef = useRef(gridConfig);
|
|
@@ -162,39 +354,44 @@ function SnapGridRuntime(props) {
|
|
|
162
354
|
compactorRef.current = compactor;
|
|
163
355
|
const containerIdRef = useRef(containerId);
|
|
164
356
|
containerIdRef.current = containerId;
|
|
357
|
+
const managerRef = useRef(manager);
|
|
358
|
+
managerRef.current = manager;
|
|
165
359
|
const sessionRef = useRef(null);
|
|
166
360
|
const containerElRef = useRef(null);
|
|
167
361
|
const keyboardRef = useRef(false);
|
|
168
362
|
const dropSpecRef = useRef(null);
|
|
169
363
|
const dropCounterRef = useRef(0);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const committedById = useMemo(() => new Map(
|
|
364
|
+
registerController(manager, containerId, controller);
|
|
365
|
+
useEffect(() => registerController(manager, containerId, controller), [
|
|
366
|
+
manager,
|
|
367
|
+
containerId,
|
|
368
|
+
controller
|
|
369
|
+
]);
|
|
370
|
+
const committedById = useMemo(() => new Map(opts.layout.map((it) => [it.i, it])), [opts.layout]);
|
|
177
371
|
const committedByIdRef = useRef(committedById);
|
|
178
372
|
committedByIdRef.current = committedById;
|
|
179
373
|
const setSessionBoth = useCallback((next) => {
|
|
180
374
|
sessionRef.current = next;
|
|
181
|
-
setSession(next);
|
|
182
|
-
}, []);
|
|
375
|
+
controller.setSession(next);
|
|
376
|
+
}, [controller]);
|
|
377
|
+
const setKeyboard = useCallback((value) => {
|
|
378
|
+
keyboardRef.current = value;
|
|
379
|
+
controller.setKeyboard(value);
|
|
380
|
+
}, [controller]);
|
|
183
381
|
const setContainerElement = useCallback((element) => {
|
|
184
382
|
containerElRef.current = element;
|
|
185
383
|
}, []);
|
|
186
384
|
/**
|
|
187
|
-
* Is THIS grid the
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* grid wins when grids overlap (`gridAt` returns a single first match).
|
|
385
|
+
* Is THIS grid the drop target dnd-kit's collision observer resolved? Both the
|
|
386
|
+
* move-phase preview and the drop-phase commit read `operation.target`, so they
|
|
387
|
+
* always agree on which grid wins (one oracle), including when grids overlap.
|
|
191
388
|
*/
|
|
192
|
-
const overMe = useCallback((
|
|
389
|
+
const overMe = useCallback((target) => target?.id === containerIdRef.current, []);
|
|
193
390
|
/** Map a client-space pointer to a grid cell within THIS grid (see {@link receiveCell}). */
|
|
194
391
|
const cellFromPointer = useCallback((p, item) => {
|
|
195
392
|
const el = containerElRef.current;
|
|
196
393
|
if (!el) return null;
|
|
197
|
-
return receiveCell(p, el.getBoundingClientRect(),
|
|
394
|
+
return receiveCell(p, el.getBoundingClientRect(), getGrabOffset(managerRef.current), item.w, item.h, ppRef.current);
|
|
198
395
|
}, []);
|
|
199
396
|
const ctx = useCallback(() => ({
|
|
200
397
|
positionParams: ppRef.current,
|
|
@@ -202,10 +399,10 @@ function SnapGridRuntime(props) {
|
|
|
202
399
|
cols: gridRef.current.cols
|
|
203
400
|
}), []);
|
|
204
401
|
const handleDragStart = useCallback((event) => {
|
|
205
|
-
|
|
402
|
+
setKeyboard(false);
|
|
206
403
|
const data = dragData(event);
|
|
207
404
|
if (!data) {
|
|
208
|
-
const spec = externalDropSpec(event.operation.source,
|
|
405
|
+
const spec = externalDropSpec(event.operation.source, optsRef.current.dropConfig);
|
|
209
406
|
if (spec) {
|
|
210
407
|
dropCounterRef.current += 1;
|
|
211
408
|
dropSpecRef.current = {
|
|
@@ -217,7 +414,7 @@ function SnapGridRuntime(props) {
|
|
|
217
414
|
return;
|
|
218
415
|
}
|
|
219
416
|
dropSpecRef.current = null;
|
|
220
|
-
const layout =
|
|
417
|
+
const layout = optsRef.current.layout;
|
|
221
418
|
const item = layout.find((it) => it.i === data.itemId);
|
|
222
419
|
const p = event.operation.position.current;
|
|
223
420
|
const pointer = {
|
|
@@ -231,11 +428,11 @@ function SnapGridRuntime(props) {
|
|
|
231
428
|
rect: calcGridItemPosition(ppRef.current, item.x, item.y, item.w, item.h),
|
|
232
429
|
pointer
|
|
233
430
|
}, data.handle));
|
|
234
|
-
|
|
431
|
+
optsRef.current.onResizeStart?.(layout, item, item, item, event.operation.activatorEvent, null);
|
|
235
432
|
return;
|
|
236
433
|
}
|
|
237
434
|
if (item) {
|
|
238
|
-
|
|
435
|
+
setKeyboard(event.operation.activatorEvent instanceof KeyboardEvent);
|
|
239
436
|
const rect = calcGridItemPosition(ppRef.current, item.x, item.y, item.w, item.h);
|
|
240
437
|
setSessionBoth(beginDrag(layout, {
|
|
241
438
|
item,
|
|
@@ -244,22 +441,13 @@ function SnapGridRuntime(props) {
|
|
|
244
441
|
pointer
|
|
245
442
|
}));
|
|
246
443
|
const cr = (event.operation.source?.element)?.getBoundingClientRect();
|
|
247
|
-
if (cr) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
setOverlay({
|
|
253
|
-
item,
|
|
254
|
-
left: cr.left,
|
|
255
|
-
top: cr.top,
|
|
256
|
-
width: cr.width,
|
|
257
|
-
height: cr.height
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
propsRef.current.onDragStart?.(layout, item, item, item, event.operation.activatorEvent, null);
|
|
444
|
+
if (cr) setGrabOffset(managerRef.current, {
|
|
445
|
+
x: pointer.x - cr.left,
|
|
446
|
+
y: pointer.y - cr.top
|
|
447
|
+
});
|
|
448
|
+
optsRef.current.onDragStart?.(layout, item, item, item, event.operation.activatorEvent, null);
|
|
261
449
|
}
|
|
262
|
-
}, [setSessionBoth]);
|
|
450
|
+
}, [setSessionBoth, setKeyboard]);
|
|
263
451
|
const handleDragMove = useCallback((event) => {
|
|
264
452
|
if (keyboardRef.current) return;
|
|
265
453
|
const p = event.operation.position.current;
|
|
@@ -271,13 +459,14 @@ function SnapGridRuntime(props) {
|
|
|
271
459
|
if (current?.kind === "resize") {
|
|
272
460
|
const next = dragResize(current, pointer, ctx());
|
|
273
461
|
setSessionBoth(next);
|
|
274
|
-
|
|
462
|
+
optsRef.current.onResize?.(next.preview, next.anchor.item, next.placeholder, next.placeholder, event.operation.activatorEvent, null);
|
|
275
463
|
return;
|
|
276
464
|
}
|
|
465
|
+
const target = event.operation.target;
|
|
277
466
|
const data = dragData(event);
|
|
278
467
|
if (!data) {
|
|
279
468
|
const spec = dropSpecRef.current;
|
|
280
|
-
if (spec && overMe(
|
|
469
|
+
if (spec && overMe(target)) {
|
|
281
470
|
const foreign = {
|
|
282
471
|
i: spec.i,
|
|
283
472
|
x: 0,
|
|
@@ -285,7 +474,7 @@ function SnapGridRuntime(props) {
|
|
|
285
474
|
w: spec.w,
|
|
286
475
|
h: spec.h
|
|
287
476
|
};
|
|
288
|
-
const committed =
|
|
477
|
+
const committed = optsRef.current.layout;
|
|
289
478
|
const cell = cellFromPointer(pointer, foreign) ?? {
|
|
290
479
|
x: 0,
|
|
291
480
|
y: 0
|
|
@@ -295,7 +484,7 @@ function SnapGridRuntime(props) {
|
|
|
295
484
|
return;
|
|
296
485
|
}
|
|
297
486
|
if (data.kind !== "move") return;
|
|
298
|
-
const here = overMe(
|
|
487
|
+
const here = overMe(target);
|
|
299
488
|
if (committedByIdRef.current.has(data.itemId)) {
|
|
300
489
|
const source = current?.kind === "move" ? current : null;
|
|
301
490
|
if (!source) return;
|
|
@@ -307,28 +496,12 @@ function SnapGridRuntime(props) {
|
|
|
307
496
|
placeholder: null
|
|
308
497
|
};
|
|
309
498
|
setSessionBoth(next);
|
|
310
|
-
|
|
311
|
-
let oLeft = pointer.x - grab.x;
|
|
312
|
-
let oTop = pointer.y - grab.y;
|
|
313
|
-
if (propsRef.current.dragConfig?.snapToGrid && here && next.placeholder) {
|
|
314
|
-
const rect = containerElRef.current?.getBoundingClientRect();
|
|
315
|
-
if (rect) {
|
|
316
|
-
const cell = calcGridItemPosition(ppRef.current, next.placeholder.x, next.placeholder.y, next.placeholder.w, next.placeholder.h);
|
|
317
|
-
oLeft = rect.left + cell.left;
|
|
318
|
-
oTop = rect.top + cell.top;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
setOverlay((o) => o ? {
|
|
322
|
-
...o,
|
|
323
|
-
left: oLeft,
|
|
324
|
-
top: oTop
|
|
325
|
-
} : o);
|
|
326
|
-
propsRef.current.onDrag?.(next.preview, next.anchor.item, next.placeholder, next.placeholder, event.operation.activatorEvent, null);
|
|
499
|
+
optsRef.current.onDrag?.(next.preview, next.anchor.item, next.placeholder, next.placeholder, event.operation.activatorEvent, null);
|
|
327
500
|
return;
|
|
328
501
|
}
|
|
329
502
|
if (here) {
|
|
330
503
|
const foreign = data.item;
|
|
331
|
-
const committed =
|
|
504
|
+
const committed = optsRef.current.layout;
|
|
332
505
|
const cell = cellFromPointer(pointer, foreign) ?? {
|
|
333
506
|
x: 0,
|
|
334
507
|
y: 0
|
|
@@ -344,17 +517,16 @@ function SnapGridRuntime(props) {
|
|
|
344
517
|
const handleDragEnd = useCallback((event) => {
|
|
345
518
|
const current = sessionRef.current;
|
|
346
519
|
const data = dragData(event);
|
|
347
|
-
const p = event.operation.position.current;
|
|
348
520
|
const myId = containerIdRef.current;
|
|
349
|
-
const dest =
|
|
350
|
-
|
|
351
|
-
|
|
521
|
+
const dest = dropDestination({
|
|
522
|
+
keyboard: keyboardRef.current,
|
|
523
|
+
targetId: event.operation.target?.id,
|
|
524
|
+
myId
|
|
352
525
|
});
|
|
353
526
|
const ownsItem = data ? committedByIdRef.current.has(data.itemId) : false;
|
|
354
|
-
|
|
355
|
-
registryRef.current.setGrabOffset(null);
|
|
527
|
+
setGrabOffset(managerRef.current, null);
|
|
356
528
|
const native = event.nativeEvent ?? null;
|
|
357
|
-
const
|
|
529
|
+
const o = optsRef.current;
|
|
358
530
|
const action = classifyDrop({
|
|
359
531
|
kind: current?.kind ?? null,
|
|
360
532
|
canceled: event.canceled,
|
|
@@ -365,74 +537,59 @@ function SnapGridRuntime(props) {
|
|
|
365
537
|
});
|
|
366
538
|
switch (action) {
|
|
367
539
|
case "cancel-resize":
|
|
368
|
-
|
|
540
|
+
o.onResizeStop?.(o.layout, current?.anchor.item ?? null, null, null, native, null);
|
|
369
541
|
break;
|
|
370
542
|
case "cancel-move":
|
|
371
|
-
|
|
543
|
+
o.onDragStop?.(o.layout, current?.anchor.item ?? null, null, null, native, null);
|
|
372
544
|
break;
|
|
373
545
|
case "commit-resize":
|
|
374
546
|
if (current) {
|
|
375
|
-
|
|
376
|
-
|
|
547
|
+
o.onLayoutChange?.(commitLayout(current));
|
|
548
|
+
o.onResizeStop?.(current.preview, current.anchor.item, current.placeholder, current.placeholder, native, null);
|
|
377
549
|
}
|
|
378
550
|
break;
|
|
379
551
|
case "commit-in-grid":
|
|
380
552
|
case "remove-source":
|
|
381
553
|
case "revert":
|
|
382
|
-
if (action === "commit-in-grid" && current)
|
|
554
|
+
if (action === "commit-in-grid" && current) o.onLayoutChange?.(commitLayout(current));
|
|
383
555
|
else if (action === "remove-source" && data) {
|
|
384
556
|
const { compactor: c, cols } = ctx();
|
|
385
|
-
|
|
557
|
+
o.onLayoutChange?.(removeItemWithCompactor(o.layout, data.itemId, {
|
|
386
558
|
compactor: c,
|
|
387
559
|
cols
|
|
388
560
|
}));
|
|
389
561
|
}
|
|
390
|
-
|
|
562
|
+
o.onDragStop?.(current?.preview ?? o.layout, current?.anchor.item ?? null, current?.placeholder ?? null, current?.placeholder ?? null, native, null);
|
|
391
563
|
break;
|
|
392
564
|
case "commit-dest":
|
|
393
|
-
if (current)
|
|
565
|
+
if (current) o.onLayoutChange?.(commitLayout(current));
|
|
394
566
|
break;
|
|
395
567
|
case "external-drop":
|
|
396
568
|
if (current) {
|
|
397
569
|
const committed = commitLayout(current);
|
|
398
570
|
const dropped = committed.find((it) => it.i === current.activeId);
|
|
399
|
-
if (dropped)
|
|
571
|
+
if (dropped) o.onDrop?.(committed, dropped, native);
|
|
400
572
|
}
|
|
401
573
|
break;
|
|
402
574
|
}
|
|
403
575
|
dropSpecRef.current = null;
|
|
404
|
-
|
|
576
|
+
setKeyboard(false);
|
|
405
577
|
setSessionBoth(null);
|
|
406
|
-
}, [
|
|
578
|
+
}, [
|
|
579
|
+
setSessionBoth,
|
|
580
|
+
setKeyboard,
|
|
581
|
+
ctx
|
|
582
|
+
]);
|
|
407
583
|
useEffect(() => {
|
|
408
|
-
const STEP = {
|
|
409
|
-
ArrowLeft: [-1, 0],
|
|
410
|
-
ArrowRight: [1, 0],
|
|
411
|
-
ArrowUp: [0, -1],
|
|
412
|
-
ArrowDown: [0, 1]
|
|
413
|
-
};
|
|
414
584
|
const onKeyDown = (e) => {
|
|
415
585
|
if (!keyboardRef.current) return;
|
|
416
586
|
const session = sessionRef.current;
|
|
417
587
|
if (!session || session.kind !== "move") return;
|
|
418
|
-
const step =
|
|
588
|
+
const step = arrowStep(e.key);
|
|
419
589
|
if (!step) return;
|
|
420
590
|
e.preventDefault();
|
|
421
591
|
e.stopImmediatePropagation();
|
|
422
|
-
|
|
423
|
-
setSessionBoth(next);
|
|
424
|
-
const cell = next.placeholder;
|
|
425
|
-
const rect = containerElRef.current?.getBoundingClientRect();
|
|
426
|
-
if (cell && rect) {
|
|
427
|
-
const pos = calcGridItemPosition(ppRef.current, cell.x, cell.y, cell.w, cell.h);
|
|
428
|
-
setOverlay((o) => o ? {
|
|
429
|
-
...o,
|
|
430
|
-
left: rect.left + pos.left,
|
|
431
|
-
top: rect.top + pos.top,
|
|
432
|
-
width: pos.width,
|
|
433
|
-
height: pos.height
|
|
434
|
-
} : o);
|
|
435
|
-
}
|
|
592
|
+
setSessionBoth(nudge(session, step[0], step[1], ctx()));
|
|
436
593
|
};
|
|
437
594
|
window.addEventListener("keydown", onKeyDown, true);
|
|
438
595
|
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
@@ -446,89 +603,53 @@ function SnapGridRuntime(props) {
|
|
|
446
603
|
handleDragMove,
|
|
447
604
|
handleDragEnd
|
|
448
605
|
]));
|
|
449
|
-
const dragThreshold =
|
|
450
|
-
const itemSensors = useMemo(() => buildItemSensors(dragThreshold, () =>
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
606
|
+
const dragThreshold = opts.dragConfig?.threshold ?? 3;
|
|
607
|
+
const itemSensors = useMemo(() => buildItemSensors(dragThreshold, () => optsRef.current.dragConfig), [dragThreshold]);
|
|
608
|
+
const itemModifiers = useMemo(() => [SnapToGrid.configure({
|
|
609
|
+
getPositionParams: () => ppRef.current,
|
|
610
|
+
isEnabled: () => optsRef.current.dragConfig?.snapToGrid ?? false
|
|
611
|
+
})], []);
|
|
612
|
+
const gridDraggable = opts.isDraggable ?? true;
|
|
613
|
+
const dragEnabled = opts.dragConfig?.enabled ?? true;
|
|
455
614
|
const isItemDraggable = useCallback((id) => {
|
|
456
615
|
const it = committedById.get(id);
|
|
457
616
|
if (!it) return false;
|
|
458
|
-
return gridDraggable && dragEnabled && (it.isDraggable
|
|
617
|
+
return gridDraggable && dragEnabled && itemGateOpen(it.isDraggable, it.static);
|
|
459
618
|
}, [
|
|
460
619
|
committedById,
|
|
461
620
|
gridDraggable,
|
|
462
621
|
dragEnabled
|
|
463
622
|
]);
|
|
464
|
-
const gridResizable =
|
|
465
|
-
const resizeEnabled =
|
|
623
|
+
const gridResizable = opts.isResizable ?? true;
|
|
624
|
+
const resizeEnabled = opts.resizeConfig?.enabled ?? true;
|
|
466
625
|
const isItemResizable = useCallback((id) => {
|
|
467
626
|
const it = committedById.get(id);
|
|
468
627
|
if (!it) return false;
|
|
469
|
-
return gridResizable && resizeEnabled && (it.isResizable
|
|
628
|
+
return gridResizable && resizeEnabled && itemGateOpen(it.isResizable, it.static);
|
|
470
629
|
}, [
|
|
471
630
|
committedById,
|
|
472
631
|
gridResizable,
|
|
473
632
|
resizeEnabled
|
|
474
633
|
]);
|
|
475
|
-
const defaultHandles =
|
|
634
|
+
const defaultHandles = opts.resizeConfig?.handles;
|
|
476
635
|
const resizeHandlesFor = useCallback((id) => committedById.get(id)?.resizeHandles ?? defaultHandles ?? DEFAULT_HANDLES, [committedById, defaultHandles]);
|
|
477
|
-
|
|
478
|
-
containerId,
|
|
479
|
-
width: props.width,
|
|
480
|
-
autoSize: props.autoSize ?? true,
|
|
481
|
-
gridConfig,
|
|
636
|
+
controller.setConfig({
|
|
482
637
|
positionParams,
|
|
483
|
-
renderedLayout,
|
|
484
|
-
itemsById,
|
|
485
|
-
session,
|
|
486
|
-
isItemDraggable,
|
|
487
|
-
isItemResizable,
|
|
488
|
-
resizeHandlesFor,
|
|
489
|
-
itemSensors,
|
|
490
|
-
setContainerElement,
|
|
491
|
-
overlay
|
|
492
|
-
}), [
|
|
493
|
-
containerId,
|
|
494
|
-
props.width,
|
|
495
|
-
props.autoSize,
|
|
496
638
|
gridConfig,
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
639
|
+
width: opts.width,
|
|
640
|
+
autoSize: opts.autoSize ?? true,
|
|
641
|
+
itemSensors,
|
|
642
|
+
itemModifiers,
|
|
501
643
|
isItemDraggable,
|
|
502
644
|
isItemResizable,
|
|
503
645
|
resizeHandlesFor,
|
|
504
|
-
|
|
505
|
-
setContainerElement,
|
|
506
|
-
overlay
|
|
507
|
-
]);
|
|
508
|
-
return /* @__PURE__ */ jsx(GridContext.Provider, {
|
|
509
|
-
value: runtime,
|
|
510
|
-
children: props.children
|
|
646
|
+
setContainerElement
|
|
511
647
|
});
|
|
512
|
-
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region src/SnapGridGroup.tsx
|
|
515
|
-
/**
|
|
516
|
-
* Wrap multiple grids to let tiles be dragged **between** them. Provides one
|
|
517
|
-
* shared dnd-kit `DragDropProvider` and a registry so each grid can tell which
|
|
518
|
-
* grid the pointer is over.
|
|
519
|
-
*
|
|
520
|
-
* Item ids must be unique across all grids in a group (they share one manager).
|
|
521
|
-
*/
|
|
522
|
-
function SnapGridGroup({ children }) {
|
|
523
|
-
const registryRef = useRef(null);
|
|
524
|
-
if (!registryRef.current) registryRef.current = createGridRegistry();
|
|
525
|
-
return /* @__PURE__ */ jsx(DragDropProvider, { children: /* @__PURE__ */ jsx(SnapGridGroupContext.Provider, {
|
|
526
|
-
value: registryRef.current,
|
|
527
|
-
children
|
|
528
|
-
}) });
|
|
648
|
+
return controller;
|
|
529
649
|
}
|
|
530
650
|
//#endregion
|
|
531
651
|
//#region src/hooks/useGridContainer.ts
|
|
652
|
+
const GRID_COLLISION_PRIORITY = 10;
|
|
532
653
|
/** Total container height in pixels for the given number of occupied rows. */
|
|
533
654
|
function containerHeight(rows, grid) {
|
|
534
655
|
const padY = (grid.containerPadding ?? grid.margin)[1];
|
|
@@ -536,81 +657,166 @@ function containerHeight(rows, grid) {
|
|
|
536
657
|
return padY * 2 + rows * grid.rowHeight + (rows - 1) * grid.margin[1];
|
|
537
658
|
}
|
|
538
659
|
/**
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* onto your own container element.
|
|
660
|
+
* The grid host: creates this grid's controller + drag monitor (see
|
|
661
|
+
* {@link useGridController}), registers the droppable surface, and returns props
|
|
662
|
+
* to spread onto your own container element. Render `useGridItem` tiles inside,
|
|
663
|
+
* passing `group` (this grid's id) so they resolve this controller.
|
|
542
664
|
*/
|
|
543
|
-
function useGridContainer() {
|
|
544
|
-
const
|
|
665
|
+
function useGridContainer(opts) {
|
|
666
|
+
const controller = useGridController(opts);
|
|
667
|
+
const { width, autoSize, gridConfig, setContainerElement } = controller.config;
|
|
545
668
|
const { ref, isDropTarget } = useDroppable$1({
|
|
546
|
-
id:
|
|
669
|
+
id: controller.id,
|
|
547
670
|
type: "grid",
|
|
548
|
-
accept:
|
|
671
|
+
accept: (source) => {
|
|
672
|
+
if (source.type === "grid-item") return true;
|
|
673
|
+
return source.data?.snapGridDrop != null;
|
|
674
|
+
},
|
|
675
|
+
collisionPriority: GRID_COLLISION_PRIORITY
|
|
549
676
|
});
|
|
550
|
-
const setContainerElement = rt.setContainerElement;
|
|
551
677
|
const setRef = useCallback((element) => {
|
|
552
678
|
ref(element);
|
|
553
679
|
setContainerElement(element);
|
|
554
680
|
}, [ref, setContainerElement]);
|
|
555
|
-
const
|
|
681
|
+
const renderedLayout = useSyncExternalStore(controller.subscribe, controller.renderedSnapshot, controller.renderedSnapshot);
|
|
556
682
|
return {
|
|
557
683
|
containerProps: {
|
|
558
684
|
ref: setRef,
|
|
559
685
|
style: {
|
|
560
686
|
position: "relative",
|
|
561
|
-
width
|
|
562
|
-
height
|
|
687
|
+
width,
|
|
688
|
+
height: autoSize ? containerHeight(bottom(renderedLayout), gridConfig) : void 0
|
|
563
689
|
},
|
|
564
690
|
"data-drop-target": isDropTarget || void 0
|
|
565
691
|
},
|
|
566
|
-
isDropTarget
|
|
692
|
+
isDropTarget,
|
|
693
|
+
group: controller.id,
|
|
694
|
+
controller
|
|
567
695
|
};
|
|
568
696
|
}
|
|
697
|
+
const REFLOW_EASING = "ease";
|
|
698
|
+
const REFLOW_TRANSITION = `transform 150ms ${REFLOW_EASING}, width 150ms ${REFLOW_EASING}, height 150ms ${REFLOW_EASING}`;
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/hooks/useResolveController.ts
|
|
701
|
+
/**
|
|
702
|
+
* Resolve a grid's controller by its `group` (= the grid's id), scoped to the
|
|
703
|
+
* ambient dnd-kit manager. Items declare `group` (mirroring useSortable); the
|
|
704
|
+
* container registered the controller under that id. Throws a helpful error if
|
|
705
|
+
* unresolved — almost always a missing `group` or a tile rendered outside any
|
|
706
|
+
* grid / `DragDropProvider`.
|
|
707
|
+
*/
|
|
708
|
+
function useResolveController(group) {
|
|
709
|
+
const controller = getController(useDragDropManager(), group);
|
|
710
|
+
if (!controller) throw new Error(`snapgrid: no grid found for group "${group}". A grid item must pass the group returned by its grid's useGridContainer, and render inside a <DragDropProvider> (or use <GridLayout>, which wires this for you).`);
|
|
711
|
+
return controller;
|
|
712
|
+
}
|
|
569
713
|
//#endregion
|
|
570
714
|
//#region src/hooks/useGridItem.ts
|
|
571
|
-
const
|
|
715
|
+
const ITEM_FEEDBACK = [Feedback$1.configure({
|
|
716
|
+
feedback: (_source, manager) => isKeyboardEvent(manager.dragOperation.activatorEvent) ? "none" : "default",
|
|
717
|
+
dropAnimation: null
|
|
718
|
+
})];
|
|
572
719
|
/**
|
|
573
|
-
* Headless hook for a single grid
|
|
574
|
-
*
|
|
575
|
-
*
|
|
720
|
+
* Headless hook for a single grid tile. The tile is a real `useSortable` (a
|
|
721
|
+
* draggable + droppable carrying `group`/`index`/`type`/`accept`), so it
|
|
722
|
+
* interoperates with the dnd-kit sortable ecosystem, yet it is positioned by RGL
|
|
723
|
+
* via the {@link GridController}. `group` is the owning grid's id (from its
|
|
724
|
+
* {@link useGridContainer}), mirroring `useSortable`'s `group`. Spread the returned
|
|
725
|
+
* `ref`, optional `handleRef`, positioning `style`, and drag state onto whatever
|
|
726
|
+
* element you render — you own the tag, className, content, and cosmetic styling.
|
|
727
|
+
*
|
|
728
|
+
* The dragged tile floats itself via dnd-kit's default feedback (no `<DragOverlay>`):
|
|
729
|
+
* the active tile renders at its committed origin so the float offset composes, and
|
|
730
|
+
* reflow is animated on the compositor via the Web Animations API — both so it stays
|
|
731
|
+
* smooth in Safari, where the float's popover top-layer repaint would jank a
|
|
732
|
+
* CSS-transition reflow.
|
|
576
733
|
*/
|
|
577
|
-
function useGridItem(id) {
|
|
578
|
-
const
|
|
579
|
-
const
|
|
580
|
-
const
|
|
734
|
+
function useGridItem(id, group) {
|
|
735
|
+
const controller = useResolveController(group);
|
|
736
|
+
const snap = useSyncExternalStore(controller.subscribe, () => controller.itemSnapshot(id), () => controller.itemSnapshot(id));
|
|
737
|
+
const item = snap.item;
|
|
738
|
+
const active = snap.isDragging;
|
|
739
|
+
const hidden = snap.hidden;
|
|
740
|
+
const config = controller.config;
|
|
741
|
+
const wasActive = useRef(false);
|
|
742
|
+
const justDropped = wasActive.current && !active;
|
|
743
|
+
wasActive.current = active;
|
|
744
|
+
const data = useMemo(() => ({ snapGrid: {
|
|
745
|
+
kind: "move",
|
|
746
|
+
itemId: id,
|
|
747
|
+
item
|
|
748
|
+
} }), [id, item]);
|
|
749
|
+
const { ref: sortableRef, handleRef, isDragging } = useSortable({
|
|
581
750
|
id,
|
|
751
|
+
index: controller.itemIndex(id),
|
|
752
|
+
group,
|
|
582
753
|
type: "grid-item",
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
item
|
|
590
|
-
} }
|
|
754
|
+
accept: "grid-item",
|
|
755
|
+
disabled: !config.isItemDraggable(id),
|
|
756
|
+
sensors: config.itemSensors,
|
|
757
|
+
modifiers: config.itemModifiers,
|
|
758
|
+
plugins: (defaults) => [...defaults, ...ITEM_FEEDBACK],
|
|
759
|
+
data
|
|
591
760
|
});
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
761
|
+
const elRef = useRef(null);
|
|
762
|
+
const setRef = useCallback((element) => {
|
|
763
|
+
sortableRef(element);
|
|
764
|
+
elRef.current = element;
|
|
765
|
+
}, [sortableRef]);
|
|
766
|
+
const session = controller.getSession();
|
|
767
|
+
const dragging = session != null;
|
|
768
|
+
const posItem = item ? active && hidden ? session?.anchor.item ?? item : item : void 0;
|
|
769
|
+
const pos = posItem ? calcGridItemPosition(config.positionParams, posItem.x, posItem.y, posItem.w, posItem.h) : void 0;
|
|
770
|
+
const posLeft = pos?.left;
|
|
771
|
+
const posTop = pos?.top;
|
|
772
|
+
const prev = useRef(null);
|
|
773
|
+
const reflowAnim = useRef(null);
|
|
774
|
+
useLayoutEffect(() => {
|
|
775
|
+
const cur = posLeft != null && posTop != null ? {
|
|
776
|
+
left: posLeft,
|
|
777
|
+
top: posTop
|
|
778
|
+
} : null;
|
|
779
|
+
const before = prev.current;
|
|
780
|
+
prev.current = cur;
|
|
781
|
+
const el = elRef.current;
|
|
782
|
+
if (!el || !cur || !before || active || justDropped || !dragging) return;
|
|
783
|
+
if (before.left === cur.left && before.top === cur.top) return;
|
|
784
|
+
let fromX = before.left;
|
|
785
|
+
let fromY = before.top;
|
|
786
|
+
if (reflowAnim.current?.playState === "running") {
|
|
787
|
+
const m = new DOMMatrix(getComputedStyle(el).transform);
|
|
788
|
+
fromX = m.m41;
|
|
789
|
+
fromY = m.m42;
|
|
790
|
+
}
|
|
791
|
+
reflowAnim.current?.cancel();
|
|
792
|
+
reflowAnim.current = el.animate([{ transform: `translate(${fromX}px, ${fromY}px)` }, { transform: `translate(${cur.left}px, ${cur.top}px)` }], {
|
|
793
|
+
duration: 150,
|
|
794
|
+
easing: REFLOW_EASING
|
|
795
|
+
});
|
|
796
|
+
}, [
|
|
797
|
+
posLeft,
|
|
798
|
+
posTop,
|
|
799
|
+
active,
|
|
800
|
+
justDropped,
|
|
801
|
+
dragging
|
|
802
|
+
]);
|
|
803
|
+
useEffect(() => () => reflowAnim.current?.cancel(), []);
|
|
804
|
+
return {
|
|
805
|
+
ref: setRef,
|
|
806
|
+
handleRef,
|
|
807
|
+
style: pos ? {
|
|
600
808
|
position: "absolute",
|
|
601
809
|
left: 0,
|
|
602
810
|
top: 0,
|
|
603
811
|
width: pos.width,
|
|
604
812
|
height: pos.height,
|
|
605
813
|
transform: `translate(${pos.left}px, ${pos.top}px)`,
|
|
606
|
-
|
|
607
|
-
transition: active ? "none" : REFLOW_TRANSITION,
|
|
814
|
+
transition: active || justDropped || dragging ? "none" : REFLOW_TRANSITION,
|
|
608
815
|
touchAction: "none"
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
style,
|
|
816
|
+
} : {
|
|
817
|
+
position: "absolute",
|
|
818
|
+
touchAction: "none"
|
|
819
|
+
},
|
|
614
820
|
isDragging,
|
|
615
821
|
item
|
|
616
822
|
};
|
|
@@ -619,13 +825,14 @@ function useGridItem(id) {
|
|
|
619
825
|
//#region src/hooks/useGridPlaceholder.ts
|
|
620
826
|
/**
|
|
621
827
|
* Headless hook returning where the drag placeholder should be rendered, or
|
|
622
|
-
* `null` when no drag is in progress.
|
|
828
|
+
* `null` when no drag is in progress. `group` is the owning grid's id (from its
|
|
829
|
+
* {@link useGridContainer}). You render the element however you like.
|
|
623
830
|
*/
|
|
624
|
-
function useGridPlaceholder() {
|
|
625
|
-
const
|
|
626
|
-
const placeholder =
|
|
831
|
+
function useGridPlaceholder(group) {
|
|
832
|
+
const controller = useResolveController(group);
|
|
833
|
+
const placeholder = useSyncExternalStore(controller.subscribe, controller.placeholderSnapshot, controller.placeholderSnapshot);
|
|
627
834
|
if (!placeholder) return null;
|
|
628
|
-
const pos = calcGridItemPosition(
|
|
835
|
+
const pos = calcGridItemPosition(controller.config.positionParams, placeholder.x, placeholder.y, placeholder.w, placeholder.h);
|
|
629
836
|
return {
|
|
630
837
|
item: placeholder,
|
|
631
838
|
style: {
|
|
@@ -643,14 +850,15 @@ function useGridPlaceholder() {
|
|
|
643
850
|
//#region src/hooks/useGridResizeHandle.ts
|
|
644
851
|
/**
|
|
645
852
|
* Headless hook for a single resize handle. Model a handle as its own draggable;
|
|
646
|
-
* dragging it resizes the item from the given edge/corner.
|
|
647
|
-
*
|
|
853
|
+
* dragging it resizes the item from the given edge/corner. `group` is the owning
|
|
854
|
+
* grid's id (from its {@link useGridContainer}). Spread `ref` and `handleProps`
|
|
855
|
+
* onto the handle element you position/style.
|
|
648
856
|
*/
|
|
649
|
-
function useGridResizeHandle(itemId, handle) {
|
|
650
|
-
const
|
|
857
|
+
function useGridResizeHandle(itemId, handle, group) {
|
|
858
|
+
const controller = useResolveController(group);
|
|
651
859
|
const { ref } = useDraggable$1({
|
|
652
860
|
id: `${itemId}::resize::${handle}`,
|
|
653
|
-
disabled: !
|
|
861
|
+
disabled: !controller.config?.isItemResizable(itemId),
|
|
654
862
|
plugins: NO_FEEDBACK,
|
|
655
863
|
data: { snapGrid: {
|
|
656
864
|
kind: "resize",
|
|
@@ -658,7 +866,7 @@ function useGridResizeHandle(itemId, handle) {
|
|
|
658
866
|
handle
|
|
659
867
|
} }
|
|
660
868
|
});
|
|
661
|
-
const isResizing =
|
|
869
|
+
const { isResizing } = useSyncExternalStore(controller.subscribe, () => controller.resizeSnapshot(itemId), () => controller.resizeSnapshot(itemId));
|
|
662
870
|
return {
|
|
663
871
|
ref,
|
|
664
872
|
handleProps: { [RESIZE_HANDLE_ATTR]: true },
|
|
@@ -666,30 +874,6 @@ function useGridResizeHandle(itemId, handle) {
|
|
|
666
874
|
};
|
|
667
875
|
}
|
|
668
876
|
//#endregion
|
|
669
|
-
//#region src/hooks/useGridDragOverlay.ts
|
|
670
|
-
/**
|
|
671
|
-
* Headless hook for the floating drag preview. Returns `null` unless this grid
|
|
672
|
-
* is the source of an in-progress drag. Render the returned `item` with `style`
|
|
673
|
-
* in a portal at `document.body` so it can float across grids unclipped (see
|
|
674
|
-
* {@link GridDragOverlay} for the convenience component).
|
|
675
|
-
*/
|
|
676
|
-
function useGridDragOverlay() {
|
|
677
|
-
const o = useGridRuntime().overlay;
|
|
678
|
-
if (!o) return null;
|
|
679
|
-
return {
|
|
680
|
-
item: o.item,
|
|
681
|
-
style: {
|
|
682
|
-
position: "fixed",
|
|
683
|
-
left: o.left,
|
|
684
|
-
top: o.top,
|
|
685
|
-
width: o.width,
|
|
686
|
-
height: o.height,
|
|
687
|
-
pointerEvents: "none",
|
|
688
|
-
zIndex: 1e3
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
//#endregion
|
|
693
877
|
//#region src/hooks/useResponsiveLayout.ts
|
|
694
878
|
/** react-grid-layout's default breakpoints (px) and column counts. */
|
|
695
879
|
const DEFAULT_BREAKPOINTS = {
|
|
@@ -756,26 +940,7 @@ function useResponsiveLayout(options) {
|
|
|
756
940
|
};
|
|
757
941
|
}
|
|
758
942
|
//#endregion
|
|
759
|
-
//#region src/
|
|
760
|
-
/**
|
|
761
|
-
* Renders the floating drag preview in a portal at `document.body` — so it
|
|
762
|
-
* follows the pointer across grids without being clipped by any container.
|
|
763
|
-
* Renders nothing when this grid isn't the drag source.
|
|
764
|
-
*/
|
|
765
|
-
function GridDragOverlay({ children, className, style }) {
|
|
766
|
-
const overlay = useGridDragOverlay();
|
|
767
|
-
if (typeof document === "undefined" || !overlay) return null;
|
|
768
|
-
return createPortal(/* @__PURE__ */ jsx("div", {
|
|
769
|
-
className: className ? `snapgrid-overlay ${className}` : "snapgrid-overlay",
|
|
770
|
-
style: style ? {
|
|
771
|
-
...overlay.style,
|
|
772
|
-
...style
|
|
773
|
-
} : overlay.style,
|
|
774
|
-
children: children(overlay.item)
|
|
775
|
-
}), document.body);
|
|
776
|
-
}
|
|
777
|
-
//#endregion
|
|
778
|
-
//#region src/GridItem.tsx
|
|
943
|
+
//#region src/components/GridItem.tsx
|
|
779
944
|
const HANDLE_CURSOR = {
|
|
780
945
|
n: "ns-resize",
|
|
781
946
|
s: "ns-resize",
|
|
@@ -804,8 +969,8 @@ function handleStyle(handle) {
|
|
|
804
969
|
if (handle === "e" || handle === "w") s.top = `calc(50% - ${SIDE / 2}px)`;
|
|
805
970
|
return s;
|
|
806
971
|
}
|
|
807
|
-
function DefaultResizeHandle({ itemId, handle }) {
|
|
808
|
-
const { ref, handleProps } = useGridResizeHandle(itemId, handle);
|
|
972
|
+
function DefaultResizeHandle({ itemId, handle, group }) {
|
|
973
|
+
const { ref, handleProps } = useGridResizeHandle(itemId, handle, group);
|
|
809
974
|
return /* @__PURE__ */ jsx("span", {
|
|
810
975
|
ref,
|
|
811
976
|
...handleProps,
|
|
@@ -816,12 +981,19 @@ function DefaultResizeHandle({ itemId, handle }) {
|
|
|
816
981
|
/**
|
|
817
982
|
* Convenience wrapper over {@link useGridItem}: an absolutely-positioned `<div>`
|
|
818
983
|
* with stable hooks (`.snapgrid-item`, `data-grid-id`, `data-dragging`) and the
|
|
819
|
-
* configured resize handles. For full control,
|
|
984
|
+
* configured resize handles. `group` is the owning grid's id. For full control,
|
|
985
|
+
* use the hooks directly.
|
|
986
|
+
*
|
|
987
|
+
* Memoized so re-rendering the surface (e.g. its auto-height tracking the drag)
|
|
988
|
+
* doesn't re-render every tile — a tile re-renders only when its own slice
|
|
989
|
+
* changes (via useGridItem's subscription). Keeps a drag's React work scoped to
|
|
990
|
+
* the moved tile (see renderScope.test).
|
|
820
991
|
*/
|
|
821
|
-
function
|
|
822
|
-
const
|
|
823
|
-
const { ref, style: positionStyle, isDragging } = useGridItem(id);
|
|
824
|
-
const
|
|
992
|
+
function GridItemImpl({ id, group, children, className, style }) {
|
|
993
|
+
const controller = useResolveController(group);
|
|
994
|
+
const { ref, style: positionStyle, isDragging } = useGridItem(id, group);
|
|
995
|
+
const config = controller.config;
|
|
996
|
+
const handles = config?.isItemResizable(id) ? config.resizeHandlesFor(id) : [];
|
|
825
997
|
return /* @__PURE__ */ jsxs("div", {
|
|
826
998
|
ref,
|
|
827
999
|
"data-grid-id": id,
|
|
@@ -833,27 +1005,29 @@ function GridItem({ id, children, className, style }) {
|
|
|
833
1005
|
} : positionStyle,
|
|
834
1006
|
children: [children, handles.map((handle) => /* @__PURE__ */ jsx(DefaultResizeHandle, {
|
|
835
1007
|
itemId: id,
|
|
836
|
-
handle
|
|
1008
|
+
handle,
|
|
1009
|
+
group
|
|
837
1010
|
}, handle))]
|
|
838
1011
|
});
|
|
839
1012
|
}
|
|
1013
|
+
const GridItem = memo(GridItemImpl);
|
|
840
1014
|
//#endregion
|
|
841
|
-
//#region src/GridPlaceholder.tsx
|
|
1015
|
+
//#region src/components/GridPlaceholder.tsx
|
|
842
1016
|
const DEFAULT_LOOK = {
|
|
843
1017
|
background: "rgba(99, 102, 241, 0.2)",
|
|
844
1018
|
border: "1px dashed rgba(99, 102, 241, 0.6)",
|
|
845
1019
|
borderRadius: 4,
|
|
846
1020
|
boxSizing: "border-box",
|
|
847
1021
|
zIndex: 2,
|
|
848
|
-
transition:
|
|
1022
|
+
transition: REFLOW_TRANSITION
|
|
849
1023
|
};
|
|
850
1024
|
/**
|
|
851
1025
|
* Convenience placeholder rendered from {@link useGridPlaceholder}. Renders
|
|
852
1026
|
* nothing when no drag is active. For a custom placeholder, call the hook
|
|
853
1027
|
* directly and render your own element with the returned `style`.
|
|
854
1028
|
*/
|
|
855
|
-
function GridPlaceholder({ className, style }) {
|
|
856
|
-
const placeholder = useGridPlaceholder();
|
|
1029
|
+
function GridPlaceholder({ group, className, style }) {
|
|
1030
|
+
const placeholder = useGridPlaceholder(group);
|
|
857
1031
|
if (!placeholder) return null;
|
|
858
1032
|
return /* @__PURE__ */ jsx("div", {
|
|
859
1033
|
"aria-hidden": "true",
|
|
@@ -866,18 +1040,15 @@ function GridPlaceholder({ className, style }) {
|
|
|
866
1040
|
});
|
|
867
1041
|
}
|
|
868
1042
|
//#endregion
|
|
869
|
-
//#region src/GridLayout.tsx
|
|
1043
|
+
//#region src/components/GridLayout.tsx
|
|
1044
|
+
const InProvider = createContext(false);
|
|
870
1045
|
/** Strip the namespacing prefix React applies to keys inside `Children.map`. */
|
|
871
1046
|
function keyToId(key) {
|
|
872
1047
|
return key.startsWith(".$") ? key.slice(2) : key;
|
|
873
1048
|
}
|
|
874
1049
|
/** The default surface: positioned container + mapped items + placeholder. */
|
|
875
|
-
function GridSurface({ className, style, children }) {
|
|
876
|
-
const { containerProps } = useGridContainer();
|
|
877
|
-
const childById = /* @__PURE__ */ new Map();
|
|
878
|
-
Children.forEach(children, (child) => {
|
|
879
|
-
if (isValidElement(child) && child.key != null) childById.set(keyToId(String(child.key)), child);
|
|
880
|
-
});
|
|
1050
|
+
function GridSurface({ className, style, children, ...opts }) {
|
|
1051
|
+
const { containerProps, group } = useGridContainer(opts);
|
|
881
1052
|
return /* @__PURE__ */ jsxs("div", {
|
|
882
1053
|
...containerProps,
|
|
883
1054
|
className: className ? `snapgrid ${className}` : "snapgrid",
|
|
@@ -885,38 +1056,50 @@ function GridSurface({ className, style, children }) {
|
|
|
885
1056
|
...containerProps.style,
|
|
886
1057
|
...style
|
|
887
1058
|
} : containerProps.style,
|
|
888
|
-
children: [
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
/* @__PURE__ */ jsx(GridPlaceholder, {}),
|
|
897
|
-
/* @__PURE__ */ jsx(GridDragOverlay, { children: (item) => childById.get(item.i) ?? null })
|
|
898
|
-
]
|
|
1059
|
+
children: [Children.map(children, (child) => {
|
|
1060
|
+
if (!isValidElement(child) || child.key == null) return child;
|
|
1061
|
+
return /* @__PURE__ */ jsx(GridItem, {
|
|
1062
|
+
id: keyToId(String(child.key)),
|
|
1063
|
+
group,
|
|
1064
|
+
children: child
|
|
1065
|
+
}, child.key);
|
|
1066
|
+
}), /* @__PURE__ */ jsx(GridPlaceholder, { group })]
|
|
899
1067
|
});
|
|
900
1068
|
}
|
|
901
1069
|
/**
|
|
902
1070
|
* Drop-in grid component: a controlled, react-grid-layout v2-compatible layout
|
|
903
|
-
* backed by dnd-kit. A thin shell over {@link
|
|
1071
|
+
* backed by dnd-kit. A thin shell over {@link useGridContainer} and the headless
|
|
904
1072
|
* hooks — children are keyed by their layout item's `i`. For full control over
|
|
905
|
-
* markup/styling, use the
|
|
1073
|
+
* markup/styling, use the hooks directly.
|
|
1074
|
+
*
|
|
1075
|
+
* Supplies the dnd-kit `DragDropProvider` for the turnkey case so consumers
|
|
1076
|
+
* don't manage one. Nest multiple `GridLayout`s and they share one provider
|
|
1077
|
+
* (the seam for cross-grid drags); a consumer's own provider is also honored.
|
|
906
1078
|
*/
|
|
907
1079
|
function GridLayout(props) {
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1080
|
+
const inProvider = useContext(InProvider);
|
|
1081
|
+
const surface = /* @__PURE__ */ jsx(GridSurface, { ...props });
|
|
1082
|
+
if (inProvider) return surface;
|
|
1083
|
+
return /* @__PURE__ */ jsx(DragDropProvider, { children: /* @__PURE__ */ jsx(InProvider.Provider, {
|
|
1084
|
+
value: true,
|
|
1085
|
+
children: surface
|
|
1086
|
+
}) });
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Share one dnd-kit `DragDropProvider` across several sibling grids so tiles can
|
|
1090
|
+
* be dragged between them. (Nested `GridLayout`s already share a provider; this
|
|
1091
|
+
* is for siblings.) A thin wrapper over `DragDropProvider` — the cross-grid seam
|
|
1092
|
+
* is the shared manager + collision target.
|
|
1093
|
+
*/
|
|
1094
|
+
function SnapGridGroup({ children }) {
|
|
1095
|
+
if (useContext(InProvider)) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1096
|
+
return /* @__PURE__ */ jsx(DragDropProvider, { children: /* @__PURE__ */ jsx(InProvider.Provider, {
|
|
1097
|
+
value: true,
|
|
1098
|
+
children
|
|
1099
|
+
}) });
|
|
917
1100
|
}
|
|
918
1101
|
//#endregion
|
|
919
|
-
//#region src/ResponsiveGridLayout.tsx
|
|
1102
|
+
//#region src/components/ResponsiveGridLayout.tsx
|
|
920
1103
|
/**
|
|
921
1104
|
* A responsive grid: switches column count and layout by breakpoint as `width`
|
|
922
1105
|
* changes, generating a breakpoint's layout from the nearest one when absent.
|
|
@@ -994,6 +1177,6 @@ function useContainerWidth(options = {}) {
|
|
|
994
1177
|
};
|
|
995
1178
|
}
|
|
996
1179
|
//#endregion
|
|
997
|
-
export { DEFAULT_BREAKPOINTS, DEFAULT_BREAKPOINT_COLS,
|
|
1180
|
+
export { DEFAULT_BREAKPOINTS, DEFAULT_BREAKPOINT_COLS, DragOverlay, Feedback, GridItem, GridLayout, GridPlaceholder, KeyboardSensor, PointerSensor, ResponsiveGridLayout, SnapGridGroup, getCompactor, horizontalCompactor, noCompactor, useContainerWidth, useDraggable, useDroppable, useGridContainer, useGridItem, useGridPlaceholder, useGridResizeHandle, useResponsiveLayout, verticalCompactor };
|
|
998
1181
|
|
|
999
1182
|
//# sourceMappingURL=index.mjs.map
|