@kywi-software/core 0.10.0 → 0.12.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 (248) hide show
  1. package/AGENT-PATTERNS.md +111 -23
  2. package/README.md +67 -0
  3. package/dist/admin/content-form.d.ts.map +1 -1
  4. package/dist/admin/content-form.js +97 -10
  5. package/dist/admin/content-form.js.map +1 -1
  6. package/dist/admin/layout-editor/component-actions.d.ts +219 -22
  7. package/dist/admin/layout-editor/component-actions.d.ts.map +1 -1
  8. package/dist/admin/layout-editor/component-actions.js +356 -36
  9. package/dist/admin/layout-editor/component-actions.js.map +1 -1
  10. package/dist/admin/layout-editor/component-fork.d.ts +224 -0
  11. package/dist/admin/layout-editor/component-fork.d.ts.map +1 -0
  12. package/dist/admin/layout-editor/component-fork.js +342 -0
  13. package/dist/admin/layout-editor/component-fork.js.map +1 -0
  14. package/dist/admin/layout-editor/editor-canvas.d.ts +28 -7
  15. package/dist/admin/layout-editor/editor-canvas.d.ts.map +1 -1
  16. package/dist/admin/layout-editor/editor-canvas.js +145 -32
  17. package/dist/admin/layout-editor/editor-canvas.js.map +1 -1
  18. package/dist/admin/layout-editor/editor-dialog.d.ts +164 -0
  19. package/dist/admin/layout-editor/editor-dialog.d.ts.map +1 -0
  20. package/dist/admin/layout-editor/editor-dialog.js +195 -0
  21. package/dist/admin/layout-editor/editor-dialog.js.map +1 -0
  22. package/dist/admin/layout-editor/editor-state.d.ts +76 -0
  23. package/dist/admin/layout-editor/editor-state.d.ts.map +1 -1
  24. package/dist/admin/layout-editor/editor-state.js +47 -1
  25. package/dist/admin/layout-editor/editor-state.js.map +1 -1
  26. package/dist/admin/layout-editor/editor.css +366 -4
  27. package/dist/admin/layout-editor/index.d.ts +10 -1
  28. package/dist/admin/layout-editor/index.d.ts.map +1 -1
  29. package/dist/admin/layout-editor/index.js +11 -1
  30. package/dist/admin/layout-editor/index.js.map +1 -1
  31. package/dist/admin/layout-editor/inline-text.d.ts +56 -4
  32. package/dist/admin/layout-editor/inline-text.d.ts.map +1 -1
  33. package/dist/admin/layout-editor/inline-text.js +134 -11
  34. package/dist/admin/layout-editor/inline-text.js.map +1 -1
  35. package/dist/admin/layout-editor/layout-editor.d.ts +58 -1
  36. package/dist/admin/layout-editor/layout-editor.d.ts.map +1 -1
  37. package/dist/admin/layout-editor/layout-editor.js +160 -190
  38. package/dist/admin/layout-editor/layout-editor.js.map +1 -1
  39. package/dist/admin/layout-editor/linked-component.d.ts +39 -0
  40. package/dist/admin/layout-editor/linked-component.d.ts.map +1 -0
  41. package/dist/admin/layout-editor/linked-component.js +42 -0
  42. package/dist/admin/layout-editor/linked-component.js.map +1 -0
  43. package/dist/admin/layout-editor/module-overlay.d.ts +36 -14
  44. package/dist/admin/layout-editor/module-overlay.d.ts.map +1 -1
  45. package/dist/admin/layout-editor/module-overlay.js +233 -25
  46. package/dist/admin/layout-editor/module-overlay.js.map +1 -1
  47. package/dist/admin/layout-editor/node-lookup.d.ts +35 -0
  48. package/dist/admin/layout-editor/node-lookup.d.ts.map +1 -1
  49. package/dist/admin/layout-editor/node-lookup.js +91 -0
  50. package/dist/admin/layout-editor/node-lookup.js.map +1 -1
  51. package/dist/admin/layout-editor/overlay-shell.d.ts.map +1 -1
  52. package/dist/admin/layout-editor/overlay-shell.js +150 -32
  53. package/dist/admin/layout-editor/overlay-shell.js.map +1 -1
  54. package/dist/admin/layout-editor/props-panel.d.ts +54 -1
  55. package/dist/admin/layout-editor/props-panel.d.ts.map +1 -1
  56. package/dist/admin/layout-editor/props-panel.js +119 -10
  57. package/dist/admin/layout-editor/props-panel.js.map +1 -1
  58. package/dist/admin/layout-editor/section-overlay.d.ts +16 -1
  59. package/dist/admin/layout-editor/section-overlay.d.ts.map +1 -1
  60. package/dist/admin/layout-editor/section-overlay.js +21 -11
  61. package/dist/admin/layout-editor/section-overlay.js.map +1 -1
  62. package/dist/admin/layout-editor/section-template-picker.d.ts +27 -1
  63. package/dist/admin/layout-editor/section-template-picker.d.ts.map +1 -1
  64. package/dist/admin/layout-editor/section-template-picker.js +71 -17
  65. package/dist/admin/layout-editor/section-template-picker.js.map +1 -1
  66. package/dist/admin/layout-editor/section-variant-container-config.js +5 -5
  67. package/dist/admin/layout-editor/section-variant-container-config.js.map +1 -1
  68. package/dist/admin/layout-editor/use-chrome-flip.d.ts +111 -11
  69. package/dist/admin/layout-editor/use-chrome-flip.d.ts.map +1 -1
  70. package/dist/admin/layout-editor/use-chrome-flip.js +233 -33
  71. package/dist/admin/layout-editor/use-chrome-flip.js.map +1 -1
  72. package/dist/admin/layout-editor/use-component-connect.d.ts +61 -0
  73. package/dist/admin/layout-editor/use-component-connect.d.ts.map +1 -0
  74. package/dist/admin/layout-editor/use-component-connect.js +292 -0
  75. package/dist/admin/layout-editor/use-component-connect.js.map +1 -0
  76. package/dist/admin/layout-editor/use-component-fork.d.ts +123 -0
  77. package/dist/admin/layout-editor/use-component-fork.d.ts.map +1 -0
  78. package/dist/admin/layout-editor/use-component-fork.js +346 -0
  79. package/dist/admin/layout-editor/use-component-fork.js.map +1 -0
  80. package/dist/admin/layout-editor/use-component-library.d.ts +94 -0
  81. package/dist/admin/layout-editor/use-component-library.d.ts.map +1 -0
  82. package/dist/admin/layout-editor/use-component-library.js +55 -0
  83. package/dist/admin/layout-editor/use-component-library.js.map +1 -0
  84. package/dist/admin/registry.d.ts +11 -2
  85. package/dist/admin/registry.d.ts.map +1 -1
  86. package/dist/admin/registry.js +40 -4
  87. package/dist/admin/registry.js.map +1 -1
  88. package/dist/admin/server.d.ts +1 -0
  89. package/dist/admin/server.d.ts.map +1 -1
  90. package/dist/admin/server.js +5 -0
  91. package/dist/admin/server.js.map +1 -1
  92. package/dist/admin/styles/admin.css +57 -1
  93. package/dist/admin/surfaces/components/component-edit.d.ts +3 -0
  94. package/dist/admin/surfaces/components/component-edit.d.ts.map +1 -0
  95. package/dist/admin/surfaces/components/component-edit.js +235 -0
  96. package/dist/admin/surfaces/components/component-edit.js.map +1 -0
  97. package/dist/admin/surfaces/components/components-data.d.ts +153 -0
  98. package/dist/admin/surfaces/components/components-data.d.ts.map +1 -0
  99. package/dist/admin/surfaces/components/components-data.js +154 -0
  100. package/dist/admin/surfaces/components/components-data.js.map +1 -0
  101. package/dist/admin/surfaces/components/components-list.d.ts +3 -0
  102. package/dist/admin/surfaces/components/components-list.d.ts.map +1 -0
  103. package/dist/admin/surfaces/components/components-list.js +196 -0
  104. package/dist/admin/surfaces/components/components-list.js.map +1 -0
  105. package/dist/admin/surfaces/components/definition-canvas.d.ts +73 -0
  106. package/dist/admin/surfaces/components/definition-canvas.d.ts.map +1 -0
  107. package/dist/admin/surfaces/components/definition-canvas.js +207 -0
  108. package/dist/admin/surfaces/components/definition-canvas.js.map +1 -0
  109. package/dist/admin/surfaces/layouts/layout-editor-client.d.ts.map +1 -1
  110. package/dist/admin/surfaces/layouts/layout-editor-client.js +14 -2
  111. package/dist/admin/surfaces/layouts/layout-editor-client.js.map +1 -1
  112. package/dist/admin/surfaces/layouts/layouts-data.d.ts +3 -14
  113. package/dist/admin/surfaces/layouts/layouts-data.d.ts.map +1 -1
  114. package/dist/admin/surfaces/layouts/layouts-data.js +4 -7
  115. package/dist/admin/surfaces/layouts/layouts-data.js.map +1 -1
  116. package/dist/admin/surfaces/layouts/layouts-list.d.ts.map +1 -1
  117. package/dist/admin/surfaces/layouts/layouts-list.js +16 -22
  118. package/dist/admin/surfaces/layouts/layouts-list.js.map +1 -1
  119. package/dist/admin/surfaces/settings/admin-features.d.ts.map +1 -1
  120. package/dist/admin/surfaces/settings/admin-features.js +1 -0
  121. package/dist/admin/surfaces/settings/admin-features.js.map +1 -1
  122. package/dist/api/bulk-routes.d.ts.map +1 -1
  123. package/dist/api/bulk-routes.js +25 -0
  124. package/dist/api/bulk-routes.js.map +1 -1
  125. package/dist/api/changeset-routes.d.ts.map +1 -1
  126. package/dist/api/changeset-routes.js +17 -3
  127. package/dist/api/changeset-routes.js.map +1 -1
  128. package/dist/api/component-routes.d.ts.map +1 -1
  129. package/dist/api/component-routes.js +228 -8
  130. package/dist/api/component-routes.js.map +1 -1
  131. package/dist/api/content-routes.d.ts.map +1 -1
  132. package/dist/api/content-routes.js +64 -0
  133. package/dist/api/content-routes.js.map +1 -1
  134. package/dist/api/feed-routes.d.ts +6 -6
  135. package/dist/api/graphql/resolvers.d.ts +4 -4
  136. package/dist/api/layout-routes.d.ts.map +1 -1
  137. package/dist/api/layout-routes.js +4 -17
  138. package/dist/api/layout-routes.js.map +1 -1
  139. package/dist/api/layout-write-guard.d.ts +80 -0
  140. package/dist/api/layout-write-guard.d.ts.map +1 -0
  141. package/dist/api/layout-write-guard.js +88 -0
  142. package/dist/api/layout-write-guard.js.map +1 -0
  143. package/dist/api/section-template-routes.d.ts.map +1 -1
  144. package/dist/api/section-template-routes.js +11 -0
  145. package/dist/api/section-template-routes.js.map +1 -1
  146. package/dist/api/translation-routes.d.ts.map +1 -1
  147. package/dist/api/translation-routes.js +8 -0
  148. package/dist/api/translation-routes.js.map +1 -1
  149. package/dist/api/version-routes.d.ts.map +1 -1
  150. package/dist/api/version-routes.js +20 -8
  151. package/dist/api/version-routes.js.map +1 -1
  152. package/dist/api/workflow-routes.d.ts.map +1 -1
  153. package/dist/api/workflow-routes.js +53 -8
  154. package/dist/api/workflow-routes.js.map +1 -1
  155. package/dist/db/analytics-queries.d.ts +3 -3
  156. package/dist/db/apply-snapshot.d.ts +75 -8
  157. package/dist/db/apply-snapshot.d.ts.map +1 -1
  158. package/dist/db/apply-snapshot.js +104 -8
  159. package/dist/db/apply-snapshot.js.map +1 -1
  160. package/dist/db/audit-queries.d.ts +12 -12
  161. package/dist/db/category-queries.d.ts +3 -3
  162. package/dist/db/changeset-audit-queries.d.ts +4 -4
  163. package/dist/db/changeset-queries.d.ts +7 -7
  164. package/dist/db/comment-queries.d.ts +8 -8
  165. package/dist/db/comment-subscription-queries.d.ts +2 -2
  166. package/dist/db/component-instance-queries.d.ts +197 -0
  167. package/dist/db/component-instance-queries.d.ts.map +1 -0
  168. package/dist/db/component-instance-queries.js +405 -0
  169. package/dist/db/component-instance-queries.js.map +1 -0
  170. package/dist/db/content-queries.d.ts.map +1 -1
  171. package/dist/db/content-queries.js +39 -2
  172. package/dist/db/content-queries.js.map +1 -1
  173. package/dist/db/extension-queries.d.ts +7 -7
  174. package/dist/db/feed-queries.d.ts +3 -3
  175. package/dist/db/index.d.ts +1 -0
  176. package/dist/db/index.d.ts.map +1 -1
  177. package/dist/db/index.js +1 -0
  178. package/dist/db/index.js.map +1 -1
  179. package/dist/db/layout-queries.d.ts +13 -1
  180. package/dist/db/layout-queries.d.ts.map +1 -1
  181. package/dist/db/layout-queries.js +52 -7
  182. package/dist/db/layout-queries.js.map +1 -1
  183. package/dist/db/locale-queries.d.ts.map +1 -1
  184. package/dist/db/locale-queries.js +0 -0
  185. package/dist/db/locale-queries.js.map +1 -1
  186. package/dist/db/media-queries.d.ts +12 -12
  187. package/dist/db/revision-queries.d.ts +3 -3
  188. package/dist/db/site-admin-queries.d.ts +2 -2
  189. package/dist/db/site-membership-queries.d.ts +3 -3
  190. package/dist/db/site-plugin-queries.d.ts +2 -2
  191. package/dist/db/site-queries.d.ts +6 -6
  192. package/dist/db/version-queries.d.ts +12 -12
  193. package/dist/db/webhook-queries.d.ts +11 -11
  194. package/dist/db/workflow-queries.d.ts +6 -6
  195. package/dist/db/workflow-queries.d.ts.map +1 -1
  196. package/dist/db/workflow-queries.js +11 -2
  197. package/dist/db/workflow-queries.js.map +1 -1
  198. package/dist/layout/comments-module.d.ts.map +1 -1
  199. package/dist/layout/comments-module.js +6 -2
  200. package/dist/layout/comments-module.js.map +1 -1
  201. package/dist/layout/component-refs.d.ts +465 -0
  202. package/dist/layout/component-refs.d.ts.map +1 -0
  203. package/dist/layout/component-refs.js +848 -0
  204. package/dist/layout/component-refs.js.map +1 -0
  205. package/dist/layout/index.d.ts +2 -1
  206. package/dist/layout/index.d.ts.map +1 -1
  207. package/dist/layout/index.js +1 -0
  208. package/dist/layout/index.js.map +1 -1
  209. package/dist/layout/module-permissions.d.ts +49 -1
  210. package/dist/layout/module-permissions.d.ts.map +1 -1
  211. package/dist/layout/module-permissions.js +117 -6
  212. package/dist/layout/module-permissions.js.map +1 -1
  213. package/dist/layout/module-render-context.d.ts +32 -0
  214. package/dist/layout/module-render-context.d.ts.map +1 -1
  215. package/dist/layout/module-render-context.js +32 -1
  216. package/dist/layout/module-render-context.js.map +1 -1
  217. package/dist/layout/modules-extended.d.ts.map +1 -1
  218. package/dist/layout/modules-extended.js +21 -11
  219. package/dist/layout/modules-extended.js.map +1 -1
  220. package/dist/layout/modules.d.ts.map +1 -1
  221. package/dist/layout/modules.js +15 -10
  222. package/dist/layout/modules.js.map +1 -1
  223. package/dist/layout/renderer.d.ts +51 -4
  224. package/dist/layout/renderer.d.ts.map +1 -1
  225. package/dist/layout/renderer.js +36 -38
  226. package/dist/layout/renderer.js.map +1 -1
  227. package/dist/layout/types.d.ts +42 -0
  228. package/dist/layout/types.d.ts.map +1 -1
  229. package/dist/layout/types.js +54 -11
  230. package/dist/layout/types.js.map +1 -1
  231. package/dist/schema/base.d.ts +1 -1
  232. package/dist/schema/components.d.ts +150 -0
  233. package/dist/schema/components.d.ts.map +1 -1
  234. package/dist/schema/components.js +50 -4
  235. package/dist/schema/components.js.map +1 -1
  236. package/dist/scope/inline-editing.d.ts.map +1 -1
  237. package/dist/scope/inline-editing.js +10 -0
  238. package/dist/scope/inline-editing.js.map +1 -1
  239. package/dist/scope/sticky-offset.d.ts +185 -0
  240. package/dist/scope/sticky-offset.d.ts.map +1 -0
  241. package/dist/scope/sticky-offset.js +432 -0
  242. package/dist/scope/sticky-offset.js.map +1 -0
  243. package/dist/site/styles.css +41 -3
  244. package/dist/versioning/scheduler.d.ts +8 -0
  245. package/dist/versioning/scheduler.d.ts.map +1 -1
  246. package/dist/versioning/scheduler.js +21 -7
  247. package/dist/versioning/scheduler.js.map +1 -1
  248. package/package.json +1 -1
