@drawnagency/primitives 0.1.59 → 0.1.61

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 (95) hide show
  1. package/dist/auth/capabilities.d.ts +16 -0
  2. package/dist/auth/capabilities.d.ts.map +1 -0
  3. package/dist/auth/index.d.ts +2 -0
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +3 -1
  6. package/dist/auth/types.d.ts +26 -22
  7. package/dist/auth/types.d.ts.map +1 -1
  8. package/dist/chunk-4OX3Z7SB.js +1421 -0
  9. package/dist/{chunk-RFZNNCAS.js → chunk-53DKVAB6.js} +1 -61
  10. package/dist/chunk-677NTHAZ.js +27 -0
  11. package/dist/chunk-7EOSDFS3.js +10 -0
  12. package/dist/{chunk-NTGSA3TI.js → chunk-AM5LZD6Q.js} +7 -30
  13. package/dist/{chunk-ICLXLWQ5.js → chunk-BT7STGDW.js} +31 -23
  14. package/dist/{chunk-L2JJFOXD.js → chunk-CBWK3KPV.js} +5 -1
  15. package/dist/{chunk-V7JN2DDU.js → chunk-ODCJQOVO.js} +1 -1
  16. package/dist/chunk-TZQPOR5A.js +24 -0
  17. package/dist/{chunk-XTK4BR27.js → chunk-UNWNT52N.js} +13 -0
  18. package/dist/chunk-UQ4OYRW7.js +670 -0
  19. package/dist/chunk-WDSVHKAH.js +54 -0
  20. package/dist/chunk-WLMJCNU7.js +70 -0
  21. package/dist/chunk-ZGFAYZWB.js +8 -0
  22. package/dist/components/editor/DropEdgeIndicator.d.ts +8 -0
  23. package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -0
  24. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  25. package/dist/components/editor/SectionOrderingModal.d.ts.map +1 -1
  26. package/dist/components/editor/index.js +640 -0
  27. package/dist/components/primitives/defineHeadingSection.d.ts +28 -0
  28. package/dist/components/primitives/defineHeadingSection.d.ts.map +1 -0
  29. package/dist/components/primitives/index.d.ts +10 -0
  30. package/dist/components/primitives/index.d.ts.map +1 -0
  31. package/dist/components/primitives/index.js +166 -0
  32. package/dist/components/sections/Colors/index.d.ts.map +1 -1
  33. package/dist/components/sections/Container/index.d.ts.map +1 -1
  34. package/dist/components/sections/IconList/IconList.d.ts +1 -6
  35. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  36. package/dist/components/sections/IconList/index.d.ts +11 -0
  37. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  38. package/dist/components/sections/LinkHeading/index.d.ts.map +1 -1
  39. package/dist/components/sections/SubHeading/index.d.ts +0 -1
  40. package/dist/components/sections/SubHeading/index.d.ts.map +1 -1
  41. package/dist/components/sections/SubSubHeading/index.d.ts +0 -1
  42. package/dist/components/sections/SubSubHeading/index.d.ts.map +1 -1
  43. package/dist/components/sections/all-sections.d.ts +0 -2
  44. package/dist/components/sections/all-sections.d.ts.map +1 -1
  45. package/dist/components/sections/register-schemas.js +376 -2453
  46. package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
  47. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  48. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  49. package/dist/hooks/useSortableRow.d.ts +25 -0
  50. package/dist/hooks/useSortableRow.d.ts.map +1 -0
  51. package/dist/index.js +46 -37
  52. package/dist/lib/image-refs.d.ts +21 -0
  53. package/dist/lib/image-refs.d.ts.map +1 -0
  54. package/dist/lib/index.js +21 -14
  55. package/dist/lib/platform-broker.d.ts +14 -0
  56. package/dist/lib/platform-broker.d.ts.map +1 -0
  57. package/dist/lib/platform-broker.js +60 -0
  58. package/dist/lib/reorder.d.ts +12 -0
  59. package/dist/lib/reorder.d.ts.map +1 -0
  60. package/dist/lib/text.d.ts +2 -0
  61. package/dist/lib/text.d.ts.map +1 -1
  62. package/dist/media/index.js +3 -2
  63. package/dist/media/utils.d.ts.map +1 -1
  64. package/dist/schemas/index.js +17 -17
  65. package/dist/schemas/link.js +13 -0
  66. package/dist/types/database.d.ts +46 -0
  67. package/dist/types/database.d.ts.map +1 -1
  68. package/package.json +14 -2
  69. package/src/auth/capabilities.ts +26 -0
  70. package/src/auth/index.ts +3 -0
  71. package/src/auth/types.ts +36 -26
  72. package/src/components/editor/DropEdgeIndicator.tsx +13 -0
  73. package/src/components/editor/PagesModal.tsx +12 -80
  74. package/src/components/editor/SectionOrderingModal.tsx +8 -78
  75. package/src/components/primitives/defineHeadingSection.tsx +56 -0
  76. package/src/components/primitives/index.ts +11 -0
  77. package/src/components/sections/Colors/index.tsx +2 -4
  78. package/src/components/sections/Container/index.tsx +2 -4
  79. package/src/components/sections/IconList/IconList.tsx +1 -7
  80. package/src/components/sections/IconList/index.tsx +11 -5
  81. package/src/components/sections/LinkHeading/index.tsx +5 -20
  82. package/src/components/sections/SubHeading/index.tsx +6 -23
  83. package/src/components/sections/SubSubHeading/index.tsx +6 -23
  84. package/src/components/shell/BugReportFAB.tsx +13 -9
  85. package/src/hooks/useEditorPublish.ts +71 -49
  86. package/src/hooks/useMediaPipeline.ts +11 -34
  87. package/src/hooks/useSortableRow.ts +99 -0
  88. package/src/lib/image-refs.ts +53 -0
  89. package/src/lib/platform-broker.ts +72 -0
  90. package/src/lib/reorder.ts +20 -0
  91. package/src/lib/text.ts +5 -0
  92. package/src/media/utils.ts +6 -1
  93. package/src/types/database.ts +41 -0
  94. package/dist/chunk-DKOUFIP6.js +0 -35
  95. /package/dist/{chunk-ZU2MKPTG.js → chunk-3DCG4DNU.js} +0 -0
