@drawnagency/primitives 0.1.56 → 0.1.58

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 (165) hide show
  1. package/dist/adapter-HH47ZPGM.js +1779 -0
  2. package/dist/auth/index.js +1 -0
  3. package/dist/{chunk-EU6NZ4GS.js → chunk-AN62WPW7.js} +22 -163
  4. package/dist/chunk-ESE5UBQI.js +73 -0
  5. package/dist/{chunk-KGYWQDBB.js → chunk-ICLXLWQ5.js} +9 -72
  6. package/dist/chunk-JSBRDJBE.js +30 -0
  7. package/dist/chunk-NSCT3AMV.js +32 -0
  8. package/dist/chunk-RFZNNCAS.js +160 -0
  9. package/dist/chunk-TG43X7JO.js +123 -0
  10. package/dist/{chunk-7IAWF7LE.js → chunk-V7JN2DDU.js} +2 -19
  11. package/dist/chunk-VKAGMEKE.js +90 -0
  12. package/dist/chunk-ZU2MKPTG.js +29 -0
  13. package/dist/closest-edge-EBOXL3YW.js +72 -0
  14. package/dist/components/editor/ChildBlockWrapper.d.ts +19 -0
  15. package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -0
  16. package/dist/components/editor/ColSpanControl.d.ts +9 -0
  17. package/dist/components/editor/ColSpanControl.d.ts.map +1 -0
  18. package/dist/components/editor/SectionWrapper.d.ts +1 -1
  19. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  20. package/dist/components/editor/SettingsForm.d.ts +5 -1
  21. package/dist/components/editor/SettingsForm.d.ts.map +1 -1
  22. package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
  23. package/dist/components/primitives/IconPicker.d.ts +7 -1
  24. package/dist/components/primitives/IconPicker.d.ts.map +1 -1
  25. package/dist/components/sections/Container/Container.d.ts +20 -0
  26. package/dist/components/sections/Container/Container.d.ts.map +1 -0
  27. package/dist/components/sections/Container/ContainerSettingsForm.d.ts +17 -0
  28. package/dist/components/sections/Container/ContainerSettingsForm.d.ts.map +1 -0
  29. package/dist/components/sections/Container/index.d.ts +11 -0
  30. package/dist/components/sections/Container/index.d.ts.map +1 -0
  31. package/dist/components/sections/IconList/IconList.d.ts +1 -0
  32. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  33. package/dist/components/sections/IconList/IconListSettings.d.ts +3 -4
  34. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
  35. package/dist/components/sections/IconList/index.d.ts +1 -0
  36. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  37. package/dist/components/sections/Media/MediaBlock.d.ts +19 -0
  38. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -0
  39. package/dist/components/sections/{MediaGrid → Media}/index.d.ts +15 -25
  40. package/dist/components/sections/Media/index.d.ts.map +1 -0
  41. package/dist/components/sections/Prose/index.d.ts.map +1 -1
  42. package/dist/components/sections/Spacer/Spacer.d.ts +2 -0
  43. package/dist/components/sections/Spacer/Spacer.d.ts.map +1 -0
  44. package/dist/components/sections/Spacer/index.d.ts +6 -0
  45. package/dist/components/sections/Spacer/index.d.ts.map +1 -0
  46. package/dist/components/sections/all-sections.d.ts +29 -103
  47. package/dist/components/sections/all-sections.d.ts.map +1 -1
  48. package/dist/components/sections/register-schemas.d.ts.map +1 -1
  49. package/dist/components/sections/register-schemas.js +4094 -0
  50. package/dist/components/shared/Tabs.d.ts +24 -0
  51. package/dist/components/shared/Tabs.d.ts.map +1 -0
  52. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  53. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  54. package/dist/components/shell/blockMoveDispatch.d.ts +21 -0
  55. package/dist/components/shell/blockMoveDispatch.d.ts.map +1 -0
  56. package/dist/hooks/useBlockDnd.d.ts +48 -0
  57. package/dist/hooks/useBlockDnd.d.ts.map +1 -0
  58. package/dist/index.js +69 -56
  59. package/dist/lib/block-dnd.d.ts +42 -0
  60. package/dist/lib/block-dnd.d.ts.map +1 -0
  61. package/dist/lib/block-move.d.ts +31 -0
  62. package/dist/lib/block-move.d.ts.map +1 -0
  63. package/dist/lib/container-grid.d.ts +29 -0
  64. package/dist/lib/container-grid.d.ts.map +1 -0
  65. package/dist/lib/container-ops.d.ts +44 -0
  66. package/dist/lib/container-ops.d.ts.map +1 -0
  67. package/dist/lib/dexie.d.ts.map +1 -1
  68. package/dist/lib/dexie.js +15 -0
  69. package/dist/lib/env.js +1 -0
  70. package/dist/lib/index.js +19 -13
  71. package/dist/lib/loader.d.ts.map +1 -1
  72. package/dist/lib/migrate-sections-transform.d.ts +12 -0
  73. package/dist/lib/migrate-sections-transform.d.ts.map +1 -0
  74. package/dist/lib/migrate-sections-transform.js +7 -0
  75. package/dist/lib/registry.d.ts +39 -0
  76. package/dist/lib/registry.d.ts.map +1 -1
  77. package/dist/lib/registry.js +27 -0
  78. package/dist/media/index.js +1 -0
  79. package/dist/schemas/auth.js +1 -0
  80. package/dist/schemas/block.d.ts +20 -0
  81. package/dist/schemas/block.d.ts.map +1 -0
  82. package/dist/schemas/block.js +15 -0
  83. package/dist/schemas/index.js +13 -4
  84. package/dist/schemas/link.d.ts +7 -0
  85. package/dist/schemas/link.d.ts.map +1 -1
  86. package/dist/schemas/rich-text.d.ts +9 -0
  87. package/dist/schemas/rich-text.d.ts.map +1 -0
  88. package/dist/schemas/sections.d.ts +2 -0
  89. package/dist/schemas/sections.d.ts.map +1 -1
  90. package/dist/schemas/shared.d.ts +30 -0
  91. package/dist/schemas/shared.d.ts.map +1 -1
  92. package/dist/types/database.js +2 -0
  93. package/package.json +17 -1
  94. package/src/components/brandguide/Colors.tsx +35 -33
  95. package/src/components/editor/ChildBlockWrapper.tsx +108 -0
  96. package/src/components/editor/ColSpanControl.tsx +56 -0
  97. package/src/components/editor/SectionWrapper.tsx +44 -20
  98. package/src/components/editor/SettingsForm.tsx +100 -73
  99. package/src/components/primitives/EditableGrid.tsx +40 -36
  100. package/src/components/primitives/IconPicker.tsx +116 -26
  101. package/src/components/sections/Container/Container.tsx +354 -0
  102. package/src/components/sections/Container/ContainerSettingsForm.tsx +113 -0
  103. package/src/components/sections/Container/index.tsx +51 -0
  104. package/src/components/sections/IconList/IconList.tsx +113 -43
  105. package/src/components/sections/IconList/IconListSettings.tsx +2 -2
  106. package/src/components/sections/IconList/index.tsx +1 -1
  107. package/src/components/sections/Media/MediaBlock.tsx +103 -0
  108. package/src/components/sections/Media/index.tsx +85 -0
  109. package/src/components/sections/Prose/index.tsx +1 -0
  110. package/src/components/sections/Spacer/Spacer.tsx +6 -0
  111. package/src/components/sections/Spacer/index.tsx +18 -0
  112. package/src/components/sections/all-sections.ts +10 -8
  113. package/src/components/sections/register-schemas.ts +5 -2
  114. package/src/components/shared/Tabs.tsx +63 -0
  115. package/src/components/shell/EditorShell.tsx +105 -13
  116. package/src/components/shell/SiteSettingsModal.tsx +41 -51
  117. package/src/components/shell/blockMoveDispatch.ts +40 -0
  118. package/src/hooks/useBlockDnd.ts +144 -0
  119. package/src/lib/block-dnd.ts +58 -0
  120. package/src/lib/block-move.ts +236 -0
  121. package/src/lib/container-grid.ts +58 -0
  122. package/src/lib/container-ops.ts +159 -0
  123. package/src/lib/dexie.ts +22 -0
  124. package/src/lib/loader.ts +16 -4
  125. package/src/lib/migrate-sections-transform.ts +147 -0
  126. package/src/lib/registry.ts +48 -0
  127. package/src/schemas/block.ts +40 -0
  128. package/src/schemas/link.ts +19 -1
  129. package/src/schemas/rich-text.ts +11 -0
  130. package/src/schemas/sections.ts +5 -1
  131. package/src/schemas/shared.ts +6 -0
  132. package/dist/components/brandguide/DoDontList.d.ts +0 -16
  133. package/dist/components/brandguide/DoDontList.d.ts.map +0 -1
  134. package/dist/components/brandguide/DoDontMediaGrid.d.ts +0 -16
  135. package/dist/components/brandguide/DoDontMediaGrid.d.ts.map +0 -1
  136. package/dist/components/primitives/MediaSettingsForms.d.ts +0 -23
  137. package/dist/components/primitives/MediaSettingsForms.d.ts.map +0 -1
  138. package/dist/components/sections/DoDontList/index.d.ts +0 -21
  139. package/dist/components/sections/DoDontList/index.d.ts.map +0 -1
  140. package/dist/components/sections/DoDontMediaGrid/index.d.ts +0 -55
  141. package/dist/components/sections/DoDontMediaGrid/index.d.ts.map +0 -1
  142. package/dist/components/sections/MediaGrid/MediaGrid.d.ts +0 -17
  143. package/dist/components/sections/MediaGrid/MediaGrid.d.ts.map +0 -1
  144. package/dist/components/sections/MediaGrid/index.d.ts.map +0 -1
  145. package/dist/components/sections/SplitContent/SplitContent.d.ts +0 -14
  146. package/dist/components/sections/SplitContent/SplitContent.d.ts.map +0 -1
  147. package/dist/components/sections/SplitContent/index.d.ts +0 -13
  148. package/dist/components/sections/SplitContent/index.d.ts.map +0 -1
  149. package/src/components/brandguide/DoDontList.d.ts.map +0 -1
  150. package/src/components/brandguide/DoDontList.tsx +0 -67
  151. package/src/components/brandguide/DoDontMediaGrid.d.ts.map +0 -1
  152. package/src/components/brandguide/DoDontMediaGrid.tsx +0 -19
  153. package/src/components/primitives/MediaSettingsForms.tsx +0 -128
  154. package/src/components/sections/DoDontList/index.d.ts.map +0 -1
  155. package/src/components/sections/DoDontList/index.tsx +0 -45
  156. package/src/components/sections/DoDontMediaGrid/index.d.ts.map +0 -1
  157. package/src/components/sections/DoDontMediaGrid/index.tsx +0 -63
  158. package/src/components/sections/MediaGrid/MediaGrid.d.ts.map +0 -1
  159. package/src/components/sections/MediaGrid/MediaGrid.tsx +0 -239
  160. package/src/components/sections/MediaGrid/index.d.ts.map +0 -1
  161. package/src/components/sections/MediaGrid/index.tsx +0 -57
  162. package/src/components/sections/SplitContent/SplitContent.d.ts.map +0 -1
  163. package/src/components/sections/SplitContent/SplitContent.tsx +0 -84
  164. package/src/components/sections/SplitContent/index.d.ts.map +0 -1
  165. package/src/components/sections/SplitContent/index.tsx +0 -55