@@ -0,0 +1,346 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import React from 'react';
4
+ import { collectComponentInstances, componentDetachSource, componentRefKey, refreshComponentCopies, } from '../../layout/component-refs.js';
5
+ import { adminFetch } from '../../host-client/index.js';
6
+ import { applyInstanceReplacements, buildUpdateAll, detachInstanceKeepingEdits, diffLinkedInstances, isBaselineStale, linkedInstanceContentEquals, } from './component-fork.js';
7
+ import { ComponentForkDialog, EditorDialog, } from './editor-dialog.js';
8
+ import { componentErrorMessage } from './use-component-connect.js';
9
+ // ── The hook ─────────────────────────────────────────────────────────────────
10
+ export function useComponentFork({ layout, originalLayout, dispatch, library, inlineEditActive = false, inert = false, }) {
11
+ const [session, setSession] = React.useState(null);
12
+ const [phase, setPhase] = React.useState({ kind: 'asking', index: 0 });
13
+ const [usage, setUsage] = React.useState({});
14
+ // ── Session baselines ──────────────────────────────────────────────────────
15
+ //
16
+ // A link created during this session has no stored self to be compared with,
17
+ // and connecting/placing already wrote the definition — so its baseline is what
18
+ // it looked like the moment the link appeared. Recorded here, first sight
19
+ // winning, because otherwise "save as component, then fix the typo you just
20
+ // spotted" would write the fix into a fallback copy the page never renders.
21
+ // Entries for placements that later reach the server are simply ignored (the
22
+ // stored document wins in `diffLinkedInstances`), so this never goes stale in a
23
+ // way that matters.
24
+ const sessionBaselines = React.useRef(new Map());
25
+ React.useEffect(() => {
26
+ const stored = new Set(collectComponentInstances(originalLayout).map(componentRefKey));
27
+ for (const instance of collectComponentInstances(layout)) {
28
+ const key = componentRefKey(instance);
29
+ if (stored.has(key) || sessionBaselines.current.has(key))
30
+ continue;
31
+ sessionBaselines.current.set(key, instance);
32
+ }
33
+ }, [layout, originalLayout]);
34
+ // ── Fallback-copy refresh (kywi-cms#147 T4 review, C1) ─────────────────────
35
+ //
36
+ // A linked section/container stores an inline copy of its definition and the
37
+ // editor edits THAT — so before anything can be edited, the copies are brought
38
+ // back in line with the definitions the library just loaded. Without it,
39
+ // update-all built the new definition out of a stale copy and reverted every
40
+ // newer edit of that component everywhere, silently, published pages included.
41
+ //
42
+ // Once per library SNAPSHOT: the components array is a fresh identity on load, on
43
+ // `add` (a component this session created) and on `update` (a definition this
44
+ // session wrote), and each of those genuinely changes what a copy should hold.
45
+ // The layout is a dependency because the effect reads it, not because a layout
46
+ // change should re-run this — the ref guard is what stops that, and it has to,
47
+ // since every edit changes the layout.
48
+ //
49
+ // The reducer refuses the dispatch on a document that is no longer pristine (see
50
+ // REFRESH_COMPONENT_COPIES), so an author already mid-edit when a slow library
51
+ // arrives keeps their work and is protected by `isBaselineStale` instead.
52
+ const refreshedForRef = React.useRef(null);
53
+ React.useEffect(() => {
54
+ // A definition canvas has no placements to refresh — see `inert`.
55
+ if (inert)
56
+ return;
57
+ // Deliberately does NOT update `refreshedForRef` on this branch, so the
58
+ // effect retries against the SAME library snapshot once the session ends
59
+ // (`inlineEditActive` flipping back to false is itself a dependency below,
60
+ // which re-runs this effect) — see `inlineEditActive`'s doc for why landing
61
+ // mid-session is unsafe rather than merely late.
62
+ if (inlineEditActive)
63
+ return;
64
+ if (refreshedForRef.current === library.components)
65
+ return;
66
+ refreshedForRef.current = library.components;
67
+ const refreshed = refreshComponentCopies(layout, library.lookup);
68
+ if (refreshed === layout)
69
+ return;
70
+ dispatch({ type: 'REFRESH_COMPONENT_COPIES', layout: refreshed });
71
+ }, [library, layout, dispatch, inlineEditActive, inert]);
72
+ const close = React.useCallback(() => {
73
+ setSession(null);
74
+ setPhase({ kind: 'asking', index: 0 });
75
+ }, []);
76
+ // ── Opening ────────────────────────────────────────────────────────────────
77
+ const guardSave = React.useCallback((perform, mode = 'save') => {
78
+ // A definition save is the propagation; there is nothing to ask (see `inert`).
79
+ if (inert) {
80
+ void perform(layout);
81
+ return;
82
+ }
83
+ // A save while the dialog is open would ask twice about the same edits.
84
+ if (session)
85
+ return;
86
+ const edits = diffLinkedInstances(originalLayout, layout, ref => sessionBaselines.current.get(componentRefKey(ref)));
87
+ if (edits.length === 0) {
88
+ void perform(layout);
89
+ return;
90
+ }
91
+ const groups = groupEdits(edits, library);
92
+ setSession({ layout, groups, decisions: {}, perform, mode });
93
+ setPhase({ kind: 'asking', index: 0 });
94
+ // Blast radius, best-effort and in parallel: one usage read per component,
95
+ // fired now so the numbers are there by the time the owner reads them.
96
+ setUsage(Object.fromEntries(groups.map(g => [g.componentId, { state: 'pending' }])));
97
+ for (const group of groups) {
98
+ void fetchUsage(group.componentId).then(radius => {
99
+ setUsage(prev => ({ ...prev, [group.componentId]: radius }));
100
+ });
101
+ }
102
+ }, [session, layout, originalLayout, library, inert]);
103
+ // ── Executing ──────────────────────────────────────────────────────────────
104
+ const execute = React.useCallback(async (current) => {
105
+ setPhase({ kind: 'saving' });
106
+ const replacements = new Map();
107
+ const updated = [];
108
+ // Definitions this save has written, so the OTHER placements of the same
109
+ // component — the ones nobody edited — can be refreshed to it before the page
110
+ // is stored (kywi-cms#147 T4 review, C1). Without this, editing placement A,
111
+ // updating everywhere, then editing placement B and updating again rebuilt the
112
+ // definition from B's now-stale copy and destroyed A's edit inside one session.
113
+ const written = new Map();
114
+ for (const group of current.groups) {
115
+ const decision = current.decisions[group.componentId] ?? 'this-page-only';
116
+ if (decision === 'this-page-only') {
117
+ for (const edit of group.edits) {
118
+ replacements.set(componentRefKey(edit), detachInstanceKeepingEdits(edit, library.lookup(edit.componentId)));
119
+ }
120
+ continue;
121
+ }
122
+ // Every edit in the group agrees (that is what makes update-all
123
+ // available), so any of them defines the component; each placement still
124
+ // gets its own clean instance, since a clean module instance is built
125
+ // from the node it replaces.
126
+ const plans = group.edits.map(edit => buildUpdateAll(edit, {
127
+ source: library.lookup(edit.componentId),
128
+ resolveLink: library.lookup,
129
+ }));
130
+ const definition = plans[0]?.definition;
131
+ if (!definition || plans.some(plan => plan === null)) {
132
+ // Only reachable if the library changed under us between the question
133
+ // and the answer (`groupEdits` already withdrew update-all otherwise).
134
+ setPhase({
135
+ kind: 'failed',
136
+ message: `${alreadyUpdated(updated)}Could not work out what to save for ` +
137
+ `“${group.name ?? 'this component'}”, so the page was not saved.`,
138
+ });
139
+ return;
140
+ }
141
+ let res;
142
+ try {
143
+ res = await adminFetch(`/api/v1/components/${group.componentId}`, {
144
+ method: 'PUT',
145
+ body: JSON.stringify({ definition }),
146
+ });
147
+ }
148
+ catch {
149
+ setPhase({
150
+ kind: 'failed',
151
+ message: `${alreadyUpdated(updated)}Could not reach the server, so “${group.name ?? 'this component'}” was not updated and the page was not saved.`,
152
+ });
153
+ return;
154
+ }
155
+ if (!res.ok) {
156
+ setPhase({
157
+ kind: 'failed',
158
+ message: `${alreadyUpdated(updated)}“${group.name ?? 'This component'}” was not updated: ${await componentErrorMessage(res)} The page was not saved.`,
159
+ });
160
+ return;
161
+ }
162
+ updated.push(group.name ?? 'the component');
163
+ group.edits.forEach((edit, index) => {
164
+ replacements.set(componentRefKey(edit), plans[index].cleanInstance);
165
+ });
166
+ // The library is the editor's copy of what the server now holds — every
167
+ // later read of it (the rail's values, the canvas preview, the next
168
+ // staleness check) must see the definition this save just wrote.
169
+ const row = library.byId(group.componentId);
170
+ if (row) {
171
+ written.set(group.componentId, componentDetachSource({ ...row, definition }));
172
+ }
173
+ library.update(group.componentId, definition);
174
+ }
175
+ // One document, one history entry, handed to the reducer BY REFERENCE so
176
+ // the shell's MARK_SAVED can recognise it (see the action's doc).
177
+ //
178
+ // Two passes, in this order: the answers first (each edited placement becomes
179
+ // a clean instance of the new definition), then the copy refresh for the
180
+ // placements nobody edited. The refresh is a no-op on the ones just replaced —
181
+ // `buildUpdateAll` builds `cleanInstance` by applying the new definition to the
182
+ // placement, which is precisely what refreshing would compute.
183
+ const answered = applyInstanceReplacements(current.layout, replacements);
184
+ const resolved = written.size > 0
185
+ ? refreshComponentCopies(answered, id => written.get(id) ?? null)
186
+ : answered;
187
+ dispatch({ type: 'APPLY_COMPONENT_FORK', layout: resolved });
188
+ let ok;
189
+ try {
190
+ ok = await Promise.resolve(current.perform(resolved));
191
+ }
192
+ catch {
193
+ ok = false;
194
+ }
195
+ if (ok === false) {
196
+ setPhase({
197
+ kind: 'failed',
198
+ message: `${alreadyUpdated(updated)}This page could not be saved, so it still has unsaved changes. ` +
199
+ 'Try again — updating a component twice changes nothing.',
200
+ });
201
+ return;
202
+ }
203
+ close();
204
+ }, [library, dispatch, close]);
205
+ const decide = React.useCallback((decision) => {
206
+ if (!session)
207
+ return;
208
+ if (phase.kind !== 'asking')
209
+ return;
210
+ const group = session.groups[phase.index];
211
+ if (!group)
212
+ return;
213
+ const next = {
214
+ ...session,
215
+ decisions: { ...session.decisions, [group.componentId]: decision },
216
+ };
217
+ setSession(next);
218
+ if (phase.index + 1 < next.groups.length) {
219
+ setPhase({ kind: 'asking', index: phase.index + 1 });
220
+ return;
221
+ }
222
+ void execute(next);
223
+ }, [session, phase, execute]);
224
+ const retry = React.useCallback(() => {
225
+ if (session)
226
+ void execute(session);
227
+ }, [session, execute]);
228
+ // ── The dialog ─────────────────────────────────────────────────────────────
229
+ const dialog = React.useMemo(() => {
230
+ if (!session)
231
+ return null;
232
+ // A failure replaces the question rather than decorating it: the answers have
233
+ // already been given, and re-offering "Save to this page only" here would
234
+ // re-run whatever was decided instead of what the label says. One honest
235
+ // action — try the same thing again — plus a way to walk away.
236
+ if (phase.kind === 'failed') {
237
+ return (_jsx(EditorDialog, { title: "Could not finish saving", error: phase.message, cancelLabel: "Close", actions: [{ label: 'Try again', action: 'fork-retry', onClick: retry }], onCancel: close }));
238
+ }
239
+ // While saving, the last question stays on screen with its actions disabled —
240
+ // the owner keeps the context of what they just decided.
241
+ const index = phase.kind === 'asking' ? phase.index : session.groups.length - 1;
242
+ const group = session.groups[Math.min(index, session.groups.length - 1)];
243
+ if (!group)
244
+ return null;
245
+ return (_jsx(ComponentForkDialog, { componentName: group.name, blastRadius: usage[group.componentId] ?? { state: 'pending' }, saveMode: session.mode, step: index + 1, stepCount: session.groups.length, updateAllUnavailable: group.updateAllUnavailable, busy: phase.kind === 'saving', onUpdateAll: () => decide('update-all'), onThisPageOnly: () => decide('this-page-only'),
246
+ // Cancel/Escape aborts the WHOLE save: nothing is written, the document
247
+ // keeps every edit, and the page stays dirty. A half-applied fork would
248
+ // be worse than no save at all.
249
+ onCancel: close }));
250
+ }, [session, phase, usage, decide, retry, close]);
251
+ return React.useMemo(() => ({ dialog, guardSave }), [dialog, guardSave]);
252
+ }
253
+ // ── Helpers ──────────────────────────────────────────────────────────────────
254
+ function alreadyUpdated(updated) {
255
+ if (updated.length === 0)
256
+ return '';
257
+ const names = updated.map(name => `“${name}”`).join(', ');
258
+ return `${names} ${updated.length === 1 ? 'was' : 'were'} already updated everywhere. `;
259
+ }
260
+ /**
261
+ * Group the edits by component — one question per component, not per node.
262
+ *
263
+ * MULTIPLE COMPONENTS IN ONE SAVE are asked about one after another ("Component 1
264
+ * of 2"), because the answers are genuinely independent: updating the shared
265
+ * footer everywhere and keeping a hero tweak local is a perfectly ordinary pair
266
+ * of intentions, and a single combined dialog would either force one answer for
267
+ * both or turn into a form.
268
+ *
269
+ * TWO PLACEMENTS OF ONE COMPONENT edited DIFFERENTLY on the same page cannot both
270
+ * become the definition. Rather than picking one silently, "update all" is
271
+ * withdrawn with the reason; "this page only" still works and detaches both.
272
+ * Identical edits to both placements are fine and stay offered.
273
+ *
274
+ * A STALE COPY withdraws it too (kywi-cms#147 T4 review, C1). If what this page
275
+ * stored as the component's content is not what the component now holds, the
276
+ * definition built from it would revert whatever changed in between — everywhere,
277
+ * published pages included. `isBaselineStale` decides; the copies are normally
278
+ * refreshed long before this point, so reaching it means the library never loaded
279
+ * or arrived too late to be applied safely.
280
+ */
281
+ function groupEdits(edits, library) {
282
+ const groups = new Map();
283
+ for (const edit of edits) {
284
+ const existing = groups.get(edit.componentId);
285
+ if (existing) {
286
+ existing.edits.push(edit);
287
+ continue;
288
+ }
289
+ groups.set(edit.componentId, {
290
+ componentId: edit.componentId,
291
+ name: library.nameOf(edit.componentId),
292
+ edits: [edit],
293
+ updateAllUnavailable: null,
294
+ });
295
+ }
296
+ for (const group of groups.values()) {
297
+ const first = group.edits[0];
298
+ const divergent = group.edits.some(edit => !linkedInstanceContentEquals(first.instance, edit.instance));
299
+ if (divergent) {
300
+ group.updateAllUnavailable =
301
+ `${group.edits.length} instances of this component on this page were edited differently, ` +
302
+ 'so they cannot all become the component. Save them to this page only, or undo one of the edits.';
303
+ continue;
304
+ }
305
+ const source = library.lookup(group.componentId);
306
+ const buildable = group.edits.every(edit => buildUpdateAll(edit, { source, resolveLink: library.lookup }) !== null);
307
+ if (!buildable) {
308
+ group.updateAllUnavailable =
309
+ 'This component’s current content could not be loaded, so the editor cannot tell which of ' +
310
+ 'its settings you changed. Save to this page only, or reload the page and try again.';
311
+ continue;
312
+ }
313
+ // The belt on C1: a definition built out of an out-of-date copy would revert
314
+ // whatever changed in between, everywhere.
315
+ if (group.edits.some(edit => isBaselineStale(edit, source))) {
316
+ group.updateAllUnavailable =
317
+ 'This page’s copy of the component is out of date, so saving it to every instance would ' +
318
+ 'undo newer changes. Save to this page only, then re-edit the component from an ' +
319
+ 'up-to-date page.';
320
+ }
321
+ }
322
+ return [...groups.values()];
323
+ }
324
+ /** Where a component is placed — best-effort; a failure degrades the wording. */
325
+ async function fetchUsage(componentId) {
326
+ try {
327
+ const res = await adminFetch(`/api/v1/components/${componentId}/usage`);
328
+ if (!res.ok)
329
+ return { state: 'unknown' };
330
+ const body = (await res.json());
331
+ const data = body.data;
332
+ if (!data || typeof data.instances !== 'number' || typeof data.pages !== 'number') {
333
+ return { state: 'unknown' };
334
+ }
335
+ return {
336
+ state: 'known',
337
+ instances: data.instances,
338
+ pages: data.pages,
339
+ trashedInstances: typeof data.trashedInstances === 'number' ? data.trashedInstances : 0,
340
+ };
341
+ }
342
+ catch {
343
+ return { state: 'unknown' };
344
+ }
345
+ }
346
+ //# sourceMappingURL=use-component-fork.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-component-fork.js","sourceRoot":"","sources":["../../../src/admin/layout-editor/use-component-fork.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AACZ,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,eAAe,EACf,sBAAsB,GAGvB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAEvD,OAAO,EACL,yBAAyB,EACzB,cAAc,EACd,0BAA0B,EAC1B,mBAAmB,EACnB,eAAe,EACf,2BAA2B,GAE5B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,mBAAmB,EACnB,YAAY,GAGb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAiGlE,gFAAgF;AAEhF,MAAM,UAAU,gBAAgB,CAAC,EAC/B,MAAM,EACN,cAAc,EACd,QAAQ,EACR,OAAO,EACP,gBAAgB,GAAG,KAAK,EACxB,KAAK,GAAG,KAAK,GAuDd;IACC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAqB,IAAI,CAAC,CAAA;IACtE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;IAC7E,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAA2C,EAAE,CAAC,CAAA;IAEtF,8EAA8E;IAC9E,EAAE;IACF,6EAA6E;IAC7E,gFAAgF;IAChF,0EAA0E;IAC1E,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,gFAAgF;IAChF,oBAAoB;IACpB,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,EAA6B,CAAC,CAAA;IAC3E,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,yBAAyB,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAA;QACtF,KAAK,MAAM,QAAQ,IAAI,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,GAAG,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;YACrC,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAQ;YAClE,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;IAE5B,8EAA8E;IAC9E,EAAE;IACF,6EAA6E;IAC7E,+EAA+E;IAC/E,yEAAyE;IACzE,6EAA6E;IAC7E,+EAA+E;IAC/E,EAAE;IACF,kFAAkF;IAClF,8EAA8E;IAC9E,+EAA+E;IAC/E,+EAA+E;IAC/E,+EAA+E;IAC/E,uCAAuC;IACvC,EAAE;IACF,iFAAiF;IACjF,+EAA+E;IAC/E,0EAA0E;IAC1E,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,CAAU,IAAI,CAAC,CAAA;IACnD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,kEAAkE;QAClE,IAAI,KAAK;YAAE,OAAM;QACjB,wEAAwE;QACxE,yEAAyE;QACzE,2EAA2E;QAC3E,4EAA4E;QAC5E,iDAAiD;QACjD,IAAI,gBAAgB;YAAE,OAAM;QAC5B,IAAI,eAAe,CAAC,OAAO,KAAK,OAAO,CAAC,UAAU;YAAE,OAAM;QAC1D,eAAe,CAAC,OAAO,GAAG,OAAO,CAAC,UAAU,CAAA;QAC5C,MAAM,SAAS,GAAG,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QAChE,IAAI,SAAS,KAAK,MAAM;YAAE,OAAM;QAChC,QAAQ,CAAC,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;IACnE,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAA;IAExD,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACnC,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;IACxC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,8EAA8E;IAE9E,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CACjC,CAAC,OAAoB,EAAE,OAA8B,MAAM,EAAE,EAAE;QAC7D,+EAA+E;QAC/E,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QACD,wEAAwE;QACxE,IAAI,OAAO;YAAE,OAAM;QACnB,MAAM,KAAK,GAAG,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAC9D,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CACnD,CAAA;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;YACpB,OAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QACzC,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5D,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QAEtC,2EAA2E;QAC3E,uEAAuE;QACvE,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAW,CAAC,CAAC,CAAC,CAAC,CAAA;QAC7F,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,KAAK,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;gBAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;YAC9D,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,EACD,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,CAAC,CAClD,CAAA;IAED,8EAA8E;IAE9E,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAC/B,KAAK,EAAE,OAAoB,EAAE,EAAE;QAC7B,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC5B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAyD,CAAA;QACrF,MAAM,OAAO,GAAa,EAAE,CAAA;QAC5B,yEAAyE;QACzE,8EAA8E;QAC9E,6EAA6E;QAC7E,+EAA+E;QAC/E,gFAAgF;QAChF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiC,CAAA;QAExD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAAA;YACzE,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;gBAClC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;oBAC/B,YAAY,CAAC,GAAG,CACd,eAAe,CAAC,IAAI,CAAC,EACrB,0BAA0B,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CACnE,CAAA;gBACH,CAAC;gBACD,SAAQ;YACV,CAAC;YAED,gEAAgE;YAChE,yEAAyE;YACzE,sEAAsE;YACtE,6BAA6B;YAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACnC,cAAc,CAAC,IAAI,EAAE;gBACnB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;gBACxC,WAAW,EAAE,OAAO,CAAC,MAAM;aAC5B,CAAC,CACH,CAAA;YACD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAA;YACvC,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBACrD,sEAAsE;gBACtE,uEAAuE;gBACvE,QAAQ,CAAC;oBACP,IAAI,EAAE,QAAQ;oBACd,OAAO,EACL,GAAG,cAAc,CAAC,OAAO,CAAC,sCAAsC;wBAChE,IAAI,KAAK,CAAC,IAAI,IAAI,gBAAgB,+BAA+B;iBACpE,CAAC,CAAA;gBACF,OAAM;YACR,CAAC;YAED,IAAI,GAAa,CAAA;YACjB,IAAI,CAAC;gBACH,GAAG,GAAG,MAAM,UAAU,CAAC,sBAAsB,KAAK,CAAC,WAAW,EAAE,EAAE;oBAChE,MAAM,EAAE,KAAK;oBACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,CAAC;iBACrC,CAAC,CAAA;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,CAAC;oBACP,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,mCAAmC,KAAK,CAAC,IAAI,IAAI,gBAAgB,+CAA+C;iBACpJ,CAAC,CAAA;gBACF,OAAM;YACR,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,QAAQ,CAAC;oBACP,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,gBAAgB,sBAAsB,MAAM,qBAAqB,CAAC,GAAG,CAAC,0BAA0B;iBACtJ,CAAC,CAAA;gBACF,OAAM;YACR,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,eAAe,CAAC,CAAA;YAC3C,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAClC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAE,CAAC,aAAa,CAAC,CAAA;YACtE,CAAC,CAAC,CAAA;YACF,wEAAwE;YACxE,oEAAoE;YACpE,iEAAiE;YACjE,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;YAC3C,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,qBAAqB,CAAC,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;YAC/E,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;QAC/C,CAAC;QAED,yEAAyE;QACzE,kEAAkE;QAClE,EAAE;QACF,8EAA8E;QAC9E,yEAAyE;QACzE,+EAA+E;QAC/E,gFAAgF;QAChF,+DAA+D;QAC/D,MAAM,QAAQ,GAAG,yBAAyB,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;QACxE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,GAAG,CAAC;YAC/B,CAAC,CAAC,sBAAsB,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC;YACjE,CAAC,CAAC,QAAQ,CAAA;QACZ,QAAQ,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAE5D,IAAI,EAAkB,CAAA;QACtB,IAAI,CAAC;YACH,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,EAAE,GAAG,KAAK,CAAA;QACZ,CAAC;QACD,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;YACjB,QAAQ,CAAC;gBACP,IAAI,EAAE,QAAQ;gBACd,OAAO,EACL,GAAG,cAAc,CAAC,OAAO,CAAC,iEAAiE;oBAC3F,yDAAyD;aAC5D,CAAC,CAAA;YACF,OAAM;QACR,CAAC;QACD,KAAK,EAAE,CAAA;IACT,CAAC,EACD,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAC3B,CAAA;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAC9B,CAAC,QAAsB,EAAE,EAAE;QACzB,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAM;QACnC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACzC,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,IAAI,GAAgB;YACxB,GAAG,OAAO;YACV,SAAS,EAAE,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE;SACnE,CAAA;QACD,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACzC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;YACpD,OAAM;QACR,CAAC;QACD,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC,EACD,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAC1B,CAAA;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACnC,IAAI,OAAO;YAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACpC,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAEtB,8EAA8E;IAE9E,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QAChC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QAEzB,8EAA8E;QAC9E,0EAA0E;QAC1E,yEAAyE;QACzE,+DAA+D;QAC/D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,CACL,KAAC,YAAY,IACX,KAAK,EAAC,yBAAyB,EAC/B,KAAK,EAAE,KAAK,CAAC,OAAO,EACpB,WAAW,EAAC,OAAO,EACnB,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EACvE,QAAQ,EAAE,KAAK,GACf,CACH,CAAA;QACH,CAAC;QAED,8EAA8E;QAC9E,yDAAyD;QACzD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;QAC/E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;QACxE,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,OAAO,CACL,KAAC,mBAAmB,IAClB,aAAa,EAAE,KAAK,CAAC,IAAI,EACzB,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAC7D,QAAQ,EAAE,OAAO,CAAC,IAAI,EACtB,IAAI,EAAE,KAAK,GAAG,CAAC,EACf,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAChC,oBAAoB,EAAE,KAAK,CAAC,oBAAoB,EAChD,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,QAAQ,EAC7B,WAAW,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EACvC,cAAc,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC;YAC9C,wEAAwE;YACxE,wEAAwE;YACxE,gCAAgC;YAChC,QAAQ,EAAE,KAAK,GACf,CACH,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IAEjD,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;AAC1E,CAAC;AAED,gFAAgF;AAEhF,SAAS,cAAc,CAAC,OAAiB;IACvC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzD,OAAO,GAAG,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,+BAA+B,CAAA;AACzF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,UAAU,CAAC,KAA2B,EAAE,OAAyB;IACxE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAA;IAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACzB,SAAQ;QACV,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YACtC,KAAK,EAAE,CAAC,IAAI,CAAC;YACb,oBAAoB,EAAE,IAAI;SAC3B,CAAC,CAAA;IACJ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAE,CAAA;QAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAChC,IAAI,CAAC,EAAE,CAAC,CAAC,2BAA2B,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CACpE,CAAA;QACD,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,CAAC,oBAAoB;gBACxB,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,qEAAqE;oBAC1F,iGAAiG,CAAA;YACnG,SAAQ;QACV,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QAChD,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CACjC,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAC/E,CAAA;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,KAAK,CAAC,oBAAoB;gBACxB,2FAA2F;oBAC3F,qFAAqF,CAAA;YACvF,SAAQ;QACV,CAAC;QACD,6EAA6E;QAC7E,2CAA2C;QAC3C,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;YAC5D,KAAK,CAAC,oBAAoB;gBACxB,yFAAyF;oBACzF,iFAAiF;oBACjF,kBAAkB,CAAA;QACtB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED,iFAAiF;AACjF,KAAK,UAAU,UAAU,CAAC,WAAmB;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,sBAAsB,WAAW,QAAQ,CAAC,CAAA;QACvE,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAA;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;QACtB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAClF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;QAC7B,CAAC;QACD,OAAO;YACL,KAAK,EAAE,OAAO;YACd,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,gBAAgB,EAAE,OAAO,IAAI,CAAC,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;SACxF,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IAC7B,CAAC;AACH,CAAC"}
@@ -0,0 +1,94 @@
1
+ import type { ComponentPlacementLookup } from '../../layout/component-refs.js';
2
+ import type { ModuleNode } from '../../layout/types.js';
3
+ import type { ComponentOption } from './component-picker-field.js';
4
+ /**
5
+ * use-component-library.ts
6
+ * The saved components an editor shell has loaded, and the projections both
7
+ * shells need from them (kywi-cms#147).
8
+ *
9
+ * WHY ONE HOOK FOR BOTH SHELLS. The admin Layout tab fetched module components
10
+ * and section components in two separate effects and kept them in two states; the
11
+ * front-of-site overlay fetched neither, which is why "Save as component" and the
12
+ * linked badge existed only in the admin editor even though the overlay is the
13
+ * PRIMARY editing surface (kywi-cms#94). One hook, one request, three types.
14
+ *
15
+ * The request is deliberately unfiltered (`GET /components`, no `?type=`): a
16
+ * document can hold links of all three kinds at once, every one of them needs a
17
+ * name for its pill and a definition for detach, and one round trip is cheaper
18
+ * than three. Best-effort throughout — a failed load leaves the library empty,
19
+ * which degrades pills to the generic "Component" and leaves save-as-component
20
+ * fully working (it only ever POSTs).
21
+ */
22
+ /** A saved component as `GET /api/v1/components` returns it. */
23
+ export interface EditorComponent {
24
+ id: string;
25
+ name: string;
26
+ type: string;
27
+ moduleType: string | null;
28
+ definition: unknown;
29
+ }
30
+ export interface ComponentLibrary {
31
+ /** Every loaded component, all types. */
32
+ components: EditorComponent[];
33
+ byId: (componentId: string) => EditorComponent | null;
34
+ /** Display name for a linked instance's pill, or null when unknown. */
35
+ nameOf: (componentId: string) => string | null;
36
+ /** Picker options for one component type. */
37
+ optionsFor: (type: 'module' | 'section' | 'variantContainer') => ComponentOption[];
38
+ /**
39
+ * The canonical lookup materialisation reads (`componentDetachSource`), used by
40
+ * detach, by save-as-component's nested-link flattening, and by anything else
41
+ * that has to answer "what does this link render".
42
+ */
43
+ lookup: ComponentPlacementLookup;
44
+ /** Module-component projection for the canvas preview (kywi-cms#46). */
45
+ resolveModule: (componentId: string) => {
46
+ props: Record<string, unknown>;
47
+ style: Record<string, string>;
48
+ moduleType: string;
49
+ } | null;
50
+ /**
51
+ * What a module node RENDERS: its own props/style for an ordinary node, and for
52
+ * a linked instance the definition read through the canonical placement rule
53
+ * (`resolveModulePlacement` — definition alone for a clean instance, definition
54
+ * then node for a legacy override one).
55
+ *
56
+ * The props rail needs exactly this (kywi-cms#147 T4): a clean instance carries
57
+ * no props of its own, so fields fed from `node.props` would show an empty form
58
+ * for a module full of content, and the first keystroke would look like it
59
+ * cleared everything. Same projection as the canvas preview and the public
60
+ * renderer, so what the rail shows is what the visitor sees.
61
+ */
62
+ renderedModule: (node: Pick<ModuleNode, 'props' | 'style' | 'componentId'>) => {
63
+ props: Record<string, unknown>;
64
+ style: Record<string, string>;
65
+ };
66
+ /**
67
+ * Which module type's registry entry describes a node's editable FIELDS, or null
68
+ * when nothing honest can be shown (kywi-cms#147 T4 review, I2).
69
+ *
70
+ * For an ordinary node that is its own `type`. For a linked instance it is the
71
+ * COMPONENT's `moduleType`, because that is what the placement renders as
72
+ * (`materializeModuleNode` retypes the node) — and because a component placed
73
+ * from the palette picker is a `component` placeholder node, whose registry entry
74
+ * declares "Component Slug" and "Component ID" props. Those are link plumbing,
75
+ * not content: seeded from the resolved definition they read empty, and typing
76
+ * into "Component ID" writes a prop the fork deliberately strips.
77
+ *
78
+ * Null for a linked placeholder whose component does not resolve — there is no
79
+ * module type, so there are no fields, and inventing the placeholder's own two is
80
+ * how the rail came to show a form nobody could use.
81
+ */
82
+ fieldModuleType: (node: Pick<ModuleNode, 'type' | 'componentId'>) => string | null;
83
+ /** Adopt a component the shell just created, so its name is available at once. */
84
+ add: (component: EditorComponent) => void;
85
+ /**
86
+ * Adopt a definition the shell just wrote (the save fork's `PUT`), so everything
87
+ * reading the library — the rail's field values, the canvas preview, the copy
88
+ * refresh, and the next fork's staleness check — sees the new content rather than
89
+ * the one the page loaded with (kywi-cms#147 T4 review, C1).
90
+ */
91
+ update: (componentId: string, definition: unknown) => void;
92
+ }
93
+ export declare function useComponentLibrary(): ComponentLibrary;
94
+ //# sourceMappingURL=use-component-library.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-component-library.d.ts","sourceRoot":"","sources":["../../../src/admin/layout-editor/use-component-library.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAGvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAElE;;;;;;;;;;;;;;;;;GAiBG;AAEH,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,UAAU,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,UAAU,EAAE,eAAe,EAAE,CAAA;IAC7B,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,eAAe,GAAG,IAAI,CAAA;IACrD,uEAAuE;IACvE,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IAC9C,6CAA6C;IAC7C,UAAU,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,kBAAkB,KAAK,eAAe,EAAE,CAAA;IAClF;;;;OAIG;IACH,MAAM,EAAE,wBAAwB,CAAA;IAChC,wEAAwE;IACxE,aAAa,EAAE,CACb,WAAW,EAAE,MAAM,KAChB;QAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACjG;;;;;;;;;;;OAWG;IACH,cAAc,EAAE,CACd,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,GAAG,aAAa,CAAC,KACtD;QAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IACtE;;;;;;;;;;;;;;;OAeG;IACH,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;IAClF,kFAAkF;IAClF,GAAG,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC;;;;;OAKG;IACH,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,KAAK,IAAI,CAAA;CAC3D;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,CAkFtD"}
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { componentDetachSource, resolveModulePlacement } from '../../layout/component-refs.js';
4
+ import { adminFetch } from '../../host-client/index.js';
5
+ import { resolveComponentDefinition } from './component-actions.js';
6
+ export function useComponentLibrary() {
7
+ const [components, setComponents] = useState([]);
8
+ useEffect(() => {
9
+ let cancelled = false;
10
+ adminFetch('/api/v1/components')
11
+ .then(r => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
12
+ .then((body) => {
13
+ if (!cancelled)
14
+ setComponents(body.data ?? []);
15
+ })
16
+ .catch(() => { });
17
+ return () => { cancelled = true; };
18
+ }, []);
19
+ const byId = useCallback((componentId) => components.find(c => c.id === componentId) ?? null, [components]);
20
+ const nameOf = useCallback((componentId) => byId(componentId)?.name ?? null, [byId]);
21
+ const optionsFor = useCallback((type) => components
22
+ .filter(c => c.type === type)
23
+ .map(c => ({ id: c.id, name: c.name, moduleType: c.moduleType })), [components]);
24
+ const lookup = useCallback((componentId) => componentDetachSource(byId(componentId)), [byId]);
25
+ const resolveModule = useCallback((componentId) => resolveComponentDefinition(byId(componentId)), [byId]);
26
+ const renderedModule = useCallback((node) => {
27
+ const resolved = node.componentId ? resolveComponentDefinition(byId(node.componentId)) : null;
28
+ if (!resolved)
29
+ return { props: node.props ?? {}, style: node.style ?? {} };
30
+ return resolveModulePlacement(node, resolved);
31
+ }, [byId]);
32
+ const fieldModuleType = useCallback((node) => {
33
+ if (!node.componentId)
34
+ return node.type;
35
+ const resolved = resolveComponentDefinition(byId(node.componentId));
36
+ if (resolved)
37
+ return resolved.moduleType;
38
+ // An unresolvable link on a placeholder node has no fields to offer.
39
+ return node.type === 'component' ? null : node.type;
40
+ }, [byId]);
41
+ const add = useCallback((component) => {
42
+ setComponents(prev => (prev.some(c => c.id === component.id) ? prev : [...prev, component]));
43
+ }, []);
44
+ const update = useCallback((componentId, definition) => {
45
+ setComponents(prev => prev.map(c => (c.id === componentId ? { ...c, definition } : c)));
46
+ }, []);
47
+ return useMemo(() => ({
48
+ components, byId, nameOf, optionsFor, lookup, resolveModule, renderedModule,
49
+ fieldModuleType, add, update,
50
+ }), [
51
+ components, byId, nameOf, optionsFor, lookup, resolveModule, renderedModule,
52
+ fieldModuleType, add, update,
53
+ ]);
54
+ }
55
+ //# sourceMappingURL=use-component-library.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-component-library.js","sourceRoot":"","sources":["../../../src/admin/layout-editor/use-component-library.ts"],"names":[],"mappings":"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"}
@@ -12,9 +12,18 @@ import type { KywiRole } from '../auth/roles.js';
12
12
  /**
13
13
  * Feature-area taxonomy — the granularity at which config (`admin.features`) and
14
14
  * runtime flags (`admin.features` site-settings key, T16) enable/disable admin.
15
- * 23 areas.
15
+ * 24 areas.
16
16
  */
17
- export type AdminArea = 'dashboard' | 'contentTree' | 'menus' | 'content' | 'approvals' | 'changesets' | 'search' | 'media' | 'users' | 'forms' | 'taxonomy' | 'trash' | 'comments' | 'translations' | 'feeds' | 'audiences' | 'experiments' | 'layouts' | 'extensions' | 'plugins' | 'webServices' | 'settings' | 'contentTypes';
17
+ export type AdminArea = 'dashboard' | 'contentTree' | 'menus' | 'content' | 'approvals' | 'changesets' | 'search' | 'media' | 'users' | 'forms' | 'taxonomy' | 'trash' | 'comments' | 'translations' | 'feeds' | 'audiences' | 'experiments' | 'layouts'
18
+ /**
19
+ * The reusable-components library (kywi-cms#147). Its own area, not a slice of
20
+ * `layouts`: a saved component is a live source that pages LINK to (editing one
21
+ * changes every page it is placed on), whereas a layout is a blueprint that is
22
+ * copied on use. A site that wants one and not the other — components without
23
+ * the standalone layout builder, say — can now say so, and disabling `layouts`
24
+ * no longer takes the components library with it.
25
+ */
26
+ | 'components' | 'extensions' | 'plugins' | 'webServices' | 'settings' | 'contentTypes';
18
27
  /** Runtime-checkable list of every area, in declaration order. */
19
28
  export declare const ADMIN_AREAS: readonly AdminArea[];
20
29
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/admin/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEhD;;;;;;;;GAQG;AAIH;;;;GAIG;AACH,MAAM,MAAM,SAAS,GACjB,WAAW,GACX,aAAa,GACb,OAAO,GACP,SAAS,GACT,WAAW,GACX,YAAY,GACZ,QAAQ,GACR,OAAO,GACP,OAAO,GACP,OAAO,GACP,UAAU,GACV,OAAO,GACP,UAAU,GACV,cAAc,GACd,OAAO,GACP,WAAW,GACX,aAAa,GACb,SAAS,GACT,YAAY,GACZ,SAAS,GACT,aAAa,GACb,UAAU,GACV,cAAc,CAAA;AAElB,kEAAkE;AAClE,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAwBlC,CAAA;AAIV;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAA;IACV,oDAAoD;IACpD,IAAI,EAAE,SAAS,CAAA;IACf;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,+FAA+F;IAC/F,GAAG,CAAC,EAAE,eAAe,CAAA;IACrB,gFAAgF;IAChF,OAAO,EAAE,QAAQ,CAAA;IACjB;;;;;;;;OAQG;IACH,IAAI,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAA;KAAE,CAAC,CAAA;CAC9D;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAA;IACV,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;IAC9C,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAIvD,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAQ3C,CAAA;AAQV,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EA8cxC,CAAA;AAEV;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,oBAAoB,CAAA"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/admin/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEhD;;;;;;;;GAQG;AAIH;;;;GAIG;AACH,MAAM,MAAM,SAAS,GACjB,WAAW,GACX,aAAa,GACb,OAAO,GACP,SAAS,GACT,WAAW,GACX,YAAY,GACZ,QAAQ,GACR,OAAO,GACP,OAAO,GACP,OAAO,GACP,UAAU,GACV,OAAO,GACP,UAAU,GACV,cAAc,GACd,OAAO,GACP,WAAW,GACX,aAAa,GACb,SAAS;AACX;;;;;;;GAOG;GACD,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,aAAa,GACb,UAAU,GACV,cAAc,CAAA;AAElB,kEAAkE;AAClE,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAyBlC,CAAA;AAIV;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAA;IACV,oDAAoD;IACpD,IAAI,EAAE,SAAS,CAAA;IACf;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,+FAA+F;IAC/F,GAAG,CAAC,EAAE,eAAe,CAAA;IACrB,gFAAgF;IAChF,OAAO,EAAE,QAAQ,CAAA;IACjB;;;;;;;;OAQG;IACH,IAAI,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAA;KAAE,CAAC,CAAA;CAC9D;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAA;IACV,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;IAC9C,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAIvD,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAQ3C,CAAA;AAQV,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAkfxC,CAAA;AAEV;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,oBAAoB,CAAA"}
@@ -18,6 +18,7 @@ export const ADMIN_AREAS = [
18
18
  'audiences',
19
19
  'experiments',
20
20
  'layouts',
21
+ 'components',
21
22
  'extensions',
22
23
  'plugins',
23
24
  'webServices',
@@ -30,9 +31,9 @@ export const ADMIN_NAV_GROUPS = [
30
31
  { id: 'users', label: 'Users', href: '/admin/users', icon: 'users', order: 8 },
31
32
  { id: 'forms', label: 'Forms', href: '/admin/forms', icon: 'clipboard', order: 9 },
32
33
  { id: 'audiences', label: 'Audiences', href: '/admin/audiences', icon: 'target', order: 16 },
33
- { id: 'extensions', label: 'Extensions', href: '/admin/extensions', icon: 'blocks', order: 19 },
34
- { id: 'webServices', label: 'Web Services', href: '/admin/web-services', icon: 'globe', order: 21 },
35
- { id: 'settings', label: 'Settings', href: '/admin/settings', icon: 'settings', order: 22 },
34
+ { id: 'extensions', label: 'Extensions', href: '/admin/extensions', icon: 'blocks', order: 20 },
35
+ { id: 'webServices', label: 'Web Services', href: '/admin/web-services', icon: 'globe', order: 22 },
36
+ { id: 'settings', label: 'Settings', href: '/admin/settings', icon: 'settings', order: 23 },
36
37
  ];
37
38
  // ─── Surfaces ───────────────────────────────────────────────────────────────────
38
39
  // Sourced by cross-referencing packages/core/src/admin/nav.ts (nav-visible set)
@@ -321,6 +322,41 @@ export const ADMIN_SURFACES = [
321
322
  },
322
323
  { id: 'layouts.new', area: 'layouts', pattern: 'layouts/new', label: 'New Layout', minRole: 'editor', load: () => import('./surfaces/layouts/layouts-new.js') },
323
324
  { id: 'layouts.detail', area: 'layouts', pattern: 'layouts/:id', label: 'Layout', minRole: 'editor', load: () => import('./surfaces/layouts/layouts-detail.js') },
325
+ // components (kywi-cms#147) — the reusable-components library, promoted out of
326
+ // the read-only sub-panel it used to occupy on the Layouts page. Sits directly
327
+ // after Layouts: both are "reusable building blocks", and inserting here shifted
328
+ // every later top-level `order` by one (the #112 precedent).
329
+ //
330
+ // ON minRole. `component-routes.ts` gates reading and writing the library at
331
+ // `write` (⇒ author) and DELETE at `delete` (⇒ siteAdmin). Only the second of
332
+ // those is reproduced in the UI (the list withholds the Delete action below
333
+ // siteAdmin) — the read floor here is `editor`, matching every other nav
334
+ // surface, because `editor` is not the admin's convention by accident: no
335
+ // surface in this registry is reachable below it, so an author-floored
336
+ // Components page would be a one-item admin (no Dashboard, no Site Tree, no
337
+ // content lists) rather than an unlocked capability. `editor` satisfies the
338
+ // route's `write` gate, so nobody who reaches this page gets a 403 from it. If
339
+ // the admin ever grows an author floor, this is the line to revisit.
340
+ {
341
+ id: 'components.list',
342
+ area: 'components',
343
+ pattern: 'components',
344
+ label: 'Components',
345
+ minRole: 'editor',
346
+ nav: { section: 'primary', icon: 'boxes', order: 19 },
347
+ load: () => import('./surfaces/components/components-list.js'),
348
+ },
349
+ {
350
+ // The definition-editing canvas. Its own surface (not a panel on the list) so
351
+ // the `LayoutEditor` it mounts for section/container components stays in its
352
+ // own chunk and the list page loads none of it — the same split Layouts uses.
353
+ id: 'components.edit',
354
+ area: 'components',
355
+ pattern: 'components/:id',
356
+ label: 'Component',
357
+ minRole: 'editor',
358
+ load: () => import('./surfaces/components/component-edit.js'),
359
+ },
324
360
  // extensions (group) — the extensions ADMIN surface manages subtypes/sets/
325
361
  // attributes and import/export, all siteAdmin-gated (extension-routes.ts). The
326
362
  // editor-gated `/extensions/for-content|/data` endpoints belong to the content
@@ -350,7 +386,7 @@ export const ADMIN_SURFACES = [
350
386
  pattern: 'plugins',
351
387
  label: 'Plugins',
352
388
  minRole: 'siteAdmin',
353
- nav: { section: 'primary', icon: 'puzzle', order: 20 },
389
+ nav: { section: 'primary', icon: 'puzzle', order: 21 },
354
390
  load: () => import('./surfaces/plugins/plugins-index.js'),
355
391
  },
356
392
  // webServices (group)