@@ -0,0 +1,670 @@
1
+ import {
2
+ DeleteIcon,
3
+ DragHandle,
4
+ IconButton,
5
+ SettingsIcon,
6
+ usePages
7
+ } from "./chunk-WLMJCNU7.js";
8
+ import {
9
+ cn
10
+ } from "./chunk-7EOSDFS3.js";
11
+
12
+ // src/components/editor/DragHandle.tsx
13
+ import { forwardRef } from "react";
14
+
15
+ // src/components/shared/Tooltip.tsx
16
+ import { jsx, jsxs } from "react/jsx-runtime";
17
+ function Tooltip({ content, children, className }) {
18
+ return /* @__PURE__ */ jsxs("div", { className: cn("group/tooltip relative inline-flex", className), children: [
19
+ children,
20
+ /* @__PURE__ */ jsx(
21
+ "span",
22
+ {
23
+ 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",
24
+ role: "tooltip",
25
+ children: /* @__PURE__ */ jsx("span", { className: "block whitespace-nowrap rounded bg-tooltip px-2 py-1 text-center text-xs leading-relaxed text-tooltip-muted", children: content })
26
+ }
27
+ )
28
+ ] });
29
+ }
30
+ function Kbd({ children }) {
31
+ return /* @__PURE__ */ jsx("strong", { className: "text-tooltip-contrast", children });
32
+ }
33
+
34
+ // src/components/editor/DragHandle.tsx
35
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
36
+ var DragHandle2 = forwardRef(function DragHandle3(_, ref) {
37
+ return /* @__PURE__ */ jsx2(Tooltip, { content: /* @__PURE__ */ jsxs2(Fragment, { children: [
38
+ /* @__PURE__ */ jsx2(Kbd, { children: "Drag" }),
39
+ " to move"
40
+ ] }), children: /* @__PURE__ */ jsx2(
41
+ IconButton,
42
+ {
43
+ ref,
44
+ icon: /* @__PURE__ */ jsx2(DragHandle, { size: 18 }),
45
+ label: "Drag to reorder section",
46
+ 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",
47
+ tabIndex: -1
48
+ }
49
+ ) });
50
+ });
51
+
52
+ // src/components/editor/DeleteButton.tsx
53
+ import { jsx as jsx3 } from "react/jsx-runtime";
54
+ function DeleteButton({ onDelete }) {
55
+ return /* @__PURE__ */ jsx3(
56
+ IconButton,
57
+ {
58
+ icon: /* @__PURE__ */ jsx3(DeleteIcon, { size: 16 }),
59
+ label: "Delete section",
60
+ intent: "destructive",
61
+ onClick: onDelete,
62
+ className: "pointer-events-auto"
63
+ }
64
+ );
65
+ }
66
+
67
+ // src/components/editor/SettingsButton.tsx
68
+ import { jsx as jsx4 } from "react/jsx-runtime";
69
+ function SettingsButton({ onClick }) {
70
+ return /* @__PURE__ */ jsx4(
71
+ IconButton,
72
+ {
73
+ icon: /* @__PURE__ */ jsx4(SettingsIcon, { size: 16 }),
74
+ label: "Section settings",
75
+ onClick,
76
+ className: "pointer-events-auto"
77
+ }
78
+ );
79
+ }
80
+
81
+ // src/components/editor/SettingsForm.tsx
82
+ import { useId as useId4, useState as useState2 } from "react";
83
+
84
+ // src/components/shared/Input.tsx
85
+ import { forwardRef as forwardRef2, useId } from "react";
86
+
87
+ // src/components/shared/FormLabel.tsx
88
+ import { jsx as jsx5 } from "react/jsx-runtime";
89
+ function FormLabel({ htmlFor, children, className }) {
90
+ return /* @__PURE__ */ jsx5(
91
+ "label",
92
+ {
93
+ htmlFor,
94
+ className: cn("mb-1.5 block text-sm font-medium text-base-contrast", className),
95
+ children
96
+ }
97
+ );
98
+ }
99
+
100
+ // src/components/shared/Input.tsx
101
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
102
+ var Input = forwardRef2(function Input2({ label, value, onChange, type = "text", className, disabled, error, ...rest }, ref) {
103
+ const id = useId();
104
+ const errorId = `${id}-error`;
105
+ return /* @__PURE__ */ jsxs3("div", { className, children: [
106
+ /* @__PURE__ */ jsx6(FormLabel, { htmlFor: id, children: label }),
107
+ /* @__PURE__ */ jsx6(
108
+ "input",
109
+ {
110
+ ref,
111
+ id,
112
+ type,
113
+ value,
114
+ onChange: (e) => onChange(e.target.value),
115
+ disabled,
116
+ "aria-invalid": error ? true : void 0,
117
+ "aria-describedby": error ? errorId : void 0,
118
+ className: cn(
119
+ "w-full rounded border bg-base px-3 py-2 text-sm text-base-contrast focus:outline-none focus:ring-1",
120
+ error ? "border-red-600 focus:border-red-600 focus:ring-red-600" : "border-base-200 focus:border-base-contrast focus:ring-base-contrast",
121
+ disabled && "cursor-not-allowed opacity-50"
122
+ ),
123
+ ...rest
124
+ }
125
+ ),
126
+ error && /* @__PURE__ */ jsx6("p", { id: errorId, className: "mt-1 text-xs text-red-600", children: error })
127
+ ] });
128
+ });
129
+
130
+ // src/components/shared/Select.tsx
131
+ import { useId as useId2 } from "react";
132
+ import { ChevronDown } from "lucide-react";
133
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
134
+ function Select({
135
+ label,
136
+ value,
137
+ onChange,
138
+ options,
139
+ disabled,
140
+ className,
141
+ selectClassName
142
+ }) {
143
+ const id = useId2();
144
+ return /* @__PURE__ */ jsxs4("div", { className, children: [
145
+ label && /* @__PURE__ */ jsx7(FormLabel, { htmlFor: id, children: label }),
146
+ /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
147
+ /* @__PURE__ */ jsx7(
148
+ "select",
149
+ {
150
+ id,
151
+ value,
152
+ onChange: (e) => onChange(e.target.value),
153
+ disabled,
154
+ className: cn(
155
+ "w-full appearance-none rounded border border-base-200 bg-base py-2 pr-8 pl-3 text-sm text-base-contrast",
156
+ "focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast",
157
+ disabled && "cursor-not-allowed opacity-50",
158
+ selectClassName
159
+ ),
160
+ children: options.map((opt) => /* @__PURE__ */ jsx7("option", { value: opt.value, children: opt.label }, opt.value))
161
+ }
162
+ ),
163
+ /* @__PURE__ */ jsx7(
164
+ ChevronDown,
165
+ {
166
+ size: 14,
167
+ className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-base-contrast-light"
168
+ }
169
+ )
170
+ ] })
171
+ ] });
172
+ }
173
+
174
+ // src/components/shared/Checkbox.tsx
175
+ import { useId as useId3 } from "react";
176
+ import { Check } from "lucide-react";
177
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
178
+ function Checkbox({
179
+ checked,
180
+ onChange,
181
+ label,
182
+ description,
183
+ disabled,
184
+ className,
185
+ startAdornment,
186
+ align = "start"
187
+ }) {
188
+ const id = useId3();
189
+ return /* @__PURE__ */ jsxs5(
190
+ "label",
191
+ {
192
+ htmlFor: id,
193
+ className: cn(
194
+ "flex cursor-pointer gap-3 select-none hover:opacity-80",
195
+ align === "start" ? "items-start" : "items-center",
196
+ disabled && "cursor-not-allowed opacity-50",
197
+ className
198
+ ),
199
+ children: [
200
+ /* @__PURE__ */ jsxs5(
201
+ "span",
202
+ {
203
+ className: cn(
204
+ "relative flex h-5 w-5 shrink-0 items-center justify-center",
205
+ align === "start" && "mt-0.5"
206
+ ),
207
+ children: [
208
+ /* @__PURE__ */ jsx8(
209
+ "input",
210
+ {
211
+ id,
212
+ type: "checkbox",
213
+ checked,
214
+ disabled,
215
+ onChange: (e) => onChange(e.target.checked),
216
+ className: "sr-only"
217
+ }
218
+ ),
219
+ /* @__PURE__ */ jsx8(
220
+ "span",
221
+ {
222
+ "aria-hidden": "true",
223
+ className: cn(
224
+ "flex h-5 w-5 items-center justify-center rounded border transition-colors",
225
+ checked ? "border-primary bg-primary text-primary-contrast" : "border-base-300 bg-base hover:border-primary",
226
+ disabled && "pointer-events-none"
227
+ ),
228
+ children: checked && /* @__PURE__ */ jsx8(Check, { size: 14, strokeWidth: 3 })
229
+ }
230
+ )
231
+ ]
232
+ }
233
+ ),
234
+ startAdornment,
235
+ /* @__PURE__ */ jsxs5("div", { className: "text-sm", children: [
236
+ /* @__PURE__ */ jsx8("span", { className: "font-medium text-base-contrast", children: label }),
237
+ description && /* @__PURE__ */ jsx8("span", { className: "block text-base-contrast-light", children: description })
238
+ ] })
239
+ ]
240
+ }
241
+ );
242
+ }
243
+
244
+ // src/components/shared/LinkField.tsx
245
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
246
+ var TARGET_OPTIONS = [
247
+ { label: "Same tab (_self)", value: "_self" },
248
+ { label: "New tab (_blank)", value: "_blank" }
249
+ ];
250
+ function LinkField({ label, value, onChange }) {
251
+ const { pages, getPageHeadings } = usePages();
252
+ const isInternal = value.kind === "internal";
253
+ function setMode(kind) {
254
+ if (kind === value.kind) return;
255
+ onChange(kind === "external" ? { kind: "external", href: "", target: value.target } : { kind: "internal", pageId: "", anchorSectionId: null, target: "_self" });
256
+ }
257
+ const pageId = isInternal ? value.pageId : "";
258
+ const headings = pageId ? getPageHeadings(pageId) : [];
259
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-3", children: [
260
+ /* @__PURE__ */ jsx9(FormLabel, { children: label }),
261
+ /* @__PURE__ */ jsx9("div", { className: "flex gap-2", role: "tablist", children: ["external", "internal"].map((kind) => /* @__PURE__ */ jsx9(
262
+ "button",
263
+ {
264
+ type: "button",
265
+ role: "tab",
266
+ "aria-selected": value.kind === kind,
267
+ onClick: () => setMode(kind),
268
+ className: cn(
269
+ "cursor-pointer rounded border px-3 py-1 text-sm capitalize",
270
+ value.kind === kind ? "border-primary bg-primary text-primary-contrast" : "border-base-200 text-base-contrast"
271
+ ),
272
+ children: kind === "external" ? "External URL" : "Internal page"
273
+ },
274
+ kind
275
+ )) }),
276
+ value.kind === "external" ? /* @__PURE__ */ jsx9(
277
+ Input,
278
+ {
279
+ label: "URL",
280
+ value: value.href,
281
+ placeholder: "https://...",
282
+ onChange: (href) => onChange({ kind: "external", href, target: value.target })
283
+ }
284
+ ) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
285
+ /* @__PURE__ */ jsx9(
286
+ Select,
287
+ {
288
+ label: "Page",
289
+ value: value.pageId,
290
+ options: [{ label: "Select a page\u2026", value: "" }, ...pages.map((p) => ({ label: p.title, value: p.id }))],
291
+ onChange: (pid) => onChange({ kind: "internal", pageId: pid, anchorSectionId: null, target: value.target })
292
+ }
293
+ ),
294
+ /* @__PURE__ */ jsx9(
295
+ Select,
296
+ {
297
+ label: "Section",
298
+ value: value.anchorSectionId ?? "",
299
+ disabled: !pageId,
300
+ options: [{ label: "None (top of page)", value: "" }, ...headings.map((h) => ({ label: h.label, value: h.id }))],
301
+ onChange: (anchor) => onChange({ kind: "internal", pageId, anchorSectionId: anchor || null, target: value.target })
302
+ }
303
+ )
304
+ ] }),
305
+ value.kind === "external" && /* @__PURE__ */ jsx9(
306
+ Select,
307
+ {
308
+ label: "Opens in",
309
+ value: value.target,
310
+ options: TARGET_OPTIONS,
311
+ onChange: (t) => onChange({ ...value, target: t })
312
+ }
313
+ )
314
+ ] });
315
+ }
316
+
317
+ // src/components/shared/Tabs.tsx
318
+ import { useState } from "react";
319
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
320
+ function Tabs({ tabs, defaultTabId, activeTabId, onTabChange, fullBleedTabBar }) {
321
+ const [uncontrolledId, setUncontrolledId] = useState(defaultTabId ?? tabs[0]?.id);
322
+ const isControlled = activeTabId !== void 0;
323
+ const activeId = isControlled ? activeTabId : uncontrolledId;
324
+ const activeTab = tabs.find((t) => t.id === activeId) ?? tabs[0];
325
+ function handleSelect(id) {
326
+ if (!isControlled) setUncontrolledId(id);
327
+ onTabChange?.(id);
328
+ }
329
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
330
+ /* @__PURE__ */ jsx10("div", { className: cn("border-b border-base-200", fullBleedTabBar && "-mx-6 px-6"), children: /* @__PURE__ */ jsx10("div", { className: "flex", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ jsx10(
331
+ "button",
332
+ {
333
+ type: "button",
334
+ role: "tab",
335
+ "aria-selected": activeTab?.id === tab.id,
336
+ onClick: () => handleSelect(tab.id),
337
+ className: cn(
338
+ "cursor-pointer px-4 py-2 text-sm font-medium border-b-2 -mb-px",
339
+ activeTab?.id === tab.id ? "border-brand text-brand" : "border-transparent text-base-contrast-light hover:text-base-contrast"
340
+ ),
341
+ children: tab.label
342
+ },
343
+ tab.id
344
+ )) }) }),
345
+ /* @__PURE__ */ jsx10("div", { role: "tabpanel", children: activeTab?.content })
346
+ ] });
347
+ }
348
+
349
+ // src/components/editor/SettingsForm.tsx
350
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
351
+ function resolveValues(schema, values) {
352
+ const resolved = {};
353
+ for (const [key, field] of Object.entries(schema)) {
354
+ resolved[key] = values[key] ?? field.default;
355
+ }
356
+ return resolved;
357
+ }
358
+ function coerceValue(field, value) {
359
+ if (field.type === "select" && "coerce" in field && field.coerce === "number") {
360
+ return Number(value);
361
+ }
362
+ return value;
363
+ }
364
+ function normalizeEmpty(field, value) {
365
+ if (field.type === "select" && "emptyIsUndefined" in field && field.emptyIsUndefined && value === "") {
366
+ return void 0;
367
+ }
368
+ return value;
369
+ }
370
+ function splitByTarget(schema, allValues) {
371
+ const content = {};
372
+ const options = {};
373
+ for (const [key, field] of Object.entries(schema)) {
374
+ const value = normalizeEmpty(field, coerceValue(field, allValues[key]));
375
+ const target = field.target ?? "options";
376
+ if (target === "content") {
377
+ content[key] = value;
378
+ } else {
379
+ options[key] = value;
380
+ }
381
+ }
382
+ return { content, options };
383
+ }
384
+ function RangeField({
385
+ field,
386
+ value,
387
+ onChange
388
+ }) {
389
+ const id = useId4();
390
+ return /* @__PURE__ */ jsxs8("div", { children: [
391
+ /* @__PURE__ */ jsx11(FormLabel, { htmlFor: id, children: field.label }),
392
+ /* @__PURE__ */ jsx11(
393
+ "input",
394
+ {
395
+ id,
396
+ type: "range",
397
+ min: field.min,
398
+ max: field.max,
399
+ step: field.step,
400
+ value,
401
+ onChange: (e) => onChange(Number(e.target.value)),
402
+ className: cn(
403
+ "w-full accent-primary"
404
+ )
405
+ }
406
+ )
407
+ ] });
408
+ }
409
+ function renderField(key, field, value, handleFieldChange) {
410
+ switch (field.type) {
411
+ case "text":
412
+ return /* @__PURE__ */ jsx11(
413
+ Input,
414
+ {
415
+ label: field.label,
416
+ value: String(value ?? ""),
417
+ placeholder: field.placeholder,
418
+ onChange: (v) => handleFieldChange(key, v)
419
+ },
420
+ key
421
+ );
422
+ case "number":
423
+ return /* @__PURE__ */ jsx11(
424
+ Input,
425
+ {
426
+ label: field.label,
427
+ type: "number",
428
+ value: String(value ?? ""),
429
+ onChange: (v) => handleFieldChange(key, v === "" ? "" : Number(v))
430
+ },
431
+ key
432
+ );
433
+ case "checkbox":
434
+ return /* @__PURE__ */ jsx11(
435
+ Checkbox,
436
+ {
437
+ label: field.label,
438
+ checked: Boolean(value),
439
+ onChange: (checked) => handleFieldChange(key, checked)
440
+ },
441
+ key
442
+ );
443
+ case "select":
444
+ return /* @__PURE__ */ jsx11(
445
+ Select,
446
+ {
447
+ label: field.label,
448
+ value: String(value ?? ""),
449
+ options: field.options,
450
+ onChange: (v) => handleFieldChange(key, v)
451
+ },
452
+ key
453
+ );
454
+ case "range":
455
+ return /* @__PURE__ */ jsx11(
456
+ RangeField,
457
+ {
458
+ field,
459
+ value: Number(value ?? field.min),
460
+ onChange: (v) => handleFieldChange(key, v)
461
+ },
462
+ key
463
+ );
464
+ case "link":
465
+ return /* @__PURE__ */ jsx11(
466
+ LinkField,
467
+ {
468
+ label: field.label,
469
+ value: value ?? field.default,
470
+ onChange: (v) => handleFieldChange(key, v)
471
+ },
472
+ key
473
+ );
474
+ default:
475
+ return null;
476
+ }
477
+ }
478
+ function SettingsForm({ schema, values, onChange, tabs }) {
479
+ const [local, setLocal] = useState2(() => resolveValues(schema, values));
480
+ function handleFieldChange(key, newValue) {
481
+ const next = { ...local, [key]: newValue };
482
+ setLocal(next);
483
+ onChange(splitByTarget(schema, next));
484
+ }
485
+ if (tabs) {
486
+ const fields = (keys) => keys.filter((key) => key in schema).map((key) => renderField(key, schema[key], local[key], handleFieldChange));
487
+ const listed = new Set(tabs.flatMap((t) => t.fields));
488
+ const otherKeys = Object.keys(schema).filter((key) => !listed.has(key));
489
+ const tabItems = [
490
+ ...tabs.map((t) => ({ id: t.label, label: t.label, content: /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4", children: fields(t.fields) }) })),
491
+ ...otherKeys.length > 0 ? [{ id: "__other__", label: "Other", content: /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4", children: fields(otherKeys) }) }] : []
492
+ ];
493
+ return /* @__PURE__ */ jsx11(Tabs, { tabs: tabItems, fullBleedTabBar: true });
494
+ }
495
+ return /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4", children: Object.entries(schema).map(([key, field]) => renderField(key, field, local[key], handleFieldChange)) });
496
+ }
497
+
498
+ // src/components/shared/Popover.tsx
499
+ import { useEffect as useEffect2, useLayoutEffect, useRef, useState as useState3 } from "react";
500
+
501
+ // src/hooks/useFocusTrap.ts
502
+ import { useEffect } from "react";
503
+ var FOCUSABLE_SELECTOR = [
504
+ "a[href]",
505
+ "button:not([disabled])",
506
+ "input:not([disabled])",
507
+ "select:not([disabled])",
508
+ "textarea:not([disabled])",
509
+ '[tabindex]:not([tabindex="-1"])'
510
+ ].join(", ");
511
+ function useFocusTrap(ref, active) {
512
+ useEffect(() => {
513
+ if (!active || !ref.current) return;
514
+ const container = ref.current;
515
+ const previouslyFocused = document.activeElement;
516
+ const getFocusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
517
+ const focusable = getFocusable();
518
+ if (focusable.length > 0) {
519
+ focusable[0].focus();
520
+ }
521
+ const handleKeyDown = (e) => {
522
+ if (e.key !== "Tab") return;
523
+ const elements = getFocusable();
524
+ if (elements.length === 0) return;
525
+ const first = elements[0];
526
+ const last = elements[elements.length - 1];
527
+ if (e.shiftKey) {
528
+ if (document.activeElement === first) {
529
+ e.preventDefault();
530
+ last.focus();
531
+ }
532
+ } else {
533
+ if (document.activeElement === last) {
534
+ e.preventDefault();
535
+ first.focus();
536
+ }
537
+ }
538
+ };
539
+ container.addEventListener("keydown", handleKeyDown);
540
+ return () => {
541
+ container.removeEventListener("keydown", handleKeyDown);
542
+ if (previouslyFocused && document.body.contains(previouslyFocused)) {
543
+ previouslyFocused.focus();
544
+ }
545
+ };
546
+ }, [active, ref]);
547
+ }
548
+
549
+ // src/components/shared/Popover.tsx
550
+ import { jsx as jsx12 } from "react/jsx-runtime";
551
+ function Popover({
552
+ isOpen,
553
+ onClose,
554
+ anchorRef,
555
+ children,
556
+ align = "auto",
557
+ className
558
+ }) {
559
+ const panelRef = useRef(null);
560
+ const [resolvedAlign, setResolvedAlign] = useState3("start");
561
+ useFocusTrap(panelRef, isOpen);
562
+ useLayoutEffect(() => {
563
+ if (!isOpen || align !== "auto") {
564
+ if (!isOpen) setResolvedAlign("start");
565
+ return;
566
+ }
567
+ const panel = panelRef.current;
568
+ if (!panel) return;
569
+ panel.style.left = "0";
570
+ panel.style.right = "auto";
571
+ const rect = panel.getBoundingClientRect();
572
+ if (rect.right > window.innerWidth) {
573
+ setResolvedAlign("end");
574
+ } else {
575
+ setResolvedAlign("start");
576
+ }
577
+ panel.style.left = "";
578
+ panel.style.right = "";
579
+ }, [isOpen, align]);
580
+ useEffect2(() => {
581
+ if (!isOpen) return;
582
+ function onKeyDown(e) {
583
+ if (e.key === "Escape") onClose();
584
+ }
585
+ function onMouseDown(e) {
586
+ const target = e.target;
587
+ if (panelRef.current?.contains(target)) return;
588
+ if (anchorRef.current?.contains(target)) return;
589
+ onClose();
590
+ }
591
+ document.addEventListener("keydown", onKeyDown);
592
+ document.addEventListener("mousedown", onMouseDown);
593
+ return () => {
594
+ document.removeEventListener("keydown", onKeyDown);
595
+ document.removeEventListener("mousedown", onMouseDown);
596
+ };
597
+ }, [isOpen, onClose, anchorRef]);
598
+ if (!isOpen) return null;
599
+ const effectiveAlign = align === "auto" ? resolvedAlign : align;
600
+ return /* @__PURE__ */ jsx12(
601
+ "div",
602
+ {
603
+ ref: panelRef,
604
+ role: "dialog",
605
+ "aria-modal": "false",
606
+ className: cn(
607
+ "absolute top-full z-50 mt-1 rounded-md border border-base-200 bg-base shadow-lg",
608
+ effectiveAlign === "end" ? "right-0" : "left-0",
609
+ className
610
+ ),
611
+ children
612
+ }
613
+ );
614
+ }
615
+
616
+ // src/components/shared/PopoverItem.tsx
617
+ import { forwardRef as forwardRef3 } from "react";
618
+ import { jsx as jsx13 } from "react/jsx-runtime";
619
+ var PopoverItem = forwardRef3(
620
+ function PopoverItem2({ className, children, ...rest }, ref) {
621
+ return /* @__PURE__ */ jsx13(
622
+ "button",
623
+ {
624
+ ref,
625
+ type: "button",
626
+ className: cn(
627
+ "flex w-full cursor-pointer items-center gap-3 px-3 py-1.5 text-left text-xs hover:bg-base-accent",
628
+ className
629
+ ),
630
+ ...rest,
631
+ children
632
+ }
633
+ );
634
+ }
635
+ );
636
+
637
+ // src/lib/block-dnd.ts
638
+ var ROOT_CONTAINER_ID = "__root__";
639
+ function buildBlockDragData(args) {
640
+ return { dragType: "block", ...args };
641
+ }
642
+ function buildBlockDropData(args) {
643
+ return { ...args };
644
+ }
645
+ function isBlockDragData(data) {
646
+ return data.dragType === "block";
647
+ }
648
+ function canDropBlock(source, dropContainerId) {
649
+ if (source.blockId === dropContainerId) return false;
650
+ if (source.isContainer && dropContainerId !== ROOT_CONTAINER_ID) return false;
651
+ return true;
652
+ }
653
+
654
+ export {
655
+ FormLabel,
656
+ Input,
657
+ Checkbox,
658
+ Tooltip,
659
+ Kbd,
660
+ DragHandle2 as DragHandle,
661
+ DeleteButton,
662
+ SettingsButton,
663
+ Popover,
664
+ PopoverItem,
665
+ SettingsForm,
666
+ buildBlockDragData,
667
+ buildBlockDropData,
668
+ isBlockDragData,
669
+ canDropBlock
670
+ };