@@ -0,0 +1,4094 @@
1
+ import {
2
+ getMediaProvider
3
+ } from "../../chunk-L2JJFOXD.js";
4
+ import {
5
+ DEFAULT_LINK,
6
+ LinkValueSchema
7
+ } from "../../chunk-ZU2MKPTG.js";
8
+ import {
9
+ cn,
10
+ curatedIcons,
11
+ getIcon,
12
+ sanitizeHtml,
13
+ toSectionId
14
+ } from "../../chunk-RFZNNCAS.js";
15
+ import {
16
+ ColorItemSchema,
17
+ SingleMediaReferenceSchema,
18
+ getSectionSchema
19
+ } from "../../chunk-ICLXLWQ5.js";
20
+ import "../../chunk-DKOUFIP6.js";
21
+ import "../../chunk-NSCT3AMV.js";
22
+ import {
23
+ defineSection,
24
+ getSection,
25
+ registerRichText,
26
+ registerSchema
27
+ } from "../../chunk-VKAGMEKE.js";
28
+ import "../../chunk-JSBRDJBE.js";
29
+
30
+ // src/components/sections/LinkHeading/index.tsx
31
+ import { z } from "zod";
32
+ import { Heading1 } from "lucide-react";
33
+
34
+ // src/components/primitives/EditablePlainText.tsx
35
+ import {
36
+ useRef as useRef2,
37
+ useEffect as useEffect2,
38
+ useCallback as useCallback2,
39
+ createElement
40
+ } from "react";
41
+
42
+ // src/components/primitives/useEditablePlainText.ts
43
+ import { useState, useEffect, useRef, useCallback } from "react";
44
+ function useEditablePlainText({
45
+ value: propValue,
46
+ onChange,
47
+ multiline = false
48
+ }) {
49
+ const [value, setValue] = useState(propValue);
50
+ const lastPropValue = useRef(propValue);
51
+ useEffect(() => {
52
+ if (propValue !== lastPropValue.current) {
53
+ setValue(propValue);
54
+ lastPropValue.current = propValue;
55
+ }
56
+ }, [propValue]);
57
+ const handleInput = useCallback((text) => {
58
+ setValue(text);
59
+ }, []);
60
+ const handleBlur = useCallback(() => {
61
+ if (value !== propValue) {
62
+ onChange(value);
63
+ lastPropValue.current = value;
64
+ }
65
+ }, [value, propValue, onChange]);
66
+ return {
67
+ value,
68
+ handleInput,
69
+ handleBlur,
70
+ shouldPreventEnter: !multiline
71
+ };
72
+ }
73
+
74
+ // src/components/primitives/EditablePlainText.tsx
75
+ function EditablePlainText({
76
+ tag,
77
+ value,
78
+ onChange,
79
+ isEditMode,
80
+ multiline = false,
81
+ placeholder,
82
+ className,
83
+ ...htmlAttrs
84
+ }) {
85
+ const ref = useRef2(null);
86
+ const {
87
+ value: localValue,
88
+ handleInput,
89
+ handleBlur,
90
+ shouldPreventEnter
91
+ } = useEditablePlainText({ value, onChange, multiline });
92
+ useEffect2(() => {
93
+ if (ref.current && ref.current.textContent !== localValue) {
94
+ ref.current.textContent = localValue;
95
+ }
96
+ }, [localValue]);
97
+ const onInput = useCallback2(() => {
98
+ if (ref.current) {
99
+ handleInput(ref.current.textContent || "");
100
+ }
101
+ }, [handleInput]);
102
+ const onKeyDown = useCallback2(
103
+ (e) => {
104
+ if (e.key === "Enter" && shouldPreventEnter) {
105
+ e.preventDefault();
106
+ }
107
+ },
108
+ [shouldPreventEnter]
109
+ );
110
+ const onPaste = useCallback2(
111
+ (e) => {
112
+ e.preventDefault();
113
+ const text = e.clipboardData.getData("text/plain");
114
+ const selection = window.getSelection();
115
+ if (selection && selection.rangeCount > 0) {
116
+ const range = selection.getRangeAt(0);
117
+ range.deleteContents();
118
+ range.insertNode(document.createTextNode(text));
119
+ range.collapse(false);
120
+ selection.removeAllRanges();
121
+ selection.addRange(range);
122
+ }
123
+ if (ref.current) {
124
+ handleInput(ref.current.textContent || "");
125
+ }
126
+ },
127
+ [handleInput]
128
+ );
129
+ return createElement(
130
+ tag,
131
+ {
132
+ ref,
133
+ className: isEditMode && placeholder ? `editable-placeholder ${className ?? ""}` : className,
134
+ contentEditable: isEditMode ? "true" : void 0,
135
+ suppressContentEditableWarning: isEditMode || void 0,
136
+ onInput: isEditMode ? onInput : void 0,
137
+ onBlur: isEditMode ? handleBlur : void 0,
138
+ onKeyDown: isEditMode ? onKeyDown : void 0,
139
+ onPaste: isEditMode ? onPaste : void 0,
140
+ "data-placeholder": isEditMode && placeholder ? placeholder : void 0,
141
+ ...htmlAttrs
142
+ },
143
+ value
144
+ );
145
+ }
146
+
147
+ // src/components/primitives/HeadingSection.tsx
148
+ import { jsx } from "react/jsx-runtime";
149
+ function HeadingSection({ heading, tag, placeholder, className, onChange }) {
150
+ return /* @__PURE__ */ jsx(
151
+ EditablePlainText,
152
+ {
153
+ tag,
154
+ value: heading,
155
+ onChange: onChange ?? (() => {
156
+ }),
157
+ isEditMode: !!onChange,
158
+ placeholder,
159
+ id: toSectionId(heading),
160
+ className: cn("scroll-mt-20", className)
161
+ }
162
+ );
163
+ }
164
+
165
+ // src/components/sections/LinkHeading/index.tsx
166
+ import { jsx as jsx2 } from "react/jsx-runtime";
167
+ var schema = z.object({
168
+ type: z.literal("link_heading"),
169
+ content: z.object({ heading: z.string() })
170
+ });
171
+ var LinkHeading_default = defineSection({
172
+ type: "link_heading",
173
+ label: "Link Heading",
174
+ icon: /* @__PURE__ */ jsx2(Heading1, { size: 18 }),
175
+ schema,
176
+ navRole: "h1",
177
+ component: ({ content, onChange }) => /* @__PURE__ */ jsx2(
178
+ HeadingSection,
179
+ {
180
+ heading: content.content.heading,
181
+ tag: "h2",
182
+ placeholder: "Section heading",
183
+ onChange: onChange ? (heading) => onChange({ type: "link_heading", content: { heading } }) : void 0
184
+ }
185
+ ),
186
+ defaults: () => ({ type: "link_heading", content: { heading: "New Section" } }),
187
+ getLabel: (content) => content.content.heading
188
+ });
189
+
190
+ // src/components/sections/SubHeading/index.tsx
191
+ import { z as z2 } from "zod";
192
+ import { Heading2 } from "lucide-react";
193
+ import { jsx as jsx3 } from "react/jsx-runtime";
194
+ var schema2 = z2.object({
195
+ type: z2.literal("sub_heading"),
196
+ content: z2.object({ heading: z2.string(), excludeFromNav: z2.boolean().optional() })
197
+ });
198
+ var SubHeading_default = defineSection({
199
+ type: "sub_heading",
200
+ label: "Sub Heading",
201
+ icon: /* @__PURE__ */ jsx3(Heading2, { size: 18 }),
202
+ schema: schema2,
203
+ navRole: "h2",
204
+ component: ({ content, onChange }) => /* @__PURE__ */ jsx3(
205
+ HeadingSection,
206
+ {
207
+ heading: content.content.heading,
208
+ tag: "h3",
209
+ placeholder: "Sub heading",
210
+ onChange: onChange ? (heading) => onChange({ ...content, content: { ...content.content, heading } }) : void 0
211
+ }
212
+ ),
213
+ defaults: () => ({ type: "sub_heading", content: { heading: "New Sub Heading" } }),
214
+ getLabel: (content) => content.content.heading,
215
+ settings: {
216
+ excludeFromNav: { type: "checkbox", label: "Exclude from navigation", default: false, target: "content" }
217
+ }
218
+ });
219
+
220
+ // src/components/sections/SubSubHeading/index.tsx
221
+ import { z as z3 } from "zod";
222
+ import { Heading3 } from "lucide-react";
223
+ import { jsx as jsx4 } from "react/jsx-runtime";
224
+ var schema3 = z3.object({
225
+ type: z3.literal("sub_sub_heading"),
226
+ content: z3.object({ heading: z3.string(), excludeFromNav: z3.boolean().optional() })
227
+ });
228
+ var SubSubHeading_default = defineSection({
229
+ type: "sub_sub_heading",
230
+ label: "Sub Sub Heading",
231
+ icon: /* @__PURE__ */ jsx4(Heading3, { size: 18 }),
232
+ schema: schema3,
233
+ navRole: "h3",
234
+ component: ({ content, onChange }) => /* @__PURE__ */ jsx4(
235
+ HeadingSection,
236
+ {
237
+ heading: content.content.heading,
238
+ tag: "h4",
239
+ placeholder: "Sub sub heading",
240
+ onChange: onChange ? (heading) => onChange({ ...content, content: { ...content.content, heading } }) : void 0
241
+ }
242
+ ),
243
+ defaults: () => ({ type: "sub_sub_heading", content: { heading: "New Sub Sub Heading" } }),
244
+ getLabel: (content) => content.content.heading,
245
+ settings: {
246
+ excludeFromNav: { type: "checkbox", label: "Exclude from navigation", default: false, target: "content" }
247
+ }
248
+ });
249
+
250
+ // src/components/sections/Prose/index.tsx
251
+ import { z as z4 } from "zod";
252
+ import { AlignLeft } from "lucide-react";
253
+
254
+ // src/components/primitives/EditableRichText.tsx
255
+ import { useRef as useRef4, useCallback as useCallback4, useEffect as useEffect4 } from "react";
256
+ import { EditorContent } from "@tiptap/react";
257
+ import { BubbleMenu } from "@tiptap/react/menus";
258
+
259
+ // src/components/primitives/useEditableRichText.ts
260
+ import { useState as useState2, useRef as useRef3, useCallback as useCallback3, useEffect as useEffect3 } from "react";
261
+ import { Editor } from "@tiptap/core";
262
+ import Placeholder from "@tiptap/extension-placeholder";
263
+
264
+ // src/components/primitives/tiptap-presets.ts
265
+ import StarterKit from "@tiptap/starter-kit";
266
+ import Underline from "@tiptap/extension-underline";
267
+ import Link from "@tiptap/extension-link";
268
+
269
+ // ../../node_modules/.pnpm/@tiptap+extension-paragraph@3.22.4_@tiptap+core@3.22.4_@tiptap+pm@3.22.4_/node_modules/@tiptap/extension-paragraph/dist/index.js
270
+ import { mergeAttributes, Node } from "@tiptap/core";
271
+ var EMPTY_PARAGRAPH_MARKDOWN = " ";
272
+ var NBSP_CHAR = "\xA0";
273
+ var Paragraph = Node.create({
274
+ name: "paragraph",
275
+ priority: 1e3,
276
+ addOptions() {
277
+ return {
278
+ HTMLAttributes: {}
279
+ };
280
+ },
281
+ group: "block",
282
+ content: "inline*",
283
+ parseHTML() {
284
+ return [{ tag: "p" }];
285
+ },
286
+ renderHTML({ HTMLAttributes }) {
287
+ return ["p", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
288
+ },
289
+ parseMarkdown: (token, helpers) => {
290
+ const tokens = token.tokens || [];
291
+ if (tokens.length === 1 && tokens[0].type === "image") {
292
+ return helpers.parseChildren([tokens[0]]);
293
+ }
294
+ const content = helpers.parseInline(tokens);
295
+ const hasExplicitEmptyParagraphMarker = tokens.length === 1 && tokens[0].type === "text" && (tokens[0].raw === EMPTY_PARAGRAPH_MARKDOWN || tokens[0].text === EMPTY_PARAGRAPH_MARKDOWN || tokens[0].raw === NBSP_CHAR || tokens[0].text === NBSP_CHAR);
296
+ if (hasExplicitEmptyParagraphMarker && content.length === 1 && content[0].type === "text" && (content[0].text === EMPTY_PARAGRAPH_MARKDOWN || content[0].text === NBSP_CHAR)) {
297
+ return helpers.createNode("paragraph", void 0, []);
298
+ }
299
+ return helpers.createNode("paragraph", void 0, content);
300
+ },
301
+ renderMarkdown: (node, h, ctx) => {
302
+ var _a, _b;
303
+ if (!node) {
304
+ return "";
305
+ }
306
+ const content = Array.isArray(node.content) ? node.content : [];
307
+ if (content.length === 0) {
308
+ const previousContent = Array.isArray((_a = ctx == null ? void 0 : ctx.previousNode) == null ? void 0 : _a.content) ? ctx.previousNode.content : [];
309
+ const previousNodeIsEmptyParagraph = ((_b = ctx == null ? void 0 : ctx.previousNode) == null ? void 0 : _b.type) === "paragraph" && previousContent.length === 0;
310
+ return previousNodeIsEmptyParagraph ? EMPTY_PARAGRAPH_MARKDOWN : "";
311
+ }
312
+ return h.renderChildren(content);
313
+ },
314
+ addCommands() {
315
+ return {
316
+ setParagraph: () => ({ commands }) => {
317
+ return commands.setNode(this.name);
318
+ }
319
+ };
320
+ },
321
+ addKeyboardShortcuts() {
322
+ return {
323
+ "Mod-Alt-0": () => this.editor.commands.setParagraph()
324
+ };
325
+ }
326
+ });
327
+ var index_default = Paragraph;
328
+
329
+ // src/components/primitives/CustomParagraph.ts
330
+ var ALLOWED_CLASSES = ["large", "lead-in"];
331
+ var CustomParagraph = index_default.extend({
332
+ addAttributes() {
333
+ return {
334
+ ...this.parent?.(),
335
+ class: {
336
+ default: null,
337
+ parseHTML: (element) => {
338
+ const cls = element.getAttribute("class");
339
+ if (cls && ALLOWED_CLASSES.includes(cls)) {
340
+ return cls;
341
+ }
342
+ return null;
343
+ },
344
+ renderHTML: (attributes) => {
345
+ if (!attributes.class) return {};
346
+ return { class: attributes.class };
347
+ }
348
+ }
349
+ };
350
+ }
351
+ });
352
+
353
+ // src/components/primitives/tiptap-presets.ts
354
+ var basic = [
355
+ StarterKit.configure({
356
+ paragraph: false,
357
+ heading: false,
358
+ blockquote: false,
359
+ bulletList: false,
360
+ orderedList: false,
361
+ listItem: false,
362
+ codeBlock: false,
363
+ horizontalRule: false,
364
+ code: false,
365
+ strike: false
366
+ }),
367
+ CustomParagraph,
368
+ Underline
369
+ ];
370
+ var rich = [
371
+ StarterKit.configure({
372
+ paragraph: false,
373
+ heading: { levels: [3, 4] },
374
+ blockquote: false,
375
+ codeBlock: false,
376
+ horizontalRule: false,
377
+ code: false,
378
+ strike: false
379
+ }),
380
+ CustomParagraph,
381
+ Underline,
382
+ // `page` protocol: stable internal-page hrefs (page://{pageId}#{sectionId})
383
+ // resolved to real routes at SSR — see lib/links.ts.
384
+ Link.configure({ openOnClick: false, protocols: ["page"] })
385
+ ];
386
+ var presets = { basic, rich };
387
+
388
+ // src/components/primitives/useEditableRichText.ts
389
+ function useEditableRichText({
390
+ value,
391
+ onChange,
392
+ preset,
393
+ placeholder
394
+ }) {
395
+ const editorRef = useRef3(null);
396
+ const [editor, setEditor] = useState2(null);
397
+ const valueRef = useRef3(value);
398
+ const onChangeRef = useRef3(onChange);
399
+ useEffect3(() => {
400
+ valueRef.current = value;
401
+ }, [value]);
402
+ useEffect3(() => {
403
+ onChangeRef.current = onChange;
404
+ }, [onChange]);
405
+ const activate = useCallback3(() => {
406
+ if (editorRef.current) return;
407
+ const extensions = placeholder ? [...presets[preset], Placeholder.configure({ placeholder })] : presets[preset];
408
+ const ed = new Editor({
409
+ extensions,
410
+ content: valueRef.current,
411
+ // Commit edits to onChange on every transaction, not only on deactivate().
412
+ // Without this, content lives only inside TipTap until blur; clicking a
413
+ // Save button while the editor is still focused flushes persistence before
414
+ // the (150ms-delayed) blur->deactivate commit fires, silently dropping the
415
+ // in-progress edit. Live-committing keeps persistence in sync continuously.
416
+ onUpdate: ({ editor: updated }) => {
417
+ const html = updated.getHTML();
418
+ if (html !== valueRef.current) {
419
+ valueRef.current = html;
420
+ onChangeRef.current(html);
421
+ }
422
+ }
423
+ });
424
+ editorRef.current = ed;
425
+ setEditor(ed);
426
+ }, [preset, placeholder]);
427
+ const deactivate = useCallback3(() => {
428
+ const ed = editorRef.current;
429
+ if (!ed) return;
430
+ const html = ed.getHTML();
431
+ ed.destroy();
432
+ editorRef.current = null;
433
+ setEditor(null);
434
+ if (html !== valueRef.current) {
435
+ onChangeRef.current(html);
436
+ }
437
+ }, []);
438
+ useEffect3(() => {
439
+ return () => {
440
+ editorRef.current?.destroy();
441
+ editorRef.current = null;
442
+ };
443
+ }, []);
444
+ return {
445
+ isEditorActive: editor !== null,
446
+ editor,
447
+ activate,
448
+ deactivate
449
+ };
450
+ }
451
+
452
+ // src/components/primitives/RichTextToolbar.tsx
453
+ import { useState as useState4 } from "react";
454
+ import { useEditorState } from "@tiptap/react";
455
+
456
+ // src/components/primitives/LinkPopover.tsx
457
+ import { useState as useState3 } from "react";
458
+
459
+ // src/components/shared/Button.tsx
460
+ import { forwardRef } from "react";
461
+ import { jsx as jsx5 } from "react/jsx-runtime";
462
+ var boxSize = {
463
+ sm: "rounded px-3 py-1.5 text-xs font-medium",
464
+ md: "rounded px-4 py-2 text-sm font-medium"
465
+ };
466
+ var ghostSize = {
467
+ sm: "text-xs font-medium",
468
+ md: "text-sm font-medium"
469
+ };
470
+ var variantClasses = {
471
+ primary: "bg-base-contrast text-base-accent hover:bg-base-contrast/90",
472
+ brand: "bg-primary text-primary-contrast hover:opacity-90",
473
+ secondary: "border border-base-200 text-base-contrast hover:bg-base-accent",
474
+ destructive: "border border-red-600 text-red-600 hover:bg-red-600 hover:text-white"
475
+ };
476
+ var ghostToneClasses = {
477
+ neutral: "text-base-contrast-light hover:text-base-contrast",
478
+ destructive: "text-red-600 hover:text-red-800"
479
+ };
480
+ var Button = forwardRef(function Button2({
481
+ variant = "primary",
482
+ size = "sm",
483
+ tone = "neutral",
484
+ fullWidth = false,
485
+ isLoading = false,
486
+ loadingLabel,
487
+ disabled,
488
+ className,
489
+ children,
490
+ ...rest
491
+ }, ref) {
492
+ const isGhost = variant === "ghost";
493
+ const resolvedDisabled = disabled || isLoading;
494
+ const label = isLoading && loadingLabel ? loadingLabel : children;
495
+ return /* @__PURE__ */ jsx5(
496
+ "button",
497
+ {
498
+ ref,
499
+ disabled: resolvedDisabled,
500
+ className: cn(
501
+ "inline-flex items-center justify-center transition-colors",
502
+ isGhost ? ghostSize[size] : boxSize[size],
503
+ isGhost ? ghostToneClasses[tone] : variantClasses[variant],
504
+ fullWidth && "w-full",
505
+ resolvedDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
506
+ className
507
+ ),
508
+ ...rest,
509
+ children: label
510
+ }
511
+ );
512
+ });
513
+
514
+ // src/components/shared/PagesContext.tsx
515
+ import { createContext, useContext } from "react";
516
+ var PagesContext = createContext({ pages: [], getPageHeadings: () => [] });
517
+ function usePages() {
518
+ return useContext(PagesContext);
519
+ }
520
+
521
+ // src/lib/links.ts
522
+ function internalHref(pageId, anchorSectionId) {
523
+ return `page://${pageId}${anchorSectionId ? `#${anchorSectionId}` : ""}`;
524
+ }
525
+ function parseInternalHref(href) {
526
+ const m = /^page:\/\/([^#]+)(?:#(.+))?$/.exec(href);
527
+ return m ? { pageId: m[1], anchorSectionId: m[2] ?? null } : null;
528
+ }
529
+
530
+ // src/components/primitives/LinkPopover.tsx
531
+ import { Fragment, jsx as jsx6, jsxs } from "react/jsx-runtime";
532
+ var selectClasses = "w-full rounded border border-base-contrast/20 bg-base px-2 py-1 text-sm text-base-contrast focus:outline-none focus:ring-1 focus:ring-primary";
533
+ function LinkPopover({ editor, onClose }) {
534
+ const { pages, getPageHeadings } = usePages();
535
+ const existingAttrs = editor.getAttributes("link");
536
+ const existingInternal = parseInternalHref(existingAttrs.href ?? "");
537
+ const [mode, setMode] = useState3(existingInternal ? "internal" : "external");
538
+ const [url, setUrl] = useState3(existingInternal ? "" : existingAttrs.href ?? "");
539
+ const [pageId, setPageId] = useState3(existingInternal?.pageId ?? "");
540
+ const [anchorId, setAnchorId] = useState3(existingInternal?.anchorSectionId ?? "");
541
+ const [openInNewTab, setOpenInNewTab] = useState3(
542
+ existingAttrs.target === "_blank"
543
+ );
544
+ const hasLink = Boolean(existingAttrs.href);
545
+ const headings = pageId ? getPageHeadings(pageId) : [];
546
+ const handleApply = () => {
547
+ if (mode === "internal") {
548
+ if (pageId) {
549
+ editor.chain().focus().extendMarkRange("link").setLink({ href: internalHref(pageId, anchorId || null), target: null }).run();
550
+ }
551
+ } else if (url.trim()) {
552
+ editor.chain().focus().extendMarkRange("link").setLink({ href: url.trim(), target: openInNewTab ? "_blank" : null }).run();
553
+ }
554
+ onClose();
555
+ };
556
+ const handleRemove = () => {
557
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
558
+ onClose();
559
+ };
560
+ const handleKeyDown = (e) => {
561
+ if (e.key === "Enter") {
562
+ e.preventDefault();
563
+ handleApply();
564
+ } else if (e.key === "Escape") {
565
+ e.preventDefault();
566
+ onClose();
567
+ }
568
+ };
569
+ return /* @__PURE__ */ jsxs(
570
+ "div",
571
+ {
572
+ className: "flex w-64 flex-col gap-2 rounded bg-base-accent p-3 shadow-lg",
573
+ onMouseDown: (e) => e.stopPropagation(),
574
+ onKeyDown: handleKeyDown,
575
+ children: [
576
+ /* @__PURE__ */ jsx6("div", { className: "flex gap-2", role: "tablist", children: ["external", "internal"].map((kind) => /* @__PURE__ */ jsx6(
577
+ "button",
578
+ {
579
+ type: "button",
580
+ role: "tab",
581
+ "aria-selected": mode === kind,
582
+ onClick: () => setMode(kind),
583
+ className: cn(
584
+ "cursor-pointer rounded border px-2 py-0.5 text-xs",
585
+ mode === kind ? "border-primary bg-primary text-primary-contrast" : "border-base-200 text-base-contrast"
586
+ ),
587
+ children: kind === "external" ? "External URL" : "Internal page"
588
+ },
589
+ kind
590
+ )) }),
591
+ mode === "external" ? /* @__PURE__ */ jsxs(Fragment, { children: [
592
+ /* @__PURE__ */ jsx6(
593
+ "input",
594
+ {
595
+ autoFocus: true,
596
+ type: "url",
597
+ value: url,
598
+ onChange: (e) => setUrl(e.target.value),
599
+ placeholder: "https://example.com",
600
+ className: "rounded border border-base-contrast/20 bg-base px-2 py-1 text-sm text-base-contrast placeholder:text-base-contrast/40 focus:outline-none focus:ring-1 focus:ring-primary"
601
+ }
602
+ ),
603
+ /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-base-contrast", children: [
604
+ /* @__PURE__ */ jsx6(
605
+ "input",
606
+ {
607
+ type: "checkbox",
608
+ checked: openInNewTab,
609
+ onChange: (e) => setOpenInNewTab(e.target.checked),
610
+ className: "accent-primary"
611
+ }
612
+ ),
613
+ "Open in new tab"
614
+ ] })
615
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
616
+ /* @__PURE__ */ jsxs(
617
+ "select",
618
+ {
619
+ autoFocus: true,
620
+ "aria-label": "Page",
621
+ value: pageId,
622
+ onChange: (e) => {
623
+ setPageId(e.target.value);
624
+ setAnchorId("");
625
+ },
626
+ className: selectClasses,
627
+ children: [
628
+ /* @__PURE__ */ jsx6("option", { value: "", children: "Select a page\u2026" }),
629
+ pages.map((p) => /* @__PURE__ */ jsx6("option", { value: p.id, children: p.title }, p.id))
630
+ ]
631
+ }
632
+ ),
633
+ /* @__PURE__ */ jsxs(
634
+ "select",
635
+ {
636
+ "aria-label": "Section",
637
+ value: anchorId,
638
+ disabled: !pageId,
639
+ onChange: (e) => setAnchorId(e.target.value),
640
+ className: cn(selectClasses, !pageId && "cursor-not-allowed opacity-50"),
641
+ children: [
642
+ /* @__PURE__ */ jsx6("option", { value: "", children: "None (top of page)" }),
643
+ headings.map((h) => /* @__PURE__ */ jsx6("option", { value: h.id, children: h.label }, h.id))
644
+ ]
645
+ }
646
+ )
647
+ ] }),
648
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
649
+ /* @__PURE__ */ jsx6(
650
+ Button,
651
+ {
652
+ variant: "brand",
653
+ size: "sm",
654
+ onMouseDown: (e) => {
655
+ e.preventDefault();
656
+ handleApply();
657
+ },
658
+ children: "Apply"
659
+ }
660
+ ),
661
+ hasLink && /* @__PURE__ */ jsx6(
662
+ Button,
663
+ {
664
+ variant: "secondary",
665
+ size: "sm",
666
+ onMouseDown: (e) => {
667
+ e.preventDefault();
668
+ handleRemove();
669
+ },
670
+ children: "Remove"
671
+ }
672
+ ),
673
+ /* @__PURE__ */ jsx6(
674
+ Button,
675
+ {
676
+ variant: "secondary",
677
+ size: "sm",
678
+ onMouseDown: (e) => {
679
+ e.preventDefault();
680
+ onClose();
681
+ },
682
+ children: "Cancel"
683
+ }
684
+ )
685
+ ] })
686
+ ]
687
+ }
688
+ );
689
+ }
690
+
691
+ // src/components/primitives/RichTextToolbar.tsx
692
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
693
+ function ToolbarButton({ isActive, onClick, title, children }) {
694
+ return /* @__PURE__ */ jsx7(
695
+ "button",
696
+ {
697
+ type: "button",
698
+ title,
699
+ "aria-label": title,
700
+ "aria-pressed": isActive,
701
+ onMouseDown: (e) => {
702
+ e.preventDefault();
703
+ onClick();
704
+ },
705
+ className: cn(
706
+ "cursor-pointer rounded px-2 py-1 text-sm font-medium transition-colors",
707
+ isActive ? "bg-primary text-primary-contrast" : "bg-base-accent text-base-contrast hover:opacity-80"
708
+ ),
709
+ children
710
+ }
711
+ );
712
+ }
713
+ function Separator() {
714
+ return /* @__PURE__ */ jsx7("div", { className: "mx-1 h-5 w-px bg-base-contrast/20" });
715
+ }
716
+ function RichTextToolbar({ editor, preset }) {
717
+ const [showLinkPopover, setShowLinkPopover] = useState4(false);
718
+ const state = useEditorState({
719
+ editor,
720
+ selector: ({ editor: ed }) => ({
721
+ isBold: ed.isActive("bold"),
722
+ isItalic: ed.isActive("italic"),
723
+ isUnderline: ed.isActive("underline"),
724
+ isLink: ed.isActive("link"),
725
+ isBulletList: ed.isActive("bulletList"),
726
+ isOrderedList: ed.isActive("orderedList"),
727
+ isH3: ed.isActive("heading", { level: 3 }),
728
+ isH4: ed.isActive("heading", { level: 4 }),
729
+ isLarge: ed.isActive("paragraph", { class: "large" }),
730
+ isLeadIn: ed.isActive("paragraph", { class: "lead-in" })
731
+ })
732
+ });
733
+ const toggleParagraphVariant = (cls, isCurrentlyActive) => {
734
+ if (isCurrentlyActive) {
735
+ editor.chain().focus().setNode("paragraph").run();
736
+ } else {
737
+ editor.chain().focus().setNode("paragraph", { class: cls }).run();
738
+ }
739
+ };
740
+ return /* @__PURE__ */ jsxs2(
741
+ "div",
742
+ {
743
+ "data-testid": "rich-text-toolbar",
744
+ className: "flex items-center gap-1 rounded border border-base-contrast/10 bg-base-accent p-1 shadow-md",
745
+ children: [
746
+ /* @__PURE__ */ jsx7(
747
+ ToolbarButton,
748
+ {
749
+ isActive: state.isBold,
750
+ onClick: () => editor.chain().focus().toggleBold().run(),
751
+ title: "Bold",
752
+ children: /* @__PURE__ */ jsx7("strong", { children: "B" })
753
+ }
754
+ ),
755
+ /* @__PURE__ */ jsx7(
756
+ ToolbarButton,
757
+ {
758
+ isActive: state.isItalic,
759
+ onClick: () => editor.chain().focus().toggleItalic().run(),
760
+ title: "Italic",
761
+ children: /* @__PURE__ */ jsx7("em", { children: "I" })
762
+ }
763
+ ),
764
+ /* @__PURE__ */ jsx7(
765
+ ToolbarButton,
766
+ {
767
+ isActive: state.isUnderline,
768
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
769
+ title: "Underline",
770
+ children: /* @__PURE__ */ jsx7("span", { className: "underline", children: "U" })
771
+ }
772
+ ),
773
+ preset === "rich" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
774
+ /* @__PURE__ */ jsx7(Separator, {}),
775
+ /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
776
+ /* @__PURE__ */ jsx7(
777
+ ToolbarButton,
778
+ {
779
+ isActive: state.isLink || showLinkPopover,
780
+ onClick: () => setShowLinkPopover((prev) => !prev),
781
+ title: "Link",
782
+ children: "\u{1F517}"
783
+ }
784
+ ),
785
+ showLinkPopover && /* @__PURE__ */ jsx7("div", { className: "absolute left-0 top-full z-50 mt-1", children: /* @__PURE__ */ jsx7(
786
+ LinkPopover,
787
+ {
788
+ editor,
789
+ onClose: () => setShowLinkPopover(false)
790
+ }
791
+ ) })
792
+ ] }),
793
+ /* @__PURE__ */ jsx7(Separator, {}),
794
+ /* @__PURE__ */ jsx7(
795
+ ToolbarButton,
796
+ {
797
+ isActive: state.isBulletList,
798
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
799
+ title: "Bullet List",
800
+ children: "\u2022\u2261"
801
+ }
802
+ ),
803
+ /* @__PURE__ */ jsx7(
804
+ ToolbarButton,
805
+ {
806
+ isActive: state.isOrderedList,
807
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
808
+ title: "Ordered List",
809
+ children: "1\u2261"
810
+ }
811
+ ),
812
+ /* @__PURE__ */ jsx7(Separator, {}),
813
+ /* @__PURE__ */ jsx7(
814
+ ToolbarButton,
815
+ {
816
+ isActive: state.isH3,
817
+ onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
818
+ title: "Heading 3",
819
+ children: "H3"
820
+ }
821
+ ),
822
+ /* @__PURE__ */ jsx7(
823
+ ToolbarButton,
824
+ {
825
+ isActive: state.isH4,
826
+ onClick: () => editor.chain().focus().toggleHeading({ level: 4 }).run(),
827
+ title: "Heading 4",
828
+ children: "H4"
829
+ }
830
+ ),
831
+ /* @__PURE__ */ jsx7(
832
+ ToolbarButton,
833
+ {
834
+ isActive: state.isLarge,
835
+ onClick: () => toggleParagraphVariant("large", state.isLarge),
836
+ title: "Large Paragraph",
837
+ children: "Lg"
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsx7(
841
+ ToolbarButton,
842
+ {
843
+ isActive: state.isLeadIn,
844
+ onClick: () => toggleParagraphVariant("lead-in", state.isLeadIn),
845
+ title: "Lead-In",
846
+ children: "Li"
847
+ }
848
+ )
849
+ ] })
850
+ ]
851
+ }
852
+ );
853
+ }
854
+
855
+ // src/components/primitives/EditableRichText.tsx
856
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
857
+ function isContentEmpty(html) {
858
+ return html.replace(/<[^>]*>/g, "").trim().length === 0;
859
+ }
860
+ var DEFAULT_PLACEHOLDER = "Click to edit\u2026";
861
+ function EditableRichText({
862
+ value,
863
+ onChange,
864
+ isEditMode,
865
+ preset = "rich",
866
+ className,
867
+ placeholder = DEFAULT_PLACEHOLDER
868
+ }) {
869
+ const { isEditorActive, editor, activate, deactivate } = useEditableRichText({ value, onChange, preset, placeholder });
870
+ const wrapperRef = useRef4(null);
871
+ const blurTimeoutRef = useRef4(null);
872
+ const clickCoordsRef = useRef4(null);
873
+ useEffect4(() => {
874
+ return () => {
875
+ if (blurTimeoutRef.current) {
876
+ clearTimeout(blurTimeoutRef.current);
877
+ }
878
+ };
879
+ }, []);
880
+ useEffect4(() => {
881
+ if (!isEditorActive || !editor) return;
882
+ requestAnimationFrame(() => {
883
+ if (editor.isDestroyed) return;
884
+ const coords = clickCoordsRef.current;
885
+ if (coords) {
886
+ try {
887
+ const pos = editor.view.posAtCoords(coords);
888
+ if (pos) {
889
+ editor.commands.focus();
890
+ editor.commands.setTextSelection(pos.pos);
891
+ } else {
892
+ editor.commands.focus("end");
893
+ }
894
+ } catch {
895
+ editor.commands.focus("end");
896
+ } finally {
897
+ clickCoordsRef.current = null;
898
+ }
899
+ } else {
900
+ editor.commands.focus("end");
901
+ }
902
+ });
903
+ }, [isEditorActive, editor]);
904
+ const handleClick = useCallback4((e) => {
905
+ if (isEditMode && !isEditorActive) {
906
+ clickCoordsRef.current = { left: e.clientX, top: e.clientY };
907
+ activate();
908
+ }
909
+ }, [isEditMode, isEditorActive, activate]);
910
+ const handleFocus = useCallback4(() => {
911
+ if (blurTimeoutRef.current) {
912
+ clearTimeout(blurTimeoutRef.current);
913
+ blurTimeoutRef.current = null;
914
+ }
915
+ }, []);
916
+ const handleBlur = useCallback4(() => {
917
+ blurTimeoutRef.current = setTimeout(() => {
918
+ if (wrapperRef.current && !wrapperRef.current.contains(document.activeElement)) {
919
+ deactivate();
920
+ }
921
+ }, 150);
922
+ }, [deactivate]);
923
+ if (!isEditMode) {
924
+ return /* @__PURE__ */ jsx8(
925
+ "div",
926
+ {
927
+ className,
928
+ dangerouslySetInnerHTML: { __html: sanitizeHtml(value) }
929
+ }
930
+ );
931
+ }
932
+ if (!isEditorActive || !editor) {
933
+ const empty = isContentEmpty(value);
934
+ if (empty) {
935
+ return /* @__PURE__ */ jsx8("div", { className, onClick: handleClick, children: /* @__PURE__ */ jsx8("p", { className: "tiptap-placeholder", children: placeholder }) });
936
+ }
937
+ return /* @__PURE__ */ jsx8(
938
+ "div",
939
+ {
940
+ className,
941
+ onClick: handleClick,
942
+ dangerouslySetInnerHTML: { __html: sanitizeHtml(value) }
943
+ }
944
+ );
945
+ }
946
+ return /* @__PURE__ */ jsxs3(
947
+ "div",
948
+ {
949
+ ref: wrapperRef,
950
+ className,
951
+ onFocus: handleFocus,
952
+ onBlur: handleBlur,
953
+ children: [
954
+ /* @__PURE__ */ jsx8(
955
+ BubbleMenu,
956
+ {
957
+ editor,
958
+ className: "z-50",
959
+ shouldShow: ({ editor: ed, state }) => {
960
+ const { from, to } = state.selection;
961
+ return ed.isFocused && from !== to;
962
+ },
963
+ children: /* @__PURE__ */ jsx8(RichTextToolbar, { editor, preset })
964
+ }
965
+ ),
966
+ /* @__PURE__ */ jsx8(EditorContent, { editor })
967
+ ]
968
+ }
969
+ );
970
+ }
971
+
972
+ // src/components/sections/Prose/Prose.tsx
973
+ import { jsx as jsx9 } from "react/jsx-runtime";
974
+ function Prose({ body, onChange }) {
975
+ if (onChange) {
976
+ return /* @__PURE__ */ jsx9("div", { className: "prose-content", children: /* @__PURE__ */ jsx9(
977
+ EditableRichText,
978
+ {
979
+ value: body,
980
+ onChange: (html) => onChange({ type: "prose", content: { body: html } }),
981
+ isEditMode: true,
982
+ preset: "rich",
983
+ placeholder: "Start writing..."
984
+ }
985
+ ) });
986
+ }
987
+ return /* @__PURE__ */ jsx9(
988
+ "div",
989
+ {
990
+ className: "prose-content",
991
+ dangerouslySetInnerHTML: { __html: sanitizeHtml(body) }
992
+ }
993
+ );
994
+ }
995
+
996
+ // src/lib/text.ts
997
+ function stripHtmlToPlainText(html) {
998
+ return html.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
999
+ }
1000
+ function truncate(text, maxLength) {
1001
+ if (text.length <= maxLength) return text;
1002
+ return text.slice(0, maxLength) + "...";
1003
+ }
1004
+
1005
+ // src/components/sections/Prose/index.tsx
1006
+ import { jsx as jsx10 } from "react/jsx-runtime";
1007
+ var schema4 = z4.object({
1008
+ type: z4.literal("prose"),
1009
+ content: z4.object({ body: z4.string() })
1010
+ });
1011
+ var Prose_default = defineSection({
1012
+ type: "prose",
1013
+ label: "Prose",
1014
+ icon: /* @__PURE__ */ jsx10(AlignLeft, { size: 18 }),
1015
+ schema: schema4,
1016
+ richTextFields: ["body"],
1017
+ component: ({ content, onChange }) => /* @__PURE__ */ jsx10(Prose, { body: content.content.body, onChange: onChange ? (c) => onChange(c) : void 0 }),
1018
+ defaults: () => ({ type: "prose", content: { body: "<p></p>" } }),
1019
+ getLabel: (content) => truncate(stripHtmlToPlainText(content.content.body), 60)
1020
+ });
1021
+
1022
+ // src/components/sections/Media/index.tsx
1023
+ import { z as z5 } from "zod";
1024
+ import { Image as ImageIcon2 } from "lucide-react";
1025
+
1026
+ // src/components/sections/Media/MediaBlock.tsx
1027
+ import { Check, X } from "lucide-react";
1028
+
1029
+ // src/components/primitives/ResolvedMedia.tsx
1030
+ import { ImageIcon } from "lucide-react";
1031
+
1032
+ // src/hooks/useResolvedMedia.ts
1033
+ import { useMemo } from "react";
1034
+
1035
+ // src/components/shell/MediaLibraryContext.tsx
1036
+ import { createContext as createContext2, useContext as useContext2 } from "react";
1037
+ var MediaLibraryContext = createContext2(null);
1038
+ function useMediaLibrary() {
1039
+ return useContext2(MediaLibraryContext);
1040
+ }
1041
+
1042
+ // src/hooks/useResolvedMedia.ts
1043
+ function useResolvedMedia(imageId) {
1044
+ const ctx = useMediaLibrary();
1045
+ return useMemo(() => {
1046
+ if (!imageId || !ctx) {
1047
+ return { src: void 0, srcset: void 0, poster: void 0, alt: "", kind: "image" };
1048
+ }
1049
+ const { manifest, pendingItems, pendingLocalUrls, siteConfig } = ctx;
1050
+ const sizes = siteConfig?.media?.sizes ?? [640, 1080, 1920];
1051
+ const manifestItem = manifest.images[imageId];
1052
+ const pendingItem = pendingItems.find((i) => i.id === imageId);
1053
+ const item = manifestItem ?? pendingItem;
1054
+ const alt = item?.alt ?? "";
1055
+ const kind = item?.kind ?? "image";
1056
+ const localUrl = pendingLocalUrls[imageId];
1057
+ if (localUrl) {
1058
+ return { src: localUrl, srcset: void 0, poster: void 0, alt, kind };
1059
+ }
1060
+ if (manifestItem) {
1061
+ const provider = getMediaProvider();
1062
+ const resolved = provider.resolve(manifestItem, sizes);
1063
+ if (resolved && resolved.tag === "img") {
1064
+ return {
1065
+ src: resolved.src,
1066
+ srcset: "srcSet" in resolved ? resolved.srcSet : void 0,
1067
+ poster: void 0,
1068
+ alt,
1069
+ kind
1070
+ };
1071
+ }
1072
+ if (resolved && resolved.tag === "video") {
1073
+ return { src: resolved.src, srcset: void 0, poster: resolved.poster, alt, kind };
1074
+ }
1075
+ }
1076
+ return { src: void 0, srcset: void 0, poster: void 0, alt, kind };
1077
+ }, [imageId, ctx]);
1078
+ }
1079
+
1080
+ // src/components/primitives/ResolvedMedia.tsx
1081
+ import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
1082
+ function ResolvedMedia({
1083
+ imageId,
1084
+ src: propSrc,
1085
+ srcset: propSrcset,
1086
+ alt: propAlt,
1087
+ className,
1088
+ imgClassName,
1089
+ invertFrom
1090
+ }) {
1091
+ const resolved = useResolvedMedia(imageId);
1092
+ const src = propSrc || resolved.src;
1093
+ const srcset = propSrcset || resolved.srcset;
1094
+ const poster = resolved.poster;
1095
+ const alt = propAlt ?? resolved.alt;
1096
+ const kind = resolved.kind;
1097
+ const invertClass = invertFrom === "light" ? "invert dark:invert-0" : invertFrom === "dark" ? "dark:invert" : void 0;
1098
+ const mediaClass = cn("h-full w-full", imgClassName, invertClass);
1099
+ return /* @__PURE__ */ jsx11("div", { className, children: src ? kind === "video" ? /* @__PURE__ */ jsx11("video", { src, poster, muted: true, playsInline: true, loop: true, autoPlay: true, className: mediaClass }) : /* @__PURE__ */ jsx11("img", { src, srcSet: srcset, alt, className: mediaClass }) : /* @__PURE__ */ jsxs4("div", { className: "flex aspect-video h-full w-full flex-col items-center justify-center gap-1 rounded bg-base-accent text-base-contrast-light/50", children: [
1100
+ /* @__PURE__ */ jsx11(ImageIcon, { size: 24 }),
1101
+ /* @__PURE__ */ jsx11("span", { className: "text-sm", children: "No Image" })
1102
+ ] }) });
1103
+ }
1104
+
1105
+ // src/components/primitives/ImageDropZone.tsx
1106
+ import { useState as useState5 } from "react";
1107
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
1108
+ var ACCEPTED = /^(image|video)\//;
1109
+ function ImageDropZone({ onImageSelected, className, children }) {
1110
+ const mediaLibrary = useMediaLibrary();
1111
+ const [dragging, setDragging] = useState5(false);
1112
+ if (!mediaLibrary) return /* @__PURE__ */ jsx12(Fragment3, { children });
1113
+ const maxFileSize = mediaLibrary.siteConfig?.media.maxFileSize;
1114
+ const hasFiles = (e) => e.dataTransfer.types.includes("Files");
1115
+ const handleDragOver = (e) => {
1116
+ if (!hasFiles(e)) return;
1117
+ e.preventDefault();
1118
+ e.dataTransfer.dropEffect = "copy";
1119
+ setDragging(true);
1120
+ };
1121
+ const handleDragLeave = (e) => {
1122
+ if (!hasFiles(e)) return;
1123
+ const related = e.relatedTarget;
1124
+ if (related && e.currentTarget.contains(related)) return;
1125
+ setDragging(false);
1126
+ };
1127
+ const handleDrop = (e) => {
1128
+ if (!hasFiles(e)) return;
1129
+ e.preventDefault();
1130
+ e.stopPropagation();
1131
+ setDragging(false);
1132
+ const file = e.dataTransfer.files[0];
1133
+ if (!file) return;
1134
+ if (!ACCEPTED.test(file.type)) {
1135
+ mediaLibrary.reportRejectedUploads([{ name: file.name, reason: "type" }]);
1136
+ return;
1137
+ }
1138
+ if (maxFileSize && file.size > maxFileSize) {
1139
+ mediaLibrary.reportRejectedUploads([{ name: file.name, reason: "size" }]);
1140
+ return;
1141
+ }
1142
+ mediaLibrary.uploadFileWithCallback(file, onImageSelected);
1143
+ };
1144
+ return /* @__PURE__ */ jsxs5(
1145
+ "div",
1146
+ {
1147
+ className: cn("relative", className),
1148
+ onDragOver: handleDragOver,
1149
+ onDragLeave: handleDragLeave,
1150
+ onDrop: handleDrop,
1151
+ children: [
1152
+ children,
1153
+ dragging && /* @__PURE__ */ jsx12("div", { className: "pointer-events-none absolute inset-0 z-10 rounded-md border-2 border-primary bg-primary/5" })
1154
+ ]
1155
+ }
1156
+ );
1157
+ }
1158
+
1159
+ // src/components/sections/Media/MediaBlock.tsx
1160
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
1161
+ function MediaBlock({ refValue, options, link, dodont, isEditMode, onRefChange }) {
1162
+ const mediaLibrary = useMediaLibrary();
1163
+ const fit = refValue.objectFit ?? options.objectFit;
1164
+ const fitClass = fit === "cover" ? "object-cover" : "object-contain";
1165
+ const showBorder = refValue.border ?? options.border;
1166
+ const captionStr = refValue.caption ? Array.isArray(refValue.caption) ? refValue.caption.join("\n") : refValue.caption : "";
1167
+ const replace = onRefChange ? (imageId) => onRefChange({ ...refValue, imageId }) : void 0;
1168
+ const resolved = /* @__PURE__ */ jsx13(
1169
+ ResolvedMedia,
1170
+ {
1171
+ imageId: refValue.imageId || void 0,
1172
+ className: "h-full w-full",
1173
+ imgClassName: fitClass,
1174
+ invertFrom: refValue.invertFrom
1175
+ }
1176
+ );
1177
+ let media = isEditMode && replace ? /* @__PURE__ */ jsx13(ImageDropZone, { onImageSelected: replace, className: "h-full w-full", children: resolved }) : resolved;
1178
+ if (!isEditMode && link && link.kind === "external" && link.href) {
1179
+ media = /* @__PURE__ */ jsx13("a", { href: link.href, target: link.target, className: "group block", children: media });
1180
+ }
1181
+ const onImageClick = isEditMode && replace && mediaLibrary ? () => mediaLibrary.openSelectModal((imageId) => replace(imageId)) : void 0;
1182
+ return /* @__PURE__ */ jsxs6("figure", { children: [
1183
+ dodont && /* @__PURE__ */ jsx13("div", { className: "flex h-10 items-center justify-center pb-1", children: dodont === "do" ? /* @__PURE__ */ jsx13(Check, { size: 28, className: "text-green-600" }) : /* @__PURE__ */ jsx13(X, { size: 28, className: "text-red-600" }) }),
1184
+ /* @__PURE__ */ jsx13(
1185
+ "div",
1186
+ {
1187
+ "data-testid": "media-open-surface",
1188
+ className: cn(
1189
+ "overflow-hidden rounded-md",
1190
+ showBorder && "border border-base-200",
1191
+ options.square && "aspect-square",
1192
+ onImageClick && "cursor-pointer"
1193
+ ),
1194
+ onClick: onImageClick,
1195
+ onPointerDown: onImageClick ? (e) => e.stopPropagation() : void 0,
1196
+ children: media
1197
+ }
1198
+ ),
1199
+ options.showCaption && /* @__PURE__ */ jsx13("figcaption", { className: "mt-2 min-h-[1em] text-sm text-base-contrast-light", children: isEditMode && onRefChange ? /* @__PURE__ */ jsx13(
1200
+ EditablePlainText,
1201
+ {
1202
+ tag: "span",
1203
+ value: captionStr,
1204
+ onChange: (caption) => onRefChange({ ...refValue, caption: caption || void 0 }),
1205
+ isEditMode: true,
1206
+ placeholder: "Caption",
1207
+ className: "block min-h-[1em]"
1208
+ }
1209
+ ) : captionStr || " " })
1210
+ ] });
1211
+ }
1212
+
1213
+ // src/components/sections/Media/index.tsx
1214
+ import { jsx as jsx14 } from "react/jsx-runtime";
1215
+ var schema5 = z5.object({
1216
+ type: z5.literal("media"),
1217
+ content: z5.object({
1218
+ ref: SingleMediaReferenceSchema,
1219
+ link: LinkValueSchema.optional(),
1220
+ dodont: z5.enum(["do", "dont"]).optional()
1221
+ }),
1222
+ options: z5.object({
1223
+ square: z5.boolean().optional(),
1224
+ showCaption: z5.boolean().optional(),
1225
+ border: z5.boolean().optional(),
1226
+ objectFit: z5.enum(["cover", "contain"]).optional()
1227
+ }).default({})
1228
+ });
1229
+ var Media_default = defineSection({
1230
+ type: "media",
1231
+ label: "Media",
1232
+ icon: /* @__PURE__ */ jsx14(ImageIcon2, { size: 18 }),
1233
+ schema: schema5,
1234
+ component: ({ content, options, onChange }) => /* @__PURE__ */ jsx14(
1235
+ MediaBlock,
1236
+ {
1237
+ refValue: content.content.ref,
1238
+ options: options ?? {},
1239
+ link: content.content.link,
1240
+ dodont: content.content.dodont,
1241
+ isEditMode: !!onChange,
1242
+ onRefChange: onChange ? (ref) => onChange({ ...content, content: { ...content.content, ref } }) : void 0
1243
+ }
1244
+ ),
1245
+ defaults: () => ({
1246
+ type: "media",
1247
+ content: { ref: { type: "image", imageId: "" } },
1248
+ options: {}
1249
+ }),
1250
+ getLabel: () => "Media",
1251
+ getThumbnails: (content) => content.content.ref.imageId ? [{ type: "image", src: content.content.ref.imageId }] : [],
1252
+ settings: {
1253
+ square: { type: "checkbox", label: "Square aspect ratio", default: false },
1254
+ showCaption: { type: "checkbox", label: "Show caption", default: false },
1255
+ border: { type: "checkbox", label: "Border", default: false },
1256
+ objectFit: {
1257
+ type: "select",
1258
+ label: "Image fit",
1259
+ default: "contain",
1260
+ options: [
1261
+ { label: "Contain", value: "contain" },
1262
+ { label: "Cover", value: "cover" }
1263
+ ]
1264
+ },
1265
+ link: { type: "link", label: "Link", default: DEFAULT_LINK, target: "content" },
1266
+ dodont: {
1267
+ type: "select",
1268
+ label: "Do / Don't",
1269
+ target: "content",
1270
+ default: "",
1271
+ emptyIsUndefined: true,
1272
+ options: [
1273
+ { label: "None", value: "" },
1274
+ { label: "Do", value: "do" },
1275
+ { label: "Don't", value: "dont" }
1276
+ ]
1277
+ }
1278
+ },
1279
+ inheritableSettings: ["square", "showCaption", "border", "objectFit"],
1280
+ settingsTabs: [
1281
+ { label: "Display", fields: ["square", "showCaption", "border", "objectFit"] },
1282
+ { label: "Link", fields: ["link"] },
1283
+ { label: "Do / Don't", fields: ["dodont"] }
1284
+ ]
1285
+ });
1286
+
1287
+ // src/components/sections/Button/index.tsx
1288
+ import { z as z6 } from "zod";
1289
+ import { RectangleHorizontal } from "lucide-react";
1290
+
1291
+ // src/components/sections/Button/CTAButton.tsx
1292
+ import { Download } from "lucide-react";
1293
+ import { jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
1294
+ function Button3({ text, link, download, onChange }) {
1295
+ const base = "inline-block rounded-md px-6 py-3 font-bold transition-colors";
1296
+ const variant = "border-2 border-primary text-primary hover:bg-primary hover:text-primary-contrast";
1297
+ if (onChange) {
1298
+ return /* @__PURE__ */ jsxs7("span", { className: cn(base, variant, "inline-flex items-center gap-2"), children: [
1299
+ download && /* @__PURE__ */ jsx15(Download, { size: 16, className: "shrink-0" }),
1300
+ /* @__PURE__ */ jsx15(
1301
+ EditablePlainText,
1302
+ {
1303
+ tag: "span",
1304
+ value: text,
1305
+ onChange: (newText) => onChange({ type: "button", content: { text: newText, link, download } }),
1306
+ isEditMode: true,
1307
+ placeholder: "Button text"
1308
+ }
1309
+ )
1310
+ ] });
1311
+ }
1312
+ const href = link && link.kind === "external" ? link.href : "";
1313
+ const target = link?.target;
1314
+ if (href) {
1315
+ return /* @__PURE__ */ jsxs7(
1316
+ "a",
1317
+ {
1318
+ href,
1319
+ target,
1320
+ download: download ? "" : void 0,
1321
+ className: cn(base, variant, download && "inline-flex items-center gap-2"),
1322
+ children: [
1323
+ download && /* @__PURE__ */ jsx15(Download, { size: 16, className: "shrink-0" }),
1324
+ text
1325
+ ]
1326
+ }
1327
+ );
1328
+ }
1329
+ return /* @__PURE__ */ jsx15("button", { className: cn("cursor-pointer", base, variant), children: text });
1330
+ }
1331
+
1332
+ // src/components/sections/Button/index.tsx
1333
+ import { jsx as jsx16 } from "react/jsx-runtime";
1334
+ var schema6 = z6.object({
1335
+ type: z6.literal("button"),
1336
+ content: z6.object({
1337
+ text: z6.string(),
1338
+ link: LinkValueSchema.optional(),
1339
+ download: z6.boolean().optional()
1340
+ })
1341
+ });
1342
+ var Button_default = defineSection({
1343
+ type: "button",
1344
+ label: "Button",
1345
+ icon: /* @__PURE__ */ jsx16(RectangleHorizontal, { size: 18 }),
1346
+ schema: schema6,
1347
+ component: ({ content, onChange }) => /* @__PURE__ */ jsx16(
1348
+ Button3,
1349
+ {
1350
+ text: content.content.text,
1351
+ link: content.content.link,
1352
+ download: content.content.download,
1353
+ onChange: onChange ? (c) => onChange(c) : void 0
1354
+ }
1355
+ ),
1356
+ defaults: () => ({ type: "button", content: { text: "Button" } }),
1357
+ getLabel: (content) => content.content.text,
1358
+ settings: {
1359
+ link: { type: "link", label: "Link", default: DEFAULT_LINK, target: "content" },
1360
+ download: { type: "checkbox", label: "Download link", default: false, target: "content" }
1361
+ }
1362
+ });
1363
+
1364
+ // src/components/sections/Colors/index.tsx
1365
+ import { z as z7 } from "zod";
1366
+ import { Palette } from "lucide-react";
1367
+
1368
+ // src/components/brandguide/Colors.tsx
1369
+ import { useState as useState9, useEffect as useEffect7 } from "react";
1370
+ import { Eye, EyeOff } from "lucide-react";
1371
+
1372
+ // src/lib/container-grid.ts
1373
+ var MAX_CONTAINER_COLUMNS = 6;
1374
+ var containerGridClass = {
1375
+ 1: "grid-cols-1",
1376
+ 2: "grid-cols-1 @sm:grid-cols-2",
1377
+ 3: "grid-cols-1 @sm:grid-cols-2 @lg:grid-cols-3",
1378
+ 4: "grid-cols-1 @xs:grid-cols-2 @lg:grid-cols-4",
1379
+ 5: "grid-cols-1 @xs:grid-cols-2 @md:grid-cols-3 @xl:grid-cols-5",
1380
+ 6: "grid-cols-1 @xs:grid-cols-2 @md:grid-cols-3 @2xl:grid-cols-6"
1381
+ };
1382
+ var colSpanClass = {
1383
+ 1: { 1: "" },
1384
+ 2: { 1: "", 2: "col-span-1 @sm:col-span-2" },
1385
+ 3: { 1: "", 2: "col-span-1 @sm:col-span-2", 3: "col-span-1 @sm:col-span-2 @lg:col-span-3" },
1386
+ 4: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @lg:col-span-3", 4: "col-span-1 @xs:col-span-2 @lg:col-span-4" },
1387
+ 5: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @md:col-span-3", 4: "col-span-1 @xs:col-span-2 @md:col-span-3 @xl:col-span-4", 5: "col-span-1 @xs:col-span-2 @md:col-span-3 @xl:col-span-5" },
1388
+ 6: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @md:col-span-3", 4: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-4", 5: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-5", 6: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-6" }
1389
+ };
1390
+ var spacerHiddenClass = {
1391
+ 1: "hidden",
1392
+ 2: "hidden @sm:block",
1393
+ 3: "hidden @sm:block",
1394
+ 4: "hidden @xs:block",
1395
+ 5: "hidden @xs:block",
1396
+ 6: "hidden @xs:block"
1397
+ };
1398
+
1399
+ // src/components/primitives/EditableGrid.tsx
1400
+ import { useRef as useRef6, useEffect as useEffect6, useState as useState7 } from "react";
1401
+ import { ImageIcon as ImageIcon3 } from "lucide-react";
1402
+
1403
+ // src/components/shared/icons.tsx
1404
+ import { GripVertical, Plus, Trash2, X as X2, Settings } from "lucide-react";
1405
+ import { GripVertical as GripVertical2, Plus as Plus2, Trash2 as Trash22, X as X3, Settings as Settings2 } from "lucide-react";
1406
+ import { jsx as jsx17 } from "react/jsx-runtime";
1407
+ function DragHandle({ size = 16 }) {
1408
+ return /* @__PURE__ */ jsx17(GripVertical, { size });
1409
+ }
1410
+ function AddIcon({ size = 14 }) {
1411
+ return /* @__PURE__ */ jsx17(Plus, { size });
1412
+ }
1413
+ function DeleteIcon({ size = 14 }) {
1414
+ return /* @__PURE__ */ jsx17(Trash2, { size });
1415
+ }
1416
+ function SettingsIcon({ size = 14 }) {
1417
+ return /* @__PURE__ */ jsx17(Settings, { size });
1418
+ }
1419
+
1420
+ // src/components/shared/IconButton.tsx
1421
+ import { forwardRef as forwardRef2 } from "react";
1422
+ import { jsx as jsx18 } from "react/jsx-runtime";
1423
+ var sizeClasses = {
1424
+ sm: "h-chrome-sm w-chrome-sm",
1425
+ md: "h-chrome-md w-chrome-md",
1426
+ lg: "h-chrome-lg w-chrome-lg"
1427
+ };
1428
+ var intentClasses = {
1429
+ default: "text-base-contrast-light hover:text-base-contrast",
1430
+ destructive: "text-destructive hover:text-destructive-hover",
1431
+ primary: "text-primary/60 hover:text-primary"
1432
+ };
1433
+ var IconButton = forwardRef2(
1434
+ function IconButton2({ icon, label, size = "md", intent = "default", className, ...rest }, ref) {
1435
+ return /* @__PURE__ */ jsx18(
1436
+ "button",
1437
+ {
1438
+ ref,
1439
+ type: "button",
1440
+ className: cn(
1441
+ "cursor-pointer flex items-center justify-center rounded transition-colors",
1442
+ sizeClasses[size],
1443
+ intentClasses[intent],
1444
+ className
1445
+ ),
1446
+ "aria-label": label,
1447
+ ...rest,
1448
+ children: icon
1449
+ }
1450
+ );
1451
+ }
1452
+ );
1453
+
1454
+ // src/components/primitives/useEditableCollection.ts
1455
+ import { useState as useState6, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect5 } from "react";
1456
+ var nextId = 0;
1457
+ function uid() {
1458
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `_id_${nextId++}_${Date.now()}`;
1459
+ }
1460
+ function wrapItems(items) {
1461
+ return items.map((data) => ({ id: uid(), data }));
1462
+ }
1463
+ function useEditableCollection({
1464
+ items,
1465
+ onChange,
1466
+ createItem
1467
+ }) {
1468
+ const [wrappedItems, setWrappedItems] = useState6(
1469
+ () => wrapItems(items)
1470
+ );
1471
+ const prevItemsRef = useRef5(items);
1472
+ useEffect5(() => {
1473
+ if (items === prevItemsRef.current) return;
1474
+ prevItemsRef.current = items;
1475
+ setWrappedItems((prev) => {
1476
+ return items.map((data, i) => {
1477
+ if (i < prev.length) {
1478
+ return { id: prev[i].id, data };
1479
+ }
1480
+ return { id: uid(), data };
1481
+ });
1482
+ });
1483
+ }, [items]);
1484
+ const onReorder = useCallback5(
1485
+ (fromIndex, toIndex) => {
1486
+ setWrappedItems((prev) => {
1487
+ const next = [...prev];
1488
+ const [moved] = next.splice(fromIndex, 1);
1489
+ next.splice(toIndex, 0, moved);
1490
+ onChange(next.map((w) => w.data));
1491
+ return next;
1492
+ });
1493
+ },
1494
+ [onChange]
1495
+ );
1496
+ const onAdd = useCallback5(() => {
1497
+ const newItem = createItem();
1498
+ setWrappedItems((prev) => {
1499
+ const next = [...prev, { id: uid(), data: newItem }];
1500
+ onChange(next.map((w) => w.data));
1501
+ return next;
1502
+ });
1503
+ }, [createItem, onChange]);
1504
+ const onInsert = useCallback5(
1505
+ (index) => {
1506
+ const newItem = createItem();
1507
+ setWrappedItems((prev) => {
1508
+ const next = [...prev];
1509
+ next.splice(index, 0, { id: uid(), data: newItem });
1510
+ onChange(next.map((w) => w.data));
1511
+ return next;
1512
+ });
1513
+ },
1514
+ [createItem, onChange]
1515
+ );
1516
+ const onRemove = useCallback5(
1517
+ (id) => {
1518
+ setWrappedItems((prev) => {
1519
+ const next = prev.filter((w) => w.id !== id);
1520
+ onChange(next.map((w) => w.data));
1521
+ return next;
1522
+ });
1523
+ },
1524
+ [onChange]
1525
+ );
1526
+ return { wrappedItems, onReorder, onAdd, onInsert, onRemove };
1527
+ }
1528
+
1529
+ // src/components/primitives/EditableGrid.tsx
1530
+ import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
1531
+ function EditableGrid({
1532
+ items,
1533
+ columns,
1534
+ onChange,
1535
+ createItem,
1536
+ isEditMode,
1537
+ renderItem,
1538
+ onItemSettings,
1539
+ onItemImageClick,
1540
+ chromeTopClass,
1541
+ className
1542
+ }) {
1543
+ const { wrappedItems, onReorder, onAdd, onInsert, onRemove } = useEditableCollection({
1544
+ items,
1545
+ onChange,
1546
+ createItem
1547
+ });
1548
+ const [dragState, setDragState] = useState7({ sourceId: null, closestEdge: null });
1549
+ const [isDragging, setIsDragging] = useState7(false);
1550
+ useEffect6(() => {
1551
+ let cleanup;
1552
+ let cancelled = false;
1553
+ import("../../adapter-HH47ZPGM.js").then(({ monitorForElements }) => {
1554
+ if (cancelled) return;
1555
+ cleanup = monitorForElements({
1556
+ onDragStart: ({ source }) => {
1557
+ if (source.data.dragType === "grid-cell") setIsDragging(true);
1558
+ },
1559
+ onDrop: ({ source }) => {
1560
+ if (source.data.dragType === "grid-cell") setIsDragging(false);
1561
+ }
1562
+ });
1563
+ });
1564
+ return () => {
1565
+ cancelled = true;
1566
+ cleanup?.();
1567
+ };
1568
+ }, []);
1569
+ if (!isEditMode) {
1570
+ return /* @__PURE__ */ jsx19("div", { className: "@container", children: /* @__PURE__ */ jsx19("div", { className: cn("grid gap-4", containerGridClass[columns] || containerGridClass[1], className), children: wrappedItems.map((wrapped, index) => /* @__PURE__ */ jsx19("div", { children: renderItem(wrapped.data, { isEditMode: false, index }) }, wrapped.id)) }) });
1571
+ }
1572
+ return /* @__PURE__ */ jsx19("div", { className: "@container", children: /* @__PURE__ */ jsxs8("div", { className: cn("group/grid relative grid gap-4", containerGridClass[columns] || containerGridClass[1], className), children: [
1573
+ wrappedItems.map((wrapped, index) => /* @__PURE__ */ jsx19(
1574
+ GridCell,
1575
+ {
1576
+ id: wrapped.id,
1577
+ index,
1578
+ isLast: index === wrappedItems.length - 1,
1579
+ isDragging,
1580
+ onReorder,
1581
+ onRemove,
1582
+ onInsert,
1583
+ onSettings: onItemSettings ? () => onItemSettings(index) : void 0,
1584
+ onImageClick: onItemImageClick ? () => onItemImageClick(index) : void 0,
1585
+ chromeTopClass,
1586
+ dragState,
1587
+ setDragState,
1588
+ children: renderItem(wrapped.data, { isEditMode: true, index })
1589
+ },
1590
+ wrapped.id
1591
+ )),
1592
+ /* @__PURE__ */ jsx19(
1593
+ IconButton,
1594
+ {
1595
+ icon: /* @__PURE__ */ jsx19(AddIcon, { size: 16 }),
1596
+ label: "Add item",
1597
+ size: "lg",
1598
+ intent: "primary",
1599
+ onClick: onAdd,
1600
+ className: "absolute -bottom-6 left-1/2 z-20 -translate-x-1/2 rounded-full border border-base-200 bg-base opacity-0 transition-opacity group-hover/grid:opacity-100"
1601
+ }
1602
+ )
1603
+ ] }) });
1604
+ }
1605
+ function GridCell({
1606
+ id,
1607
+ index,
1608
+ isLast,
1609
+ isDragging,
1610
+ onReorder,
1611
+ onRemove,
1612
+ onInsert,
1613
+ onSettings,
1614
+ onImageClick,
1615
+ chromeTopClass,
1616
+ dragState,
1617
+ setDragState,
1618
+ children
1619
+ }) {
1620
+ const cellRef = useRef6(null);
1621
+ const handleRef = useRef6(null);
1622
+ useEffect6(() => {
1623
+ const cell = cellRef.current;
1624
+ const handle = handleRef.current;
1625
+ if (!cell || !handle) return;
1626
+ let cleanup;
1627
+ let cancelled = false;
1628
+ Promise.all([
1629
+ import("../../adapter-HH47ZPGM.js"),
1630
+ import("../../closest-edge-EBOXL3YW.js")
1631
+ ]).then(([{ draggable, dropTargetForElements }, { attachClosestEdge, extractClosestEdge }]) => {
1632
+ if (cancelled) return;
1633
+ const cleanupDraggable = draggable({
1634
+ element: handle,
1635
+ getInitialData: () => ({ dragType: "grid-cell", id, index }),
1636
+ onDragStart: () => setDragState({ sourceId: id, closestEdge: null }),
1637
+ onDrop: () => setDragState({ sourceId: null, closestEdge: null })
1638
+ });
1639
+ const cleanupDropTarget = dropTargetForElements({
1640
+ element: cell,
1641
+ canDrop: ({ source }) => source.data.dragType === "grid-cell",
1642
+ getData: ({ input, element }) => attachClosestEdge(
1643
+ { id, index },
1644
+ { input, element, allowedEdges: ["left", "right"] }
1645
+ ),
1646
+ onDragEnter: ({ self }) => {
1647
+ const edge = extractClosestEdge(self.data);
1648
+ if (edge) setDragState((prev) => ({ ...prev, closestEdge: { id, edge } }));
1649
+ },
1650
+ onDrag: ({ self }) => {
1651
+ const edge = extractClosestEdge(self.data);
1652
+ if (edge) setDragState((prev) => ({ ...prev, closestEdge: { id, edge } }));
1653
+ },
1654
+ onDragLeave: () => {
1655
+ setDragState((prev) => prev.closestEdge?.id === id ? { ...prev, closestEdge: null } : prev);
1656
+ },
1657
+ onDrop: ({ source, self }) => {
1658
+ const fromIndex = source.data.index;
1659
+ const edge = extractClosestEdge(self.data);
1660
+ let toIndex = index;
1661
+ if (edge === "right") toIndex = index + 1;
1662
+ if (fromIndex < toIndex) toIndex--;
1663
+ if (fromIndex !== toIndex) {
1664
+ onReorder(fromIndex, toIndex);
1665
+ }
1666
+ }
1667
+ });
1668
+ cleanup = () => {
1669
+ cleanupDraggable();
1670
+ cleanupDropTarget();
1671
+ };
1672
+ });
1673
+ return () => {
1674
+ cancelled = true;
1675
+ cleanup?.();
1676
+ };
1677
+ }, [id, index, onReorder, setDragState]);
1678
+ const isCellDragging = dragState.sourceId === id;
1679
+ const showLeftEdge = dragState.closestEdge?.id === id && dragState.closestEdge.edge === "left" && dragState.sourceId !== id;
1680
+ const showRightEdge = dragState.closestEdge?.id === id && dragState.closestEdge.edge === "right" && dragState.sourceId !== id;
1681
+ return /* @__PURE__ */ jsxs8(
1682
+ "div",
1683
+ {
1684
+ ref: cellRef,
1685
+ className: cn(
1686
+ "group/cell relative",
1687
+ isCellDragging && "opacity-50"
1688
+ ),
1689
+ children: [
1690
+ showLeftEdge && /* @__PURE__ */ jsx19("div", { className: "absolute top-0 bottom-0 left-0 z-10 w-0.5 -translate-x-2 bg-primary" }),
1691
+ showRightEdge && /* @__PURE__ */ jsx19("div", { className: "absolute top-0 right-0 bottom-0 z-10 w-0.5 translate-x-2 bg-primary" }),
1692
+ !isDragging && !isLast && /* @__PURE__ */ jsx19(
1693
+ IconButton,
1694
+ {
1695
+ icon: /* @__PURE__ */ jsx19(AddIcon, { size: 12 }),
1696
+ label: "Insert item",
1697
+ size: "sm",
1698
+ intent: "primary",
1699
+ onClick: () => onInsert(index + 1),
1700
+ className: "absolute top-1/2 -right-2 z-20 -translate-y-1/2 translate-x-1/2 rounded-full border border-base-200 bg-base opacity-0 group-hover/cell:opacity-100"
1701
+ }
1702
+ ),
1703
+ /* @__PURE__ */ jsx19(
1704
+ IconButton,
1705
+ {
1706
+ ref: handleRef,
1707
+ icon: /* @__PURE__ */ jsx19(DragHandle, { size: 16 }),
1708
+ label: "Drag to reorder",
1709
+ className: cn("absolute left-1.5 z-10 cursor-grab bg-base/80 opacity-0 shadow-sm group-hover/cell:opacity-100 no-hover:opacity-100", chromeTopClass || "top-1.5"),
1710
+ tabIndex: -1
1711
+ }
1712
+ ),
1713
+ /* @__PURE__ */ jsxs8("div", { className: cn("absolute right-1.5 z-10 flex items-center gap-1 opacity-0 group-hover/cell:opacity-100 no-hover:opacity-100", chromeTopClass || "top-1.5"), children: [
1714
+ onImageClick && /* @__PURE__ */ jsx19(
1715
+ IconButton,
1716
+ {
1717
+ icon: /* @__PURE__ */ jsx19(ImageIcon3, { size: 16 }),
1718
+ label: "Change image",
1719
+ onClick: onImageClick,
1720
+ className: "bg-base/80 shadow-sm"
1721
+ }
1722
+ ),
1723
+ onSettings && /* @__PURE__ */ jsx19(
1724
+ IconButton,
1725
+ {
1726
+ icon: /* @__PURE__ */ jsx19(SettingsIcon, { size: 16 }),
1727
+ label: "Item settings",
1728
+ onClick: onSettings,
1729
+ className: "bg-base/80 shadow-sm"
1730
+ }
1731
+ ),
1732
+ /* @__PURE__ */ jsx19(
1733
+ IconButton,
1734
+ {
1735
+ icon: /* @__PURE__ */ jsx19(DeleteIcon, { size: 16 }),
1736
+ label: "Delete item",
1737
+ intent: "destructive",
1738
+ onClick: () => onRemove(id),
1739
+ className: "bg-base/80 shadow-sm"
1740
+ }
1741
+ )
1742
+ ] }),
1743
+ children
1744
+ ]
1745
+ }
1746
+ );
1747
+ }
1748
+
1749
+ // src/components/brandguide/ColorSwatchSettings.tsx
1750
+ import { useState as useState8 } from "react";
1751
+
1752
+ // src/components/shared/Input.tsx
1753
+ import { forwardRef as forwardRef3, useId } from "react";
1754
+
1755
+ // src/components/shared/FormLabel.tsx
1756
+ import { jsx as jsx20 } from "react/jsx-runtime";
1757
+ function FormLabel({ htmlFor, children, className }) {
1758
+ return /* @__PURE__ */ jsx20(
1759
+ "label",
1760
+ {
1761
+ htmlFor,
1762
+ className: cn("mb-1.5 block text-sm font-medium text-base-contrast", className),
1763
+ children
1764
+ }
1765
+ );
1766
+ }
1767
+
1768
+ // src/components/shared/Input.tsx
1769
+ import { jsx as jsx21, jsxs as jsxs9 } from "react/jsx-runtime";
1770
+ var Input = forwardRef3(function Input2({ label, value, onChange, type = "text", className, disabled, error, ...rest }, ref) {
1771
+ const id = useId();
1772
+ const errorId = `${id}-error`;
1773
+ return /* @__PURE__ */ jsxs9("div", { className, children: [
1774
+ /* @__PURE__ */ jsx21(FormLabel, { htmlFor: id, children: label }),
1775
+ /* @__PURE__ */ jsx21(
1776
+ "input",
1777
+ {
1778
+ ref,
1779
+ id,
1780
+ type,
1781
+ value,
1782
+ onChange: (e) => onChange(e.target.value),
1783
+ disabled,
1784
+ "aria-invalid": error ? true : void 0,
1785
+ "aria-describedby": error ? errorId : void 0,
1786
+ className: cn(
1787
+ "w-full rounded border bg-base px-3 py-2 text-sm text-base-contrast focus:outline-none focus:ring-1",
1788
+ error ? "border-red-600 focus:border-red-600 focus:ring-red-600" : "border-base-200 focus:border-base-contrast focus:ring-base-contrast",
1789
+ disabled && "cursor-not-allowed opacity-50"
1790
+ ),
1791
+ ...rest
1792
+ }
1793
+ ),
1794
+ error && /* @__PURE__ */ jsx21("p", { id: errorId, className: "mt-1 text-xs text-red-600", children: error })
1795
+ ] });
1796
+ });
1797
+
1798
+ // src/components/brandguide/ColorSwatchSettings.tsx
1799
+ import { jsx as jsx22, jsxs as jsxs10 } from "react/jsx-runtime";
1800
+ function mergeSpaces(spaces) {
1801
+ return Object.assign({}, ...spaces);
1802
+ }
1803
+ function ColorSwatchSettings({ color, onChange }) {
1804
+ const [localColor, setLocalColor] = useState8(() => ({
1805
+ ...color,
1806
+ spaces: [mergeSpaces(color.spaces)]
1807
+ }));
1808
+ const space = localColor.spaces[0] || {};
1809
+ const update = (updated) => {
1810
+ setLocalColor(updated);
1811
+ onChange(updated);
1812
+ };
1813
+ const updateSpace = (key, value) => {
1814
+ const newSpace = { ...space, [key]: value || void 0 };
1815
+ update({ ...localColor, spaces: [newSpace] });
1816
+ };
1817
+ return /* @__PURE__ */ jsxs10("div", { className: "space-y-3", children: [
1818
+ /* @__PURE__ */ jsx22(
1819
+ Input,
1820
+ {
1821
+ label: "Name",
1822
+ value: localColor.name || "",
1823
+ onChange: (name) => update({ ...localColor, name })
1824
+ }
1825
+ ),
1826
+ ["hex", "rgb", "cmyk", "pantone"].map((key) => /* @__PURE__ */ jsx22(
1827
+ Input,
1828
+ {
1829
+ label: key.toUpperCase(),
1830
+ value: space[key] || "",
1831
+ onChange: (value) => updateSpace(key, value)
1832
+ },
1833
+ key
1834
+ ))
1835
+ ] });
1836
+ }
1837
+
1838
+ // src/components/brandguide/Colors.tsx
1839
+ import { jsx as jsx23, jsxs as jsxs11 } from "react/jsx-runtime";
1840
+ function getContrastClass(hex) {
1841
+ if (!hex) return "text-white";
1842
+ const h = hex.replace("#", "");
1843
+ if (h.length < 6) return "text-white";
1844
+ const r = parseInt(h.substring(0, 2), 16);
1845
+ const g = parseInt(h.substring(2, 4), 16);
1846
+ const b = parseInt(h.substring(4, 6), 16);
1847
+ const yiq = (r * 299 + g * 587 + b * 114) / 1e3;
1848
+ return yiq >= 128 ? "text-black" : "text-white";
1849
+ }
1850
+ var createColorItem = () => ({
1851
+ name: "",
1852
+ spaces: [{ hex: "#cccccc" }]
1853
+ });
1854
+ function buildContent(colors, options) {
1855
+ const { label, columns, collapsing, showLabel } = options;
1856
+ return {
1857
+ type: "colors",
1858
+ content: { colors },
1859
+ ...label || columns || collapsing !== void 0 || showLabel !== void 0 ? { options: { label, columns, collapsing, showLabel } } : {}
1860
+ };
1861
+ }
1862
+ function Colors({ colors, columns = 3, label, collapsing, showLabel = true, onChange, openModal }) {
1863
+ if (onChange && openModal) {
1864
+ return /* @__PURE__ */ jsx23(
1865
+ ColorsEditable,
1866
+ {
1867
+ colors,
1868
+ columns,
1869
+ label,
1870
+ collapsing,
1871
+ showLabel,
1872
+ onChange,
1873
+ openModal
1874
+ }
1875
+ );
1876
+ }
1877
+ return /* @__PURE__ */ jsx23(ColorsView, { colors, columns, label, collapsing, showLabel });
1878
+ }
1879
+ function ColorsView({
1880
+ colors,
1881
+ columns,
1882
+ label,
1883
+ collapsing,
1884
+ showLabel
1885
+ }) {
1886
+ const [expanded, setExpanded] = useState9(!collapsing);
1887
+ const [copiedIndex, setCopiedIndex] = useState9(null);
1888
+ const handleCopy = (value, colorIndex) => {
1889
+ if (navigator.clipboard?.writeText) {
1890
+ navigator.clipboard.writeText(value);
1891
+ } else {
1892
+ const ta = document.createElement("textarea");
1893
+ ta.value = value;
1894
+ ta.style.position = "fixed";
1895
+ ta.style.opacity = "0";
1896
+ document.body.appendChild(ta);
1897
+ ta.select();
1898
+ document.execCommand("copy");
1899
+ document.body.removeChild(ta);
1900
+ }
1901
+ setCopiedIndex(colorIndex);
1902
+ setTimeout(() => setCopiedIndex(null), 1500);
1903
+ };
1904
+ return /* @__PURE__ */ jsxs11("div", { children: [
1905
+ (showLabel && !!label || collapsing) && /* @__PURE__ */ jsxs11("div", { className: "mb-4 flex items-center gap-2", children: [
1906
+ showLabel && !!label && /* @__PURE__ */ jsx23("span", { className: "text-lg font-bold", children: label }),
1907
+ collapsing && /* @__PURE__ */ jsxs11(
1908
+ "button",
1909
+ {
1910
+ onClick: () => setExpanded(!expanded),
1911
+ className: "cursor-pointer ml-auto flex items-center gap-1.5 text-sm text-base-contrast-light hover:text-base-contrast",
1912
+ children: [
1913
+ /* @__PURE__ */ jsx23("span", { children: expanded ? "Hide Details" : "Show Details" }),
1914
+ expanded ? /* @__PURE__ */ jsx23(EyeOff, { size: 16 }) : /* @__PURE__ */ jsx23(Eye, { size: 16 })
1915
+ ]
1916
+ }
1917
+ )
1918
+ ] }),
1919
+ /* @__PURE__ */ jsx23("div", { className: "@container", children: /* @__PURE__ */ jsx23("div", { className: cn("grid gap-4", containerGridClass[columns] || containerGridClass[3]), children: colors.map((color, i) => {
1920
+ const hex = color.spaces[0]?.hex;
1921
+ const contrast = getContrastClass(hex);
1922
+ return /* @__PURE__ */ jsxs11("div", { className: "overflow-hidden rounded-md border border-base-200", children: [
1923
+ /* @__PURE__ */ jsxs11("div", { className: cn("relative flex min-h-[80px] items-end p-3", contrast), style: { backgroundColor: hex || "#ccc" }, children: [
1924
+ color.name && /* @__PURE__ */ jsx23("span", { className: "text-sm font-bold", children: color.name }),
1925
+ copiedIndex === i && /* @__PURE__ */ jsx23("span", { className: cn(
1926
+ "absolute top-2 right-2 rounded-full px-2.5 py-0.5 text-xs font-medium",
1927
+ getContrastClass(hex) === "text-black" ? "bg-black/15 text-black" : "bg-white/25 text-white"
1928
+ ), children: "Copied!" })
1929
+ ] }),
1930
+ expanded && /* @__PURE__ */ jsx23("div", { className: "space-y-1 bg-base-accent p-3 text-sm", children: color.spaces.map(
1931
+ (space, j) => Object.entries(space).map(
1932
+ ([key, value]) => value ? /* @__PURE__ */ jsxs11("button", { onClick: () => handleCopy(value, i), className: "cursor-pointer flex w-full justify-between hover:text-primary", children: [
1933
+ /* @__PURE__ */ jsx23("span", { className: "font-medium uppercase", children: key }),
1934
+ /* @__PURE__ */ jsx23("span", { children: value })
1935
+ ] }, `${j}-${key}`) : null
1936
+ )
1937
+ ) })
1938
+ ] }, i);
1939
+ }) }) })
1940
+ ] });
1941
+ }
1942
+ function ColorsEditable({
1943
+ colors,
1944
+ columns,
1945
+ label,
1946
+ collapsing,
1947
+ showLabel,
1948
+ onChange,
1949
+ openModal
1950
+ }) {
1951
+ const [expanded, setExpanded] = useState9(!collapsing);
1952
+ const options = { label, columns, collapsing, showLabel };
1953
+ useEffect7(() => {
1954
+ setExpanded(!collapsing);
1955
+ }, [collapsing]);
1956
+ const handleItemSettings = (index) => {
1957
+ const color = colors[index];
1958
+ openModal(
1959
+ "Color Settings",
1960
+ /* @__PURE__ */ jsx23(ColorSwatchSettings, { color, onChange: (updated) => {
1961
+ const newColors = colors.map((c, i) => i === index ? updated : c);
1962
+ onChange(buildContent(newColors, options));
1963
+ } })
1964
+ );
1965
+ };
1966
+ return /* @__PURE__ */ jsxs11("div", { children: [
1967
+ (showLabel && !!label || collapsing) && /* @__PURE__ */ jsxs11("div", { className: "mb-4 flex items-center gap-2", children: [
1968
+ showLabel && !!label && /* @__PURE__ */ jsx23(
1969
+ EditablePlainText,
1970
+ {
1971
+ tag: "span",
1972
+ value: label,
1973
+ onChange: (newLabel) => onChange(buildContent(colors, { ...options, label: newLabel })),
1974
+ isEditMode: true,
1975
+ className: "text-lg font-bold"
1976
+ }
1977
+ ),
1978
+ collapsing && /* @__PURE__ */ jsxs11(
1979
+ "button",
1980
+ {
1981
+ onClick: () => setExpanded(!expanded),
1982
+ className: "cursor-pointer ml-auto flex items-center gap-1.5 text-sm text-base-contrast-light hover:text-base-contrast",
1983
+ children: [
1984
+ /* @__PURE__ */ jsx23("span", { children: expanded ? "Hide Details" : "Show Details" }),
1985
+ expanded ? /* @__PURE__ */ jsx23(EyeOff, { size: 16 }) : /* @__PURE__ */ jsx23(Eye, { size: 16 })
1986
+ ]
1987
+ }
1988
+ )
1989
+ ] }),
1990
+ /* @__PURE__ */ jsx23(
1991
+ EditableGrid,
1992
+ {
1993
+ items: colors,
1994
+ columns,
1995
+ onChange: (newColors) => onChange(buildContent(newColors, options)),
1996
+ createItem: createColorItem,
1997
+ isEditMode: true,
1998
+ onItemSettings: handleItemSettings,
1999
+ renderItem: (color, { index }) => /* @__PURE__ */ jsx23(
2000
+ ColorSwatchEditable,
2001
+ {
2002
+ color,
2003
+ index,
2004
+ colors,
2005
+ options,
2006
+ onChange,
2007
+ expanded
2008
+ }
2009
+ )
2010
+ }
2011
+ )
2012
+ ] });
2013
+ }
2014
+ function ColorSwatchEditable({
2015
+ color,
2016
+ index,
2017
+ colors,
2018
+ options,
2019
+ onChange,
2020
+ expanded
2021
+ }) {
2022
+ const hex = color.spaces[0]?.hex;
2023
+ const contrast = getContrastClass(hex);
2024
+ const updateColor = (updated) => {
2025
+ const newColors = colors.map((c, i) => i === index ? updated : c);
2026
+ onChange(buildContent(newColors, options));
2027
+ };
2028
+ return /* @__PURE__ */ jsxs11("div", { className: "overflow-hidden rounded-md border border-base-200", children: [
2029
+ /* @__PURE__ */ jsx23("div", { className: cn("flex min-h-[80px] items-end p-3", contrast), style: { backgroundColor: hex || "#ccc" }, children: /* @__PURE__ */ jsx23(
2030
+ EditablePlainText,
2031
+ {
2032
+ tag: "span",
2033
+ value: color.name || "",
2034
+ onChange: (name) => updateColor({ ...color, name }),
2035
+ isEditMode: true,
2036
+ placeholder: "Color name",
2037
+ className: "text-sm font-bold"
2038
+ }
2039
+ ) }),
2040
+ expanded && /* @__PURE__ */ jsx23("div", { className: "space-y-1 bg-base-accent p-3 text-sm", children: color.spaces.map(
2041
+ (space, j) => Object.entries(space).map(
2042
+ ([key, value]) => value ? /* @__PURE__ */ jsxs11("div", { className: "flex w-full justify-between", children: [
2043
+ /* @__PURE__ */ jsx23("span", { className: "font-medium uppercase", children: key }),
2044
+ /* @__PURE__ */ jsx23("span", { children: value })
2045
+ ] }, `${j}-${key}`) : null
2046
+ )
2047
+ ) })
2048
+ ] });
2049
+ }
2050
+
2051
+ // src/components/sections/Colors/index.tsx
2052
+ import { jsx as jsx24 } from "react/jsx-runtime";
2053
+ var schema7 = z7.object({
2054
+ type: z7.literal("colors"),
2055
+ content: z7.object({ colors: z7.array(ColorItemSchema) }),
2056
+ options: z7.object({
2057
+ label: z7.string().optional(),
2058
+ columns: z7.number().int().min(2).max(4).optional(),
2059
+ collapsing: z7.boolean().optional(),
2060
+ showLabel: z7.boolean().optional()
2061
+ }).optional()
2062
+ });
2063
+ var Colors_default = defineSection({
2064
+ type: "colors",
2065
+ label: "Colors",
2066
+ icon: /* @__PURE__ */ jsx24(Palette, { size: 18 }),
2067
+ schema: schema7,
2068
+ component: ({ content, options, onChange, openModal }) => /* @__PURE__ */ jsx24(
2069
+ Colors,
2070
+ {
2071
+ colors: content.content.colors,
2072
+ columns: options?.columns,
2073
+ label: options?.label,
2074
+ collapsing: options?.collapsing,
2075
+ showLabel: options?.showLabel,
2076
+ onChange: onChange ? (c) => onChange(c) : void 0,
2077
+ openModal
2078
+ }
2079
+ ),
2080
+ defaults: () => ({
2081
+ type: "colors",
2082
+ content: { colors: [{ spaces: [{ hex: "#000000" }] }] }
2083
+ }),
2084
+ getLabel: (content) => {
2085
+ const n = content.content.colors.length;
2086
+ return `${n} color${n === 1 ? "" : "s"}`;
2087
+ },
2088
+ getThumbnails: (content) => content.content.colors.filter((c) => c.spaces[0]?.hex).map((c) => ({ type: "color", value: c.spaces[0].hex })),
2089
+ settings: {
2090
+ columns: {
2091
+ type: "select",
2092
+ label: "Columns",
2093
+ default: "3",
2094
+ coerce: "number",
2095
+ options: [{ label: "2", value: "2" }, { label: "3", value: "3" }, { label: "4", value: "4" }]
2096
+ },
2097
+ label: { type: "text", label: "Label", default: "", placeholder: "Color Section Label" },
2098
+ showLabel: { type: "checkbox", label: "Show label", default: true },
2099
+ collapsing: { type: "checkbox", label: "Collapsing layout", default: false }
2100
+ }
2101
+ });
2102
+
2103
+ // src/components/sections/IconList/index.tsx
2104
+ import { z as z8 } from "zod";
2105
+ import { List } from "lucide-react";
2106
+
2107
+ // src/components/sections/IconList/IconList.tsx
2108
+ import { useRef as useRef8, useEffect as useEffect9, useState as useState11, useCallback as useCallback6 } from "react";
2109
+ import { CopyPlus, Pencil } from "lucide-react";
2110
+
2111
+ // src/components/primitives/IconPicker.tsx
2112
+ import { useEffect as useEffect8, useRef as useRef7, useState as useState10 } from "react";
2113
+ import { Check as Check2, X as X4 } from "lucide-react";
2114
+ import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs12 } from "react/jsx-runtime";
2115
+ function IconPicker({
2116
+ selected,
2117
+ onSelect,
2118
+ onClose,
2119
+ showRemove = true,
2120
+ allowDoDont = false,
2121
+ dodont,
2122
+ onSelectDoDont
2123
+ }) {
2124
+ const panelRef = useRef7(null);
2125
+ const [mode, setMode] = useState10(dodont ? "dodont" : "none");
2126
+ useEffect8(() => {
2127
+ function handleMouseDown(e) {
2128
+ if (panelRef.current && !panelRef.current.contains(e.target)) {
2129
+ onClose();
2130
+ }
2131
+ }
2132
+ document.addEventListener("mousedown", handleMouseDown);
2133
+ return () => document.removeEventListener("mousedown", handleMouseDown);
2134
+ }, [onClose]);
2135
+ const showDoDont = allowDoDont && mode === "dodont";
2136
+ return /* @__PURE__ */ jsxs12(
2137
+ "div",
2138
+ {
2139
+ ref: panelRef,
2140
+ className: "absolute z-50 w-56 rounded-lg border border-base-200 bg-base p-2 shadow-lg",
2141
+ children: [
2142
+ allowDoDont && /* @__PURE__ */ jsxs12("div", { role: "tablist", "aria-label": "Icon mode", className: "mb-2 flex gap-1 rounded-md bg-base-200 p-0.5", children: [
2143
+ /* @__PURE__ */ jsx25(
2144
+ "button",
2145
+ {
2146
+ type: "button",
2147
+ role: "tab",
2148
+ "aria-label": "None",
2149
+ "aria-selected": mode === "none",
2150
+ className: cn(
2151
+ "cursor-pointer flex-1 rounded px-2 py-1 text-xs font-medium transition-colors",
2152
+ mode === "none" ? "bg-base text-base-contrast shadow-sm" : "text-base-contrast-light hover:text-base-contrast"
2153
+ ),
2154
+ onClick: () => {
2155
+ setMode("none");
2156
+ onSelectDoDont?.(void 0);
2157
+ },
2158
+ children: "None"
2159
+ }
2160
+ ),
2161
+ /* @__PURE__ */ jsx25(
2162
+ "button",
2163
+ {
2164
+ type: "button",
2165
+ role: "tab",
2166
+ "aria-label": "Do / Don't",
2167
+ "aria-selected": mode === "dodont",
2168
+ className: cn(
2169
+ "cursor-pointer flex-1 rounded px-2 py-1 text-xs font-medium transition-colors",
2170
+ mode === "dodont" ? "bg-base text-base-contrast shadow-sm" : "text-base-contrast-light hover:text-base-contrast"
2171
+ ),
2172
+ onClick: () => setMode("dodont"),
2173
+ children: "Do / Don't"
2174
+ }
2175
+ )
2176
+ ] }),
2177
+ showDoDont ? /* @__PURE__ */ jsxs12("div", { className: "grid grid-cols-2 gap-2", children: [
2178
+ /* @__PURE__ */ jsxs12(
2179
+ "button",
2180
+ {
2181
+ type: "button",
2182
+ "aria-label": "Do",
2183
+ className: cn(
2184
+ "cursor-pointer flex flex-col items-center justify-center gap-1 rounded py-2 transition-colors",
2185
+ "text-green-600 hover:bg-base-accent",
2186
+ dodont === "do" && "ring-2 ring-primary bg-base-accent"
2187
+ ),
2188
+ onClick: () => onSelectDoDont?.("do"),
2189
+ children: [
2190
+ /* @__PURE__ */ jsx25(Check2, { size: 20 }),
2191
+ /* @__PURE__ */ jsx25("span", { className: "text-xs font-medium", children: "Do" })
2192
+ ]
2193
+ }
2194
+ ),
2195
+ /* @__PURE__ */ jsxs12(
2196
+ "button",
2197
+ {
2198
+ type: "button",
2199
+ "aria-label": "Don't",
2200
+ className: cn(
2201
+ "cursor-pointer flex flex-col items-center justify-center gap-1 rounded py-2 transition-colors",
2202
+ "text-red-600 hover:bg-base-accent",
2203
+ dodont === "dont" && "ring-2 ring-primary bg-base-accent"
2204
+ ),
2205
+ onClick: () => onSelectDoDont?.("dont"),
2206
+ children: [
2207
+ /* @__PURE__ */ jsx25(X4, { size: 20 }),
2208
+ /* @__PURE__ */ jsx25("span", { className: "text-xs font-medium", children: "Don't" })
2209
+ ]
2210
+ }
2211
+ )
2212
+ ] }) : /* @__PURE__ */ jsxs12(Fragment4, { children: [
2213
+ /* @__PURE__ */ jsx25("div", { className: "grid grid-cols-5 gap-1", children: curatedIcons.map((entry) => {
2214
+ const Icon = entry.icon;
2215
+ const isSelected = selected === entry.id;
2216
+ return /* @__PURE__ */ jsx25(
2217
+ "button",
2218
+ {
2219
+ "aria-label": entry.label,
2220
+ className: cn(
2221
+ "cursor-pointer flex h-9 w-9 items-center justify-center rounded transition-colors",
2222
+ "text-base-contrast-light hover:bg-base-accent hover:text-base-contrast",
2223
+ isSelected && "ring-2 ring-primary bg-base-accent text-primary"
2224
+ ),
2225
+ onClick: () => onSelect(entry.id),
2226
+ children: /* @__PURE__ */ jsx25(Icon, { size: 18 })
2227
+ },
2228
+ entry.id
2229
+ );
2230
+ }) }),
2231
+ showRemove && /* @__PURE__ */ jsx25(
2232
+ "button",
2233
+ {
2234
+ className: "cursor-pointer mt-2 w-full rounded px-2 py-1.5 text-center text-xs text-base-contrast-light hover:bg-base-accent hover:text-base-contrast",
2235
+ onClick: () => onSelect(null),
2236
+ children: "Remove icon"
2237
+ }
2238
+ )
2239
+ ] })
2240
+ ]
2241
+ }
2242
+ );
2243
+ }
2244
+
2245
+ // src/components/sections/IconList/IconList.tsx
2246
+ import { jsx as jsx26, jsxs as jsxs13 } from "react/jsx-runtime";
2247
+ function IconList({
2248
+ items,
2249
+ icon = null,
2250
+ showLabel = true,
2251
+ stackText = false,
2252
+ labelClassName = "font-bold text-base-contrast",
2253
+ textClassName = "text-base-contrast-light",
2254
+ iconClassName = "text-primary",
2255
+ onChange,
2256
+ onItemsChange
2257
+ }) {
2258
+ const isEditMode = !!onChange || !!onItemsChange;
2259
+ const emitItems = useCallback6(
2260
+ (newItems) => {
2261
+ onChange?.({
2262
+ type: "icon_list",
2263
+ content: { items: newItems },
2264
+ options: { icon, showLabel, stackText }
2265
+ });
2266
+ onItemsChange?.(newItems);
2267
+ },
2268
+ [onChange, onItemsChange, icon, showLabel, stackText]
2269
+ );
2270
+ if (!isEditMode) {
2271
+ return /* @__PURE__ */ jsx26(
2272
+ ViewIconList,
2273
+ {
2274
+ items,
2275
+ defaultIcon: icon,
2276
+ showLabel,
2277
+ stackText,
2278
+ labelClassName,
2279
+ textClassName,
2280
+ iconClassName
2281
+ }
2282
+ );
2283
+ }
2284
+ return /* @__PURE__ */ jsx26(
2285
+ EditIconList,
2286
+ {
2287
+ items,
2288
+ defaultIcon: icon,
2289
+ showLabel,
2290
+ stackText,
2291
+ labelClassName,
2292
+ textClassName,
2293
+ iconClassName,
2294
+ onItemsChange: emitItems
2295
+ }
2296
+ );
2297
+ }
2298
+ function resolveIcon(item, defaultIcon) {
2299
+ if (item.dodont === "do") return getIcon("check") ?? null;
2300
+ if (item.dodont === "dont") return getIcon("x") ?? null;
2301
+ const id = item.icon ?? defaultIcon;
2302
+ return id ? getIcon(id) : null;
2303
+ }
2304
+ function resolveIconColor(item, iconClassName) {
2305
+ if (item.dodont === "do") return "text-green-600";
2306
+ if (item.dodont === "dont") return "text-red-600";
2307
+ return iconClassName;
2308
+ }
2309
+ function ViewIconList({
2310
+ items,
2311
+ defaultIcon,
2312
+ showLabel,
2313
+ stackText,
2314
+ labelClassName,
2315
+ textClassName,
2316
+ iconClassName
2317
+ }) {
2318
+ const hasAnyIcon = items.some((item) => resolveIcon(item, defaultIcon));
2319
+ return /* @__PURE__ */ jsx26("div", { className: "grid gap-4 pb-4", children: items.map((item, i) => {
2320
+ const iconEntry = resolveIcon(item, defaultIcon);
2321
+ const iconColor = resolveIconColor(item, iconClassName);
2322
+ return /* @__PURE__ */ jsxs13(
2323
+ "div",
2324
+ {
2325
+ className: cn(
2326
+ "grid gap-x-3",
2327
+ hasAnyIcon ? "grid-cols-[24px_1fr]" : "grid-cols-1"
2328
+ ),
2329
+ children: [
2330
+ hasAnyIcon && /* @__PURE__ */ jsx26(
2331
+ "div",
2332
+ {
2333
+ "data-testid": "icon-list-icon",
2334
+ className: cn("flex items-start pt-0.5", iconColor),
2335
+ children: iconEntry && /* @__PURE__ */ jsx26(iconEntry.icon, { size: 18 })
2336
+ }
2337
+ ),
2338
+ /* @__PURE__ */ jsxs13(
2339
+ "div",
2340
+ {
2341
+ "data-testid": "icon-list-content",
2342
+ className: stackText ? "flex flex-col" : void 0,
2343
+ children: [
2344
+ showLabel && /* @__PURE__ */ jsxs13("span", { className: labelClassName, children: [
2345
+ item.label,
2346
+ !stackText && " "
2347
+ ] }),
2348
+ /* @__PURE__ */ jsx26("span", { className: textClassName, children: item.text })
2349
+ ]
2350
+ }
2351
+ )
2352
+ ]
2353
+ },
2354
+ i
2355
+ );
2356
+ }) });
2357
+ }
2358
+ function EditIconList({
2359
+ items,
2360
+ defaultIcon,
2361
+ showLabel,
2362
+ stackText,
2363
+ labelClassName,
2364
+ textClassName,
2365
+ iconClassName,
2366
+ onItemsChange
2367
+ }) {
2368
+ const { wrappedItems, onReorder, onAdd, onRemove } = useEditableCollection({
2369
+ items,
2370
+ onChange: onItemsChange,
2371
+ createItem: () => ({ label: "", text: "" })
2372
+ });
2373
+ const [dragState, setDragState] = useState11({ sourceId: null, targetId: null });
2374
+ const updateItem = useCallback6(
2375
+ (index, patch) => {
2376
+ const updated = wrappedItems.map(
2377
+ (w, i) => i === index ? { ...w.data, ...patch } : w.data
2378
+ );
2379
+ onItemsChange(updated);
2380
+ },
2381
+ [wrappedItems, onItemsChange]
2382
+ );
2383
+ const duplicateItem = useCallback6(
2384
+ (index) => {
2385
+ const copy = { ...wrappedItems[index].data };
2386
+ const next = [
2387
+ ...wrappedItems.slice(0, index + 1).map((w) => w.data),
2388
+ copy,
2389
+ ...wrappedItems.slice(index + 1).map((w) => w.data)
2390
+ ];
2391
+ onItemsChange(next);
2392
+ },
2393
+ [wrappedItems, onItemsChange]
2394
+ );
2395
+ const hasAnyIcon = true;
2396
+ return /* @__PURE__ */ jsxs13("div", { className: "group/list relative grid gap-4 pb-4", children: [
2397
+ wrappedItems.map((wrapped, index) => /* @__PURE__ */ jsx26(
2398
+ EditableRow,
2399
+ {
2400
+ id: wrapped.id,
2401
+ index,
2402
+ item: wrapped.data,
2403
+ defaultIcon,
2404
+ hasAnyIcon,
2405
+ showLabel,
2406
+ stackText,
2407
+ labelClassName,
2408
+ textClassName,
2409
+ iconClassName,
2410
+ dragState,
2411
+ setDragState,
2412
+ onReorder,
2413
+ onRemove,
2414
+ onUpdateItem: (patch) => updateItem(index, patch),
2415
+ onDuplicate: () => duplicateItem(index)
2416
+ },
2417
+ wrapped.id
2418
+ )),
2419
+ /* @__PURE__ */ jsx26(
2420
+ TrailingDropZone,
2421
+ {
2422
+ index: wrappedItems.length,
2423
+ dragState,
2424
+ setDragState,
2425
+ onReorder
2426
+ }
2427
+ ),
2428
+ /* @__PURE__ */ jsx26(
2429
+ IconButton,
2430
+ {
2431
+ icon: /* @__PURE__ */ jsx26(AddIcon, { size: 16 }),
2432
+ label: "Add item",
2433
+ size: "lg",
2434
+ intent: "primary",
2435
+ onClick: onAdd,
2436
+ className: "absolute -bottom-6 left-1/2 z-20 -translate-x-1/2 rounded-full border border-base-200 bg-base opacity-0 transition-opacity group-hover/list:opacity-100"
2437
+ }
2438
+ )
2439
+ ] });
2440
+ }
2441
+ function EditableRow({
2442
+ id,
2443
+ index,
2444
+ item,
2445
+ defaultIcon,
2446
+ hasAnyIcon,
2447
+ showLabel,
2448
+ stackText,
2449
+ labelClassName,
2450
+ textClassName,
2451
+ iconClassName,
2452
+ dragState,
2453
+ setDragState,
2454
+ onReorder,
2455
+ onRemove,
2456
+ onUpdateItem,
2457
+ onDuplicate
2458
+ }) {
2459
+ const rowRef = useRef8(null);
2460
+ const handleRef = useRef8(null);
2461
+ const [iconHover, setIconHover] = useState11(false);
2462
+ const [pickerOpen, setPickerOpen] = useState11(false);
2463
+ const [editingSuppressed, setEditingSuppressed] = useState11(false);
2464
+ const iconEntry = resolveIcon(item, defaultIcon);
2465
+ const effectiveIconId = item.icon ?? defaultIcon ?? null;
2466
+ const iconColor = resolveIconColor(item, iconClassName);
2467
+ useEffect9(() => {
2468
+ const row = rowRef.current;
2469
+ const handle = handleRef.current;
2470
+ if (!row || !handle) return;
2471
+ let cleanup;
2472
+ let cancelled = false;
2473
+ import("../../adapter-HH47ZPGM.js").then(({ draggable, dropTargetForElements }) => {
2474
+ if (cancelled) return;
2475
+ const cleanupDraggable = draggable({
2476
+ element: handle,
2477
+ getInitialData: () => ({ dragType: "icon-list-row", id, index }),
2478
+ onDragStart: () => setDragState({ sourceId: id, targetId: null }),
2479
+ onDrop: () => setDragState({ sourceId: null, targetId: null })
2480
+ });
2481
+ const cleanupDropTarget = dropTargetForElements({
2482
+ element: row,
2483
+ canDrop: ({ source }) => source.data.dragType === "icon-list-row",
2484
+ getData: () => ({ id, index }),
2485
+ onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: id })),
2486
+ onDragLeave: () => setDragState(
2487
+ (prev) => prev.targetId === id ? { ...prev, targetId: null } : prev
2488
+ ),
2489
+ onDrop: ({ source }) => {
2490
+ const fromIndex = source.data.index;
2491
+ if (fromIndex !== index) onReorder(fromIndex, index);
2492
+ }
2493
+ });
2494
+ cleanup = () => {
2495
+ cleanupDraggable();
2496
+ cleanupDropTarget();
2497
+ };
2498
+ });
2499
+ return () => {
2500
+ cancelled = true;
2501
+ cleanup?.();
2502
+ };
2503
+ }, [id, index, onReorder, setDragState]);
2504
+ const isDropTarget = dragState.targetId === id && dragState.sourceId !== id;
2505
+ const onEditableActivity = (e) => {
2506
+ const t = e.target;
2507
+ if (t.matches?.("input, textarea, [contenteditable], [contenteditable='true']")) {
2508
+ setEditingSuppressed(true);
2509
+ }
2510
+ };
2511
+ return /* @__PURE__ */ jsxs13(
2512
+ "div",
2513
+ {
2514
+ ref: rowRef,
2515
+ className: cn(
2516
+ "group/row relative grid gap-x-3",
2517
+ hasAnyIcon ? "grid-cols-[24px_1fr]" : "grid-cols-1",
2518
+ isDropTarget && "border-t-2 border-primary"
2519
+ ),
2520
+ onFocusCapture: onEditableActivity,
2521
+ onInput: onEditableActivity,
2522
+ onMouseMove: () => setEditingSuppressed(false),
2523
+ children: [
2524
+ hasAnyIcon && /* @__PURE__ */ jsxs13(
2525
+ "div",
2526
+ {
2527
+ "data-testid": "icon-list-icon",
2528
+ className: cn("relative flex items-start pt-0.5", iconColor),
2529
+ onMouseEnter: () => setIconHover(true),
2530
+ onMouseLeave: () => setIconHover(false),
2531
+ children: [
2532
+ /* @__PURE__ */ jsx26(
2533
+ "button",
2534
+ {
2535
+ className: "cursor-pointer transition-opacity hover:opacity-70",
2536
+ "aria-label": "Change icon",
2537
+ onClick: () => setPickerOpen((prev) => !prev),
2538
+ children: iconHover && !pickerOpen ? /* @__PURE__ */ jsx26(Pencil, { size: 18, className: "text-base-contrast-light" }) : iconEntry ? /* @__PURE__ */ jsx26(iconEntry.icon, { size: 18 }) : /* @__PURE__ */ jsx26("span", { className: "flex h-[18px] w-[18px] items-center justify-center rounded-sm border border-dashed border-base-contrast-light/40" })
2539
+ }
2540
+ ),
2541
+ pickerOpen && /* @__PURE__ */ jsx26("div", { className: "absolute top-full left-0 z-50 mt-1", children: /* @__PURE__ */ jsx26(
2542
+ IconPicker,
2543
+ {
2544
+ selected: effectiveIconId,
2545
+ showRemove: true,
2546
+ allowDoDont: true,
2547
+ dodont: item.dodont,
2548
+ onSelect: (newIcon) => {
2549
+ onUpdateItem({ icon: newIcon ?? void 0, dodont: void 0 });
2550
+ setPickerOpen(false);
2551
+ setIconHover(false);
2552
+ },
2553
+ onSelectDoDont: (v) => {
2554
+ onUpdateItem({ dodont: v });
2555
+ if (v !== void 0) {
2556
+ setPickerOpen(false);
2557
+ setIconHover(false);
2558
+ }
2559
+ },
2560
+ onClose: () => {
2561
+ setPickerOpen(false);
2562
+ setIconHover(false);
2563
+ }
2564
+ }
2565
+ ) })
2566
+ ]
2567
+ }
2568
+ ),
2569
+ /* @__PURE__ */ jsxs13("div", { className: stackText ? "flex flex-col" : void 0, children: [
2570
+ showLabel && /* @__PURE__ */ jsx26(
2571
+ EditablePlainText,
2572
+ {
2573
+ tag: "span",
2574
+ value: item.label,
2575
+ onChange: (label) => onUpdateItem({ label }),
2576
+ isEditMode: true,
2577
+ placeholder: "Label",
2578
+ className: cn(labelClassName, !stackText && "mr-1")
2579
+ }
2580
+ ),
2581
+ /* @__PURE__ */ jsx26(
2582
+ EditablePlainText,
2583
+ {
2584
+ tag: "span",
2585
+ value: item.text,
2586
+ onChange: (text) => onUpdateItem({ text }),
2587
+ isEditMode: true,
2588
+ placeholder: "Text",
2589
+ className: textClassName
2590
+ }
2591
+ )
2592
+ ] }),
2593
+ /* @__PURE__ */ jsxs13(
2594
+ "div",
2595
+ {
2596
+ className: cn(
2597
+ "absolute right-1 top-1 z-10 flex items-center gap-1",
2598
+ editingSuppressed ? "opacity-0 pointer-events-none" : "opacity-0 group-hover/row:opacity-100 no-hover:opacity-100"
2599
+ ),
2600
+ children: [
2601
+ /* @__PURE__ */ jsx26(
2602
+ IconButton,
2603
+ {
2604
+ ref: handleRef,
2605
+ icon: /* @__PURE__ */ jsx26(DragHandle, { size: 14 }),
2606
+ label: "Drag to reorder",
2607
+ size: "sm",
2608
+ className: "cursor-grab rounded bg-base/80 p-1 shadow-sm text-base-contrast-light/80 hover:text-base-contrast active:cursor-grabbing",
2609
+ tabIndex: -1
2610
+ }
2611
+ ),
2612
+ /* @__PURE__ */ jsx26(
2613
+ "button",
2614
+ {
2615
+ "aria-label": "Duplicate item",
2616
+ onClick: onDuplicate,
2617
+ className: "cursor-pointer rounded bg-base/80 p-1 shadow-sm text-base-contrast-light hover:text-primary",
2618
+ children: /* @__PURE__ */ jsx26(CopyPlus, { size: 14 })
2619
+ }
2620
+ ),
2621
+ /* @__PURE__ */ jsx26(
2622
+ IconButton,
2623
+ {
2624
+ icon: /* @__PURE__ */ jsx26(DeleteIcon, { size: 14 }),
2625
+ label: "Delete item",
2626
+ size: "sm",
2627
+ intent: "destructive",
2628
+ onClick: () => onRemove(id),
2629
+ className: "bg-base/80 shadow-sm"
2630
+ }
2631
+ )
2632
+ ]
2633
+ }
2634
+ )
2635
+ ]
2636
+ }
2637
+ );
2638
+ }
2639
+ function TrailingDropZone({
2640
+ index,
2641
+ dragState,
2642
+ setDragState,
2643
+ onReorder
2644
+ }) {
2645
+ const ref = useRef8(null);
2646
+ const trailingId = "__trailing__";
2647
+ useEffect9(() => {
2648
+ const el = ref.current;
2649
+ if (!el) return;
2650
+ let cleanup;
2651
+ let cancelled = false;
2652
+ import("../../adapter-HH47ZPGM.js").then(({ dropTargetForElements }) => {
2653
+ if (cancelled) return;
2654
+ cleanup = dropTargetForElements({
2655
+ element: el,
2656
+ canDrop: ({ source }) => source.data.dragType === "icon-list-row",
2657
+ getData: () => ({ id: trailingId, index }),
2658
+ onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: trailingId })),
2659
+ onDragLeave: () => setDragState(
2660
+ (prev) => prev.targetId === trailingId ? { ...prev, targetId: null } : prev
2661
+ ),
2662
+ onDrop: ({ source }) => {
2663
+ const fromIndex = source.data.index;
2664
+ if (fromIndex !== index - 1) onReorder(fromIndex, index - 1);
2665
+ }
2666
+ });
2667
+ });
2668
+ return () => {
2669
+ cancelled = true;
2670
+ cleanup?.();
2671
+ };
2672
+ }, [index, onReorder, setDragState]);
2673
+ const isDropTarget = dragState.targetId === trailingId && dragState.sourceId !== null;
2674
+ return /* @__PURE__ */ jsx26(
2675
+ "div",
2676
+ {
2677
+ ref,
2678
+ className: cn("absolute bottom-0 left-0 right-0 h-4", isDropTarget && "border-t-2 border-primary")
2679
+ }
2680
+ );
2681
+ }
2682
+
2683
+ // src/components/sections/IconList/IconListSettings.tsx
2684
+ import { useState as useState12 } from "react";
2685
+
2686
+ // src/components/shared/Checkbox.tsx
2687
+ import { useId as useId2 } from "react";
2688
+ import { Check as Check3 } from "lucide-react";
2689
+ import { jsx as jsx27, jsxs as jsxs14 } from "react/jsx-runtime";
2690
+ function Checkbox({
2691
+ checked,
2692
+ onChange,
2693
+ label,
2694
+ description,
2695
+ disabled,
2696
+ className,
2697
+ startAdornment,
2698
+ align = "start"
2699
+ }) {
2700
+ const id = useId2();
2701
+ return /* @__PURE__ */ jsxs14(
2702
+ "label",
2703
+ {
2704
+ htmlFor: id,
2705
+ className: cn(
2706
+ "flex cursor-pointer gap-3 select-none hover:opacity-80",
2707
+ align === "start" ? "items-start" : "items-center",
2708
+ disabled && "cursor-not-allowed opacity-50",
2709
+ className
2710
+ ),
2711
+ children: [
2712
+ /* @__PURE__ */ jsxs14(
2713
+ "span",
2714
+ {
2715
+ className: cn(
2716
+ "relative flex h-5 w-5 shrink-0 items-center justify-center",
2717
+ align === "start" && "mt-0.5"
2718
+ ),
2719
+ children: [
2720
+ /* @__PURE__ */ jsx27(
2721
+ "input",
2722
+ {
2723
+ id,
2724
+ type: "checkbox",
2725
+ checked,
2726
+ disabled,
2727
+ onChange: (e) => onChange(e.target.checked),
2728
+ className: "sr-only"
2729
+ }
2730
+ ),
2731
+ /* @__PURE__ */ jsx27(
2732
+ "span",
2733
+ {
2734
+ "aria-hidden": "true",
2735
+ className: cn(
2736
+ "flex h-5 w-5 items-center justify-center rounded border transition-colors",
2737
+ checked ? "border-primary bg-primary text-primary-contrast" : "border-base-300 bg-base hover:border-primary",
2738
+ disabled && "pointer-events-none"
2739
+ ),
2740
+ children: checked && /* @__PURE__ */ jsx27(Check3, { size: 14, strokeWidth: 3 })
2741
+ }
2742
+ )
2743
+ ]
2744
+ }
2745
+ ),
2746
+ startAdornment,
2747
+ /* @__PURE__ */ jsxs14("div", { className: "text-sm", children: [
2748
+ /* @__PURE__ */ jsx27("span", { className: "font-medium text-base-contrast", children: label }),
2749
+ description && /* @__PURE__ */ jsx27("span", { className: "block text-base-contrast-light", children: description })
2750
+ ] })
2751
+ ]
2752
+ }
2753
+ );
2754
+ }
2755
+
2756
+ // src/components/sections/IconList/IconListSettings.tsx
2757
+ import { jsx as jsx28, jsxs as jsxs15 } from "react/jsx-runtime";
2758
+ function IconListSettings({
2759
+ icon: initialIcon,
2760
+ showLabel: initialShowLabel,
2761
+ stackText: initialStackText,
2762
+ onChange
2763
+ }) {
2764
+ const [icon, setIcon] = useState12(initialIcon);
2765
+ const [showLabel, setShowLabel] = useState12(initialShowLabel);
2766
+ const [stackText, setStackText] = useState12(initialStackText);
2767
+ const [pickerOpen, setPickerOpen] = useState12(false);
2768
+ const emit = (overrides) => onChange({ content: {}, options: { icon, showLabel, stackText, ...overrides } });
2769
+ const showIcons = icon !== null;
2770
+ const iconEntry = icon ? getIcon(icon) : null;
2771
+ return /* @__PURE__ */ jsxs15("div", { className: "space-y-4", children: [
2772
+ /* @__PURE__ */ jsxs15("div", { className: "relative", children: [
2773
+ /* @__PURE__ */ jsx28(FormLabel, { htmlFor: "icon-picker-btn", children: "Default Icon" }),
2774
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", children: [
2775
+ /* @__PURE__ */ jsxs15(
2776
+ "button",
2777
+ {
2778
+ id: "icon-picker-btn",
2779
+ className: "cursor-pointer flex items-center gap-2 rounded border border-base-200 px-3 py-1.5 text-sm text-base-contrast-light hover:border-primary hover:text-primary",
2780
+ onClick: () => setPickerOpen((prev) => !prev),
2781
+ children: [
2782
+ iconEntry && /* @__PURE__ */ jsx28(iconEntry.icon, { size: 16 }),
2783
+ /* @__PURE__ */ jsx28("span", { children: iconEntry?.label ?? "None" })
2784
+ ]
2785
+ }
2786
+ ),
2787
+ icon && /* @__PURE__ */ jsx28(
2788
+ "button",
2789
+ {
2790
+ className: "cursor-pointer text-xs text-base-contrast-light hover:text-base-contrast",
2791
+ onClick: () => {
2792
+ setIcon(null);
2793
+ emit({ icon: null });
2794
+ },
2795
+ children: "Clear"
2796
+ }
2797
+ )
2798
+ ] }),
2799
+ pickerOpen && /* @__PURE__ */ jsx28("div", { className: "absolute top-full left-0 z-50 mt-1", children: /* @__PURE__ */ jsx28(
2800
+ IconPicker,
2801
+ {
2802
+ selected: icon,
2803
+ onSelect: (newIcon) => {
2804
+ if (newIcon !== null) {
2805
+ setIcon(newIcon);
2806
+ emit({ icon: newIcon });
2807
+ }
2808
+ setPickerOpen(false);
2809
+ },
2810
+ onClose: () => setPickerOpen(false)
2811
+ }
2812
+ ) })
2813
+ ] }),
2814
+ /* @__PURE__ */ jsx28(
2815
+ Checkbox,
2816
+ {
2817
+ checked: showLabel,
2818
+ onChange: (v) => {
2819
+ setShowLabel(v);
2820
+ emit({ showLabel: v });
2821
+ },
2822
+ label: "Show labels"
2823
+ }
2824
+ ),
2825
+ showLabel && /* @__PURE__ */ jsx28(
2826
+ Checkbox,
2827
+ {
2828
+ checked: stackText,
2829
+ onChange: (v) => {
2830
+ setStackText(v);
2831
+ emit({ stackText: v });
2832
+ },
2833
+ label: "Stack label above text"
2834
+ }
2835
+ )
2836
+ ] });
2837
+ }
2838
+
2839
+ // src/components/sections/IconList/index.tsx
2840
+ import { jsx as jsx29 } from "react/jsx-runtime";
2841
+ var schema8 = z8.object({
2842
+ type: z8.literal("icon_list"),
2843
+ content: z8.object({
2844
+ items: z8.array(z8.object({ label: z8.string(), text: z8.string(), icon: z8.string().optional(), dodont: z8.enum(["do", "dont"]).optional() }))
2845
+ }),
2846
+ options: z8.object({
2847
+ icon: z8.string().nullable().optional(),
2848
+ showLabel: z8.boolean().optional(),
2849
+ stackText: z8.boolean().optional()
2850
+ }).optional()
2851
+ });
2852
+ var IconList_default = defineSection({
2853
+ type: "icon_list",
2854
+ label: "Icon List",
2855
+ icon: /* @__PURE__ */ jsx29(List, { size: 18 }),
2856
+ schema: schema8,
2857
+ component: ({ content, options, onChange }) => /* @__PURE__ */ jsx29(
2858
+ IconList,
2859
+ {
2860
+ items: content.content.items,
2861
+ icon: options?.icon,
2862
+ showLabel: options?.showLabel,
2863
+ stackText: options?.stackText,
2864
+ onChange: onChange ? (c) => onChange(c) : void 0
2865
+ }
2866
+ ),
2867
+ defaults: () => ({
2868
+ type: "icon_list",
2869
+ content: { items: [{ label: "", text: "" }] }
2870
+ }),
2871
+ getLabel: (content) => {
2872
+ const n = content.content.items.length;
2873
+ return `${n} icon${n === 1 ? "" : "s"}`;
2874
+ },
2875
+ settingsForm: IconListSettings
2876
+ });
2877
+
2878
+ // src/components/sections/Container/index.tsx
2879
+ import { z as z9 } from "zod";
2880
+ import { LayoutTemplate } from "lucide-react";
2881
+
2882
+ // src/components/sections/Container/Container.tsx
2883
+ import "react";
2884
+
2885
+ // src/components/editor/ChildBlockWrapper.tsx
2886
+ import { forwardRef as forwardRef6 } from "react";
2887
+ import { CopyPlus as CopyPlus2 } from "lucide-react";
2888
+
2889
+ // src/components/editor/DragHandle.tsx
2890
+ import { forwardRef as forwardRef4 } from "react";
2891
+
2892
+ // src/components/shared/Tooltip.tsx
2893
+ import { jsx as jsx30, jsxs as jsxs16 } from "react/jsx-runtime";
2894
+ function Tooltip({ content, children, className }) {
2895
+ return /* @__PURE__ */ jsxs16("div", { className: cn("group/tooltip relative inline-flex", className), children: [
2896
+ children,
2897
+ /* @__PURE__ */ jsx30(
2898
+ "span",
2899
+ {
2900
+ className: "pointer-events-none absolute left-1/2 top-full z-50 mt-1.5 -translate-x-1/2 opacity-0 transition-opacity delay-300 group-hover/tooltip:opacity-100",
2901
+ role: "tooltip",
2902
+ children: /* @__PURE__ */ jsx30("span", { className: "block whitespace-nowrap rounded bg-tooltip px-2 py-1 text-center text-xs leading-relaxed text-tooltip-muted", children: content })
2903
+ }
2904
+ )
2905
+ ] });
2906
+ }
2907
+ function Kbd({ children }) {
2908
+ return /* @__PURE__ */ jsx30("strong", { className: "text-tooltip-contrast", children });
2909
+ }
2910
+
2911
+ // src/components/editor/DragHandle.tsx
2912
+ import { Fragment as Fragment5, jsx as jsx31, jsxs as jsxs17 } from "react/jsx-runtime";
2913
+ var DragHandle2 = forwardRef4(function DragHandle3(_, ref) {
2914
+ return /* @__PURE__ */ jsx31(Tooltip, { content: /* @__PURE__ */ jsxs17(Fragment5, { children: [
2915
+ /* @__PURE__ */ jsx31(Kbd, { children: "Drag" }),
2916
+ " to move"
2917
+ ] }), children: /* @__PURE__ */ jsx31(
2918
+ IconButton,
2919
+ {
2920
+ ref,
2921
+ icon: /* @__PURE__ */ jsx31(DragHandle, { size: 18 }),
2922
+ label: "Drag to reorder section",
2923
+ className: "pointer-events-auto cursor-grab active:cursor-grabbing rounded-md text-base-contrast-light/80 hover:bg-base-contrast-light/10 hover:text-base-contrast",
2924
+ tabIndex: -1
2925
+ }
2926
+ ) });
2927
+ });
2928
+
2929
+ // src/components/editor/DeleteButton.tsx
2930
+ import { jsx as jsx32 } from "react/jsx-runtime";
2931
+ function DeleteButton({ onDelete }) {
2932
+ return /* @__PURE__ */ jsx32(
2933
+ IconButton,
2934
+ {
2935
+ icon: /* @__PURE__ */ jsx32(DeleteIcon, { size: 16 }),
2936
+ label: "Delete section",
2937
+ intent: "destructive",
2938
+ onClick: onDelete,
2939
+ className: "pointer-events-auto"
2940
+ }
2941
+ );
2942
+ }
2943
+
2944
+ // src/components/editor/SettingsButton.tsx
2945
+ import { jsx as jsx33 } from "react/jsx-runtime";
2946
+ function SettingsButton({ onClick }) {
2947
+ return /* @__PURE__ */ jsx33(
2948
+ IconButton,
2949
+ {
2950
+ icon: /* @__PURE__ */ jsx33(SettingsIcon, { size: 16 }),
2951
+ label: "Section settings",
2952
+ onClick,
2953
+ className: "pointer-events-auto"
2954
+ }
2955
+ );
2956
+ }
2957
+
2958
+ // src/components/editor/ColSpanControl.tsx
2959
+ import { useRef as useRef10, useState as useState14 } from "react";
2960
+ import { Columns3 } from "lucide-react";
2961
+
2962
+ // src/components/shared/Popover.tsx
2963
+ import { useEffect as useEffect11, useLayoutEffect, useRef as useRef9, useState as useState13 } from "react";
2964
+
2965
+ // src/hooks/useFocusTrap.ts
2966
+ import { useEffect as useEffect10 } from "react";
2967
+ var FOCUSABLE_SELECTOR = [
2968
+ "a[href]",
2969
+ "button:not([disabled])",
2970
+ "input:not([disabled])",
2971
+ "select:not([disabled])",
2972
+ "textarea:not([disabled])",
2973
+ '[tabindex]:not([tabindex="-1"])'
2974
+ ].join(", ");
2975
+ function useFocusTrap(ref, active) {
2976
+ useEffect10(() => {
2977
+ if (!active || !ref.current) return;
2978
+ const container = ref.current;
2979
+ const previouslyFocused = document.activeElement;
2980
+ const getFocusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
2981
+ const focusable = getFocusable();
2982
+ if (focusable.length > 0) {
2983
+ focusable[0].focus();
2984
+ }
2985
+ const handleKeyDown = (e) => {
2986
+ if (e.key !== "Tab") return;
2987
+ const elements = getFocusable();
2988
+ if (elements.length === 0) return;
2989
+ const first = elements[0];
2990
+ const last = elements[elements.length - 1];
2991
+ if (e.shiftKey) {
2992
+ if (document.activeElement === first) {
2993
+ e.preventDefault();
2994
+ last.focus();
2995
+ }
2996
+ } else {
2997
+ if (document.activeElement === last) {
2998
+ e.preventDefault();
2999
+ first.focus();
3000
+ }
3001
+ }
3002
+ };
3003
+ container.addEventListener("keydown", handleKeyDown);
3004
+ return () => {
3005
+ container.removeEventListener("keydown", handleKeyDown);
3006
+ if (previouslyFocused && document.body.contains(previouslyFocused)) {
3007
+ previouslyFocused.focus();
3008
+ }
3009
+ };
3010
+ }, [active, ref]);
3011
+ }
3012
+
3013
+ // src/components/shared/Popover.tsx
3014
+ import { jsx as jsx34 } from "react/jsx-runtime";
3015
+ function Popover({
3016
+ isOpen,
3017
+ onClose,
3018
+ anchorRef,
3019
+ children,
3020
+ align = "auto",
3021
+ className
3022
+ }) {
3023
+ const panelRef = useRef9(null);
3024
+ const [resolvedAlign, setResolvedAlign] = useState13("start");
3025
+ useFocusTrap(panelRef, isOpen);
3026
+ useLayoutEffect(() => {
3027
+ if (!isOpen || align !== "auto") {
3028
+ if (!isOpen) setResolvedAlign("start");
3029
+ return;
3030
+ }
3031
+ const panel = panelRef.current;
3032
+ if (!panel) return;
3033
+ panel.style.left = "0";
3034
+ panel.style.right = "auto";
3035
+ const rect = panel.getBoundingClientRect();
3036
+ if (rect.right > window.innerWidth) {
3037
+ setResolvedAlign("end");
3038
+ } else {
3039
+ setResolvedAlign("start");
3040
+ }
3041
+ panel.style.left = "";
3042
+ panel.style.right = "";
3043
+ }, [isOpen, align]);
3044
+ useEffect11(() => {
3045
+ if (!isOpen) return;
3046
+ function onKeyDown(e) {
3047
+ if (e.key === "Escape") onClose();
3048
+ }
3049
+ function onMouseDown(e) {
3050
+ const target = e.target;
3051
+ if (panelRef.current?.contains(target)) return;
3052
+ if (anchorRef.current?.contains(target)) return;
3053
+ onClose();
3054
+ }
3055
+ document.addEventListener("keydown", onKeyDown);
3056
+ document.addEventListener("mousedown", onMouseDown);
3057
+ return () => {
3058
+ document.removeEventListener("keydown", onKeyDown);
3059
+ document.removeEventListener("mousedown", onMouseDown);
3060
+ };
3061
+ }, [isOpen, onClose, anchorRef]);
3062
+ if (!isOpen) return null;
3063
+ const effectiveAlign = align === "auto" ? resolvedAlign : align;
3064
+ return /* @__PURE__ */ jsx34(
3065
+ "div",
3066
+ {
3067
+ ref: panelRef,
3068
+ role: "dialog",
3069
+ "aria-modal": "false",
3070
+ className: cn(
3071
+ "absolute top-full z-50 mt-1 rounded-md border border-base-200 bg-base shadow-lg",
3072
+ effectiveAlign === "end" ? "right-0" : "left-0",
3073
+ className
3074
+ ),
3075
+ children
3076
+ }
3077
+ );
3078
+ }
3079
+
3080
+ // src/components/shared/PopoverItem.tsx
3081
+ import { forwardRef as forwardRef5 } from "react";
3082
+ import { jsx as jsx35 } from "react/jsx-runtime";
3083
+ var PopoverItem = forwardRef5(
3084
+ function PopoverItem2({ className, children, ...rest }, ref) {
3085
+ return /* @__PURE__ */ jsx35(
3086
+ "button",
3087
+ {
3088
+ ref,
3089
+ type: "button",
3090
+ className: cn(
3091
+ "flex w-full cursor-pointer items-center gap-3 px-3 py-1.5 text-left text-xs hover:bg-base-accent",
3092
+ className
3093
+ ),
3094
+ ...rest,
3095
+ children
3096
+ }
3097
+ );
3098
+ }
3099
+ );
3100
+
3101
+ // src/components/editor/ColSpanControl.tsx
3102
+ import { jsx as jsx36, jsxs as jsxs18 } from "react/jsx-runtime";
3103
+ function ColSpanControl({ colSpan, columns, onChange }) {
3104
+ const buttonRef = useRef10(null);
3105
+ const [open, setOpen] = useState14(false);
3106
+ if (columns <= 1) return null;
3107
+ const current = Math.min(Math.max(colSpan, 1), columns);
3108
+ return /* @__PURE__ */ jsxs18("div", { className: "relative", children: [
3109
+ /* @__PURE__ */ jsx36(
3110
+ "button",
3111
+ {
3112
+ ref: buttonRef,
3113
+ type: "button",
3114
+ "aria-label": "Column span",
3115
+ "aria-haspopup": "true",
3116
+ "aria-expanded": open,
3117
+ onClick: () => setOpen((v) => !v),
3118
+ className: "pointer-events-auto inline-flex cursor-pointer items-center gap-1 rounded p-1 text-xs text-base-contrast-light hover:text-primary",
3119
+ children: /* @__PURE__ */ jsx36(Columns3, { size: 14 })
3120
+ }
3121
+ ),
3122
+ /* @__PURE__ */ jsx36(Popover, { isOpen: open, onClose: () => setOpen(false), anchorRef: buttonRef, className: "min-w-28", children: /* @__PURE__ */ jsx36("ul", { role: "list", className: "py-1", children: Array.from({ length: columns }, (_, i) => i + 1).map((span) => /* @__PURE__ */ jsx36("li", { children: /* @__PURE__ */ jsxs18(
3123
+ PopoverItem,
3124
+ {
3125
+ "aria-current": span === current,
3126
+ onClick: () => {
3127
+ onChange(span);
3128
+ setOpen(false);
3129
+ },
3130
+ className: cn(span === current && "font-semibold text-primary"),
3131
+ children: [
3132
+ "Span ",
3133
+ span
3134
+ ]
3135
+ }
3136
+ ) }, span)) }) })
3137
+ ] });
3138
+ }
3139
+
3140
+ // src/components/editor/ChildBlockWrapper.tsx
3141
+ import { jsx as jsx37, jsxs as jsxs19 } from "react/jsx-runtime";
3142
+ var ChildBlockWrapper = forwardRef6(
3143
+ function ChildBlockWrapper2({
3144
+ label,
3145
+ columns,
3146
+ colSpan,
3147
+ closestEdge,
3148
+ isDragging,
3149
+ hasSettings,
3150
+ dragHandleRef,
3151
+ onDelete,
3152
+ onDuplicate,
3153
+ onColSpanChange,
3154
+ onOpenSettings,
3155
+ children
3156
+ }, ref) {
3157
+ return /* @__PURE__ */ jsxs19(
3158
+ "div",
3159
+ {
3160
+ ref,
3161
+ className: cn(
3162
+ "group/childblock relative h-full rounded-sm",
3163
+ isDragging && "opacity-50",
3164
+ "hover:outline hover:outline-2 hover:outline-primary/30"
3165
+ ),
3166
+ children: [
3167
+ /* @__PURE__ */ jsxs19("div", { className: "absolute top-0 left-0 -translate-x-full z-20 flex flex-col items-center gap-1 opacity-0 transition-opacity group-hover/childblock:opacity-100 group-focus-within/childblock:opacity-100 pointer-events-none", children: [
3168
+ /* @__PURE__ */ jsx37("span", { className: "sr-only", children: label }),
3169
+ /* @__PURE__ */ jsx37("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx37(DragHandle2, { ref: dragHandleRef }) }),
3170
+ /* @__PURE__ */ jsx37("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx37(ColSpanControl, { colSpan, columns, onChange: onColSpanChange }) }),
3171
+ hasSettings && onOpenSettings && /* @__PURE__ */ jsx37("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx37(SettingsButton, { onClick: onOpenSettings }) }),
3172
+ /* @__PURE__ */ jsx37("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx37(
3173
+ "button",
3174
+ {
3175
+ type: "button",
3176
+ "aria-label": "Duplicate block",
3177
+ onClick: onDuplicate,
3178
+ className: "cursor-pointer rounded p-1 text-base-contrast-light hover:text-primary",
3179
+ children: /* @__PURE__ */ jsx37(CopyPlus2, { size: 16 })
3180
+ }
3181
+ ) }),
3182
+ /* @__PURE__ */ jsx37("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx37(DeleteButton, { onDelete }) })
3183
+ ] }),
3184
+ closestEdge === "left" && /* @__PURE__ */ jsx37(
3185
+ "div",
3186
+ {
3187
+ "data-drop-edge": "left",
3188
+ className: "absolute bottom-0 left-0 top-0 z-10 w-0.5 -translate-x-1 bg-primary"
3189
+ }
3190
+ ),
3191
+ closestEdge === "right" && /* @__PURE__ */ jsx37(
3192
+ "div",
3193
+ {
3194
+ "data-drop-edge": "right",
3195
+ className: "absolute bottom-0 right-0 top-0 z-10 w-0.5 translate-x-1 bg-primary"
3196
+ }
3197
+ ),
3198
+ closestEdge === "top" && /* @__PURE__ */ jsx37("div", { "data-drop-edge": "top", className: "absolute left-0 right-0 top-0 z-10 h-0.5 -translate-y-1 bg-primary" }),
3199
+ closestEdge === "bottom" && /* @__PURE__ */ jsx37("div", { "data-drop-edge": "bottom", className: "absolute left-0 right-0 bottom-0 z-10 h-0.5 translate-y-1 bg-primary" }),
3200
+ children
3201
+ ]
3202
+ }
3203
+ );
3204
+ }
3205
+ );
3206
+
3207
+ // src/components/editor/SettingsForm.tsx
3208
+ import { useId as useId4, useState as useState16 } from "react";
3209
+
3210
+ // src/components/shared/Select.tsx
3211
+ import { useId as useId3 } from "react";
3212
+ import { ChevronDown } from "lucide-react";
3213
+ import { jsx as jsx38, jsxs as jsxs20 } from "react/jsx-runtime";
3214
+ function Select({
3215
+ label,
3216
+ value,
3217
+ onChange,
3218
+ options,
3219
+ disabled,
3220
+ className,
3221
+ selectClassName
3222
+ }) {
3223
+ const id = useId3();
3224
+ return /* @__PURE__ */ jsxs20("div", { className, children: [
3225
+ label && /* @__PURE__ */ jsx38(FormLabel, { htmlFor: id, children: label }),
3226
+ /* @__PURE__ */ jsxs20("div", { className: "relative", children: [
3227
+ /* @__PURE__ */ jsx38(
3228
+ "select",
3229
+ {
3230
+ id,
3231
+ value,
3232
+ onChange: (e) => onChange(e.target.value),
3233
+ disabled,
3234
+ className: cn(
3235
+ "w-full appearance-none rounded border border-base-200 bg-base py-2 pr-8 pl-3 text-sm text-base-contrast",
3236
+ "focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast",
3237
+ disabled && "cursor-not-allowed opacity-50",
3238
+ selectClassName
3239
+ ),
3240
+ children: options.map((opt) => /* @__PURE__ */ jsx38("option", { value: opt.value, children: opt.label }, opt.value))
3241
+ }
3242
+ ),
3243
+ /* @__PURE__ */ jsx38(
3244
+ ChevronDown,
3245
+ {
3246
+ size: 14,
3247
+ className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-base-contrast-light"
3248
+ }
3249
+ )
3250
+ ] })
3251
+ ] });
3252
+ }
3253
+
3254
+ // src/components/shared/LinkField.tsx
3255
+ import { Fragment as Fragment6, jsx as jsx39, jsxs as jsxs21 } from "react/jsx-runtime";
3256
+ var TARGET_OPTIONS = [
3257
+ { label: "Same tab (_self)", value: "_self" },
3258
+ { label: "New tab (_blank)", value: "_blank" }
3259
+ ];
3260
+ function LinkField({ label, value, onChange }) {
3261
+ const { pages, getPageHeadings } = usePages();
3262
+ const isInternal = value.kind === "internal";
3263
+ function setMode(kind) {
3264
+ if (kind === value.kind) return;
3265
+ onChange(kind === "external" ? { kind: "external", href: "", target: value.target } : { kind: "internal", pageId: "", anchorSectionId: null, target: "_self" });
3266
+ }
3267
+ const pageId = isInternal ? value.pageId : "";
3268
+ const headings = pageId ? getPageHeadings(pageId) : [];
3269
+ return /* @__PURE__ */ jsxs21("div", { className: "flex flex-col gap-3", children: [
3270
+ /* @__PURE__ */ jsx39(FormLabel, { children: label }),
3271
+ /* @__PURE__ */ jsx39("div", { className: "flex gap-2", role: "tablist", children: ["external", "internal"].map((kind) => /* @__PURE__ */ jsx39(
3272
+ "button",
3273
+ {
3274
+ type: "button",
3275
+ role: "tab",
3276
+ "aria-selected": value.kind === kind,
3277
+ onClick: () => setMode(kind),
3278
+ className: cn(
3279
+ "cursor-pointer rounded border px-3 py-1 text-sm capitalize",
3280
+ value.kind === kind ? "border-primary bg-primary text-primary-contrast" : "border-base-200 text-base-contrast"
3281
+ ),
3282
+ children: kind === "external" ? "External URL" : "Internal page"
3283
+ },
3284
+ kind
3285
+ )) }),
3286
+ value.kind === "external" ? /* @__PURE__ */ jsx39(
3287
+ Input,
3288
+ {
3289
+ label: "URL",
3290
+ value: value.href,
3291
+ placeholder: "https://...",
3292
+ onChange: (href) => onChange({ kind: "external", href, target: value.target })
3293
+ }
3294
+ ) : /* @__PURE__ */ jsxs21(Fragment6, { children: [
3295
+ /* @__PURE__ */ jsx39(
3296
+ Select,
3297
+ {
3298
+ label: "Page",
3299
+ value: value.pageId,
3300
+ options: [{ label: "Select a page\u2026", value: "" }, ...pages.map((p) => ({ label: p.title, value: p.id }))],
3301
+ onChange: (pid) => onChange({ kind: "internal", pageId: pid, anchorSectionId: null, target: value.target })
3302
+ }
3303
+ ),
3304
+ /* @__PURE__ */ jsx39(
3305
+ Select,
3306
+ {
3307
+ label: "Section",
3308
+ value: value.anchorSectionId ?? "",
3309
+ disabled: !pageId,
3310
+ options: [{ label: "None (top of page)", value: "" }, ...headings.map((h) => ({ label: h.label, value: h.id }))],
3311
+ onChange: (anchor) => onChange({ kind: "internal", pageId, anchorSectionId: anchor || null, target: value.target })
3312
+ }
3313
+ )
3314
+ ] }),
3315
+ value.kind === "external" && /* @__PURE__ */ jsx39(
3316
+ Select,
3317
+ {
3318
+ label: "Opens in",
3319
+ value: value.target,
3320
+ options: TARGET_OPTIONS,
3321
+ onChange: (t) => onChange({ ...value, target: t })
3322
+ }
3323
+ )
3324
+ ] });
3325
+ }
3326
+
3327
+ // src/components/shared/Tabs.tsx
3328
+ import { useState as useState15 } from "react";
3329
+ import { jsx as jsx40, jsxs as jsxs22 } from "react/jsx-runtime";
3330
+ function Tabs({ tabs, defaultTabId, activeTabId, onTabChange, fullBleedTabBar }) {
3331
+ const [uncontrolledId, setUncontrolledId] = useState15(defaultTabId ?? tabs[0]?.id);
3332
+ const isControlled = activeTabId !== void 0;
3333
+ const activeId = isControlled ? activeTabId : uncontrolledId;
3334
+ const activeTab = tabs.find((t) => t.id === activeId) ?? tabs[0];
3335
+ function handleSelect(id) {
3336
+ if (!isControlled) setUncontrolledId(id);
3337
+ onTabChange?.(id);
3338
+ }
3339
+ return /* @__PURE__ */ jsxs22("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
3340
+ /* @__PURE__ */ jsx40("div", { className: cn("border-b border-base-200", fullBleedTabBar && "-mx-6 px-6"), children: /* @__PURE__ */ jsx40("div", { className: "flex", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ jsx40(
3341
+ "button",
3342
+ {
3343
+ type: "button",
3344
+ role: "tab",
3345
+ "aria-selected": activeTab?.id === tab.id,
3346
+ onClick: () => handleSelect(tab.id),
3347
+ className: cn(
3348
+ "cursor-pointer px-4 py-2 text-sm font-medium border-b-2 -mb-px",
3349
+ activeTab?.id === tab.id ? "border-brand text-brand" : "border-transparent text-base-contrast-light hover:text-base-contrast"
3350
+ ),
3351
+ children: tab.label
3352
+ },
3353
+ tab.id
3354
+ )) }) }),
3355
+ /* @__PURE__ */ jsx40("div", { role: "tabpanel", children: activeTab?.content })
3356
+ ] });
3357
+ }
3358
+
3359
+ // src/components/editor/SettingsForm.tsx
3360
+ import { jsx as jsx41, jsxs as jsxs23 } from "react/jsx-runtime";
3361
+ function resolveValues(schema11, values) {
3362
+ const resolved = {};
3363
+ for (const [key, field] of Object.entries(schema11)) {
3364
+ resolved[key] = values[key] ?? field.default;
3365
+ }
3366
+ return resolved;
3367
+ }
3368
+ function coerceValue(field, value) {
3369
+ if (field.type === "select" && "coerce" in field && field.coerce === "number") {
3370
+ return Number(value);
3371
+ }
3372
+ return value;
3373
+ }
3374
+ function normalizeEmpty(field, value) {
3375
+ if (field.type === "select" && "emptyIsUndefined" in field && field.emptyIsUndefined && value === "") {
3376
+ return void 0;
3377
+ }
3378
+ return value;
3379
+ }
3380
+ function splitByTarget(schema11, allValues) {
3381
+ const content = {};
3382
+ const options = {};
3383
+ for (const [key, field] of Object.entries(schema11)) {
3384
+ const value = normalizeEmpty(field, coerceValue(field, allValues[key]));
3385
+ const target = field.target ?? "options";
3386
+ if (target === "content") {
3387
+ content[key] = value;
3388
+ } else {
3389
+ options[key] = value;
3390
+ }
3391
+ }
3392
+ return { content, options };
3393
+ }
3394
+ function RangeField({
3395
+ field,
3396
+ value,
3397
+ onChange
3398
+ }) {
3399
+ const id = useId4();
3400
+ return /* @__PURE__ */ jsxs23("div", { children: [
3401
+ /* @__PURE__ */ jsx41(FormLabel, { htmlFor: id, children: field.label }),
3402
+ /* @__PURE__ */ jsx41(
3403
+ "input",
3404
+ {
3405
+ id,
3406
+ type: "range",
3407
+ min: field.min,
3408
+ max: field.max,
3409
+ step: field.step,
3410
+ value,
3411
+ onChange: (e) => onChange(Number(e.target.value)),
3412
+ className: cn(
3413
+ "w-full accent-primary"
3414
+ )
3415
+ }
3416
+ )
3417
+ ] });
3418
+ }
3419
+ function renderField(key, field, value, handleFieldChange) {
3420
+ switch (field.type) {
3421
+ case "text":
3422
+ return /* @__PURE__ */ jsx41(
3423
+ Input,
3424
+ {
3425
+ label: field.label,
3426
+ value: String(value ?? ""),
3427
+ placeholder: field.placeholder,
3428
+ onChange: (v) => handleFieldChange(key, v)
3429
+ },
3430
+ key
3431
+ );
3432
+ case "number":
3433
+ return /* @__PURE__ */ jsx41(
3434
+ Input,
3435
+ {
3436
+ label: field.label,
3437
+ type: "number",
3438
+ value: String(value ?? ""),
3439
+ onChange: (v) => handleFieldChange(key, v === "" ? "" : Number(v))
3440
+ },
3441
+ key
3442
+ );
3443
+ case "checkbox":
3444
+ return /* @__PURE__ */ jsx41(
3445
+ Checkbox,
3446
+ {
3447
+ label: field.label,
3448
+ checked: Boolean(value),
3449
+ onChange: (checked) => handleFieldChange(key, checked)
3450
+ },
3451
+ key
3452
+ );
3453
+ case "select":
3454
+ return /* @__PURE__ */ jsx41(
3455
+ Select,
3456
+ {
3457
+ label: field.label,
3458
+ value: String(value ?? ""),
3459
+ options: field.options,
3460
+ onChange: (v) => handleFieldChange(key, v)
3461
+ },
3462
+ key
3463
+ );
3464
+ case "range":
3465
+ return /* @__PURE__ */ jsx41(
3466
+ RangeField,
3467
+ {
3468
+ field,
3469
+ value: Number(value ?? field.min),
3470
+ onChange: (v) => handleFieldChange(key, v)
3471
+ },
3472
+ key
3473
+ );
3474
+ case "link":
3475
+ return /* @__PURE__ */ jsx41(
3476
+ LinkField,
3477
+ {
3478
+ label: field.label,
3479
+ value: value ?? field.default,
3480
+ onChange: (v) => handleFieldChange(key, v)
3481
+ },
3482
+ key
3483
+ );
3484
+ default:
3485
+ return null;
3486
+ }
3487
+ }
3488
+ function SettingsForm({ schema: schema11, values, onChange, tabs }) {
3489
+ const [local, setLocal] = useState16(() => resolveValues(schema11, values));
3490
+ function handleFieldChange(key, newValue) {
3491
+ const next = { ...local, [key]: newValue };
3492
+ setLocal(next);
3493
+ onChange(splitByTarget(schema11, next));
3494
+ }
3495
+ if (tabs) {
3496
+ const fields = (keys) => keys.filter((key) => key in schema11).map((key) => renderField(key, schema11[key], local[key], handleFieldChange));
3497
+ const listed = new Set(tabs.flatMap((t) => t.fields));
3498
+ const otherKeys = Object.keys(schema11).filter((key) => !listed.has(key));
3499
+ const tabItems = [
3500
+ ...tabs.map((t) => ({ id: t.label, label: t.label, content: /* @__PURE__ */ jsx41("div", { className: "flex flex-col gap-4", children: fields(t.fields) }) })),
3501
+ ...otherKeys.length > 0 ? [{ id: "__other__", label: "Other", content: /* @__PURE__ */ jsx41("div", { className: "flex flex-col gap-4", children: fields(otherKeys) }) }] : []
3502
+ ];
3503
+ return /* @__PURE__ */ jsx41(Tabs, { tabs: tabItems, fullBleedTabBar: true });
3504
+ }
3505
+ return /* @__PURE__ */ jsx41("div", { className: "flex flex-col gap-4", children: Object.entries(schema11).map(([key, field]) => renderField(key, field, local[key], handleFieldChange)) });
3506
+ }
3507
+
3508
+ // src/hooks/useBlockDnd.ts
3509
+ import { useEffect as useEffect12, useRef as useRef11, useState as useState17 } from "react";
3510
+
3511
+ // src/lib/block-dnd.ts
3512
+ var ROOT_CONTAINER_ID = "__root__";
3513
+ function buildBlockDragData(args) {
3514
+ return { dragType: "block", ...args };
3515
+ }
3516
+ function buildBlockDropData(args) {
3517
+ return { ...args };
3518
+ }
3519
+ function isBlockDragData(data) {
3520
+ return data.dragType === "block";
3521
+ }
3522
+ function canDropBlock(source, dropContainerId) {
3523
+ if (source.blockId === dropContainerId) return false;
3524
+ if (source.isContainer && dropContainerId !== ROOT_CONTAINER_ID) return false;
3525
+ return true;
3526
+ }
3527
+
3528
+ // src/hooks/useBlockDnd.ts
3529
+ function useBlockDnd({ blockId, containerId, index, isContainer, allowedEdges, enabled }) {
3530
+ const dragRef = useRef11(null);
3531
+ const handleRef = useRef11(null);
3532
+ const [closestEdge, setClosestEdge] = useState17(null);
3533
+ const [isDragging, setIsDragging] = useState17(false);
3534
+ useEffect12(() => {
3535
+ const element = dragRef.current;
3536
+ const handle = handleRef.current;
3537
+ if (!element || !enabled) return;
3538
+ let cleanup;
3539
+ let cancelled = false;
3540
+ Promise.all([
3541
+ import("../../adapter-HH47ZPGM.js"),
3542
+ import("../../closest-edge-EBOXL3YW.js")
3543
+ ]).then(([{ draggable, dropTargetForElements }, { attachClosestEdge, extractClosestEdge }]) => {
3544
+ if (cancelled) return;
3545
+ const cleanupDraggable = draggable({
3546
+ element,
3547
+ dragHandle: handle ?? void 0,
3548
+ getInitialData: () => buildBlockDragData({ blockId, containerId, index, isContainer }),
3549
+ onGenerateDragPreview: () => {
3550
+ element.style.opacity = "0.4";
3551
+ requestAnimationFrame(() => {
3552
+ element.style.opacity = "";
3553
+ });
3554
+ },
3555
+ onDragStart: () => setIsDragging(true),
3556
+ onDrop: () => setIsDragging(false)
3557
+ });
3558
+ const cleanupDrop = dropTargetForElements({
3559
+ element,
3560
+ canDrop: ({ source }) => isBlockDragData(source.data) && canDropBlock(source.data, containerId),
3561
+ getData: ({ input, element: el }) => attachClosestEdge(buildBlockDropData({ dropContainerId: containerId, index }), {
3562
+ input,
3563
+ element: el,
3564
+ allowedEdges
3565
+ }),
3566
+ onDrag: ({ self, source, location }) => {
3567
+ const innermost = location.current.dropTargets[0]?.element === self.element;
3568
+ if (!innermost || !isBlockDragData(source.data) || source.data.blockId === blockId) {
3569
+ setClosestEdge(null);
3570
+ return;
3571
+ }
3572
+ setClosestEdge(extractClosestEdge(self.data));
3573
+ },
3574
+ onDragLeave: () => setClosestEdge(null),
3575
+ onDrop: () => setClosestEdge(null)
3576
+ });
3577
+ cleanup = () => {
3578
+ cleanupDraggable();
3579
+ cleanupDrop();
3580
+ };
3581
+ });
3582
+ return () => {
3583
+ cancelled = true;
3584
+ cleanup?.();
3585
+ };
3586
+ }, [blockId, containerId, index, isContainer, enabled, allowedEdges]);
3587
+ return { dragRef, handleRef, closestEdge, isDragging };
3588
+ }
3589
+ function useBlockDropZone({ containerId, index, toColumn, allowedEdges, enabled }) {
3590
+ const dropRef = useRef11(null);
3591
+ const [closestEdge, setClosestEdge] = useState17(null);
3592
+ useEffect12(() => {
3593
+ const element = dropRef.current;
3594
+ if (!element || !enabled) return;
3595
+ let cleanup;
3596
+ let cancelled = false;
3597
+ Promise.all([
3598
+ import("../../adapter-HH47ZPGM.js"),
3599
+ import("../../closest-edge-EBOXL3YW.js")
3600
+ ]).then(([{ dropTargetForElements }, { attachClosestEdge, extractClosestEdge }]) => {
3601
+ if (cancelled) return;
3602
+ cleanup = dropTargetForElements({
3603
+ element,
3604
+ canDrop: ({ source }) => isBlockDragData(source.data) && canDropBlock(source.data, containerId),
3605
+ getData: ({ input, element: el }) => attachClosestEdge(buildBlockDropData({ dropContainerId: containerId, index, toColumn }), {
3606
+ input,
3607
+ element: el,
3608
+ allowedEdges
3609
+ }),
3610
+ onDrag: ({ self, source, location }) => {
3611
+ const innermost = location.current.dropTargets[0]?.element === self.element;
3612
+ if (!innermost || !isBlockDragData(source.data)) {
3613
+ setClosestEdge(null);
3614
+ return;
3615
+ }
3616
+ setClosestEdge(extractClosestEdge(self.data));
3617
+ },
3618
+ onDragLeave: () => setClosestEdge(null),
3619
+ onDrop: () => setClosestEdge(null)
3620
+ });
3621
+ });
3622
+ return () => {
3623
+ cancelled = true;
3624
+ cleanup?.();
3625
+ };
3626
+ }, [containerId, index, toColumn, enabled, allowedEdges]);
3627
+ return { dropRef, closestEdge };
3628
+ }
3629
+
3630
+ // src/lib/container-ops.ts
3631
+ function getContainerChildren(block) {
3632
+ const content = block.content;
3633
+ return Array.isArray(content?.children) ? content.children : [];
3634
+ }
3635
+ function withChildren(container, children) {
3636
+ return { ...container, content: { ...container.content, children } };
3637
+ }
3638
+ function containerColumns(container) {
3639
+ const n = container.content?.columns;
3640
+ return typeof n === "number" && n >= 1 ? n : 1;
3641
+ }
3642
+ function removeChildAt(container, index) {
3643
+ const children = getContainerChildren(container);
3644
+ if (index < 0 || index >= children.length) return { container, removed: null };
3645
+ const next = [...children];
3646
+ const [removed] = next.splice(index, 1);
3647
+ return { container: withChildren(container, next), removed };
3648
+ }
3649
+ function duplicateChildAt(container, index, newId2) {
3650
+ const children = getContainerChildren(container);
3651
+ if (index < 0 || index >= children.length) return container;
3652
+ const clone = { ...JSON.parse(JSON.stringify(children[index])), id: newId2() };
3653
+ const next = [...children];
3654
+ next.splice(index + 1, 0, clone);
3655
+ return withChildren(container, next);
3656
+ }
3657
+ function setChildColSpan(container, index, span) {
3658
+ const children = getContainerChildren(container);
3659
+ if (index < 0 || index >= children.length) return container;
3660
+ const clamped = Math.max(1, Math.min(span, containerColumns(container)));
3661
+ const next = children.map((child, i) => {
3662
+ if (i !== index) return child;
3663
+ if (clamped <= 1) {
3664
+ const { layout: _drop, ...rest } = child;
3665
+ return rest;
3666
+ }
3667
+ return { ...child, layout: { ...child.layout, colSpan: clamped } };
3668
+ });
3669
+ return withChildren(container, next);
3670
+ }
3671
+ function effectiveSpan(child, columns) {
3672
+ return isSpacer(child) ? 1 : Math.min(child.layout?.colSpan ?? 1, columns);
3673
+ }
3674
+ function isSpacer(child) {
3675
+ return child.type === "spacer";
3676
+ }
3677
+ function occupiedColumns(children, columns) {
3678
+ return children.reduce((sum, c) => sum + effectiveSpan(c, columns), 0);
3679
+ }
3680
+ function trimTrailingSpacers(container) {
3681
+ const children = getContainerChildren(container);
3682
+ let end = children.length;
3683
+ while (end > 0 && isSpacer(children[end - 1])) end--;
3684
+ if (end === children.length) return container;
3685
+ return withChildren(container, children.slice(0, end));
3686
+ }
3687
+
3688
+ // src/components/sections/Container/Container.tsx
3689
+ import { jsx as jsx42, jsxs as jsxs24 } from "react/jsx-runtime";
3690
+ var ROW_EDGES = ["left", "right"];
3691
+ var COL_EDGES = ["top", "bottom"];
3692
+ function ContainerChild({
3693
+ containerId,
3694
+ index,
3695
+ child,
3696
+ columns,
3697
+ flow,
3698
+ cellClass,
3699
+ label,
3700
+ hasSettings,
3701
+ mergedOptions,
3702
+ isEditMode,
3703
+ ChildComp,
3704
+ onDelete,
3705
+ onDuplicate,
3706
+ onColSpanChange,
3707
+ onOpenSettings,
3708
+ onChildContentChange,
3709
+ openModal
3710
+ }) {
3711
+ const isContainer = Array.isArray(child.content?.children);
3712
+ const { dragRef, handleRef, closestEdge, isDragging } = useBlockDnd({
3713
+ blockId: child.id ?? `child-${index}`,
3714
+ containerId,
3715
+ index,
3716
+ isContainer,
3717
+ allowedEdges: flow === "column" ? COL_EDGES : ROW_EDGES,
3718
+ enabled: true
3719
+ });
3720
+ return /* @__PURE__ */ jsx42(
3721
+ "div",
3722
+ {
3723
+ ref: dragRef,
3724
+ className: cellClass,
3725
+ "data-child-index": index,
3726
+ "data-child-id": child.id,
3727
+ children: /* @__PURE__ */ jsx42(
3728
+ ChildBlockWrapper,
3729
+ {
3730
+ label,
3731
+ columns,
3732
+ colSpan: Math.min(child.layout?.colSpan ?? 1, columns),
3733
+ closestEdge,
3734
+ isDragging,
3735
+ hasSettings,
3736
+ dragHandleRef: handleRef,
3737
+ onDelete,
3738
+ onDuplicate,
3739
+ onColSpanChange,
3740
+ onOpenSettings,
3741
+ children: /* @__PURE__ */ jsx42(
3742
+ ChildComp,
3743
+ {
3744
+ content: child,
3745
+ options: mergedOptions,
3746
+ isEditMode,
3747
+ onChange: onChildContentChange,
3748
+ openModal
3749
+ }
3750
+ )
3751
+ }
3752
+ )
3753
+ }
3754
+ );
3755
+ }
3756
+ function EmptyColumnCell({
3757
+ containerId,
3758
+ targetColumn,
3759
+ testId
3760
+ }) {
3761
+ const { dropRef, closestEdge } = useBlockDropZone({
3762
+ containerId,
3763
+ index: 0,
3764
+ toColumn: targetColumn,
3765
+ allowedEdges: ROW_EDGES,
3766
+ enabled: true
3767
+ });
3768
+ const active = closestEdge !== null;
3769
+ return /* @__PURE__ */ jsx42(
3770
+ "div",
3771
+ {
3772
+ ref: dropRef,
3773
+ "data-empty-dropzone": true,
3774
+ "data-testid": testId,
3775
+ "data-target-column": targetColumn,
3776
+ className: cn(
3777
+ "flex min-h-20 items-center justify-center rounded-md border-2 border-dashed text-xs text-base-contrast-light/50",
3778
+ active ? "border-primary" : "border-base-200"
3779
+ ),
3780
+ children: "Empty"
3781
+ }
3782
+ );
3783
+ }
3784
+ function newId() {
3785
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `child-${Math.random().toString(36).slice(2)}-${Date.now()}`;
3786
+ }
3787
+ function Container({ content, isEditMode, onChange, openModal }) {
3788
+ const { columns, flow, childDefaults, children } = content.content;
3789
+ const editable = isEditMode && !!onChange;
3790
+ const container = {
3791
+ id: content.id ?? "container",
3792
+ type: "container",
3793
+ content: content.content
3794
+ };
3795
+ const emit = (nextSection) => onChange?.({ ...content, content: nextSection.content });
3796
+ const updateChild = (index, childContent) => {
3797
+ const next = children.map((c, i) => i === index ? childContent : c);
3798
+ onChange?.({ ...content, content: { ...content.content, children: next } });
3799
+ };
3800
+ const gridClass = cn(
3801
+ "grid",
3802
+ "gap-8",
3803
+ containerGridClass[columns] ?? "grid-cols-1",
3804
+ flow === "column" && "grid-flow-col"
3805
+ );
3806
+ if (!editable) {
3807
+ return /* @__PURE__ */ jsx42("div", { className: "@container", children: /* @__PURE__ */ jsx42("div", { className: gridClass, children: children.map((child, index) => {
3808
+ const def = getSection(child.type);
3809
+ if (!def) return null;
3810
+ const Child = def.component;
3811
+ const span = child.layout?.colSpan ?? 1;
3812
+ const childOptions = "options" in child ? child.options : void 0;
3813
+ const mergedOptions = { ...childDefaults ?? {}, ...childOptions ?? {} };
3814
+ const cellClass = cn(
3815
+ colSpanClass[columns]?.[Math.min(span, columns)] || void 0,
3816
+ child.type === "spacer" && spacerHiddenClass[columns]
3817
+ );
3818
+ return /* @__PURE__ */ jsx42("div", { className: cellClass, children: /* @__PURE__ */ jsx42(Child, { content: child, options: mergedOptions, isEditMode }) }, child.id ?? index);
3819
+ }) }) });
3820
+ }
3821
+ function columnStartOf(index) {
3822
+ let c = 0;
3823
+ for (let i = 0; i < index; i++) {
3824
+ c += isSpacer(children[i]) ? 1 : Math.min(children[i].layout?.colSpan ?? 1, columns);
3825
+ }
3826
+ return c + 1;
3827
+ }
3828
+ function renderEditChild(child, index) {
3829
+ const def = getSection(child.type);
3830
+ if (!def) return null;
3831
+ if (isSpacer(child)) {
3832
+ return /* @__PURE__ */ jsx42(
3833
+ EmptyColumnCell,
3834
+ {
3835
+ containerId: container.id,
3836
+ targetColumn: columnStartOf(index),
3837
+ testId: "spacer-cell"
3838
+ },
3839
+ child.id ?? index
3840
+ );
3841
+ }
3842
+ const span = child.layout?.colSpan ?? 1;
3843
+ const childOptions = "options" in child ? child.options : void 0;
3844
+ const mergedOptions = { ...childDefaults ?? {}, ...childOptions ?? {} };
3845
+ const cellClass = colSpanClass[columns]?.[Math.min(span, columns)] || void 0;
3846
+ const hasSettings = !!(def.settings || def.settingsForm) && !!openModal;
3847
+ const onOpenSettings = hasSettings && openModal ? () => {
3848
+ const childOpts = childOptions ?? {};
3849
+ const childContent = child.content ?? {};
3850
+ const seed = { ...childContent, ...childOpts };
3851
+ const applyResult = (result) => updateChild(index, {
3852
+ ...child,
3853
+ content: { ...child.content, ...result.content },
3854
+ options: { ...childOpts, ...result.options }
3855
+ });
3856
+ if (def.settingsForm) {
3857
+ const CustomForm = def.settingsForm;
3858
+ openModal(
3859
+ `${def.label} Settings`,
3860
+ /* @__PURE__ */ jsx42(CustomForm, { ...seed, onChange: applyResult })
3861
+ );
3862
+ } else if (def.settings) {
3863
+ openModal(
3864
+ `${def.label} Settings`,
3865
+ /* @__PURE__ */ jsx42(
3866
+ SettingsForm,
3867
+ {
3868
+ schema: def.settings,
3869
+ values: seed,
3870
+ onChange: applyResult,
3871
+ tabs: def.settingsTabs
3872
+ }
3873
+ )
3874
+ );
3875
+ }
3876
+ } : void 0;
3877
+ return /* @__PURE__ */ jsx42(
3878
+ ContainerChild,
3879
+ {
3880
+ containerId: container.id,
3881
+ index,
3882
+ child,
3883
+ columns,
3884
+ flow,
3885
+ cellClass,
3886
+ label: def.label,
3887
+ hasSettings,
3888
+ mergedOptions,
3889
+ isEditMode,
3890
+ ChildComp: def.component,
3891
+ onDelete: () => emit(trimTrailingSpacers(removeChildAt(container, index).container)),
3892
+ onDuplicate: () => emit(duplicateChildAt(container, index, newId)),
3893
+ onColSpanChange: (s) => emit(setChildColSpan(container, index, s)),
3894
+ onOpenSettings,
3895
+ onChildContentChange: (c) => updateChild(index, c),
3896
+ openModal
3897
+ },
3898
+ child.id ?? index
3899
+ );
3900
+ }
3901
+ const occupied = occupiedColumns(children, columns);
3902
+ const emptyCols = occupied < columns ? columns - occupied : 0;
3903
+ return /* @__PURE__ */ jsx42("div", { className: "@container", children: /* @__PURE__ */ jsxs24("div", { className: gridClass, children: [
3904
+ children.map(renderEditChild),
3905
+ Array.from({ length: emptyCols }, (_, i) => /* @__PURE__ */ jsx42(
3906
+ EmptyColumnCell,
3907
+ {
3908
+ containerId: container.id,
3909
+ targetColumn: occupied + i + 1,
3910
+ testId: "ghost-cell"
3911
+ },
3912
+ `ghost-${i}`
3913
+ ))
3914
+ ] }) });
3915
+ }
3916
+
3917
+ // src/components/sections/Container/ContainerSettingsForm.tsx
3918
+ import { useState as useState18 } from "react";
3919
+ import { jsx as jsx43, jsxs as jsxs25 } from "react/jsx-runtime";
3920
+ function inheritableSchema(children) {
3921
+ const out = {};
3922
+ for (const child of children) {
3923
+ const def = getSection(child.type);
3924
+ if (!def?.settings || !def.inheritableSettings) continue;
3925
+ for (const key of def.inheritableSettings) {
3926
+ const field = def.settings[key];
3927
+ if (field && !out[key]) out[key] = field;
3928
+ }
3929
+ }
3930
+ return out;
3931
+ }
3932
+ function ContainerSettingsForm({
3933
+ columns = 1,
3934
+ flow = "row",
3935
+ childDefaults = {},
3936
+ children = [],
3937
+ onChange
3938
+ }) {
3939
+ const [layout, setLayout] = useState18({ columns, flow });
3940
+ const [defaults, setDefaults] = useState18(childDefaults);
3941
+ const childSchema = inheritableSchema(children);
3942
+ const hasChildDefaults = Object.keys(childSchema).length > 0;
3943
+ function emit(nextLayout, nextDefaults) {
3944
+ onChange({ content: { ...nextLayout, childDefaults: nextDefaults }, options: {} });
3945
+ }
3946
+ const layoutSchema = {
3947
+ columns: {
3948
+ type: "select",
3949
+ label: "Columns",
3950
+ default: String(layout.columns),
3951
+ target: "content",
3952
+ coerce: "number",
3953
+ options: Array.from({ length: MAX_CONTAINER_COLUMNS }, (_, i) => ({
3954
+ label: String(i + 1),
3955
+ value: String(i + 1)
3956
+ }))
3957
+ },
3958
+ flow: {
3959
+ type: "select",
3960
+ label: "Flow",
3961
+ default: layout.flow,
3962
+ target: "content",
3963
+ options: [
3964
+ { label: "Rows (wrap across)", value: "row" },
3965
+ { label: "Columns (fill down)", value: "column" }
3966
+ ]
3967
+ }
3968
+ };
3969
+ return /* @__PURE__ */ jsxs25("div", { className: "flex flex-col gap-6", children: [
3970
+ /* @__PURE__ */ jsxs25("fieldset", { className: "flex flex-col gap-4", children: [
3971
+ /* @__PURE__ */ jsx43("legend", { className: "mb-2 text-sm font-semibold text-base-contrast", children: "Layout" }),
3972
+ /* @__PURE__ */ jsx43(
3973
+ SettingsForm,
3974
+ {
3975
+ schema: layoutSchema,
3976
+ values: { columns: String(layout.columns), flow: layout.flow },
3977
+ onChange: (result) => {
3978
+ const next = {
3979
+ columns: Number(result.content.columns ?? layout.columns),
3980
+ flow: String(result.content.flow ?? layout.flow)
3981
+ };
3982
+ setLayout(next);
3983
+ emit(next, defaults);
3984
+ }
3985
+ }
3986
+ )
3987
+ ] }),
3988
+ hasChildDefaults && /* @__PURE__ */ jsxs25("fieldset", { className: "flex flex-col gap-4 border-t border-base-200 pt-4", children: [
3989
+ /* @__PURE__ */ jsx43("legend", { className: "mb-2 text-sm font-semibold text-base-contrast", children: "Apply to all items" }),
3990
+ /* @__PURE__ */ jsx43(
3991
+ SettingsForm,
3992
+ {
3993
+ schema: childSchema,
3994
+ values: defaults,
3995
+ onChange: (result) => {
3996
+ const next = { ...defaults, ...result.options };
3997
+ setDefaults(next);
3998
+ emit(layout, next);
3999
+ }
4000
+ }
4001
+ )
4002
+ ] })
4003
+ ] });
4004
+ }
4005
+
4006
+ // src/components/sections/Container/index.tsx
4007
+ import { jsx as jsx44 } from "react/jsx-runtime";
4008
+ var schema9 = z9.object({
4009
+ type: z9.literal("container"),
4010
+ content: z9.object({
4011
+ columns: z9.number().int().min(1).max(MAX_CONTAINER_COLUMNS).default(1),
4012
+ flow: z9.enum(["row", "column"]).default("row"),
4013
+ childDefaults: z9.record(z9.string(), z9.unknown()).optional(),
4014
+ // Recursive: children are full blocks. z.lazy defers evaluation to parse
4015
+ // time, by which point every section schema (incl. container) is registered.
4016
+ children: z9.array(z9.lazy(() => getSectionSchema())).default([])
4017
+ }).transform((c) => ({
4018
+ ...c,
4019
+ children: c.children.map(
4020
+ (child) => child.layout?.colSpan && child.layout.colSpan > c.columns ? { ...child, layout: { ...child.layout, colSpan: c.columns } } : child
4021
+ )
4022
+ }))
4023
+ });
4024
+ var Container_default = defineSection({
4025
+ type: "container",
4026
+ label: "Container",
4027
+ icon: /* @__PURE__ */ jsx44(LayoutTemplate, { size: 18 }),
4028
+ schema: schema9,
4029
+ component: ({ content, onChange, isEditMode, openModal }) => /* @__PURE__ */ jsx44(Container, { content, isEditMode: !!isEditMode, onChange, openModal }),
4030
+ defaults: () => ({
4031
+ type: "container",
4032
+ content: { columns: 1, flow: "row", children: [] }
4033
+ }),
4034
+ getLabel: (content) => {
4035
+ const n = content.content.children.length;
4036
+ return `Container (${n} item${n === 1 ? "" : "s"})`;
4037
+ },
4038
+ settingsForm: ContainerSettingsForm
4039
+ });
4040
+
4041
+ // src/components/sections/Spacer/index.tsx
4042
+ import { z as z10 } from "zod";
4043
+ import { SquareDashed } from "lucide-react";
4044
+
4045
+ // src/components/sections/Spacer/Spacer.tsx
4046
+ import { jsx as jsx45 } from "react/jsx-runtime";
4047
+ function Spacer() {
4048
+ return /* @__PURE__ */ jsx45("div", { "data-spacer": true, "aria-hidden": "true", className: "h-full" });
4049
+ }
4050
+
4051
+ // src/components/sections/Spacer/index.tsx
4052
+ import { jsx as jsx46 } from "react/jsx-runtime";
4053
+ var schema10 = z10.object({
4054
+ type: z10.literal("spacer"),
4055
+ content: z10.object({}).default({})
4056
+ });
4057
+ var Spacer_default = defineSection({
4058
+ type: "spacer",
4059
+ label: "Spacer",
4060
+ icon: /* @__PURE__ */ jsx46(SquareDashed, { size: 18 }),
4061
+ schema: schema10,
4062
+ component: () => /* @__PURE__ */ jsx46(Spacer, {}),
4063
+ defaults: () => ({ type: "spacer", content: {} })
4064
+ });
4065
+
4066
+ // src/components/sections/all-sections.ts
4067
+ var allSectionDefs = [
4068
+ LinkHeading_default,
4069
+ SubHeading_default,
4070
+ SubSubHeading_default,
4071
+ Prose_default,
4072
+ Media_default,
4073
+ Button_default,
4074
+ Colors_default,
4075
+ IconList_default,
4076
+ Container_default,
4077
+ Spacer_default
4078
+ ];
4079
+
4080
+ // src/components/sections/register-schemas.ts
4081
+ var _ensured = false;
4082
+ function ensureSchemasRegistered() {
4083
+ if (!_ensured) {
4084
+ allSectionDefs.forEach((def) => {
4085
+ registerSchema(def.type, def.schema);
4086
+ if (def.richTextFields) registerRichText(def.type, def.richTextFields);
4087
+ });
4088
+ _ensured = true;
4089
+ }
4090
+ return allSectionDefs.length;
4091
+ }
4092
+ export {
4093
+ ensureSchemasRegistered
4094
+ };