@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.
Files changed (80) hide show
  1. package/dist/admin/audience-list.d.ts.map +1 -1
  2. package/dist/admin/audience-list.js +9 -0
  3. package/dist/admin/audience-list.js.map +1 -1
  4. package/dist/admin/content-form.d.ts.map +1 -1
  5. package/dist/admin/content-form.js +7 -1
  6. package/dist/admin/content-form.js.map +1 -1
  7. package/dist/admin/layout-editor/column-overlay.d.ts +10 -1
  8. package/dist/admin/layout-editor/column-overlay.d.ts.map +1 -1
  9. package/dist/admin/layout-editor/column-overlay.js +8 -2
  10. package/dist/admin/layout-editor/column-overlay.js.map +1 -1
  11. package/dist/admin/layout-editor/component-actions.js +1 -1
  12. package/dist/admin/layout-editor/component-actions.js.map +1 -1
  13. package/dist/admin/layout-editor/drag-feedback.d.ts +75 -0
  14. package/dist/admin/layout-editor/drag-feedback.d.ts.map +1 -0
  15. package/dist/admin/layout-editor/drag-feedback.js +60 -0
  16. package/dist/admin/layout-editor/drag-feedback.js.map +1 -0
  17. package/dist/admin/layout-editor/drag-utils.d.ts +106 -8
  18. package/dist/admin/layout-editor/drag-utils.d.ts.map +1 -1
  19. package/dist/admin/layout-editor/drag-utils.js +136 -24
  20. package/dist/admin/layout-editor/drag-utils.js.map +1 -1
  21. package/dist/admin/layout-editor/drop-indicator.d.ts +18 -1
  22. package/dist/admin/layout-editor/drop-indicator.d.ts.map +1 -1
  23. package/dist/admin/layout-editor/drop-indicator.js +23 -3
  24. package/dist/admin/layout-editor/drop-indicator.js.map +1 -1
  25. package/dist/admin/layout-editor/editor-canvas.d.ts +11 -1
  26. package/dist/admin/layout-editor/editor-canvas.d.ts.map +1 -1
  27. package/dist/admin/layout-editor/editor-canvas.js +109 -6
  28. package/dist/admin/layout-editor/editor-canvas.js.map +1 -1
  29. package/dist/admin/layout-editor/editor-state.d.ts +46 -0
  30. package/dist/admin/layout-editor/editor-state.d.ts.map +1 -1
  31. package/dist/admin/layout-editor/editor-state.js +40 -3
  32. package/dist/admin/layout-editor/editor-state.js.map +1 -1
  33. package/dist/admin/layout-editor/editor.css +19 -6
  34. package/dist/admin/layout-editor/generate-id.d.ts +23 -0
  35. package/dist/admin/layout-editor/generate-id.d.ts.map +1 -0
  36. package/dist/admin/layout-editor/generate-id.js +34 -0
  37. package/dist/admin/layout-editor/generate-id.js.map +1 -0
  38. package/dist/admin/layout-editor/layout-editor.d.ts +8 -1
  39. package/dist/admin/layout-editor/layout-editor.d.ts.map +1 -1
  40. package/dist/admin/layout-editor/layout-editor.js +66 -13
  41. package/dist/admin/layout-editor/layout-editor.js.map +1 -1
  42. package/dist/admin/layout-editor/module-overlay.d.ts.map +1 -1
  43. package/dist/admin/layout-editor/module-overlay.js +7 -2
  44. package/dist/admin/layout-editor/module-overlay.js.map +1 -1
  45. package/dist/admin/layout-editor/overlay-shell.d.ts +11 -3
  46. package/dist/admin/layout-editor/overlay-shell.d.ts.map +1 -1
  47. package/dist/admin/layout-editor/overlay-shell.js +93 -50
  48. package/dist/admin/layout-editor/overlay-shell.js.map +1 -1
  49. package/dist/admin/layout-editor/section-variant-container-config.js +1 -1
  50. package/dist/admin/layout-editor/section-variant-container-config.js.map +1 -1
  51. package/dist/admin/layout-editor/use-editor-dnd.d.ts +79 -0
  52. package/dist/admin/layout-editor/use-editor-dnd.d.ts.map +1 -0
  53. package/dist/admin/layout-editor/use-editor-dnd.js +373 -0
  54. package/dist/admin/layout-editor/use-editor-dnd.js.map +1 -0
  55. package/dist/admin/styles/admin.css +8 -0
  56. package/dist/admin/surfaces/layouts/layout-editor-client.d.ts.map +1 -1
  57. package/dist/admin/surfaces/layouts/layout-editor-client.js +12 -1
  58. package/dist/admin/surfaces/layouts/layout-editor-client.js.map +1 -1
  59. package/dist/api/content-routes.d.ts.map +1 -1
  60. package/dist/api/content-routes.js +34 -7
  61. package/dist/api/content-routes.js.map +1 -1
  62. package/dist/api/index.d.ts.map +1 -1
  63. package/dist/api/index.js +7 -73
  64. package/dist/api/index.js.map +1 -1
  65. package/dist/cache/content-invalidation.d.ts +28 -0
  66. package/dist/cache/content-invalidation.d.ts.map +1 -0
  67. package/dist/cache/content-invalidation.js +72 -0
  68. package/dist/cache/content-invalidation.js.map +1 -0
  69. package/dist/db/content-restore.d.ts +5 -1
  70. package/dist/db/content-restore.d.ts.map +1 -1
  71. package/dist/db/content-restore.js +9 -2
  72. package/dist/db/content-restore.js.map +1 -1
  73. package/dist/layout/modules.d.ts.map +1 -1
  74. package/dist/layout/modules.js +52 -2
  75. package/dist/layout/modules.js.map +1 -1
  76. package/dist/layout/registry.d.ts +6 -0
  77. package/dist/layout/registry.d.ts.map +1 -1
  78. package/dist/layout/registry.js +21 -5
  79. package/dist/layout/registry.js.map +1 -1
  80. 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":"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"}
@@ -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,2CAkHrE"}
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
- return;
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,CAixBpF"}
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. Both the
179
- // fetch and the restore are scoped to the request's resolved site so a
180
- // foreign-site id can't be restored by id alone (cross-site guard).
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 getContentById(db, null, params.id, restoreSiteId);
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. Both the
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 getContentById(db, null, params.id, permSiteId);
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');