@snapgridjs/react 0.4.0 → 0.6.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 +1 -0
- package/dist/index.cjs +129 -647
- package/dist/index.d.cts +34 -167
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +34 -167
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +94 -642
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -7
package/dist/index.mjs
CHANGED
|
@@ -1,394 +1,36 @@
|
|
|
1
|
-
import { DragDropProvider, DragOverlay, useDragDropManager,
|
|
2
|
-
import {
|
|
1
|
+
import { DragDropProvider, DragOverlay, useDragDropManager, useDraggable, useDraggable as useDraggable$1, useDroppable, useDroppable as useDroppable$1, useInstance } from "@dnd-kit/react";
|
|
2
|
+
import { bottom, calcGridItemPosition, defaultGridConfig, defaultGridConfig as defaultGridConfig$1, findOrGenerateResponsiveLayout, getBreakpointFromWidth, getColsFromBreakpoint, getCompactor, horizontalCompactor, insertItemWithCompactor, noCompactor, removeItemWithCompactor, toPositionParams, toPositionParams as toPositionParams$1, verticalCompactor, verticalCompactor as verticalCompactor$1 } from "@snapgridjs/core";
|
|
3
|
+
import { GridController, NO_FEEDBACK, RESIZE_HANDLE_ATTR, SNAPGRID_GRID_ATTR, SnapToGrid, attachEngine, buildItemSensors, domElement, getController, gridCollisionDetector, registerController, snapMove } from "@snapgridjs/dnd";
|
|
3
4
|
import { Children, createContext, isValidElement, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import { Modifier } from "@dnd-kit/abstract";
|
|
6
|
-
import { Feedback, Feedback as Feedback$1, KeyboardSensor, KeyboardSensor as KeyboardSensor$1, PointerActivationConstraints, PointerSensor, PointerSensor as PointerSensor$1 } from "@dnd-kit/dom";
|
|
5
|
+
import { Feedback, Feedback as Feedback$1, KeyboardSensor, PointerSensor } from "@dnd-kit/dom";
|
|
7
6
|
import { isKeyboardEvent } from "@dnd-kit/dom/utilities";
|
|
8
7
|
import { useSortable } from "@dnd-kit/react/sortable";
|
|
9
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
//#region src/dnd/entity.ts
|
|
11
|
-
/**
|
|
12
|
-
* The DOM element of a dnd-kit entity (a draggable, droppable, or drag source).
|
|
13
|
-
* dnd-kit's abstract types don't expose `element`, but the DOM layer snapgrid
|
|
14
|
-
* runs on always sets it — this centralizes that one assumption (and the cast)
|
|
15
|
-
* in a single place instead of scattering it across the drag code.
|
|
16
|
-
*/
|
|
17
|
-
function domElement(entity) {
|
|
18
|
-
return entity?.element ?? null;
|
|
19
|
-
}
|
|
20
|
-
//#endregion
|
|
21
|
-
//#region src/dnd/collision.ts
|
|
22
|
-
/**
|
|
23
|
-
* Marker attribute set on every grid container element. Used by {@link gridDepth}
|
|
24
|
-
* to measure how deeply a grid is nested, purely from the DOM.
|
|
25
|
-
*/
|
|
26
|
-
const SNAPGRID_GRID_ATTR = "data-snapgrid-grid";
|
|
27
|
-
const GRID_COLLISION_PRIORITY = 10;
|
|
28
|
-
/**
|
|
29
|
-
* How deeply `el`'s grid is nested: the number of ancestor grid containers above
|
|
30
|
-
* it. A top-level grid is 0; a grid rendered inside another grid's tile is 1; and
|
|
31
|
-
* so on. DOM containment is the ground truth, so this is correct regardless of the
|
|
32
|
-
* React tree shape or how priorities are assigned elsewhere.
|
|
33
|
-
*/
|
|
34
|
-
function gridDepth(el) {
|
|
35
|
-
let depth = 0;
|
|
36
|
-
let node = el?.parentElement ?? null;
|
|
37
|
-
while (node) {
|
|
38
|
-
if (node.hasAttribute("data-snapgrid-grid")) depth++;
|
|
39
|
-
node = node.parentElement;
|
|
40
|
-
}
|
|
41
|
-
return depth;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Collision detector for grid droppables. Runs dnd-kit's default detector, then —
|
|
45
|
-
* when nested grid rects overlap (the pointer is over both an inner grid and its
|
|
46
|
-
* outer one) — ranks the **innermost** grid highest by boosting priority with the
|
|
47
|
-
* grid's nesting depth. Without this, overlapping grids tie on priority and the
|
|
48
|
-
* winner is arbitrary. For non-nested grids depth is 0, so priority is unchanged.
|
|
49
|
-
*/
|
|
50
|
-
const gridCollisionDetector = (input) => {
|
|
51
|
-
const collision = defaultCollisionDetection(input);
|
|
52
|
-
if (!collision) return null;
|
|
53
|
-
return {
|
|
54
|
-
...collision,
|
|
55
|
-
priority: GRID_COLLISION_PRIORITY + gridDepth(domElement(input.droppable))
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
//#endregion
|
|
59
|
-
//#region src/controller/GridController.ts
|
|
60
|
-
function sameItem(a, b) {
|
|
61
|
-
if (a === b) return true;
|
|
62
|
-
if (!a || !b) return false;
|
|
63
|
-
return a.i === b.i && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Live per-grid drag/resize state as a plain observable: the provider writes
|
|
67
|
-
* (`setSession`/`setKeyboard`/`setCommitted`), hooks subscribe to just their own
|
|
68
|
-
* slice via `useSyncExternalStore`. Value-cached snapshots mean a drag re-renders
|
|
69
|
-
* only the tiles whose slice changed, not the whole subtree (the old
|
|
70
|
-
* context-value model re-rendered every tile every frame).
|
|
71
|
-
*/
|
|
72
|
-
var GridController = class {
|
|
73
|
-
id;
|
|
74
|
-
#committed;
|
|
75
|
-
#session = null;
|
|
76
|
-
#keyboard = false;
|
|
77
|
-
#listeners = /* @__PURE__ */ new Set();
|
|
78
|
-
config = null;
|
|
79
|
-
#itemCache = /* @__PURE__ */ new Map();
|
|
80
|
-
#resizeCache = /* @__PURE__ */ new Map();
|
|
81
|
-
#placeholderCache = null;
|
|
82
|
-
#renderedMap = null;
|
|
83
|
-
#renderedMapSource = null;
|
|
84
|
-
#indexById = /* @__PURE__ */ new Map();
|
|
85
|
-
#nextIndex = 0;
|
|
86
|
-
/** The dnd-kit manager this grid is registered with (set by useInstance). */
|
|
87
|
-
manager;
|
|
88
|
-
constructor(id, committed = [], manager) {
|
|
89
|
-
this.id = id;
|
|
90
|
-
this.#committed = committed;
|
|
91
|
-
this.manager = manager;
|
|
92
|
-
}
|
|
93
|
-
/** Replace the per-grid config (called by the container host during render). */
|
|
94
|
-
setConfig(config) {
|
|
95
|
-
this.config = config;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Re-point this grid's id. The container host syncs it (during render, before
|
|
99
|
-
* the droppable/group read it) when the controlled `id` prop changes, so the
|
|
100
|
-
* returned `group`, the droppable id, and the registry key never drift apart.
|
|
101
|
-
*/
|
|
102
|
-
setId(id) {
|
|
103
|
-
this.id = id;
|
|
104
|
-
}
|
|
105
|
-
register = () => {};
|
|
106
|
-
subscribe = (listener) => {
|
|
107
|
-
this.#listeners.add(listener);
|
|
108
|
-
return () => {
|
|
109
|
-
this.#listeners.delete(listener);
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
#emit() {
|
|
113
|
-
for (const listener of this.#listeners) listener();
|
|
114
|
-
}
|
|
115
|
-
/** The layout currently shown: the drag preview while dragging, else committed. */
|
|
116
|
-
#rendered() {
|
|
117
|
-
return this.#session ? this.#session.preview : this.#committed;
|
|
118
|
-
}
|
|
119
|
-
#renderedById() {
|
|
120
|
-
const rendered = this.#rendered();
|
|
121
|
-
if (this.#renderedMapSource !== rendered) {
|
|
122
|
-
this.#renderedMap = new Map(rendered.map((it) => [it.i, it]));
|
|
123
|
-
this.#renderedMapSource = rendered;
|
|
124
|
-
}
|
|
125
|
-
return this.#renderedMap;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Sync the committed layout from the controlled `layout` prop. Called during
|
|
129
|
-
* the provider's render, so it must NOT notify — emitting here would update
|
|
130
|
-
* subscribed GridItems mid-render (a React "setState while rendering" error).
|
|
131
|
-
* No notify is needed: a `layout` prop change already re-renders the whole
|
|
132
|
-
* provider subtree, so every GridItem re-reads its snapshot on that pass.
|
|
133
|
-
*/
|
|
134
|
-
setCommitted(layout) {
|
|
135
|
-
if (this.#committed === layout) return;
|
|
136
|
-
this.#committed = layout;
|
|
137
|
-
const present = new Set(layout.map((it) => it.i));
|
|
138
|
-
for (const id of this.#indexById.keys()) if (!present.has(id)) this.#indexById.delete(id);
|
|
139
|
-
for (const id of this.#itemCache.keys()) if (!present.has(id)) this.#itemCache.delete(id);
|
|
140
|
-
for (const id of this.#resizeCache.keys()) if (!present.has(id)) this.#resizeCache.delete(id);
|
|
141
|
-
}
|
|
142
|
-
setSession(next) {
|
|
143
|
-
this.#session = next;
|
|
144
|
-
this.#emit();
|
|
145
|
-
}
|
|
146
|
-
getSession() {
|
|
147
|
-
return this.#session;
|
|
148
|
-
}
|
|
149
|
-
/** Record whether the active drag is keyboard-driven (drives `hidden`). */
|
|
150
|
-
setKeyboard(value) {
|
|
151
|
-
if (this.#keyboard === value) return;
|
|
152
|
-
this.#keyboard = value;
|
|
153
|
-
this.#emit();
|
|
154
|
-
}
|
|
155
|
-
itemSnapshot = (id) => {
|
|
156
|
-
const item = this.#renderedById().get(id);
|
|
157
|
-
const isDragging = this.#session?.activeId === id;
|
|
158
|
-
const hidden = isDragging && this.#session?.kind === "move" && !this.#keyboard;
|
|
159
|
-
const prev = this.#itemCache.get(id);
|
|
160
|
-
if (prev && prev.isDragging === isDragging && prev.hidden === hidden && sameItem(prev.item, item)) return prev;
|
|
161
|
-
const snap = {
|
|
162
|
-
item,
|
|
163
|
-
isDragging,
|
|
164
|
-
hidden
|
|
165
|
-
};
|
|
166
|
-
this.#itemCache.set(id, snap);
|
|
167
|
-
return snap;
|
|
168
|
-
};
|
|
169
|
-
placeholderSnapshot = () => {
|
|
170
|
-
const next = this.#session?.placeholder ?? null;
|
|
171
|
-
if (sameItem(this.#placeholderCache ?? void 0, next ?? void 0)) return this.#placeholderCache;
|
|
172
|
-
this.#placeholderCache = next;
|
|
173
|
-
return next;
|
|
174
|
-
};
|
|
175
|
-
resizeSnapshot = (itemId) => {
|
|
176
|
-
const isResizing = this.#session?.kind === "resize" && this.#session.activeId === itemId;
|
|
177
|
-
const prev = this.#resizeCache.get(itemId);
|
|
178
|
-
if (prev && prev.isResizing === isResizing) return prev;
|
|
179
|
-
const snap = { isResizing };
|
|
180
|
-
this.#resizeCache.set(itemId, snap);
|
|
181
|
-
return snap;
|
|
182
|
-
};
|
|
183
|
-
renderedSnapshot = () => this.#rendered();
|
|
184
|
-
/** A stable index for `id` (see {@link GridController.#indexById}). */
|
|
185
|
-
itemIndex(id) {
|
|
186
|
-
let i = this.#indexById.get(id);
|
|
187
|
-
if (i === void 0) {
|
|
188
|
-
i = this.#nextIndex++;
|
|
189
|
-
this.#indexById.set(id, i);
|
|
190
|
-
}
|
|
191
|
-
return i;
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region src/controller/registry.ts
|
|
196
|
-
/**
|
|
197
|
-
* Resolves a grid's {@link GridController} by its id, scoped to the dnd-kit
|
|
198
|
-
* manager the grid is registered with. A container registers its controller
|
|
199
|
-
* here (during render, so child items resolve it on their first render); items
|
|
200
|
-
* look it up by their `group` (= the grid id). Replaces the old geometry
|
|
201
|
-
* `GridRegistry` — which grid the pointer is over now comes from the collision
|
|
202
|
-
* target, so the registry's only job is id → controller resolution.
|
|
203
|
-
*
|
|
204
|
-
* Keyed by manager so two apps (or two providers) never collide, and grids in
|
|
205
|
-
* one provider share a map (the cross-grid seam).
|
|
206
|
-
*/
|
|
207
|
-
const byManager = /* @__PURE__ */ new WeakMap();
|
|
208
|
-
const noManager = /* @__PURE__ */ new Map();
|
|
209
|
-
function mapFor(manager) {
|
|
210
|
-
if (!manager) return noManager;
|
|
211
|
-
let map = byManager.get(manager);
|
|
212
|
-
if (!map) {
|
|
213
|
-
map = /* @__PURE__ */ new Map();
|
|
214
|
-
byManager.set(manager, map);
|
|
215
|
-
}
|
|
216
|
-
return map;
|
|
217
|
-
}
|
|
218
|
-
/** Register a controller under `id` for `manager`. Returns an unregister fn. */
|
|
219
|
-
function registerController(manager, id, controller) {
|
|
220
|
-
const map = mapFor(manager);
|
|
221
|
-
map.set(id, controller);
|
|
222
|
-
return () => {
|
|
223
|
-
if (map.get(id) === controller) map.delete(id);
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
/** The controller registered under `id` for `manager`, or undefined. */
|
|
227
|
-
function getController(manager, id) {
|
|
228
|
-
return mapFor(manager).get(id);
|
|
229
|
-
}
|
|
230
|
-
const grabOffsets = /* @__PURE__ */ new WeakMap();
|
|
231
|
-
const noManagerGrab = { current: null };
|
|
232
|
-
function setGrabOffset(manager, offset) {
|
|
233
|
-
if (!manager) {
|
|
234
|
-
noManagerGrab.current = offset;
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (offset) grabOffsets.set(manager, offset);
|
|
238
|
-
else grabOffsets.delete(manager);
|
|
239
|
-
}
|
|
240
|
-
function getGrabOffset(manager) {
|
|
241
|
-
return (manager ? grabOffsets.get(manager) : noManagerGrab.current) ?? {
|
|
242
|
-
x: 0,
|
|
243
|
-
y: 0
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
//#endregion
|
|
247
|
-
//#region src/dnd/dragFlow.ts
|
|
248
|
-
/**
|
|
249
|
-
* Pure decision helpers for the drag interaction so the tricky bits — grab-offset
|
|
250
|
-
* cell mapping, the cross-grid drop lifecycle, and external-drop acceptance — are
|
|
251
|
-
* unit-testable without a DOM or dnd-kit.
|
|
252
|
-
*/
|
|
253
|
-
/** Read snapgrid's payload off a dnd-kit drag source. */
|
|
254
|
-
function dragData(event) {
|
|
255
|
-
return (event.operation.source?.data)?.snapGrid;
|
|
256
|
-
}
|
|
257
|
-
/** Size/id spec for an external (non-grid) draggable the grid may accept, or null. */
|
|
258
|
-
function externalDropSpec(source, dropConfig) {
|
|
259
|
-
if (!dropConfig?.enabled || !source) return null;
|
|
260
|
-
const data = source.data;
|
|
261
|
-
if (data?.snapGrid) return null;
|
|
262
|
-
if (dropConfig.accept && !dropConfig.accept(source)) return null;
|
|
263
|
-
const spec = data?.snapGridDrop;
|
|
264
|
-
return {
|
|
265
|
-
i: spec?.i,
|
|
266
|
-
w: spec?.w ?? dropConfig.defaultItem?.w ?? 1,
|
|
267
|
-
h: spec?.h ?? dropConfig.defaultItem?.h ?? 1
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Map a client-space pointer to a grid cell, accounting for where *within* the
|
|
272
|
-
* dragged tile the pointer grabbed it. Subtracting the grab offset means the
|
|
273
|
-
* tile's top-left (not the cursor) maps to the cell, so a received tile's
|
|
274
|
-
* placeholder aligns with the floating overlay instead of jumping its corner to
|
|
275
|
-
* the cursor. External drops pass `{ x: 0, y: 0 }` (no meaningful grab point).
|
|
276
|
-
*/
|
|
277
|
-
function receiveCell(pointer, gridRect, grabOffset, w, h, pp) {
|
|
278
|
-
return calcXY(pp, pointer.y - grabOffset.y - gridRect.top, pointer.x - grabOffset.x - gridRect.left, w, h);
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Map a keyboard event key to a one-cell grid step while a keyboard drag is
|
|
282
|
-
* active, or null for keys snapgrid doesn't own — Enter/Space (drop) and Escape
|
|
283
|
-
* (cancel) fall through to dnd-kit's KeyboardSensor.
|
|
284
|
-
*/
|
|
285
|
-
function arrowStep(key) {
|
|
286
|
-
switch (key) {
|
|
287
|
-
case "ArrowLeft": return [-1, 0];
|
|
288
|
-
case "ArrowRight": return [1, 0];
|
|
289
|
-
case "ArrowUp": return [0, -1];
|
|
290
|
-
case "ArrowDown": return [0, 1];
|
|
291
|
-
default: return null;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Which grid a drop commits to, as fed to {@link classifyDrop} as `dest`. A
|
|
296
|
-
* keyboard drag has no pointer, so it can only ever land in its own grid; a
|
|
297
|
-
* pointer drag lands in whichever grid the collision observer resolved (or none).
|
|
298
|
-
*/
|
|
299
|
-
function dropDestination(opts) {
|
|
300
|
-
if (opts.keyboard) return opts.myId;
|
|
301
|
-
return opts.targetId != null ? String(opts.targetId) : null;
|
|
302
|
-
}
|
|
303
|
-
/** Pure classification of a drag end. See {@link DropAction}. */
|
|
304
|
-
function classifyDrop(s) {
|
|
305
|
-
if (s.canceled) {
|
|
306
|
-
if (s.kind === "resize") return "cancel-resize";
|
|
307
|
-
if (s.ownsItem) return "cancel-move";
|
|
308
|
-
return "noop";
|
|
309
|
-
}
|
|
310
|
-
if (s.kind === "resize") return "commit-resize";
|
|
311
|
-
if (s.ownsItem && s.hasData) {
|
|
312
|
-
if (s.dest === s.myId && s.kind === "move") return "commit-in-grid";
|
|
313
|
-
if (s.dest) return "remove-source";
|
|
314
|
-
return "revert";
|
|
315
|
-
}
|
|
316
|
-
if (s.dest === s.myId && s.kind === "move") return s.hasData ? "commit-dest" : "external-drop";
|
|
317
|
-
return "noop";
|
|
318
|
-
}
|
|
319
|
-
//#endregion
|
|
320
|
-
//#region src/dnd/snapToGrid.ts
|
|
321
|
-
/**
|
|
322
|
-
* Quantizes the dragged item's transform to whole grid cells, so the floating
|
|
323
|
-
* <DragOverlay> clone jumps cell-to-cell in lockstep with the (always-snapped)
|
|
324
|
-
* placeholder instead of tracking the pointer smoothly. Applied on the item
|
|
325
|
-
* draggable; a no-op unless `dragConfig.snapToGrid` is set.
|
|
326
|
-
*/
|
|
327
|
-
var SnapToGrid = class extends Modifier {
|
|
328
|
-
apply({ transform }) {
|
|
329
|
-
const opts = this.options;
|
|
330
|
-
if (!opts?.isEnabled()) return transform;
|
|
331
|
-
const pp = opts.getPositionParams();
|
|
332
|
-
const colStep = calcGridColWidth(pp) + pp.margin[0];
|
|
333
|
-
const rowStep = pp.rowHeight + pp.margin[1];
|
|
334
|
-
if (colStep <= 0 || rowStep <= 0) return transform;
|
|
335
|
-
return {
|
|
336
|
-
x: Math.round(transform.x / colStep) * colStep,
|
|
337
|
-
y: Math.round(transform.y / rowStep) * rowStep
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
//#endregion
|
|
342
|
-
//#region src/hooks/dndShared.ts
|
|
343
|
-
/** Marker attribute placed on resize-handle elements. */
|
|
344
|
-
const RESIZE_HANDLE_ATTR = "data-snapgrid-resize-handle";
|
|
345
|
-
const NO_FEEDBACK = [Feedback$1.configure({ feedback: "none" })];
|
|
346
|
-
/**
|
|
347
|
-
* Whether a pointer-down on `target` should NOT start an item move. Pure and
|
|
348
|
-
* exported for testing. Honors three rules, in order:
|
|
349
|
-
* - never start a move from a resize handle;
|
|
350
|
-
* - never start from a region matching `dragConfig.cancel`;
|
|
351
|
-
* - if `dragConfig.handle` is set, only start from within it.
|
|
352
|
-
*/
|
|
353
|
-
function shouldPreventItemDrag(target, cfg) {
|
|
354
|
-
if (!(target instanceof Element)) return false;
|
|
355
|
-
if (target.closest(`[data-snapgrid-resize-handle]`)) return true;
|
|
356
|
-
if (cfg?.cancel && target.closest(cfg.cancel)) return true;
|
|
357
|
-
if (cfg?.handle && !target.closest(cfg.handle)) return true;
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Sensors for item (move) draggables, built from the drag config: a distance
|
|
362
|
-
* activation threshold (so clicks don't start drags) plus handle/cancel/resize
|
|
363
|
-
* gating, with the keyboard sensor kept for accessibility.
|
|
364
|
-
*/
|
|
365
|
-
function buildItemSensors(threshold, getDragConfig) {
|
|
366
|
-
return [PointerSensor$1.configure({
|
|
367
|
-
activationConstraints: () => threshold > 0 ? [new PointerActivationConstraints.Distance({ value: threshold })] : void 0,
|
|
368
|
-
preventActivation: (event) => shouldPreventItemDrag(event.target, getDragConfig())
|
|
369
|
-
}), KeyboardSensor$1];
|
|
370
|
-
}
|
|
371
|
-
//#endregion
|
|
372
9
|
//#region src/hooks/useGridController.ts
|
|
373
10
|
const DEFAULT_HANDLES = ["se"];
|
|
374
11
|
function itemGateOpen(flag, isStatic) {
|
|
375
12
|
return isStatic ? flag === true : flag ?? true;
|
|
376
13
|
}
|
|
377
14
|
/**
|
|
378
|
-
* The grid's
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
15
|
+
* The grid's React seam: owns the {@link GridController} (an observable render
|
|
16
|
+
* bridge), publishes per-grid config to it each render, registers it for id →
|
|
17
|
+
* controller resolution, and attaches the manager-wide {@link SnapGridEngine}.
|
|
18
|
+
*
|
|
19
|
+
* The drag/resize *orchestration* lives in the engine (one per manager), not
|
|
20
|
+
* here — this hook only wires the React-specific parts: the controller, the item
|
|
21
|
+
* sensors/modifiers descriptors, the draggable/resizable gates, and the config
|
|
22
|
+
* the engine reads. Created by {@link useGridContainer}; items resolve the same
|
|
23
|
+
* controller by their `group` (= this grid's id). Consumes the ambient
|
|
382
24
|
* `DragDropProvider` — it does not mint one.
|
|
383
25
|
*/
|
|
384
26
|
function useGridController(opts) {
|
|
385
27
|
const autoId = useId();
|
|
386
28
|
const containerId = opts.id ?? autoId;
|
|
387
29
|
const gridConfig = useMemo(() => ({
|
|
388
|
-
...defaultGridConfig,
|
|
30
|
+
...defaultGridConfig$1,
|
|
389
31
|
...opts.gridConfig
|
|
390
32
|
}), [opts.gridConfig]);
|
|
391
|
-
const positionParams = useMemo(() => toPositionParams(gridConfig, opts.width), [gridConfig, opts.width]);
|
|
33
|
+
const positionParams = useMemo(() => toPositionParams$1(gridConfig, opts.width), [gridConfig, opts.width]);
|
|
392
34
|
const compactor = opts.compactor ?? verticalCompactor$1;
|
|
393
35
|
const manager = useDragDropManager();
|
|
394
36
|
const controller = useInstance((m) => new GridController(containerId, opts.layout, m ?? void 0));
|
|
@@ -398,261 +40,17 @@ function useGridController(opts) {
|
|
|
398
40
|
optsRef.current = opts;
|
|
399
41
|
const ppRef = useRef(positionParams);
|
|
400
42
|
ppRef.current = positionParams;
|
|
401
|
-
const gridRef = useRef(gridConfig);
|
|
402
|
-
gridRef.current = gridConfig;
|
|
403
|
-
const compactorRef = useRef(compactor);
|
|
404
|
-
compactorRef.current = compactor;
|
|
405
|
-
const containerIdRef = useRef(containerId);
|
|
406
|
-
containerIdRef.current = containerId;
|
|
407
|
-
const managerRef = useRef(manager);
|
|
408
|
-
managerRef.current = manager;
|
|
409
|
-
const sessionRef = useRef(null);
|
|
410
|
-
const containerElRef = useRef(null);
|
|
411
|
-
const keyboardRef = useRef(false);
|
|
412
|
-
const dropSpecRef = useRef(null);
|
|
413
|
-
const dropCounterRef = useRef(0);
|
|
414
43
|
registerController(manager, containerId, controller);
|
|
415
44
|
useEffect(() => registerController(manager, containerId, controller), [
|
|
416
45
|
manager,
|
|
417
46
|
containerId,
|
|
418
47
|
controller
|
|
419
48
|
]);
|
|
420
|
-
const committedById = useMemo(() => new Map(opts.layout.map((it) => [it.i, it])), [opts.layout]);
|
|
421
|
-
const committedByIdRef = useRef(committedById);
|
|
422
|
-
committedByIdRef.current = committedById;
|
|
423
|
-
const setSessionBoth = useCallback((next) => {
|
|
424
|
-
sessionRef.current = next;
|
|
425
|
-
controller.setSession(next);
|
|
426
|
-
}, [controller]);
|
|
427
|
-
const setKeyboard = useCallback((value) => {
|
|
428
|
-
keyboardRef.current = value;
|
|
429
|
-
controller.setKeyboard(value);
|
|
430
|
-
}, [controller]);
|
|
431
|
-
const setContainerElement = useCallback((element) => {
|
|
432
|
-
containerElRef.current = element;
|
|
433
|
-
}, []);
|
|
434
|
-
/**
|
|
435
|
-
* Is THIS grid the drop target dnd-kit's collision observer resolved? Both the
|
|
436
|
-
* move-phase preview and the drop-phase commit read `operation.target`, so they
|
|
437
|
-
* always agree on which grid wins (one oracle), including when grids overlap.
|
|
438
|
-
*/
|
|
439
|
-
const overMe = useCallback((target) => target?.id === containerIdRef.current, []);
|
|
440
|
-
/** Map a client-space pointer to a grid cell within THIS grid (see {@link receiveCell}). */
|
|
441
|
-
const cellFromPointer = useCallback((p, item) => {
|
|
442
|
-
const el = containerElRef.current;
|
|
443
|
-
if (!el) return null;
|
|
444
|
-
return receiveCell(p, el.getBoundingClientRect(), getGrabOffset(managerRef.current), item.w, item.h, ppRef.current);
|
|
445
|
-
}, []);
|
|
446
|
-
const ctx = useCallback(() => ({
|
|
447
|
-
positionParams: ppRef.current,
|
|
448
|
-
compactor: compactorRef.current,
|
|
449
|
-
cols: gridRef.current.cols
|
|
450
|
-
}), []);
|
|
451
|
-
const handleDragStart = useCallback((event) => {
|
|
452
|
-
setKeyboard(false);
|
|
453
|
-
const data = dragData(event);
|
|
454
|
-
if (!data) {
|
|
455
|
-
const spec = externalDropSpec(event.operation.source, optsRef.current.dropConfig);
|
|
456
|
-
if (spec) {
|
|
457
|
-
dropCounterRef.current += 1;
|
|
458
|
-
dropSpecRef.current = {
|
|
459
|
-
i: spec.i ?? `${containerIdRef.current}-dropped-${dropCounterRef.current}`,
|
|
460
|
-
w: spec.w,
|
|
461
|
-
h: spec.h
|
|
462
|
-
};
|
|
463
|
-
} else dropSpecRef.current = null;
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
dropSpecRef.current = null;
|
|
467
|
-
const layout = optsRef.current.layout;
|
|
468
|
-
const item = layout.find((it) => it.i === data.itemId);
|
|
469
|
-
const p = event.operation.position.current;
|
|
470
|
-
const pointer = {
|
|
471
|
-
x: p.x,
|
|
472
|
-
y: p.y
|
|
473
|
-
};
|
|
474
|
-
if (data.kind === "resize") {
|
|
475
|
-
if (!item) return;
|
|
476
|
-
setSessionBoth(beginResize(layout, {
|
|
477
|
-
item,
|
|
478
|
-
rect: calcGridItemPosition(ppRef.current, item.x, item.y, item.w, item.h),
|
|
479
|
-
pointer
|
|
480
|
-
}, data.handle));
|
|
481
|
-
optsRef.current.onResizeStart?.(layout, item, item, item, event.operation.activatorEvent, null);
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
if (item) {
|
|
485
|
-
setKeyboard(event.operation.activatorEvent instanceof KeyboardEvent);
|
|
486
|
-
const rect = calcGridItemPosition(ppRef.current, item.x, item.y, item.w, item.h);
|
|
487
|
-
setSessionBoth(beginDrag(layout, {
|
|
488
|
-
item,
|
|
489
|
-
left: rect.left,
|
|
490
|
-
top: rect.top,
|
|
491
|
-
pointer
|
|
492
|
-
}));
|
|
493
|
-
const cr = domElement(event.operation.source)?.getBoundingClientRect();
|
|
494
|
-
if (cr) setGrabOffset(managerRef.current, {
|
|
495
|
-
x: pointer.x - cr.left,
|
|
496
|
-
y: pointer.y - cr.top
|
|
497
|
-
});
|
|
498
|
-
optsRef.current.onDragStart?.(layout, item, item, item, event.operation.activatorEvent, null);
|
|
499
|
-
}
|
|
500
|
-
}, [setSessionBoth, setKeyboard]);
|
|
501
|
-
const handleDragMove = useCallback((event) => {
|
|
502
|
-
if (keyboardRef.current) return;
|
|
503
|
-
const p = event.operation.position.current;
|
|
504
|
-
const pointer = {
|
|
505
|
-
x: p.x,
|
|
506
|
-
y: p.y
|
|
507
|
-
};
|
|
508
|
-
const current = sessionRef.current;
|
|
509
|
-
if (current?.kind === "resize") {
|
|
510
|
-
const next = dragResize(current, pointer, ctx());
|
|
511
|
-
setSessionBoth(next);
|
|
512
|
-
optsRef.current.onResize?.(next.preview, next.anchor.item, next.placeholder, next.placeholder, event.operation.activatorEvent, null);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
const target = event.operation.target;
|
|
516
|
-
const data = dragData(event);
|
|
517
|
-
if (!data) {
|
|
518
|
-
const spec = dropSpecRef.current;
|
|
519
|
-
if (spec && overMe(target)) {
|
|
520
|
-
const foreign = {
|
|
521
|
-
i: spec.i,
|
|
522
|
-
x: 0,
|
|
523
|
-
y: 0,
|
|
524
|
-
w: spec.w,
|
|
525
|
-
h: spec.h
|
|
526
|
-
};
|
|
527
|
-
const committed = optsRef.current.layout;
|
|
528
|
-
const cell = cellFromPointer(pointer, foreign) ?? {
|
|
529
|
-
x: 0,
|
|
530
|
-
y: 0
|
|
531
|
-
};
|
|
532
|
-
setSessionBoth(beginReceive(committed, foreign, cell.x, cell.y, pointer, ctx()));
|
|
533
|
-
} else if (sessionRef.current) setSessionBoth(null);
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
if (data.kind !== "move") return;
|
|
537
|
-
const here = overMe(target);
|
|
538
|
-
if (committedByIdRef.current.has(data.itemId)) {
|
|
539
|
-
const source = current?.kind === "move" ? current : null;
|
|
540
|
-
if (!source) return;
|
|
541
|
-
let next;
|
|
542
|
-
if (here) next = dragTo(source, pointer, ctx());
|
|
543
|
-
else next = {
|
|
544
|
-
...source,
|
|
545
|
-
preview: source.committed,
|
|
546
|
-
placeholder: null
|
|
547
|
-
};
|
|
548
|
-
setSessionBoth(next);
|
|
549
|
-
optsRef.current.onDrag?.(next.preview, next.anchor.item, next.placeholder, next.placeholder, event.operation.activatorEvent, null);
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
if (here) {
|
|
553
|
-
const foreign = data.item;
|
|
554
|
-
const committed = optsRef.current.layout;
|
|
555
|
-
const cell = cellFromPointer(pointer, foreign) ?? {
|
|
556
|
-
x: 0,
|
|
557
|
-
y: 0
|
|
558
|
-
};
|
|
559
|
-
setSessionBoth(beginReceive(committed, foreign, cell.x, cell.y, pointer, ctx()));
|
|
560
|
-
} else if (sessionRef.current) setSessionBoth(null);
|
|
561
|
-
}, [
|
|
562
|
-
setSessionBoth,
|
|
563
|
-
overMe,
|
|
564
|
-
cellFromPointer,
|
|
565
|
-
ctx
|
|
566
|
-
]);
|
|
567
|
-
const handleDragEnd = useCallback((event) => {
|
|
568
|
-
const current = sessionRef.current;
|
|
569
|
-
const data = dragData(event);
|
|
570
|
-
const myId = containerIdRef.current;
|
|
571
|
-
const dest = dropDestination({
|
|
572
|
-
keyboard: keyboardRef.current,
|
|
573
|
-
targetId: event.operation.target?.id,
|
|
574
|
-
myId
|
|
575
|
-
});
|
|
576
|
-
const ownsItem = data ? committedByIdRef.current.has(data.itemId) : false;
|
|
577
|
-
setGrabOffset(managerRef.current, null);
|
|
578
|
-
const native = event.nativeEvent ?? null;
|
|
579
|
-
const o = optsRef.current;
|
|
580
|
-
const action = classifyDrop({
|
|
581
|
-
kind: current?.kind ?? null,
|
|
582
|
-
canceled: event.canceled,
|
|
583
|
-
ownsItem,
|
|
584
|
-
hasData: !!data,
|
|
585
|
-
dest,
|
|
586
|
-
myId
|
|
587
|
-
});
|
|
588
|
-
switch (action) {
|
|
589
|
-
case "cancel-resize":
|
|
590
|
-
o.onResizeStop?.(o.layout, current?.anchor.item ?? null, null, null, native, null);
|
|
591
|
-
break;
|
|
592
|
-
case "cancel-move":
|
|
593
|
-
o.onDragStop?.(o.layout, current?.anchor.item ?? null, null, null, native, null);
|
|
594
|
-
break;
|
|
595
|
-
case "commit-resize":
|
|
596
|
-
if (current) {
|
|
597
|
-
o.onLayoutChange?.(commitLayout(current));
|
|
598
|
-
o.onResizeStop?.(current.preview, current.anchor.item, current.placeholder, current.placeholder, native, null);
|
|
599
|
-
}
|
|
600
|
-
break;
|
|
601
|
-
case "commit-in-grid":
|
|
602
|
-
case "remove-source":
|
|
603
|
-
case "revert":
|
|
604
|
-
if (action === "commit-in-grid" && current) o.onLayoutChange?.(commitLayout(current));
|
|
605
|
-
else if (action === "remove-source" && data) {
|
|
606
|
-
const { compactor: c, cols } = ctx();
|
|
607
|
-
o.onLayoutChange?.(removeItemWithCompactor(o.layout, data.itemId, {
|
|
608
|
-
compactor: c,
|
|
609
|
-
cols
|
|
610
|
-
}));
|
|
611
|
-
}
|
|
612
|
-
o.onDragStop?.(current?.preview ?? o.layout, current?.anchor.item ?? null, current?.placeholder ?? null, current?.placeholder ?? null, native, null);
|
|
613
|
-
break;
|
|
614
|
-
case "commit-dest":
|
|
615
|
-
if (current) o.onLayoutChange?.(commitLayout(current));
|
|
616
|
-
break;
|
|
617
|
-
case "external-drop":
|
|
618
|
-
if (current) {
|
|
619
|
-
const committed = commitLayout(current);
|
|
620
|
-
const dropped = committed.find((it) => it.i === current.activeId);
|
|
621
|
-
if (dropped) o.onDrop?.(committed, dropped, native);
|
|
622
|
-
}
|
|
623
|
-
break;
|
|
624
|
-
}
|
|
625
|
-
dropSpecRef.current = null;
|
|
626
|
-
setKeyboard(false);
|
|
627
|
-
setSessionBoth(null);
|
|
628
|
-
}, [
|
|
629
|
-
setSessionBoth,
|
|
630
|
-
setKeyboard,
|
|
631
|
-
ctx
|
|
632
|
-
]);
|
|
633
49
|
useEffect(() => {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const step = arrowStep(e.key);
|
|
639
|
-
if (!step) return;
|
|
640
|
-
e.preventDefault();
|
|
641
|
-
e.stopImmediatePropagation();
|
|
642
|
-
setSessionBoth(nudge(session, step[0], step[1], ctx()));
|
|
643
|
-
};
|
|
644
|
-
window.addEventListener("keydown", onKeyDown, true);
|
|
645
|
-
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
646
|
-
}, [ctx, setSessionBoth]);
|
|
647
|
-
useDragDropMonitor(useMemo(() => ({
|
|
648
|
-
onDragStart: handleDragStart,
|
|
649
|
-
onDragMove: handleDragMove,
|
|
650
|
-
onDragEnd: handleDragEnd
|
|
651
|
-
}), [
|
|
652
|
-
handleDragStart,
|
|
653
|
-
handleDragMove,
|
|
654
|
-
handleDragEnd
|
|
655
|
-
]));
|
|
50
|
+
if (!manager) return;
|
|
51
|
+
return attachEngine(manager);
|
|
52
|
+
}, [manager]);
|
|
53
|
+
const committedById = useMemo(() => new Map(opts.layout.map((it) => [it.i, it])), [opts.layout]);
|
|
656
54
|
const dragThreshold = opts.dragConfig?.threshold ?? 3;
|
|
657
55
|
const itemSensors = useMemo(() => buildItemSensors(dragThreshold, () => optsRef.current.dragConfig), [dragThreshold]);
|
|
658
56
|
const itemModifiers = useMemo(() => [SnapToGrid.configure({
|
|
@@ -683,6 +81,25 @@ function useGridController(opts) {
|
|
|
683
81
|
]);
|
|
684
82
|
const defaultHandles = opts.resizeConfig?.handles;
|
|
685
83
|
const resizeHandlesFor = useCallback((id) => committedById.get(id)?.resizeHandles ?? defaultHandles ?? DEFAULT_HANDLES, [committedById, defaultHandles]);
|
|
84
|
+
const callbacks = useMemo(() => ({
|
|
85
|
+
onDragStart: opts.onDragStart,
|
|
86
|
+
onDrag: opts.onDrag,
|
|
87
|
+
onDragStop: opts.onDragStop,
|
|
88
|
+
onResizeStart: opts.onResizeStart,
|
|
89
|
+
onResize: opts.onResize,
|
|
90
|
+
onResizeStop: opts.onResizeStop,
|
|
91
|
+
onLayoutChange: opts.onLayoutChange,
|
|
92
|
+
onDrop: opts.onDrop
|
|
93
|
+
}), [
|
|
94
|
+
opts.onDragStart,
|
|
95
|
+
opts.onDrag,
|
|
96
|
+
opts.onDragStop,
|
|
97
|
+
opts.onResizeStart,
|
|
98
|
+
opts.onResize,
|
|
99
|
+
opts.onResizeStop,
|
|
100
|
+
opts.onLayoutChange,
|
|
101
|
+
opts.onDrop
|
|
102
|
+
]);
|
|
686
103
|
controller.setConfig({
|
|
687
104
|
positionParams,
|
|
688
105
|
gridConfig,
|
|
@@ -693,7 +110,10 @@ function useGridController(opts) {
|
|
|
693
110
|
isItemDraggable,
|
|
694
111
|
isItemResizable,
|
|
695
112
|
resizeHandlesFor,
|
|
696
|
-
|
|
113
|
+
compactor,
|
|
114
|
+
dragConfig: opts.dragConfig,
|
|
115
|
+
dropConfig: opts.dropConfig,
|
|
116
|
+
callbacks
|
|
697
117
|
});
|
|
698
118
|
return controller;
|
|
699
119
|
}
|
|
@@ -713,7 +133,7 @@ function containerHeight(rows, grid) {
|
|
|
713
133
|
*/
|
|
714
134
|
function useGridContainer(opts) {
|
|
715
135
|
const controller = useGridController(opts);
|
|
716
|
-
const { width, autoSize, gridConfig
|
|
136
|
+
const { width, autoSize, gridConfig } = controller.config;
|
|
717
137
|
const gridElRef = useRef(null);
|
|
718
138
|
const { ref, isDropTarget } = useDroppable$1({
|
|
719
139
|
id: controller.id,
|
|
@@ -722,16 +142,17 @@ function useGridContainer(opts) {
|
|
|
722
142
|
const srcEl = domElement(source);
|
|
723
143
|
if (srcEl && gridElRef.current && srcEl.contains(gridElRef.current)) return false;
|
|
724
144
|
if (source.type === "grid-item") return true;
|
|
725
|
-
|
|
145
|
+
if (source.data?.snapGridDrop != null) return true;
|
|
146
|
+
return opts.accept?.(source) ?? false;
|
|
726
147
|
},
|
|
727
148
|
collisionDetector: gridCollisionDetector
|
|
728
149
|
});
|
|
729
150
|
const setRef = useCallback((element) => {
|
|
730
151
|
ref(element);
|
|
731
|
-
|
|
152
|
+
controller.element = element;
|
|
732
153
|
gridElRef.current = element;
|
|
733
154
|
if (element) element.setAttribute(SNAPGRID_GRID_ATTR, "");
|
|
734
|
-
}, [ref,
|
|
155
|
+
}, [ref, controller]);
|
|
735
156
|
const renderedLayout = useSyncExternalStore(controller.subscribe, controller.renderedSnapshot, controller.renderedSnapshot);
|
|
736
157
|
return {
|
|
737
158
|
containerProps: {
|
|
@@ -750,6 +171,7 @@ function useGridContainer(opts) {
|
|
|
750
171
|
}
|
|
751
172
|
const REFLOW_EASING = "ease";
|
|
752
173
|
const REFLOW_TRANSITION = `transform 150ms ${REFLOW_EASING}, width 150ms ${REFLOW_EASING}, height 150ms ${REFLOW_EASING}`;
|
|
174
|
+
const TILE_TRANSITION = `width 150ms ${REFLOW_EASING}, height 150ms ${REFLOW_EASING}`;
|
|
753
175
|
//#endregion
|
|
754
176
|
//#region src/hooks/useResolveController.ts
|
|
755
177
|
/**
|
|
@@ -770,6 +192,7 @@ const ITEM_FEEDBACK = [Feedback$1.configure({
|
|
|
770
192
|
feedback: (_source, manager) => isKeyboardEvent(manager.dragOperation.activatorEvent) ? "none" : "default",
|
|
771
193
|
dropAnimation: null
|
|
772
194
|
})];
|
|
195
|
+
const tileNeverTarget = () => null;
|
|
773
196
|
/**
|
|
774
197
|
* Headless hook for a single grid tile. The tile is a real `useSortable` (a
|
|
775
198
|
* draggable + droppable carrying `group`/`index`/`type`/`accept`), so it
|
|
@@ -780,10 +203,18 @@ const ITEM_FEEDBACK = [Feedback$1.configure({
|
|
|
780
203
|
* element you render — you own the tag, className, content, and cosmetic styling.
|
|
781
204
|
*
|
|
782
205
|
* The dragged tile floats itself via dnd-kit's default feedback (no `<DragOverlay>`):
|
|
783
|
-
* the active tile renders at its committed origin
|
|
784
|
-
* reflow is animated on the compositor via the Web
|
|
785
|
-
* smooth in Safari, where the float's
|
|
786
|
-
* CSS-transition reflow.
|
|
206
|
+
* the active tile renders at its committed origin and dnd-kit's float follows the
|
|
207
|
+
* pointer from there, while reflow is animated on the compositor via the Web
|
|
208
|
+
* Animations API (a FLIP) — both so it stays smooth in Safari, where the float's
|
|
209
|
+
* popover top-layer repaint would jank a CSS-transition reflow.
|
|
210
|
+
*
|
|
211
|
+
* Tiles are positioned with `left`/`top` (not `transform`). dnd-kit's self-float
|
|
212
|
+
* measures the source element's rect ignoring transforms and re-applies its current
|
|
213
|
+
* transform each frame; a transform-positioned tile leans on that, but the
|
|
214
|
+
* compensation is lost the instant the dragged element is swapped for a foreign one
|
|
215
|
+
* mid-drag (grid → sortable interop — the tile becomes a flow card), which would
|
|
216
|
+
* make the float jump by the tile's grid offset. Plain left/top has nothing to lose
|
|
217
|
+
* on the swap, matching how dnd-kit's own flow-positioned sortables hand off cleanly.
|
|
787
218
|
*/
|
|
788
219
|
function useGridItem(id, group) {
|
|
789
220
|
const controller = useResolveController(group);
|
|
@@ -798,14 +229,20 @@ function useGridItem(id, group) {
|
|
|
798
229
|
const data = useMemo(() => ({ snapGrid: {
|
|
799
230
|
kind: "move",
|
|
800
231
|
itemId: id,
|
|
801
|
-
item
|
|
802
|
-
|
|
232
|
+
item,
|
|
233
|
+
group
|
|
234
|
+
} }), [
|
|
235
|
+
id,
|
|
236
|
+
item,
|
|
237
|
+
group
|
|
238
|
+
]);
|
|
803
239
|
const { ref: sortableRef, handleRef, isDragging } = useSortable({
|
|
804
240
|
id,
|
|
805
241
|
index: controller.itemIndex(id),
|
|
806
242
|
group,
|
|
807
243
|
type: "grid-item",
|
|
808
244
|
accept: "grid-item",
|
|
245
|
+
collisionDetector: tileNeverTarget,
|
|
809
246
|
disabled: !config.isItemDraggable(id),
|
|
810
247
|
sensors: config.itemSensors,
|
|
811
248
|
modifiers: config.itemModifiers,
|
|
@@ -825,6 +262,7 @@ function useGridItem(id, group) {
|
|
|
825
262
|
const posTop = pos?.top;
|
|
826
263
|
const prev = useRef(null);
|
|
827
264
|
const reflowAnim = useRef(null);
|
|
265
|
+
const settleAnchor = useRef(null);
|
|
828
266
|
useLayoutEffect(() => {
|
|
829
267
|
const cur = posLeft != null && posTop != null ? {
|
|
830
268
|
left: posLeft,
|
|
@@ -833,17 +271,31 @@ function useGridItem(id, group) {
|
|
|
833
271
|
const before = prev.current;
|
|
834
272
|
prev.current = cur;
|
|
835
273
|
const el = elRef.current;
|
|
836
|
-
if (!el || !cur
|
|
274
|
+
if (!el || !cur) return;
|
|
275
|
+
if (active) {
|
|
276
|
+
settleAnchor.current = cur;
|
|
277
|
+
reflowAnim.current?.cancel();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (dragging) settleAnchor.current = null;
|
|
281
|
+
else if (settleAnchor.current) {
|
|
282
|
+
const a = settleAnchor.current;
|
|
283
|
+
reflowAnim.current?.cancel();
|
|
284
|
+
if (cur.left !== a.left || cur.top !== a.top) settleAnchor.current = null;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!before || justDropped) return;
|
|
837
288
|
if (before.left === cur.left && before.top === cur.top) return;
|
|
289
|
+
if (typeof el.animate !== "function") return;
|
|
838
290
|
let fromX = before.left;
|
|
839
291
|
let fromY = before.top;
|
|
840
292
|
if (reflowAnim.current?.playState === "running") {
|
|
841
293
|
const m = new DOMMatrix(getComputedStyle(el).transform);
|
|
842
|
-
fromX = m.m41;
|
|
843
|
-
fromY = m.m42;
|
|
294
|
+
fromX = before.left + m.m41;
|
|
295
|
+
fromY = before.top + m.m42;
|
|
844
296
|
}
|
|
845
297
|
reflowAnim.current?.cancel();
|
|
846
|
-
reflowAnim.current = el.animate([{ transform: `translate(${fromX}px, ${fromY}px)` }, { transform:
|
|
298
|
+
reflowAnim.current = el.animate([{ transform: `translate(${fromX - cur.left}px, ${fromY - cur.top}px)` }, { transform: "translate(0px, 0px)" }], {
|
|
847
299
|
duration: 150,
|
|
848
300
|
easing: REFLOW_EASING
|
|
849
301
|
});
|
|
@@ -860,12 +312,11 @@ function useGridItem(id, group) {
|
|
|
860
312
|
handleRef,
|
|
861
313
|
style: pos ? {
|
|
862
314
|
position: "absolute",
|
|
863
|
-
left:
|
|
864
|
-
top:
|
|
315
|
+
left: pos.left,
|
|
316
|
+
top: pos.top,
|
|
865
317
|
width: pos.width,
|
|
866
318
|
height: pos.height,
|
|
867
|
-
|
|
868
|
-
transition: active || justDropped || dragging ? "none" : REFLOW_TRANSITION,
|
|
319
|
+
transition: justDropped || dragging ? "none" : TILE_TRANSITION,
|
|
869
320
|
touchAction: "none"
|
|
870
321
|
} : {
|
|
871
322
|
position: "absolute",
|
|
@@ -917,7 +368,8 @@ function useGridResizeHandle(itemId, handle, group) {
|
|
|
917
368
|
data: { snapGrid: {
|
|
918
369
|
kind: "resize",
|
|
919
370
|
itemId,
|
|
920
|
-
handle
|
|
371
|
+
handle,
|
|
372
|
+
group
|
|
921
373
|
} }
|
|
922
374
|
});
|
|
923
375
|
const { isResizing } = useSyncExternalStore(controller.subscribe, () => controller.resizeSnapshot(itemId), () => controller.resizeSnapshot(itemId));
|
|
@@ -1231,6 +683,6 @@ function useContainerWidth(options = {}) {
|
|
|
1231
683
|
};
|
|
1232
684
|
}
|
|
1233
685
|
//#endregion
|
|
1234
|
-
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 };
|
|
686
|
+
export { DEFAULT_BREAKPOINTS, DEFAULT_BREAKPOINT_COLS, DragOverlay, Feedback, GridItem, GridLayout, GridPlaceholder, KeyboardSensor, PointerSensor, ResponsiveGridLayout, SnapGridGroup, defaultGridConfig, getCompactor, horizontalCompactor, insertItemWithCompactor, noCompactor, removeItemWithCompactor, snapMove, toPositionParams, useContainerWidth, useDraggable, useDroppable, useGridContainer, useGridItem, useGridPlaceholder, useGridResizeHandle, useResponsiveLayout, verticalCompactor };
|
|
1235
687
|
|
|
1236
688
|
//# sourceMappingURL=index.mjs.map
|