@drawnagency/primitives 0.1.55 → 0.1.57

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 (173) hide show
  1. package/dist/auth/cookies.d.ts.map +1 -1
  2. package/dist/auth/index.js +1 -1
  3. package/dist/{chunk-24SUF2BC.js → chunk-ICLXLWQ5.js} +13 -74
  4. package/dist/chunk-NSCT3AMV.js +32 -0
  5. package/dist/{chunk-KDGYHU36.js → chunk-PRKUXM7E.js} +35 -10
  6. package/dist/{chunk-PUNXQK4M.js → chunk-PYWS3MOJ.js} +12 -2
  7. package/dist/chunk-TG43X7JO.js +123 -0
  8. package/dist/chunk-VKAGMEKE.js +90 -0
  9. package/dist/{chunk-B5VYSTPB.js → chunk-XTK4BR27.js} +1 -1
  10. package/dist/components/editor/ChildBlockWrapper.d.ts +19 -0
  11. package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -0
  12. package/dist/components/editor/ColSpanControl.d.ts +9 -0
  13. package/dist/components/editor/ColSpanControl.d.ts.map +1 -0
  14. package/dist/components/editor/SectionWrapper.d.ts +1 -1
  15. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  16. package/dist/components/editor/SettingsForm.d.ts +5 -1
  17. package/dist/components/editor/SettingsForm.d.ts.map +1 -1
  18. package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
  19. package/dist/components/primitives/IconPicker.d.ts +7 -1
  20. package/dist/components/primitives/IconPicker.d.ts.map +1 -1
  21. package/dist/components/sections/Container/Container.d.ts +20 -0
  22. package/dist/components/sections/Container/Container.d.ts.map +1 -0
  23. package/dist/components/sections/Container/ContainerSettingsForm.d.ts +17 -0
  24. package/dist/components/sections/Container/ContainerSettingsForm.d.ts.map +1 -0
  25. package/dist/components/sections/Container/index.d.ts +11 -0
  26. package/dist/components/sections/Container/index.d.ts.map +1 -0
  27. package/dist/components/sections/IconList/IconList.d.ts +1 -0
  28. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  29. package/dist/components/sections/IconList/IconListSettings.d.ts +3 -4
  30. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
  31. package/dist/components/sections/IconList/index.d.ts +1 -0
  32. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  33. package/dist/components/sections/Media/MediaBlock.d.ts +19 -0
  34. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -0
  35. package/dist/components/sections/{MediaGrid → Media}/index.d.ts +15 -25
  36. package/dist/components/sections/Media/index.d.ts.map +1 -0
  37. package/dist/components/sections/Prose/index.d.ts.map +1 -1
  38. package/dist/components/sections/Spacer/Spacer.d.ts +2 -0
  39. package/dist/components/sections/Spacer/Spacer.d.ts.map +1 -0
  40. package/dist/components/sections/Spacer/index.d.ts +6 -0
  41. package/dist/components/sections/Spacer/index.d.ts.map +1 -0
  42. package/dist/components/sections/all-sections.d.ts +140 -0
  43. package/dist/components/sections/all-sections.d.ts.map +1 -0
  44. package/dist/components/sections/register-schemas.d.ts.map +1 -1
  45. package/dist/components/sections/register.d.ts.map +1 -1
  46. package/dist/components/shared/Tabs.d.ts +24 -0
  47. package/dist/components/shared/Tabs.d.ts.map +1 -0
  48. package/dist/components/shell/EditorShell.d.ts +2 -1
  49. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  50. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  51. package/dist/components/shell/blockMoveDispatch.d.ts +21 -0
  52. package/dist/components/shell/blockMoveDispatch.d.ts.map +1 -0
  53. package/dist/hooks/useBlockDnd.d.ts +48 -0
  54. package/dist/hooks/useBlockDnd.d.ts.map +1 -0
  55. package/dist/hooks/useEditorPublish.d.ts +2 -1
  56. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  57. package/dist/index.js +69 -48
  58. package/dist/lib/block-dnd.d.ts +42 -0
  59. package/dist/lib/block-dnd.d.ts.map +1 -0
  60. package/dist/lib/block-move.d.ts +31 -0
  61. package/dist/lib/block-move.d.ts.map +1 -0
  62. package/dist/lib/container-grid.d.ts +29 -0
  63. package/dist/lib/container-grid.d.ts.map +1 -0
  64. package/dist/lib/container-ops.d.ts +44 -0
  65. package/dist/lib/container-ops.d.ts.map +1 -0
  66. package/dist/lib/dexie.d.ts +12 -1
  67. package/dist/lib/dexie.d.ts.map +1 -1
  68. package/dist/lib/dexie.js +28 -3
  69. package/dist/lib/index.js +10 -7
  70. package/dist/lib/loader.d.ts.map +1 -1
  71. package/dist/lib/migrate-sections-transform.d.ts +12 -0
  72. package/dist/lib/migrate-sections-transform.d.ts.map +1 -0
  73. package/dist/lib/migrate-sections-transform.js +6 -0
  74. package/dist/lib/registry.d.ts +39 -2
  75. package/dist/lib/registry.d.ts.map +1 -1
  76. package/dist/lib/registry.js +26 -0
  77. package/dist/lib/sanitize.d.ts.map +1 -1
  78. package/dist/schemas/block.d.ts +20 -0
  79. package/dist/schemas/block.d.ts.map +1 -0
  80. package/dist/schemas/block.js +14 -0
  81. package/dist/schemas/index.js +10 -2
  82. package/dist/schemas/link.d.ts +7 -0
  83. package/dist/schemas/link.d.ts.map +1 -1
  84. package/dist/schemas/rich-text.d.ts +9 -0
  85. package/dist/schemas/rich-text.d.ts.map +1 -0
  86. package/dist/schemas/sections.d.ts +2 -0
  87. package/dist/schemas/sections.d.ts.map +1 -1
  88. package/dist/schemas/shared.d.ts +31 -0
  89. package/dist/schemas/shared.d.ts.map +1 -1
  90. package/dist/storage/index.d.ts +1 -0
  91. package/dist/storage/index.d.ts.map +1 -1
  92. package/dist/storage/types.d.ts +13 -1
  93. package/dist/storage/types.d.ts.map +1 -1
  94. package/package.json +13 -1
  95. package/src/auth/cookies.ts +6 -1
  96. package/src/components/brandguide/Colors.tsx +35 -33
  97. package/src/components/editor/ChildBlockWrapper.tsx +108 -0
  98. package/src/components/editor/ColSpanControl.tsx +56 -0
  99. package/src/components/editor/SectionWrapper.tsx +44 -20
  100. package/src/components/editor/SettingsForm.tsx +100 -73
  101. package/src/components/primitives/EditableGrid.tsx +40 -36
  102. package/src/components/primitives/IconPicker.tsx +116 -26
  103. package/src/components/sections/Container/Container.tsx +354 -0
  104. package/src/components/sections/Container/ContainerSettingsForm.tsx +113 -0
  105. package/src/components/sections/Container/index.tsx +51 -0
  106. package/src/components/sections/IconList/IconList.tsx +113 -43
  107. package/src/components/sections/IconList/IconListSettings.tsx +2 -2
  108. package/src/components/sections/IconList/index.tsx +1 -1
  109. package/src/components/sections/Media/MediaBlock.tsx +103 -0
  110. package/src/components/sections/Media/index.tsx +85 -0
  111. package/src/components/sections/Prose/index.tsx +1 -0
  112. package/src/components/sections/Spacer/Spacer.tsx +6 -0
  113. package/src/components/sections/Spacer/index.tsx +18 -0
  114. package/src/components/sections/all-sections.ts +40 -0
  115. package/src/components/sections/register-schemas.ts +13 -18
  116. package/src/components/sections/register.ts +3 -17
  117. package/src/components/shared/Tabs.tsx +63 -0
  118. package/src/components/shell/EditorShell.tsx +147 -18
  119. package/src/components/shell/SiteSettingsModal.tsx +41 -51
  120. package/src/components/shell/blockMoveDispatch.ts +40 -0
  121. package/src/hooks/useBlockDnd.ts +144 -0
  122. package/src/hooks/useEditorPublish.ts +17 -4
  123. package/src/lib/block-dnd.ts +58 -0
  124. package/src/lib/block-move.ts +236 -0
  125. package/src/lib/container-grid.ts +58 -0
  126. package/src/lib/container-ops.ts +159 -0
  127. package/src/lib/dexie.ts +47 -0
  128. package/src/lib/loader.ts +16 -4
  129. package/src/lib/migrate-sections-transform.ts +147 -0
  130. package/src/lib/registry.ts +48 -2
  131. package/src/lib/sanitize.ts +22 -1
  132. package/src/schemas/block.ts +40 -0
  133. package/src/schemas/link.ts +19 -1
  134. package/src/schemas/rich-text.ts +11 -0
  135. package/src/schemas/sections.ts +5 -1
  136. package/src/schemas/shared.ts +16 -0
  137. package/src/schemas/site-config.ts +3 -3
  138. package/src/storage/index.ts +1 -0
  139. package/src/storage/types.ts +17 -0
  140. package/dist/components/brandguide/DoDontList.d.ts +0 -16
  141. package/dist/components/brandguide/DoDontList.d.ts.map +0 -1
  142. package/dist/components/brandguide/DoDontMediaGrid.d.ts +0 -16
  143. package/dist/components/brandguide/DoDontMediaGrid.d.ts.map +0 -1
  144. package/dist/components/primitives/MediaSettingsForms.d.ts +0 -23
  145. package/dist/components/primitives/MediaSettingsForms.d.ts.map +0 -1
  146. package/dist/components/sections/DoDontList/index.d.ts +0 -21
  147. package/dist/components/sections/DoDontList/index.d.ts.map +0 -1
  148. package/dist/components/sections/DoDontMediaGrid/index.d.ts +0 -55
  149. package/dist/components/sections/DoDontMediaGrid/index.d.ts.map +0 -1
  150. package/dist/components/sections/MediaGrid/MediaGrid.d.ts +0 -17
  151. package/dist/components/sections/MediaGrid/MediaGrid.d.ts.map +0 -1
  152. package/dist/components/sections/MediaGrid/index.d.ts.map +0 -1
  153. package/dist/components/sections/SplitContent/SplitContent.d.ts +0 -14
  154. package/dist/components/sections/SplitContent/SplitContent.d.ts.map +0 -1
  155. package/dist/components/sections/SplitContent/index.d.ts +0 -13
  156. package/dist/components/sections/SplitContent/index.d.ts.map +0 -1
  157. package/src/components/brandguide/DoDontList.d.ts.map +0 -1
  158. package/src/components/brandguide/DoDontList.tsx +0 -67
  159. package/src/components/brandguide/DoDontMediaGrid.d.ts.map +0 -1
  160. package/src/components/brandguide/DoDontMediaGrid.tsx +0 -19
  161. package/src/components/primitives/MediaSettingsForms.tsx +0 -128
  162. package/src/components/sections/DoDontList/index.d.ts.map +0 -1
  163. package/src/components/sections/DoDontList/index.tsx +0 -45
  164. package/src/components/sections/DoDontMediaGrid/index.d.ts.map +0 -1
  165. package/src/components/sections/DoDontMediaGrid/index.tsx +0 -63
  166. package/src/components/sections/MediaGrid/MediaGrid.d.ts.map +0 -1
  167. package/src/components/sections/MediaGrid/MediaGrid.tsx +0 -239
  168. package/src/components/sections/MediaGrid/index.d.ts.map +0 -1
  169. package/src/components/sections/MediaGrid/index.tsx +0 -57
  170. package/src/components/sections/SplitContent/SplitContent.d.ts.map +0 -1
  171. package/src/components/sections/SplitContent/SplitContent.tsx +0 -84
  172. package/src/components/sections/SplitContent/index.d.ts.map +0 -1
  173. package/src/components/sections/SplitContent/index.tsx +0 -55
