@kywi-software/core 0.8.0 → 0.10.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/dist/admin/audience-list.d.ts.map +1 -1
- package/dist/admin/audience-list.js +9 -0
- package/dist/admin/audience-list.js.map +1 -1
- package/dist/admin/content-form.d.ts.map +1 -1
- package/dist/admin/content-form.js +7 -1
- package/dist/admin/content-form.js.map +1 -1
- package/dist/admin/layout-editor/column-overlay.d.ts +10 -1
- package/dist/admin/layout-editor/column-overlay.d.ts.map +1 -1
- package/dist/admin/layout-editor/column-overlay.js +8 -2
- package/dist/admin/layout-editor/column-overlay.js.map +1 -1
- package/dist/admin/layout-editor/component-actions.js +1 -1
- package/dist/admin/layout-editor/component-actions.js.map +1 -1
- package/dist/admin/layout-editor/drag-feedback.d.ts +75 -0
- package/dist/admin/layout-editor/drag-feedback.d.ts.map +1 -0
- package/dist/admin/layout-editor/drag-feedback.js +60 -0
- package/dist/admin/layout-editor/drag-feedback.js.map +1 -0
- package/dist/admin/layout-editor/drag-utils.d.ts +106 -8
- package/dist/admin/layout-editor/drag-utils.d.ts.map +1 -1
- package/dist/admin/layout-editor/drag-utils.js +136 -24
- package/dist/admin/layout-editor/drag-utils.js.map +1 -1
- package/dist/admin/layout-editor/drop-indicator.d.ts +18 -1
- package/dist/admin/layout-editor/drop-indicator.d.ts.map +1 -1
- package/dist/admin/layout-editor/drop-indicator.js +23 -3
- package/dist/admin/layout-editor/drop-indicator.js.map +1 -1
- package/dist/admin/layout-editor/editor-canvas.d.ts +11 -1
- package/dist/admin/layout-editor/editor-canvas.d.ts.map +1 -1
- package/dist/admin/layout-editor/editor-canvas.js +109 -6
- package/dist/admin/layout-editor/editor-canvas.js.map +1 -1
- package/dist/admin/layout-editor/editor-state.d.ts +46 -0
- package/dist/admin/layout-editor/editor-state.d.ts.map +1 -1
- package/dist/admin/layout-editor/editor-state.js +40 -3
- package/dist/admin/layout-editor/editor-state.js.map +1 -1
- package/dist/admin/layout-editor/editor.css +19 -6
- package/dist/admin/layout-editor/generate-id.d.ts +23 -0
- package/dist/admin/layout-editor/generate-id.d.ts.map +1 -0
- package/dist/admin/layout-editor/generate-id.js +34 -0
- package/dist/admin/layout-editor/generate-id.js.map +1 -0
- package/dist/admin/layout-editor/layout-editor.d.ts +8 -1
- package/dist/admin/layout-editor/layout-editor.d.ts.map +1 -1
- package/dist/admin/layout-editor/layout-editor.js +66 -13
- package/dist/admin/layout-editor/layout-editor.js.map +1 -1
- package/dist/admin/layout-editor/module-overlay.d.ts.map +1 -1
- package/dist/admin/layout-editor/module-overlay.js +7 -2
- package/dist/admin/layout-editor/module-overlay.js.map +1 -1
- package/dist/admin/layout-editor/overlay-shell.d.ts +11 -3
- package/dist/admin/layout-editor/overlay-shell.d.ts.map +1 -1
- package/dist/admin/layout-editor/overlay-shell.js +93 -50
- package/dist/admin/layout-editor/overlay-shell.js.map +1 -1
- package/dist/admin/layout-editor/section-variant-container-config.js +1 -1
- package/dist/admin/layout-editor/section-variant-container-config.js.map +1 -1
- package/dist/admin/layout-editor/use-editor-dnd.d.ts +79 -0
- package/dist/admin/layout-editor/use-editor-dnd.d.ts.map +1 -0
- package/dist/admin/layout-editor/use-editor-dnd.js +373 -0
- package/dist/admin/layout-editor/use-editor-dnd.js.map +1 -0
- package/dist/admin/styles/admin.css +8 -0
- package/dist/admin/surfaces/layouts/layout-editor-client.d.ts.map +1 -1
- package/dist/admin/surfaces/layouts/layout-editor-client.js +12 -1
- package/dist/admin/surfaces/layouts/layout-editor-client.js.map +1 -1
- package/dist/api/content-routes.d.ts.map +1 -1
- package/dist/api/content-routes.js +34 -7
- package/dist/api/content-routes.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +7 -73
- package/dist/api/index.js.map +1 -1
- package/dist/cache/content-invalidation.d.ts +28 -0
- package/dist/cache/content-invalidation.d.ts.map +1 -0
- package/dist/cache/content-invalidation.js +72 -0
- package/dist/cache/content-invalidation.js.map +1 -0
- package/dist/db/content-restore.d.ts +5 -1
- package/dist/db/content-restore.d.ts.map +1 -1
- package/dist/db/content-restore.js +9 -2
- package/dist/db/content-restore.js.map +1 -1
- package/dist/layout/modules.d.ts.map +1 -1
- package/dist/layout/modules.js +52 -2
- package/dist/layout/modules.js.map +1 -1
- package/dist/layout/registry.d.ts +6 -0
- package/dist/layout/registry.d.ts.map +1 -1
- package/dist/layout/registry.js +21 -5
- package/dist/layout/registry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { PointerSensor, KeyboardSensor, KeyboardCode, useSensors, useSensor, closestCorners, getFirstCollision, } from '@dnd-kit/core';
|
|
4
|
+
import { sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
5
|
+
import { createDwellTimer, editorCollisionDetection, resolveDragEndAction, resolvePaletteDropSlot, } from './drag-utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* How long a drag has to hold one slot before the thin line grows into a
|
|
8
|
+
* module-sized placeholder. Long enough that sweeping across a column doesn't
|
|
9
|
+
* make every slot bloom in turn; short enough that pausing where you mean to
|
|
10
|
+
* drop shows you the room the module will take.
|
|
11
|
+
*/
|
|
12
|
+
const DWELL_MS = 600;
|
|
13
|
+
/**
|
|
14
|
+
* The dwell timer's identity for one drop slot. Column AND index, because
|
|
15
|
+
* moving between two slots of the same column is a change of destination and
|
|
16
|
+
* has to restart the escalation just like crossing into another column does.
|
|
17
|
+
*/
|
|
18
|
+
function zoneKey(columnId, index) {
|
|
19
|
+
return `${columnId}:${index}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The screen-reader subject of a drag: the registry label, for both a
|
|
23
|
+
* palette item and a canvas reorder — `ModuleOverlay`'s `useSortable` data
|
|
24
|
+
* carries `moduleType` too (kywi-cms#139 review), so both sources resolve the
|
|
25
|
+
* same way now. The generic fallback only fires if drag data is ever missing
|
|
26
|
+
* `moduleType` altogether (there is no such caller today; kept for safety).
|
|
27
|
+
*/
|
|
28
|
+
function describeSubject(data, labelFor) {
|
|
29
|
+
if (data?.moduleType)
|
|
30
|
+
return labelFor(data.moduleType);
|
|
31
|
+
return 'the module';
|
|
32
|
+
}
|
|
33
|
+
/** Upper-cases the first character, for `describeSubject`'s output at the
|
|
34
|
+
* start of a sentence (`onDragOver`/`onDragEnd`) — a registry label is
|
|
35
|
+
* already capitalized ("Heading"), so this only changes anything for the
|
|
36
|
+
* generic "the module" fallback ("The module …"). */
|
|
37
|
+
function capitalize(text) {
|
|
38
|
+
return text.length ? text[0].toUpperCase() + text.slice(1) : text;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Where a drag is over, or would drop, in words — position only, never the
|
|
42
|
+
* column's own id. Production column ids are `crypto.randomUUID()`, so
|
|
43
|
+
* speaking one (`column 3f2a9c1e-4b7d-4a1e-9c2f-8ab0d5e61a77`) would read 36
|
|
44
|
+
* hex-and-dash characters aloud on every arrow press (kywi-cms#139 review;
|
|
45
|
+
* caught late because the original tests' `col-1`/`col-2` fixtures read fine
|
|
46
|
+
* spoken and masked it). The announcement callbacks only receive `{ active,
|
|
47
|
+
* over }` (dnd-kit's `Accessibility.Arguments`) — no droppable list, no
|
|
48
|
+
* ordinal — so "column 2 of 3" isn't available without threading new layout
|
|
49
|
+
* state into the hook just to answer a screen reader once per keypress; the
|
|
50
|
+
* per-column position already on `over.data` (see `EditorDragDatum`) is.
|
|
51
|
+
* That index is deliberately the *declared* slot, not the pointer-corrected
|
|
52
|
+
* one `resolveDragEndAction` computes for a mouse drop: a keyboard drag has
|
|
53
|
+
* no pointer at all, so the declared slot is the only honest answer to
|
|
54
|
+
* "where does the screen reader say this is" that a keyboard user's own
|
|
55
|
+
* arrow presses actually produced.
|
|
56
|
+
*/
|
|
57
|
+
function describeLocation(over) {
|
|
58
|
+
const data = over?.data.current;
|
|
59
|
+
if (!data?.columnId)
|
|
60
|
+
return null;
|
|
61
|
+
const index = data.index ?? 0;
|
|
62
|
+
return `this column, position ${index + 1}`;
|
|
63
|
+
}
|
|
64
|
+
/** Arrow-key codes `sortableKeyboardCoordinates` (and this function) act on. */
|
|
65
|
+
const KEYBOARD_DIRECTIONS = [
|
|
66
|
+
KeyboardCode.Down,
|
|
67
|
+
KeyboardCode.Up,
|
|
68
|
+
KeyboardCode.Left,
|
|
69
|
+
KeyboardCode.Right,
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* kywi-cms#139: `sortableKeyboardCoordinates` — the repo's standing
|
|
73
|
+
* coordinateGetter for every other keyboard drag (audience-list.tsx,
|
|
74
|
+
* site-tree.tsx, site-tree-table.tsx, menu-editor.tsx) — turned out to be
|
|
75
|
+
* silently inert for a palette item. Verified by hand: Tab to a palette item,
|
|
76
|
+
* Space to pick it up, arrow keys did *nothing* — no drop indicator, no
|
|
77
|
+
* screen-reader "over" announcement, and Space dropped it "not over a drop
|
|
78
|
+
* target" every time.
|
|
79
|
+
*
|
|
80
|
+
* The cause is in dnd-kit's own implementation, not this repo: it resolves
|
|
81
|
+
* the dragged item's OWN slot via `droppableContainers.get(active.id)`, to
|
|
82
|
+
* offset the move against where the item already sits in a shared sortable
|
|
83
|
+
* list. Every other caller drags a `useSortable` row — which, being sortable,
|
|
84
|
+
* is also its own droppable, so that lookup succeeds. A palette item
|
|
85
|
+
* (module-palette.tsx) is a plain `useDraggable` with no droppable
|
|
86
|
+
* counterpart of its own; the lookup misses, an internal guard silently
|
|
87
|
+
* fails, and the getter returns nothing — dnd-kit never even calls
|
|
88
|
+
* `onMove`, so no re-render, no collision recompute, no announcement.
|
|
89
|
+
*
|
|
90
|
+
* Delegate to the real thing whenever the active item IS also a droppable —
|
|
91
|
+
* a canvas reorder (module-overlay.tsx's `useSortable`) behaves exactly as
|
|
92
|
+
* it did before this existed. Only when that lookup misses (a palette item)
|
|
93
|
+
* does this take over, with the same direction-filter-then-closestCorners
|
|
94
|
+
* dnd-kit's own version uses, landing the drag on the target droppable's
|
|
95
|
+
* top-left corner rather than computing a same-list offset that doesn't
|
|
96
|
+
* apply here. That is enough: `resolveDragEndAction`/`resolvePaletteDropSlot`
|
|
97
|
+
* read the drop from `over.data`, not from the translate's exact pixels, so
|
|
98
|
+
* a keyboard move only has to make `over` become the right droppable — which
|
|
99
|
+
* this does — not reproduce dnd-kit's reorder-preview polish.
|
|
100
|
+
*/
|
|
101
|
+
export const paletteAwareKeyboardCoordinates = (event, args) => {
|
|
102
|
+
const { context } = args;
|
|
103
|
+
if (context.droppableContainers.get(args.active)) {
|
|
104
|
+
return sortableKeyboardCoordinates(event, args);
|
|
105
|
+
}
|
|
106
|
+
if (!KEYBOARD_DIRECTIONS.includes(event.code))
|
|
107
|
+
return undefined;
|
|
108
|
+
// Hoisted here (dnd-kit's own getter does the same): an arrow press that
|
|
109
|
+
// resolves no candidate below still must not fall through to the browser's
|
|
110
|
+
// native page-scroll mid-drag — that would shift `scrollAdjustedTranslate`
|
|
111
|
+
// and could move `over` without the owner having pressed anything meant to
|
|
112
|
+
// move it (kywi-cms#139 review).
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
const { active, collisionRect, droppableRects, droppableContainers, over } = context;
|
|
115
|
+
if (!active || !collisionRect)
|
|
116
|
+
return undefined;
|
|
117
|
+
const candidates = [];
|
|
118
|
+
droppableContainers.getEnabled().forEach((entry) => {
|
|
119
|
+
if (!entry || entry.disabled)
|
|
120
|
+
return;
|
|
121
|
+
const rect = droppableRects.get(entry.id);
|
|
122
|
+
if (!rect)
|
|
123
|
+
return;
|
|
124
|
+
switch (event.code) {
|
|
125
|
+
case KeyboardCode.Down:
|
|
126
|
+
if (collisionRect.top < rect.top)
|
|
127
|
+
candidates.push(entry);
|
|
128
|
+
break;
|
|
129
|
+
case KeyboardCode.Up:
|
|
130
|
+
if (collisionRect.top > rect.top)
|
|
131
|
+
candidates.push(entry);
|
|
132
|
+
break;
|
|
133
|
+
case KeyboardCode.Left:
|
|
134
|
+
if (collisionRect.left > rect.left)
|
|
135
|
+
candidates.push(entry);
|
|
136
|
+
break;
|
|
137
|
+
case KeyboardCode.Right:
|
|
138
|
+
if (collisionRect.left < rect.left)
|
|
139
|
+
candidates.push(entry);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const collisions = closestCorners({
|
|
144
|
+
active,
|
|
145
|
+
collisionRect,
|
|
146
|
+
droppableRects,
|
|
147
|
+
droppableContainers: candidates,
|
|
148
|
+
pointerCoordinates: null,
|
|
149
|
+
});
|
|
150
|
+
let closestId = getFirstCollision(collisions, 'id');
|
|
151
|
+
// Same "don't re-announce where we already are" rule as dnd-kit's own
|
|
152
|
+
// version: if the nearest candidate is the droppable we're already over,
|
|
153
|
+
// and there is a second one, prefer that — otherwise repeated presses in
|
|
154
|
+
// the same direction would never move past the first neighbour.
|
|
155
|
+
if (closestId === over?.id && collisions.length > 1)
|
|
156
|
+
closestId = collisions[1]?.id ?? null;
|
|
157
|
+
if (closestId == null)
|
|
158
|
+
return undefined;
|
|
159
|
+
const targetRect = droppableRects.get(closestId);
|
|
160
|
+
if (!targetRect)
|
|
161
|
+
return undefined;
|
|
162
|
+
return { x: targetRect.left, y: targetRect.top };
|
|
163
|
+
};
|
|
164
|
+
export function useEditorDnd({ registry, dispatch }) {
|
|
165
|
+
const sensors = useSensors(
|
|
166
|
+
// kywi-cms#134: without a distance constraint a bare CLICK on a palette
|
|
167
|
+
// item counts as a drag that starts and ends on the same spot, so dnd-kit
|
|
168
|
+
// resolves a drop target under the cursor and the editor silently inserts
|
|
169
|
+
// a module the user never dragged anywhere. Requiring 5px of travel makes
|
|
170
|
+
// a click a no-op and leaves a real drag untouched.
|
|
171
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
172
|
+
// kywi-cms#139: without a coordinateGetter, dnd-kit's KeyboardSensor has no
|
|
173
|
+
// way to translate an arrow-key press into a position at all — Tab could
|
|
174
|
+
// focus a palette item and Space could "pick it up", but arrows did
|
|
175
|
+
// nothing and the drag could never move. See `paletteAwareKeyboardCoordinates`
|
|
176
|
+
// above for why the repo's usual `sortableKeyboardCoordinates` doesn't
|
|
177
|
+
// reach a palette item on its own.
|
|
178
|
+
useSensor(KeyboardSensor, { coordinateGetter: paletteAwareKeyboardCoordinates }));
|
|
179
|
+
/**
|
|
180
|
+
* The pointer's live viewport position, in the same coordinate space
|
|
181
|
+
* `getBoundingClientRect()` reports. dnd-kit computes exactly this value on
|
|
182
|
+
* every frame of a drag and hands it to collision detection, so we take it
|
|
183
|
+
* from there rather than deriving our own.
|
|
184
|
+
*
|
|
185
|
+
* kywi-cms#134: the obvious derivation — `activatorEvent.clientY +
|
|
186
|
+
* event.delta.y` — is wrong whenever the page scrolls mid-drag. dnd-kit 6.3.1
|
|
187
|
+
* sets `DragEndEvent.delta` to its `scrollAdjustedTranslate` (the pointer's
|
|
188
|
+
* translation *plus* every ancestor scroll accumulated since the drag began),
|
|
189
|
+
* while `pointerCoordinates` is `activationCoordinates + translate` with no
|
|
190
|
+
* scroll term. Column and module rects are already scroll-current, so adding
|
|
191
|
+
* the delta double-counts the scroll and lands the module one or two slots off
|
|
192
|
+
* — and autoScroll is on by default in both shells, so dragging from the fixed
|
|
193
|
+
* palette rail down a long in-place page scrolls by construction.
|
|
194
|
+
*
|
|
195
|
+
* Per hook instance, never module-global: two editors on one page must not
|
|
196
|
+
* share a pointer. A keyboard drag has no coordinates at all (dnd-kit's
|
|
197
|
+
* `getEventCoordinates` returns null for a KeyboardEvent), so this stays null
|
|
198
|
+
* and `resolveDragEndAction` keeps the droppable's declared index.
|
|
199
|
+
*/
|
|
200
|
+
const pointerRef = useRef(null);
|
|
201
|
+
const collisionDetection = useCallback((args) => {
|
|
202
|
+
pointerRef.current = args.pointerCoordinates;
|
|
203
|
+
return editorCollisionDetection(args);
|
|
204
|
+
}, []);
|
|
205
|
+
// ── Drag feedback (kywi-cms#134) ──────────────────────────────────────────
|
|
206
|
+
// A drag used to be entirely silent: no ghost on the cursor, no mark on the
|
|
207
|
+
// canvas. The hook already computes everything needed to say what is moving
|
|
208
|
+
// and where it would land, so it now publishes both.
|
|
209
|
+
const [activeDrag, setActiveDrag] = useState(null);
|
|
210
|
+
const [dropTarget, setDropTarget] = useState(null);
|
|
211
|
+
/**
|
|
212
|
+
* The line -> placeholder escalation timer. One per hook instance, created
|
|
213
|
+
* lazily on first render and never re-created: a fresh timer each render
|
|
214
|
+
* would drop the pending escalation on every drag frame, and a module-global
|
|
215
|
+
* one would be shared by two editors on the same page.
|
|
216
|
+
*/
|
|
217
|
+
const dwellRef = useRef(null);
|
|
218
|
+
if (dwellRef.current === null) {
|
|
219
|
+
dwellRef.current = createDwellTimer(DWELL_MS, (zoneId) => {
|
|
220
|
+
// Escalate only if the drag is still on the zone the timer was armed for
|
|
221
|
+
// — a move that lands elsewhere in the same tick must not be overwritten.
|
|
222
|
+
setDropTarget(prev => prev && zoneKey(prev.columnId, prev.index) === zoneId
|
|
223
|
+
? { ...prev, feedback: 'placeholder' }
|
|
224
|
+
: prev);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const dwell = dwellRef.current;
|
|
228
|
+
// The timer outlives a render but not the component: an editor unmounted
|
|
229
|
+
// mid-drag (navigating away from the Layout tab) must not fire into a
|
|
230
|
+
// setState on a dead tree.
|
|
231
|
+
useEffect(() => () => { dwellRef.current?.destroy(); }, []);
|
|
232
|
+
/**
|
|
233
|
+
* The human name for a module type. Read from the dragged item's own data on
|
|
234
|
+
* every frame rather than from `activeDrag` state: within one dnd-kit tick
|
|
235
|
+
* the start handler's `setActiveDrag` has not rendered yet, so a move that
|
|
236
|
+
* arrived in the same tick would see a stale (null) label.
|
|
237
|
+
*/
|
|
238
|
+
const labelFor = useCallback((moduleType) => registry?.get(moduleType)?.label ?? moduleType, [registry]);
|
|
239
|
+
// ── Screen-reader announcements (kywi-cms#139) ────────────────────────────
|
|
240
|
+
// A keyboard drag is otherwise silent: sighted mouse users see the ghost and
|
|
241
|
+
// drop indicator this hook already publishes above, but a screen-reader
|
|
242
|
+
// user picking a palette item up with Space and moving it with arrow keys
|
|
243
|
+
// gets no feedback that anything happened at all. dnd-kit renders whatever
|
|
244
|
+
// these return into its own visually-hidden live region.
|
|
245
|
+
const announcements = useMemo(() => ({
|
|
246
|
+
onDragStart({ active }) {
|
|
247
|
+
const data = active.data.current;
|
|
248
|
+
return `Picked up ${describeSubject(data, labelFor)}.`;
|
|
249
|
+
},
|
|
250
|
+
onDragOver({ active, over }) {
|
|
251
|
+
const data = active.data.current;
|
|
252
|
+
const subject = capitalize(describeSubject(data, labelFor));
|
|
253
|
+
const location = describeLocation(over);
|
|
254
|
+
return location
|
|
255
|
+
? `${subject} is over ${location}.`
|
|
256
|
+
: `${subject} is no longer over a drop target.`;
|
|
257
|
+
},
|
|
258
|
+
onDragEnd({ active, over }) {
|
|
259
|
+
const data = active.data.current;
|
|
260
|
+
const subject = capitalize(describeSubject(data, labelFor));
|
|
261
|
+
const location = describeLocation(over);
|
|
262
|
+
return location
|
|
263
|
+
? `${subject} was dropped over ${location}.`
|
|
264
|
+
: `${subject} was dropped, but not over a drop target.`;
|
|
265
|
+
},
|
|
266
|
+
onDragCancel({ active }) {
|
|
267
|
+
const data = active.data.current;
|
|
268
|
+
return `${capitalize(describeSubject(data, labelFor))} drag was cancelled.`;
|
|
269
|
+
},
|
|
270
|
+
}), [labelFor]);
|
|
271
|
+
// Stable identity like `announcements` itself: DndContext takes this whole
|
|
272
|
+
// object as a prop, and dnd-kit's own Accessibility component re-subscribes
|
|
273
|
+
// its live region whenever it changes.
|
|
274
|
+
const accessibility = useMemo(() => ({ announcements }), [announcements]);
|
|
275
|
+
/** Stand the whole feedback layer down — no ghost, no indicator, no timer. */
|
|
276
|
+
const clearFeedback = useCallback(() => {
|
|
277
|
+
dwell.reset();
|
|
278
|
+
setActiveDrag(null);
|
|
279
|
+
setDropTarget(null);
|
|
280
|
+
}, [dwell]);
|
|
281
|
+
const handleDragStart = useCallback((event) => {
|
|
282
|
+
// Start clean: a projection left over from the previous drag would be drawn
|
|
283
|
+
// for the frames before this drag's first move lands.
|
|
284
|
+
dwell.reset();
|
|
285
|
+
setDropTarget(null);
|
|
286
|
+
const data = event.active.data.current;
|
|
287
|
+
// Palette-only. A canvas reorder already has dnd-kit's own sortable preview
|
|
288
|
+
// — the item lifts and its neighbours shuffle — so a second ghost of the
|
|
289
|
+
// same module would just be a duplicate chasing the cursor.
|
|
290
|
+
if (data?.source !== 'palette' || !data.moduleType) {
|
|
291
|
+
setActiveDrag(null);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const moduleType = data.moduleType;
|
|
295
|
+
setActiveDrag({ moduleType, label: labelFor(moduleType) });
|
|
296
|
+
}, [dwell, labelFor]);
|
|
297
|
+
const handleDragMove = useCallback((event) => {
|
|
298
|
+
const activeData = event.active.data.current;
|
|
299
|
+
// Palette-only, deliberately (kywi-cms#134). During a CANVAS reorder the
|
|
300
|
+
// dragged module is itself a `[data-kywi-module-slot]` carrying a live
|
|
301
|
+
// dnd-kit transform, so its `getBoundingClientRect()` reports where it has
|
|
302
|
+
// been dragged TO — measuring the column would measure the moved element
|
|
303
|
+
// against itself and project a slot that shifts as the drag moves. Reorders
|
|
304
|
+
// therefore get no projection at all; dnd-kit's sortable preview is already
|
|
305
|
+
// an honest picture of where the item lands.
|
|
306
|
+
if (activeData?.source !== 'palette' || !activeData.moduleType)
|
|
307
|
+
return;
|
|
308
|
+
const label = labelFor(activeData.moduleType);
|
|
309
|
+
// The pointer comes from the collision frame, NEVER from
|
|
310
|
+
// `activatorEvent.clientY + event.delta.y`: dnd-kit's delta is
|
|
311
|
+
// scroll-adjusted, so under mid-drag auto-scroll it drifts away from the
|
|
312
|
+
// rects being measured (see `pointerRef` above).
|
|
313
|
+
const slot = resolvePaletteDropSlot(event.over, pointerRef.current?.y ?? null);
|
|
314
|
+
if (!slot) {
|
|
315
|
+
// Over dead space. The ghost stays — the drag is still live — but there
|
|
316
|
+
// is nothing to promise, and a stale line would promise a lie.
|
|
317
|
+
dwell.leaveZone();
|
|
318
|
+
setDropTarget(null);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
dwell.enterZone(zoneKey(slot.columnId, slot.index));
|
|
322
|
+
setDropTarget(prev => {
|
|
323
|
+
// Same slot as last frame: keep the object (and any escalation it has
|
|
324
|
+
// already earned) so the indicator doesn't restart on every mouse move.
|
|
325
|
+
if (prev && prev.columnId === slot.columnId && prev.index === slot.index)
|
|
326
|
+
return prev;
|
|
327
|
+
return { columnId: slot.columnId, index: slot.index, feedback: 'line', label };
|
|
328
|
+
});
|
|
329
|
+
}, [dwell, labelFor]);
|
|
330
|
+
const handleDragCancel = useCallback(() => {
|
|
331
|
+
clearFeedback();
|
|
332
|
+
// Same reason handleDragEnd clears it below: the captured pointer belongs
|
|
333
|
+
// to the drag that just ended, and an abandoned drag leaves one behind just
|
|
334
|
+
// as readily as a completed one. A later keyboard drag records no
|
|
335
|
+
// coordinates of its own and would silently inherit these.
|
|
336
|
+
pointerRef.current = null;
|
|
337
|
+
}, [clearFeedback]);
|
|
338
|
+
const handleDragEnd = useCallback((event) => {
|
|
339
|
+
// A drop ends the drag WITHOUT firing onDragCancel, so this is the only
|
|
340
|
+
// place the feedback layer gets stood down on the happy path — leave the
|
|
341
|
+
// dwell timer armed here and a placeholder blooms after the drag is over.
|
|
342
|
+
clearFeedback();
|
|
343
|
+
// Read and clear together. The captured pointer belongs to the drag that
|
|
344
|
+
// just ended; leaving it behind would let a keyboard drag (which never
|
|
345
|
+
// records coordinates) silently inherit the previous drag's position.
|
|
346
|
+
const pointerY = pointerRef.current?.y ?? null;
|
|
347
|
+
pointerRef.current = null;
|
|
348
|
+
const action = resolveDragEndAction(event.active, event.over, { registry, pointerY });
|
|
349
|
+
if (!action)
|
|
350
|
+
return;
|
|
351
|
+
dispatch(action);
|
|
352
|
+
// kywi-cms#134: a freshly dropped module opens its own props panel, so the
|
|
353
|
+
// drop lands somewhere visible AND immediately editable. `resolveDragEndAction`
|
|
354
|
+
// mints the id up front precisely so we can select it without reading it
|
|
355
|
+
// back out of the reducer. Canvas reorders dispatch their action and nothing
|
|
356
|
+
// else — the moved module keeps whatever selection was already in play.
|
|
357
|
+
if (action.type === 'ADD_MODULE' && action.moduleId) {
|
|
358
|
+
dispatch({ type: 'SELECT', id: action.moduleId, selectionType: 'module' });
|
|
359
|
+
}
|
|
360
|
+
}, [registry, dispatch, clearFeedback]);
|
|
361
|
+
return {
|
|
362
|
+
sensors,
|
|
363
|
+
collisionDetection,
|
|
364
|
+
activeDrag,
|
|
365
|
+
dropTarget,
|
|
366
|
+
handleDragStart,
|
|
367
|
+
handleDragMove,
|
|
368
|
+
handleDragEnd,
|
|
369
|
+
handleDragCancel,
|
|
370
|
+
accessibility,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=use-editor-dnd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-editor-dnd.js","sourceRoot":"","sources":["../../../src/admin/layout-editor/use-editor-dnd.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AACZ,OAAc,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChF,OAAO,EACL,aAAa,EACb,cAAc,EACd,YAAY,EACZ,UAAU,EACV,SAAS,EACT,cAAc,EACd,iBAAiB,GAClB,MAAM,eAAe,CAAA;AAYtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAA;AAG/D,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,GAGvB,MAAM,iBAAiB,CAAA;AA4CxB;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,GAAG,CAAA;AAEpB;;;;GAIG;AACH,SAAS,OAAO,CAAC,QAAgB,EAAE,KAAa;IAC9C,OAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAA;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CACtB,IAAiC,EACjC,QAAwC;IAExC,IAAI,IAAI,EAAE,UAAU;QAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACtD,OAAO,YAAY,CAAA;AACrB,CAAC;AAED;;;qDAGqD;AACrD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACpE,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,gBAAgB,CAAC,IAA2C;IACnE,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,OAAsC,CAAA;IAC9D,IAAI,CAAC,IAAI,EAAE,QAAQ;QAAE,OAAO,IAAI,CAAA;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;IAC7B,OAAO,yBAAyB,KAAK,GAAG,CAAC,EAAE,CAAA;AAC7C,CAAC;AAED,gFAAgF;AAChF,MAAM,mBAAmB,GAAa;IACpC,YAAY,CAAC,IAAI;IACjB,YAAY,CAAC,EAAE;IACf,YAAY,CAAC,IAAI;IACjB,YAAY,CAAC,KAAK;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAA6B,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACvF,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IACxB,IAAI,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACjD,OAAO,2BAA2B,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACjD,CAAC;IAED,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IAC/D,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,2EAA2E;IAC3E,iCAAiC;IACjC,KAAK,CAAC,cAAc,EAAE,CAAA;IACtB,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACpF,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAA;IAE/C,MAAM,UAAU,GAAyB,EAAE,CAAA;IAC3C,mBAAmB,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC,KAAqC,EAAE,EAAE;QACjF,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ;YAAE,OAAM;QACpC,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,YAAY,CAAC,IAAI;gBACpB,IAAI,aAAa,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG;oBAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACxD,MAAK;YACP,KAAK,YAAY,CAAC,EAAE;gBAClB,IAAI,aAAa,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG;oBAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACxD,MAAK;YACP,KAAK,YAAY,CAAC,IAAI;gBACpB,IAAI,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI;oBAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAC1D,MAAK;YACP,KAAK,YAAY,CAAC,KAAK;gBACrB,IAAI,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI;oBAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAC1D,MAAK;QACT,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,UAAU,GAAG,cAAc,CAAC;QAChC,MAAM;QACN,aAAa;QACb,cAAc;QACd,mBAAmB,EAAE,UAAU;QAC/B,kBAAkB,EAAE,IAAI;KACzB,CAAC,CAAA;IACF,IAAI,SAAS,GAAG,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IACnD,sEAAsE;IACtE,yEAAyE;IACzE,yEAAyE;IACzE,gEAAgE;IAChE,IAAI,SAAS,KAAK,IAAI,EAAE,EAAE,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI,CAAA;IAE1F,IAAI,SAAS,IAAI,IAAI;QAAE,OAAO,SAAS,CAAA;IACvC,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAChD,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAA;IAEjC,OAAO,EAAE,CAAC,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,GAAG,EAAE,CAAA;AAClD,CAAC,CAAA;AAED,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAoB;IACnE,MAAM,OAAO,GAAG,UAAU;IACxB,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,0EAA0E;IAC1E,oDAAoD;IACpD,SAAS,CAAC,aAAa,EAAE,EAAE,oBAAoB,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;IACnE,4EAA4E;IAC5E,yEAAyE;IACzE,oEAAoE;IACpE,+EAA+E;IAC/E,uEAAuE;IACvE,mCAAmC;IACnC,SAAS,CAAC,cAAc,EAAE,EAAE,gBAAgB,EAAE,+BAA+B,EAAE,CAAC,CACjF,CAAA;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,UAAU,GAAG,MAAM,CAAkC,IAAI,CAAC,CAAA;IAEhE,MAAM,kBAAkB,GAAG,WAAW,CAAqB,CAAC,IAAI,EAAE,EAAE;QAClE,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAA;QAC5C,OAAO,wBAAwB,CAAC,IAAI,CAAC,CAAA;IACvC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,qDAAqD;IAErD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAoB,IAAI,CAAC,CAAA;IACrE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAoB,IAAI,CAAC,CAAA;IAErE;;;;;OAKG;IACH,MAAM,QAAQ,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAA;IACrD,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC9B,QAAQ,CAAC,OAAO,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;YACvD,yEAAyE;YACzE,0EAA0E;YAC1E,aAAa,CAAC,IAAI,CAAC,EAAE,CACnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,MAAM;gBACnD,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE;gBACtC,CAAC,CAAC,IAAI,CACT,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAA;IAE9B,yEAAyE;IACzE,sEAAsE;IACtE,2BAA2B;IAC3B,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAE1D;;;;;OAKG;IACH,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,UAAkB,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,EAAE,KAAK,IAAI,UAAU,EACtE,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,wEAAwE;IACxE,0EAA0E;IAC1E,2EAA2E;IAC3E,yDAAyD;IACzD,MAAM,aAAa,GAAG,OAAO,CAAgB,GAAG,EAAE,CAAC,CAAC;QAClD,WAAW,CAAC,EAAE,MAAM,EAAsB;YACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAsC,CAAA;YAC/D,OAAO,aAAa,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAA;QACxD,CAAC;QACD,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAyC;YAChE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAsC,CAAA;YAC/D,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAA;YAC3D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;YACvC,OAAO,QAAQ;gBACb,CAAC,CAAC,GAAG,OAAO,YAAY,QAAQ,GAAG;gBACnC,CAAC,CAAC,GAAG,OAAO,mCAAmC,CAAA;QACnD,CAAC;QACD,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAyC;YAC/D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAsC,CAAA;YAC/D,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAA;YAC3D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;YACvC,OAAO,QAAQ;gBACb,CAAC,CAAC,GAAG,OAAO,qBAAqB,QAAQ,GAAG;gBAC5C,CAAC,CAAC,GAAG,OAAO,2CAA2C,CAAA;QAC3D,CAAC;QACD,YAAY,CAAC,EAAE,MAAM,EAAsB;YACzC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAsC,CAAA;YAC/D,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,sBAAsB,CAAA;QAC7E,CAAC;KACF,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEf,2EAA2E;IAC3E,4EAA4E;IAC5E,uCAAuC;IACvC,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAA;IAEzE,8EAA8E;IAC9E,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,aAAa,CAAC,IAAI,CAAC,CAAA;QACnB,aAAa,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEX,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,KAAqB,EAAE,EAAE;QAC5D,4EAA4E;QAC5E,sDAAsD;QACtD,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,aAAa,CAAC,IAAI,CAAC,CAAA;QAEnB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAsC,CAAA;QACrE,4EAA4E;QAC5E,yEAAyE;QACzE,4DAA4D;QAC5D,IAAI,IAAI,EAAE,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACnD,aAAa,CAAC,IAAI,CAAC,CAAA;YACnB,OAAM;QACR,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAClC,aAAa,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;IAC5D,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;IAErB,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,KAAoB,EAAE,EAAE;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAsC,CAAA;QAC3E,yEAAyE;QACzE,uEAAuE;QACvE,2EAA2E;QAC3E,yEAAyE;QACzE,4EAA4E;QAC5E,4EAA4E;QAC5E,6CAA6C;QAC7C,IAAI,UAAU,EAAE,MAAM,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,UAAU;YAAE,OAAM;QACtE,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;QAE7C,yDAAyD;QACzD,+DAA+D;QAC/D,yEAAyE;QACzE,iDAAiD;QACjD,MAAM,IAAI,GAAG,sBAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,IAAI,IAAI,CAAC,CAAA;QAC9E,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,wEAAwE;YACxE,+DAA+D;YAC/D,KAAK,CAAC,SAAS,EAAE,CAAA;YACjB,aAAa,CAAC,IAAI,CAAC,CAAA;YACnB,OAAM;QACR,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACnD,aAAa,CAAC,IAAI,CAAC,EAAE;YACnB,sEAAsE;YACtE,wEAAwE;YACxE,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAA;YACrF,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;QAChF,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;IAErB,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;QACxC,aAAa,EAAE,CAAA;QACf,0EAA0E;QAC1E,4EAA4E;QAC5E,kEAAkE;QAClE,2DAA2D;QAC3D,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;IAC3B,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAA;IAEnB,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,KAAmB,EAAE,EAAE;QACxD,wEAAwE;QACxE,yEAAyE;QACzE,0EAA0E;QAC1E,aAAa,EAAE,CAAA;QAEf,yEAAyE;QACzE,uEAAuE;QACvE,sEAAsE;QACtE,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,IAAI,IAAI,CAAA;QAC9C,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;QAEzB,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAA;QACrF,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,QAAQ,CAAC,MAAM,CAAC,CAAA;QAChB,2EAA2E;QAC3E,gFAAgF;QAChF,yEAAyE;QACzE,6EAA6E;QAC7E,wEAAwE;QACxE,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpD,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAA;IAEvC,OAAO;QACL,OAAO;QACP,kBAAkB;QAClB,UAAU;QACV,UAAU;QACV,eAAe;QACf,cAAc;QACd,aAAa;QACb,gBAAgB;QAChB,aAAa;KACd,CAAA;AACH,CAAC"}
|
|
@@ -191,6 +191,14 @@
|
|
|
191
191
|
--kywi-editor-selected-border: var(--kywi-green-400);
|
|
192
192
|
--kywi-editor-drop-line: var(--kywi-green-500);
|
|
193
193
|
--kywi-editor-placeholder-bg: rgba(18, 128, 95, 0.1);
|
|
194
|
+
/* The dwell placeholder is overlay chrome now (kywi-cms#138): it floats over
|
|
195
|
+
real page content rather than occupying flow, so it needs its own
|
|
196
|
+
near-opaque fill instead of the faint tint above. Without a green-tinted
|
|
197
|
+
value here the admin Layout tab would inherit editor.css's indigo-white
|
|
198
|
+
default inside the green admin theme. `--kywi-green-50` at 0.95 — same
|
|
199
|
+
lightness and alpha as editor.css's default, green hue family.
|
|
200
|
+
Pinned against editor.css by overlay-editor.test.tsx. */
|
|
201
|
+
--kywi-editor-placeholder-overlay-bg: rgba(233, 245, 239, 0.95);
|
|
194
202
|
--kywi-editor-region-border: rgba(18, 128, 95, 0.32);
|
|
195
203
|
}
|
|
196
204
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout-editor-client.d.ts","sourceRoot":"","sources":["../../../../src/admin/surfaces/layouts/layout-editor-client.tsx"],"names":[],"mappings":"AA2BA,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE5F,UAAU,uBAAuB;IAC/B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"layout-editor-client.d.ts","sourceRoot":"","sources":["../../../../src/admin/surfaces/layouts/layout-editor-client.tsx"],"names":[],"mappings":"AA2BA,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE5F,UAAU,uBAAuB;IAC/B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,uBAAuB,2CA6HrE"}
|
|
@@ -58,7 +58,14 @@ export function LayoutEditorClient({ layout }) {
|
|
|
58
58
|
setError(null);
|
|
59
59
|
if (!name.trim()) {
|
|
60
60
|
setError('A layout name is required before saving.');
|
|
61
|
-
|
|
61
|
+
// kywi-cms#142: an explicit `false` is the only way LayoutEditor's
|
|
62
|
+
// `onSave !== false` contract (layout-editor.tsx ~177-195) reads a save
|
|
63
|
+
// as failed. Resolving `undefined` here — LayoutEditor's own "treat as
|
|
64
|
+
// success" default for a caller that reports nothing — let a blank-name
|
|
65
|
+
// guard rejection dispatch MARK_SAVED anyway, marking the canvas clean
|
|
66
|
+
// (Save disables, Cancel stops confirming) over an edit that never
|
|
67
|
+
// reached the server.
|
|
68
|
+
return false;
|
|
62
69
|
}
|
|
63
70
|
setSaving(true);
|
|
64
71
|
try {
|
|
@@ -73,6 +80,10 @@ export function LayoutEditorClient({ layout }) {
|
|
|
73
80
|
catch (e) {
|
|
74
81
|
setError(e instanceof Error ? e.message : 'Save failed');
|
|
75
82
|
setSaving(false);
|
|
83
|
+
// Same contract violation as the blank-name branch above: a request
|
|
84
|
+
// failure must report `false`, not resolve `undefined` into an
|
|
85
|
+
// optimistic MARK_SAVED.
|
|
86
|
+
return false;
|
|
76
87
|
}
|
|
77
88
|
}, [name, category, isEdit, layout, navigate, adminFetch]);
|
|
78
89
|
return (_jsxs("div", { className: "kywi-layout-editor-page", children: [_jsxs("div", { className: "kywi-admin-page-header", children: [_jsxs("div", { children: [_jsx("h1", { children: isEdit ? `Edit layout: ${layout.name}` : 'New layout' }), _jsxs("p", { className: "kywi-text-muted", children: ["Build a reusable layout, then click ", _jsx("strong", { children: "Save" }), " in the editor toolbar to persist it."] })] }), _jsx("button", { type: "button", className: "kywi-btn kywi-btn-secondary", onClick: () => navigate('/admin/layouts'), children: "Back to layouts" })] }), error && _jsx(ErrorState, { message: error }), saving && _jsx("p", { className: "kywi-text-muted", role: "status", children: "Saving\u2026" }), _jsxs("div", { className: "kywi-layout-editor-meta", children: [_jsxs("div", { className: "kywi-field", children: [_jsx("label", { htmlFor: "layout-name", className: "kywi-field-label", children: "Layout name" }), _jsx("input", { id: "layout-name", type: "text", className: "kywi-input", value: name, onChange: e => setName(e.target.value), placeholder: "Landing hero + features" })] }), _jsxs("div", { className: "kywi-field", children: [_jsx("label", { htmlFor: "layout-category", className: "kywi-field-label", children: "Category" }), _jsx("input", { id: "layout-category", type: "text", className: "kywi-input", value: category, onChange: e => setCategory(e.target.value), placeholder: "general" })] })] }), _jsx("div", { className: "kywi-layout-editor-host", children: _jsx(LayoutEditor, { initialLayout: initialLayout, contentId: layout?.id ?? 'new', contentType: "layout", moduleRegistry: moduleRegistry, moduleComponents: layoutModuleComponents, audiences: audiences, onSave: persist, onCancel: () => navigate('/admin/layouts') }) })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout-editor-client.js","sourceRoot":"","sources":["../../../../src/admin/surfaces/layouts/layout-editor-client.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"layout-editor-client.js","sourceRoot":"","sources":["../../../../src/admin/surfaces/layouts/layout-editor-client.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-routes.d.ts","sourceRoot":"","sources":["../../src/api/content-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACpF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAE7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAoCxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEtE,UAAU,mBAAmB;IAC3B,MAAM,EAAE,UAAU,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAC9C,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAA;IACpD,YAAY,EAAE,YAAY,CAAA;IAC1B,OAAO,EAAE,WAAW,CAAA;IACpB,aAAa,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAMD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"content-routes.d.ts","sourceRoot":"","sources":["../../src/api/content-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACpF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAE7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAoCxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEtE,UAAU,mBAAmB;IAC3B,MAAM,EAAE,UAAU,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAC9C,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAA;IACpD,YAAY,EAAE,YAAY,CAAA;IAC1B,OAAO,EAAE,WAAW,CAAA;IACpB,aAAa,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAMD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,IAAI,CA6yBpF"}
|
|
@@ -13,7 +13,7 @@ import { listContent, getContentById, getContentBySlug, createContent, updateCon
|
|
|
13
13
|
import { getChildren, cascadeSlugPath } from '../db/tree-queries.js';
|
|
14
14
|
import { recordContentVersion } from '../db/version-queries.js';
|
|
15
15
|
import { incrementViews, getStats } from '../db/stats-queries.js';
|
|
16
|
-
import { listDeletedContent, listAllDeletedContent, restoreContentWithRecovery, emptyTrashForSite, emptyTrashGlobal, } from '../db/content-restore.js';
|
|
16
|
+
import { getDeletedContentById, listDeletedContent, listAllDeletedContent, restoreContentWithRecovery, emptyTrashForSite, emptyTrashGlobal, } from '../db/content-restore.js';
|
|
17
17
|
import { syncContentTags } from './tag-usage-wiring.js';
|
|
18
18
|
import { archivePath } from '../db/filename-archive-queries.js';
|
|
19
19
|
async function authenticate(req, authSecret, db) {
|
|
@@ -93,9 +93,32 @@ export function registerContentRoutes(router, ctx) {
|
|
|
93
93
|
const siteDenied = assertSiteAccess(auth.user, siteId);
|
|
94
94
|
if (siteDenied)
|
|
95
95
|
return siteDenied;
|
|
96
|
+
// Read the row before deleting: it is both the 404 guard and the hook
|
|
97
|
+
// payload. Without it this route dispatched nothing at all, so the cache
|
|
98
|
+
// subscribers never ran and the slug-keyed entry kept serving the trashed
|
|
99
|
+
// row after a delete + recreate (#137). Mirrors the typed DELETE below.
|
|
100
|
+
const base = await getContentById(db, null, params.id, siteId);
|
|
101
|
+
if (!base)
|
|
102
|
+
return notFound('Content not found');
|
|
103
|
+
// Re-read through the type's custom-field table so hook subscribers get the
|
|
104
|
+
// same merged shape the typed route hands them (same pattern as the by-id
|
|
105
|
+
// GET). An unregistered/orphaned content type just means no dispatch — the
|
|
106
|
+
// delete itself still proceeds (mirrors the by-id publish route).
|
|
107
|
+
const contentType = getContentType(base.contentTypeName);
|
|
108
|
+
const customTable = contentType ? customTableMap.get(contentType.name) ?? null : null;
|
|
109
|
+
const existing = customTable
|
|
110
|
+
? (await getContentById(db, customTable, params.id, siteId)) ?? base
|
|
111
|
+
: base;
|
|
112
|
+
const dispatch = contentType
|
|
113
|
+
? createContentHookDispatcher(contentType, ctx.hookRegistry, { ...ctx.kywiCtx, user: auth.user })
|
|
114
|
+
: null;
|
|
115
|
+
if (dispatch)
|
|
116
|
+
await dispatch.beforeDelete(existing);
|
|
96
117
|
const deleted = await deleteContent(db, params.id, siteId);
|
|
97
118
|
if (!deleted)
|
|
98
119
|
return notFound('Content not found');
|
|
120
|
+
if (dispatch)
|
|
121
|
+
await dispatch.afterDelete(existing);
|
|
99
122
|
return jsonOk({ deleted: true });
|
|
100
123
|
});
|
|
101
124
|
// POST /content/by-id/:id/publish — Publish (auth required, publish role)
|
|
@@ -175,14 +198,17 @@ export function registerContentRoutes(router, ctx) {
|
|
|
175
198
|
return forbidden();
|
|
176
199
|
if (!(await checkDocumentMutationAccess(db, params.id, auth.user, 'write')))
|
|
177
200
|
return forbidden();
|
|
178
|
-
// Fetch the content before restoring so we can pass it to hooks.
|
|
179
|
-
//
|
|
180
|
-
//
|
|
201
|
+
// Fetch the content before restoring so we can pass it to hooks. The row is
|
|
202
|
+
// in the trash, so it has to come from getDeletedContentById — getContentById
|
|
203
|
+
// filters `deleted_at IS NULL` and always resolved to null here, which meant
|
|
204
|
+
// the trash hooks (and with them the cache invalidation) never fired (#137).
|
|
205
|
+
// Both the fetch and the restore are scoped to the request's resolved site
|
|
206
|
+
// so a foreign-site id can't be restored by id alone (cross-site guard).
|
|
181
207
|
const restoreSiteId = resolveSiteId(req, ctx.config, ctx.siteIdMap);
|
|
182
208
|
const siteDenied = assertSiteAccess(auth.user, restoreSiteId);
|
|
183
209
|
if (siteDenied)
|
|
184
210
|
return siteDenied;
|
|
185
|
-
const existing = await
|
|
211
|
+
const existing = await getDeletedContentById(db, params.id, restoreSiteId);
|
|
186
212
|
const { restored, movedToRecovered } = await restoreContentWithRecovery(db, params.id, restoreSiteId);
|
|
187
213
|
if (!restored)
|
|
188
214
|
return notFound('Content not found or not deleted');
|
|
@@ -201,14 +227,15 @@ export function registerContentRoutes(router, ctx) {
|
|
|
201
227
|
return forbidden();
|
|
202
228
|
if (!(await checkDocumentMutationAccess(db, params.id, auth.user, 'delete')))
|
|
203
229
|
return forbidden();
|
|
204
|
-
// Fetch the content before deleting so we can pass it to hooks
|
|
230
|
+
// Fetch the content before deleting so we can pass it to hooks — from the
|
|
231
|
+
// trash, for the same reason as the restore route above (#137). Both the
|
|
205
232
|
// fetch and the permanent delete are scoped to the request's resolved site so
|
|
206
233
|
// a foreign-site id can't be purged by id alone (cross-site guard).
|
|
207
234
|
const permSiteId = resolveSiteId(req, ctx.config, ctx.siteIdMap);
|
|
208
235
|
const siteDenied = assertSiteAccess(auth.user, permSiteId);
|
|
209
236
|
if (siteDenied)
|
|
210
237
|
return siteDenied;
|
|
211
|
-
const existing = await
|
|
238
|
+
const existing = await getDeletedContentById(db, params.id, permSiteId);
|
|
212
239
|
const deleted = await permanentDeleteContent(db, params.id, permSiteId);
|
|
213
240
|
if (!deleted)
|
|
214
241
|
return notFound('Content not found or not in trash');
|