@@ -0,0 +1,26 @@
1
+ import {
2
+ clearRegistry,
3
+ createRegistry,
4
+ defineSection,
5
+ getAllSchemas,
6
+ getAllSections,
7
+ getRichTextFields,
8
+ getSchema,
9
+ getSection,
10
+ registerRichText,
11
+ registerSchema,
12
+ registerSection
13
+ } from "../chunk-VKAGMEKE.js";
14
+ export {
15
+ clearRegistry,
16
+ createRegistry,
17
+ defineSection,
18
+ getAllSchemas,
19
+ getAllSections,
20
+ getRichTextFields,
21
+ getSchema,
22
+ getSection,
23
+ registerRichText,
24
+ registerSchema,
25
+ registerSection
26
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD"}
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD"}
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * A block's participation in its parent container's layout — distinct from its
4
+ * content. Optional, and absent at the page root (the root is an implicit single
5
+ * column). Intentionally NOT strict so future keys (rowSpan, alignSelf) are
6
+ * forward-compatible.
7
+ */
8
+ export declare const LayoutEnvelopeSchema: z.ZodObject<{
9
+ colSpan: z.ZodOptional<z.ZodNumber>;
10
+ }, z.core.$strip>;
11
+ export type LayoutEnvelope = z.infer<typeof LayoutEnvelopeSchema>;
12
+ /** v1 nesting cap: a top-level container (depth 2) may hold leaves but not containers. */
13
+ export declare const MAX_BLOCK_DEPTH = 2;
14
+ /** Content-agnostic: any block whose `content.children` is an array is a container. */
15
+ export declare function getBlockChildren(block: unknown): unknown[];
16
+ /** Depth of a block tree: a leaf is 1; a container is 1 + max(child depths). */
17
+ export declare function blockDepth(block: unknown): number;
18
+ /** Throws if the block tree is deeper than `max`. */
19
+ export declare function assertMaxDepth(block: unknown, max?: number): void;
20
+ //# sourceMappingURL=block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../src/schemas/block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;iBAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,0FAA0F;AAC1F,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,CAM1D;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIjD;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,MAAwB,GAAG,IAAI,CAKlF"}
@@ -0,0 +1,14 @@
1
+ import {
2
+ LayoutEnvelopeSchema,
3
+ MAX_BLOCK_DEPTH,
4
+ assertMaxDepth,
5
+ blockDepth,
6
+ getBlockChildren
7
+ } from "../chunk-NSCT3AMV.js";
8
+ export {
9
+ LayoutEnvelopeSchema,
10
+ MAX_BLOCK_DEPTH,
11
+ assertMaxDepth,
12
+ blockDepth,
13
+ getBlockChildren
14
+ };
@@ -5,8 +5,9 @@ import {
5
5
  LinkTargetSchema,
6
6
  LinkValueSchema,
7
7
  MediaGridOptionsSchema,
8
+ isSafeHref,
8
9
  slugifyAudienceName
9
- } from "../chunk-PUNXQK4M.js";
10
+ } from "../chunk-PYWS3MOJ.js";
10
11
  import {
11
12
  AudienceSchema,
12
13
  RoleSchema,
@@ -16,6 +17,7 @@ import {
16
17
  import {
17
18
  ColorItemSchema,
18
19
  ColorSpaceSchema,
20
+ FontNameSchema,
19
21
  HexColorSchema,
20
22
  IndexSchema,
21
23
  MediaReferenceSchema,
@@ -23,18 +25,21 @@ import {
23
25
  PageStatusSchema,
24
26
  RESERVED_SLUGS,
25
27
  SectionMetaSchema,
28
+ SingleMediaReferenceSchema,
26
29
  SiteConfigSchema,
27
30
  TextLineSchema,
28
31
  getSectionContentSchema,
29
32
  getSectionSchema,
30
33
  normalizeSiteIndex
31
- } from "../chunk-24SUF2BC.js";
34
+ } from "../chunk-ICLXLWQ5.js";
32
35
  import {
33
36
  ImageManifestSchema,
34
37
  MediaConfigSchema,
35
38
  MediaItemSchema,
36
39
  VariantSchema
37
40
  } from "../chunk-DKOUFIP6.js";
41
+ import "../chunk-NSCT3AMV.js";
42
+ import "../chunk-VKAGMEKE.js";
38
43
  export {
39
44
  AudienceColorSchema,
40
45
  AudienceNameSchema,
@@ -42,6 +47,7 @@ export {
42
47
  ColorItemSchema,
43
48
  ColorSpaceSchema,
44
49
  DEFAULT_LINK,
50
+ FontNameSchema,
45
51
  HexColorSchema,
46
52
  ImageManifestSchema,
47
53
  IndexSchema,
@@ -57,12 +63,14 @@ export {
57
63
  RoleSchema,
58
64
  SectionMetaSchema,
59
65
  SessionSchema,
66
+ SingleMediaReferenceSchema,
60
67
  SiteConfigSchema,
61
68
  SiteUserSchema,
62
69
  TextLineSchema,
63
70
  VariantSchema,
64
71
  getSectionContentSchema,
65
72
  getSectionSchema,
73
+ isSafeHref,
66
74
  normalizeSiteIndex,
67
75
  slugifyAudienceName
68
76
  };
@@ -3,6 +3,13 @@ export declare const LinkTargetSchema: z.ZodEnum<{
3
3
  _self: "_self";
4
4
  _blank: "_blank";
5
5
  }>;
6
+ /**
7
+ * Block dangerous href schemes (javascript:, data:, vbscript:) and protocol-
8
+ * relative URLs at the schema boundary. Allows empty (unset), relative paths /
9
+ * fragments / queries, and http(s)/mailto absolute URLs. Links are not walked by
10
+ * the recursive sanitizer, so this is the only XSS guard for stored link hrefs.
11
+ */
12
+ export declare function isSafeHref(href: string): boolean;
6
13
  export declare const LinkValueSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7
14
  kind: z.ZodLiteral<"external">;
8
15
  href: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/schemas/link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,gBAAgB;;;EAA8B,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;2BAQ1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,YAAY,EAAE,SAA2D,CAAC"}
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/schemas/link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,gBAAgB;;;EAA8B,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUhD;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;2BAQ1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,YAAY,EAAE,SAA2D,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Marks a schema field as HTML rich text. Readability marker only — the runtime
4
+ * source of truth for sanitization is each block definition's `richTextFields`
5
+ * list (see registry). Kept as a helper so schemas read self-documentingly
6
+ * (`body: richText()`) and a future Zod version could make it introspectable.
7
+ */
8
+ export declare function richText(): z.ZodString;
9
+ //# sourceMappingURL=rich-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text.d.ts","sourceRoot":"","sources":["../../src/schemas/rich-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,wBAAgB,QAAQ,gBAEvB"}
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { type LayoutEnvelope } from "./block";
2
3
  export interface SectionContent<C = Record<string, unknown>> {
3
4
  type: string;
4
5
  content: C;
@@ -6,6 +7,7 @@ export interface SectionContent<C = Record<string, unknown>> {
6
7
  }
7
8
  export interface Section<C = Record<string, unknown>> extends SectionContent<C> {
8
9
  id: string;
10
+ layout?: LayoutEnvelope;
9
11
  }
10
12
  export declare function getSectionContentSchema(): z.ZodType<SectionContent>;
11
13
  export declare function getSectionSchema(): z.ZodType<Section>;
@@ -1 +1 @@
1
- {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../src/schemas/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAC7E,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wBAAgB,uBAAuB,IAAI,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAMnE;AAED,wBAAgB,gBAAgB,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAErD"}
1
+ {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../src/schemas/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAEpE,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,wBAAgB,uBAAuB,IAAI,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAMnE;AAED,wBAAgB,gBAAgB,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAIrD"}
@@ -77,7 +77,38 @@ export declare const MediaReferenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
77
77
  linkText: z.ZodOptional<z.ZodString>;
78
78
  }, z.core.$strip>], "type">;
79
79
  export type MediaReference = z.infer<typeof MediaReferenceSchema>;
80
+ /** A single image or video reference — for the standalone `media` block
81
+ * (excludes the grid-only doDontImage/linkedImage variants). */
82
+ export declare const SingleMediaReferenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
83
+ imageId: z.ZodDefault<z.ZodString>;
84
+ caption: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
85
+ background: z.ZodOptional<z.ZodString>;
86
+ invertFrom: z.ZodOptional<z.ZodString>;
87
+ border: z.ZodOptional<z.ZodBoolean>;
88
+ objectFit: z.ZodOptional<z.ZodEnum<{
89
+ cover: "cover";
90
+ contain: "contain";
91
+ }>>;
92
+ type: z.ZodLiteral<"image">;
93
+ }, z.core.$strip>, z.ZodObject<{
94
+ imageId: z.ZodDefault<z.ZodString>;
95
+ caption: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
96
+ background: z.ZodOptional<z.ZodString>;
97
+ invertFrom: z.ZodOptional<z.ZodString>;
98
+ border: z.ZodOptional<z.ZodBoolean>;
99
+ objectFit: z.ZodOptional<z.ZodEnum<{
100
+ cover: "cover";
101
+ contain: "contain";
102
+ }>>;
103
+ type: z.ZodLiteral<"video">;
104
+ poster: z.ZodOptional<z.ZodString>;
105
+ autoplay: z.ZodOptional<z.ZodBoolean>;
106
+ loop: z.ZodOptional<z.ZodBoolean>;
107
+ muted: z.ZodOptional<z.ZodBoolean>;
108
+ }, z.core.$strip>], "type">;
109
+ export type SingleMediaReference = z.infer<typeof SingleMediaReferenceSchema>;
80
110
  export declare const HexColorSchema: z.ZodString;
111
+ export declare const FontNameSchema: z.ZodString;
81
112
  export declare const ColorSpaceSchema: z.ZodObject<{
82
113
  hex: z.ZodOptional<z.ZodString>;
83
114
  rgb: z.ZodOptional<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/schemas/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwBxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;2BAKzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAoCtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAGlE,eAAO,MAAM,cAAc,aAEiC,CAAC;AAE7D,eAAO,MAAM,gBAAgB;;;;;iBAQ5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,eAAe;;;;;;;;iBAG1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/schemas/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwBxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;2BAKzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAoCtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;iEACiE;AACjE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;2BAAqD,CAAC;AAE7F,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAG9E,eAAO,MAAM,cAAc,aAEiC,CAAC;AAO7D,eAAO,MAAM,cAAc,aAG6C,CAAC;AAEzE,eAAO,MAAM,gBAAgB;;;;;iBAQ5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,eAAe;;;;;;;;iBAG1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export type { StorageProvider, FileWrite } from "./types";
2
+ export { StorageConflictError } from "./types";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"}
@@ -1,5 +1,17 @@
1
+ /**
2
+ * Thrown by `writeFiles` when `options.baseVersion` no longer matches the
3
+ * draft's current version — i.e. another editor (or the same user on another
4
+ * device) committed since this editor loaded. Callers should surface a 409 and
5
+ * prompt a reload rather than clobbering the concurrent work (last-write-wins).
6
+ */
7
+ export declare class StorageConflictError extends Error {
8
+ readonly currentVersion: string | null;
9
+ constructor(currentVersion: string | null);
10
+ }
1
11
  export interface StorageProvider {
2
- writeFiles(files: FileWrite[], message: string): Promise<{
12
+ writeFiles(files: FileWrite[], message: string, options?: {
13
+ baseVersion?: string | null;
14
+ }): Promise<{
3
15
  version: string;
4
16
  }>;
5
17
  readFile(path: string, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",
@@ -16,6 +16,10 @@
16
16
  "types": "./dist/schemas/auth.d.ts",
17
17
  "default": "./dist/schemas/auth.js"
18
18
  },
19
+ "./schemas/block": {
20
+ "types": "./dist/schemas/block.d.ts",
21
+ "default": "./dist/schemas/block.js"
22
+ },
19
23
  "./schemas/link": {
20
24
  "types": "./dist/schemas/link.d.ts",
21
25
  "default": "./dist/schemas/link.js"
@@ -32,6 +36,14 @@
32
36
  "types": "./dist/lib/env.d.ts",
33
37
  "default": "./dist/lib/env.js"
34
38
  },
39
+ "./lib/registry": {
40
+ "types": "./dist/lib/registry.d.ts",
41
+ "default": "./dist/lib/registry.js"
42
+ },
43
+ "./lib/migrate-sections-transform": {
44
+ "types": "./dist/lib/migrate-sections-transform.d.ts",
45
+ "default": "./dist/lib/migrate-sections-transform.js"
46
+ },
35
47
  "./auth": {
36
48
  "types": "./dist/auth/index.d.ts",
37
49
  "default": "./dist/auth/index.js"
@@ -5,7 +5,12 @@ import { requireSessionSecret } from "./security";
5
5
  export const SESSION_COOKIE = "bp-session";
6
6
  export const AUDIENCE_COOKIE = "bp-audience";
7
7
  export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24;
8
- export const AUDIENCE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
8
+ // 24h, matching the editor session. A viewer audience JWT carries no rotation
9
+ // token, so rotating a leaked viewer password can't actively revoke an
10
+ // outstanding cookie — a short lifetime bounds the leak window instead of the
11
+ // previous 30 days. (A `token_version` claim checked at verify time would give
12
+ // immediate revocation without forcing daily re-auth; see backlog.)
13
+ export const AUDIENCE_MAX_AGE_SECONDS = 60 * 60 * 24;
9
14
 
10
15
  export async function signSessionToken(session: Session): Promise<string> {
11
16
  return new jose.SignJWT({
@@ -1,7 +1,7 @@
1
1
  import { useState, useEffect } from "react";
2
2
  import { Eye, EyeOff } from "lucide-react";
3
3
  import { cn } from "../../lib/cn";
4
- import { gridColsClass } from "../../lib/grid";
4
+ import { containerGridClass } from "../../lib/container-grid";
5
5
  import { EditableGrid } from "../primitives/EditableGrid";
6
6
  import { EditablePlainText } from "../primitives/EditablePlainText";
7
7
  import type { ReactNode } from "react";
@@ -119,40 +119,42 @@ function ColorsView({
119
119
  </div>
120
120
  )}
121
121
 
122
- <div className={cn("grid gap-4", gridColsClass[columns] || "grid-cols-3")}>
123
- {colors.map((color, i) => {
124
- const hex = color.spaces[0]?.hex;
125
- const contrast = getContrastClass(hex);
126
- return (
127
- <div key={i} className="overflow-hidden rounded-md border border-base-200">
128
- <div className={cn("relative flex min-h-[80px] items-end p-3", contrast)} style={{ backgroundColor: hex || "#ccc" }}>
129
- {color.name && <span className="text-sm font-bold">{color.name}</span>}
130
- {copiedIndex === i && (
131
- <span className={cn(
132
- "absolute top-2 right-2 rounded-full px-2.5 py-0.5 text-xs font-medium",
133
- getContrastClass(hex) === "text-black" ? "bg-black/15 text-black" : "bg-white/25 text-white",
134
- )}>
135
- Copied!
136
- </span>
137
- )}
138
- </div>
139
- {expanded && (
140
- <div className="space-y-1 bg-base-accent p-3 text-sm">
141
- {color.spaces.map((space, j) =>
142
- Object.entries(space).map(([key, value]) =>
143
- value ? (
144
- <button key={`${j}-${key}`} onClick={() => handleCopy(value, i)} className="cursor-pointer flex w-full justify-between hover:text-primary">
145
- <span className="font-medium uppercase">{key}</span>
146
- <span>{value}</span>
147
- </button>
148
- ) : null
149
- )
122
+ <div className="@container">
123
+ <div className={cn("grid gap-4", containerGridClass[columns] || containerGridClass[3])}>
124
+ {colors.map((color, i) => {
125
+ const hex = color.spaces[0]?.hex;
126
+ const contrast = getContrastClass(hex);
127
+ return (
128
+ <div key={i} className="overflow-hidden rounded-md border border-base-200">
129
+ <div className={cn("relative flex min-h-[80px] items-end p-3", contrast)} style={{ backgroundColor: hex || "#ccc" }}>
130
+ {color.name && <span className="text-sm font-bold">{color.name}</span>}
131
+ {copiedIndex === i && (
132
+ <span className={cn(
133
+ "absolute top-2 right-2 rounded-full px-2.5 py-0.5 text-xs font-medium",
134
+ getContrastClass(hex) === "text-black" ? "bg-black/15 text-black" : "bg-white/25 text-white",
135
+ )}>
136
+ Copied!
137
+ </span>
150
138
  )}
151
139
  </div>
152
- )}
153
- </div>
154
- );
155
- })}
140
+ {expanded && (
141
+ <div className="space-y-1 bg-base-accent p-3 text-sm">
142
+ {color.spaces.map((space, j) =>
143
+ Object.entries(space).map(([key, value]) =>
144
+ value ? (
145
+ <button key={`${j}-${key}`} onClick={() => handleCopy(value, i)} className="cursor-pointer flex w-full justify-between hover:text-primary">
146
+ <span className="font-medium uppercase">{key}</span>
147
+ <span>{value}</span>
148
+ </button>
149
+ ) : null
150
+ )
151
+ )}
152
+ </div>
153
+ )}
154
+ </div>
155
+ );
156
+ })}
157
+ </div>
156
158
  </div>
157
159
  </div>
158
160
  );
@@ -0,0 +1,108 @@
1
+ import { forwardRef, type ReactNode, type Ref } from "react";
2
+ import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
3
+ import { CopyPlus } from "lucide-react";
4
+ import { DragHandle } from "./DragHandle";
5
+ import { DeleteButton } from "./DeleteButton";
6
+ import { SettingsButton } from "./SettingsButton";
7
+ import { ColSpanControl } from "./ColSpanControl";
8
+ import { cn } from "../../lib/cn";
9
+
10
+ export interface ChildBlockWrapperProps {
11
+ label: string;
12
+ columns: number;
13
+ colSpan: number;
14
+ closestEdge: Edge | null;
15
+ isDragging: boolean;
16
+ hasSettings: boolean;
17
+ /** Forwarded to the drag handle button (pragmatic-dnd dragHandle target). */
18
+ dragHandleRef?: Ref<HTMLButtonElement>;
19
+ onDelete: () => void;
20
+ onDuplicate: () => void;
21
+ onColSpanChange: (span: number) => void;
22
+ onOpenSettings?: () => void;
23
+ children: ReactNode;
24
+ }
25
+
26
+ export const ChildBlockWrapper = forwardRef<HTMLDivElement, ChildBlockWrapperProps>(
27
+ function ChildBlockWrapper(
28
+ {
29
+ label,
30
+ columns,
31
+ colSpan,
32
+ closestEdge,
33
+ isDragging,
34
+ hasSettings,
35
+ dragHandleRef,
36
+ onDelete,
37
+ onDuplicate,
38
+ onColSpanChange,
39
+ onOpenSettings,
40
+ children,
41
+ },
42
+ ref,
43
+ ) {
44
+ return (
45
+ <div
46
+ ref={ref}
47
+ className={cn(
48
+ "group/childblock relative h-full rounded-sm",
49
+ isDragging && "opacity-50",
50
+ "hover:outline hover:outline-2 hover:outline-primary/30",
51
+ )}
52
+ >
53
+ {/* Controls — vertical rail in the cell's LEFT gutter. Revealed on hover OR
54
+ focus-within, so an open col-span popover (which traps focus) keeps the rail
55
+ visible even when the pointer moves off the block to reach an option. */}
56
+ <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">
57
+ <span className="sr-only">{label}</span>
58
+ <div className="pointer-events-auto">
59
+ <DragHandle ref={dragHandleRef} />
60
+ </div>
61
+ <div className="pointer-events-auto">
62
+ <ColSpanControl colSpan={colSpan} columns={columns} onChange={onColSpanChange} />
63
+ </div>
64
+ {hasSettings && onOpenSettings && (
65
+ <div className="pointer-events-auto">
66
+ <SettingsButton onClick={onOpenSettings} />
67
+ </div>
68
+ )}
69
+ <div className="pointer-events-auto">
70
+ <button
71
+ type="button"
72
+ aria-label="Duplicate block"
73
+ onClick={onDuplicate}
74
+ className="cursor-pointer rounded p-1 text-base-contrast-light hover:text-primary"
75
+ >
76
+ <CopyPlus size={16} />
77
+ </button>
78
+ </div>
79
+ <div className="pointer-events-auto">
80
+ <DeleteButton onDelete={onDelete} />
81
+ </div>
82
+ </div>
83
+
84
+ {/* Drop-edge indicators */}
85
+ {closestEdge === "left" && (
86
+ <div
87
+ data-drop-edge="left"
88
+ className="absolute bottom-0 left-0 top-0 z-10 w-0.5 -translate-x-1 bg-primary"
89
+ />
90
+ )}
91
+ {closestEdge === "right" && (
92
+ <div
93
+ data-drop-edge="right"
94
+ className="absolute bottom-0 right-0 top-0 z-10 w-0.5 translate-x-1 bg-primary"
95
+ />
96
+ )}
97
+ {closestEdge === "top" && (
98
+ <div data-drop-edge="top" className="absolute left-0 right-0 top-0 z-10 h-0.5 -translate-y-1 bg-primary" />
99
+ )}
100
+ {closestEdge === "bottom" && (
101
+ <div data-drop-edge="bottom" className="absolute left-0 right-0 bottom-0 z-10 h-0.5 translate-y-1 bg-primary" />
102
+ )}
103
+
104
+ {children}
105
+ </div>
106
+ );
107
+ },
108
+ );
@@ -0,0 +1,56 @@
1
+ import { useRef, useState } from "react";
2
+ import { Columns3 } from "lucide-react";
3
+ import { Popover } from "../shared/Popover";
4
+ import { PopoverItem } from "../shared/PopoverItem";
5
+ import { cn } from "../../lib/cn";
6
+
7
+ interface ColSpanControlProps {
8
+ colSpan: number;
9
+ columns: number;
10
+ onChange: (span: number) => void;
11
+ }
12
+
13
+ /** Contextual col-span picker — only meaningful inside a multi-column container. */
14
+ export function ColSpanControl({ colSpan, columns, onChange }: ColSpanControlProps) {
15
+ const buttonRef = useRef<HTMLButtonElement>(null);
16
+ const [open, setOpen] = useState(false);
17
+
18
+ if (columns <= 1) return null;
19
+
20
+ const current = Math.min(Math.max(colSpan, 1), columns);
21
+
22
+ return (
23
+ <div className="relative">
24
+ <button
25
+ ref={buttonRef}
26
+ type="button"
27
+ aria-label="Column span"
28
+ aria-haspopup="true"
29
+ aria-expanded={open}
30
+ onClick={() => setOpen((v) => !v)}
31
+ className="pointer-events-auto inline-flex cursor-pointer items-center gap-1 rounded p-1 text-xs text-base-contrast-light hover:text-primary"
32
+ >
33
+ <Columns3 size={14} />
34
+ </button>
35
+
36
+ <Popover isOpen={open} onClose={() => setOpen(false)} anchorRef={buttonRef} className="min-w-28">
37
+ <ul role="list" className="py-1">
38
+ {Array.from({ length: columns }, (_, i) => i + 1).map((span) => (
39
+ <li key={span}>
40
+ <PopoverItem
41
+ aria-current={span === current}
42
+ onClick={() => {
43
+ onChange(span);
44
+ setOpen(false);
45
+ }}
46
+ className={cn(span === current && "font-semibold text-primary")}
47
+ >
48
+ Span {span}
49
+ </PopoverItem>
50
+ </li>
51
+ ))}
52
+ </ul>
53
+ </Popover>
54
+ </div>
55
+ );
56
+ }