@drawnagency/primitives 0.1.111 → 0.1.113

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 (203) hide show
  1. package/dist/auth/types.d.ts +1 -0
  2. package/dist/auth/types.d.ts.map +1 -1
  3. package/dist/chunk-4I5OO5UB.js +785 -0
  4. package/dist/{chunk-7WQJ57XG.js → chunk-6BZ2KNTP.js} +68 -260
  5. package/dist/{chunk-UVIYI4J6.js → chunk-AHALSCQH.js} +19 -3
  6. package/dist/{chunk-7MI3HCOF.js → chunk-BDFTMGCL.js} +10 -6
  7. package/dist/chunk-CXZ5YJG4.js +85 -0
  8. package/dist/{chunk-RA5IOFPY.js → chunk-DNY72JZN.js} +16 -38
  9. package/dist/{chunk-3GAG6ZXB.js → chunk-DS4GCYV2.js} +8 -4
  10. package/dist/{chunk-XQMDWXD6.js → chunk-FRE2CCLV.js} +3 -2
  11. package/dist/chunk-FV7JQRCN.js +94 -0
  12. package/dist/chunk-IIDKW7AV.js +41 -0
  13. package/dist/chunk-IINIZBQB.js +204 -0
  14. package/dist/{chunk-ELQ4C677.js → chunk-JH762VUH.js} +9 -44
  15. package/dist/{chunk-OUYB2GCW.js → chunk-K7DYZXVU.js} +1 -57
  16. package/dist/{chunk-NXGIHSL5.js → chunk-KR2MHTNJ.js} +11 -91
  17. package/dist/chunk-MM7KZS5C.js +30 -0
  18. package/dist/{chunk-OTDA6KWL.js → chunk-NHN2LSIU.js} +1 -1
  19. package/dist/chunk-QQ3S3P5P.js +38 -0
  20. package/dist/chunk-VAYZJKVR.js +209 -0
  21. package/dist/{chunk-VCCNKTFD.js → chunk-VWWY5TL5.js} +3 -31
  22. package/dist/chunk-WUJ3XNFI.js +62 -0
  23. package/dist/{chunk-ALMEC3XN.js → chunk-Z7W3DQTR.js} +22 -18
  24. package/dist/collab/rebase.js +1 -1
  25. package/dist/components/chat/ChatComposer.d.ts +25 -0
  26. package/dist/components/chat/ChatComposer.d.ts.map +1 -0
  27. package/dist/components/chat/ChatPanel.d.ts +5 -4
  28. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  29. package/dist/components/chat/ChatWidget.d.ts.map +1 -1
  30. package/dist/components/chat/markdown.d.ts +14 -0
  31. package/dist/components/chat/markdown.d.ts.map +1 -0
  32. package/dist/components/editor/index.js +16 -11
  33. package/dist/components/primitives/EditablePlainText.d.ts.map +1 -1
  34. package/dist/components/primitives/index.js +15 -10
  35. package/dist/components/sections/brand-guide.js +11 -9
  36. package/dist/components/sections/register-schemas.js +19 -14
  37. package/dist/components/sections/register.js +19 -14
  38. package/dist/components/shared/Navigation.d.ts +1 -0
  39. package/dist/components/shared/Navigation.d.ts.map +1 -1
  40. package/dist/components/shell/AudienceChips.d.ts +10 -0
  41. package/dist/components/shell/AudienceChips.d.ts.map +1 -0
  42. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  43. package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
  44. package/dist/components/shell/SiteSettingsDecks.d.ts +13 -0
  45. package/dist/components/shell/SiteSettingsDecks.d.ts.map +1 -0
  46. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
  47. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  48. package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -1
  49. package/dist/deck/DeckComponentView.d.ts +13 -0
  50. package/dist/deck/DeckComponentView.d.ts.map +1 -0
  51. package/dist/deck/DeckPlayer.d.ts +26 -0
  52. package/dist/deck/DeckPlayer.d.ts.map +1 -0
  53. package/dist/deck/DeckSlideView.d.ts +17 -0
  54. package/dist/deck/DeckSlideView.d.ts.map +1 -0
  55. package/dist/deck/DeckStage.d.ts +16 -0
  56. package/dist/deck/DeckStage.d.ts.map +1 -0
  57. package/dist/deck/components/DeckImage.d.ts +14 -0
  58. package/dist/deck/components/DeckImage.d.ts.map +1 -0
  59. package/dist/deck/components/DeckImageGrid.d.ts +27 -0
  60. package/dist/deck/components/DeckImageGrid.d.ts.map +1 -0
  61. package/dist/deck/components/DeckRichText.d.ts +22 -0
  62. package/dist/deck/components/DeckRichText.d.ts.map +1 -0
  63. package/dist/deck/components/DeckVideo.d.ts +15 -0
  64. package/dist/deck/components/DeckVideo.d.ts.map +1 -0
  65. package/dist/deck/editing.d.ts +50 -0
  66. package/dist/deck/editing.d.ts.map +1 -0
  67. package/dist/deck/flip.d.ts +11 -0
  68. package/dist/deck/flip.d.ts.map +1 -0
  69. package/dist/deck/index.d.ts +24 -0
  70. package/dist/deck/index.d.ts.map +1 -0
  71. package/dist/deck/index.js +53 -0
  72. package/dist/deck/layouts/CenteredTextLayout.d.ts +12 -0
  73. package/dist/deck/layouts/CenteredTextLayout.d.ts.map +1 -0
  74. package/dist/deck/layouts/ColumnSplitLayout.d.ts +14 -0
  75. package/dist/deck/layouts/ColumnSplitLayout.d.ts.map +1 -0
  76. package/dist/deck/layouts/FullBleedImageLayout.d.ts +14 -0
  77. package/dist/deck/layouts/FullBleedImageLayout.d.ts.map +1 -0
  78. package/dist/deck/media.d.ts +33 -0
  79. package/dist/deck/media.d.ts.map +1 -0
  80. package/dist/deck/tokens.d.ts +22 -0
  81. package/dist/deck/tokens.d.ts.map +1 -0
  82. package/dist/deck/useStageScale.d.ts +10 -0
  83. package/dist/deck/useStageScale.d.ts.map +1 -0
  84. package/dist/deck-builder/DeckBuilderApp.d.ts +30 -0
  85. package/dist/deck-builder/DeckBuilderApp.d.ts.map +1 -0
  86. package/dist/deck-builder/DeckBuilderTopBar.d.ts +24 -0
  87. package/dist/deck-builder/DeckBuilderTopBar.d.ts.map +1 -0
  88. package/dist/deck-builder/DeckChatPanel.d.ts +45 -0
  89. package/dist/deck-builder/DeckChatPanel.d.ts.map +1 -0
  90. package/dist/deck-builder/DeckEditor.d.ts +37 -0
  91. package/dist/deck-builder/DeckEditor.d.ts.map +1 -0
  92. package/dist/deck-builder/DeckLibrary.d.ts +22 -0
  93. package/dist/deck-builder/DeckLibrary.d.ts.map +1 -0
  94. package/dist/deck-builder/DeckSlideSurface.d.ts +31 -0
  95. package/dist/deck-builder/DeckSlideSurface.d.ts.map +1 -0
  96. package/dist/deck-builder/deck-api.d.ts +76 -0
  97. package/dist/deck-builder/deck-api.d.ts.map +1 -0
  98. package/dist/deck-builder/deck-chat-client.d.ts +44 -0
  99. package/dist/deck-builder/deck-chat-client.d.ts.map +1 -0
  100. package/dist/deck-builder/deck-chat-history.d.ts +10 -0
  101. package/dist/deck-builder/deck-chat-history.d.ts.map +1 -0
  102. package/dist/deck-builder/index.d.ts +13 -0
  103. package/dist/deck-builder/index.d.ts.map +1 -0
  104. package/dist/deck-builder/index.js +1505 -0
  105. package/dist/deck-builder/starter-deck.d.ts +23 -0
  106. package/dist/deck-builder/starter-deck.d.ts.map +1 -0
  107. package/dist/deck-builder/types.d.ts +29 -0
  108. package/dist/deck-builder/types.d.ts.map +1 -0
  109. package/dist/deck-builder/useSortableSlide.d.ts +29 -0
  110. package/dist/deck-builder/useSortableSlide.d.ts.map +1 -0
  111. package/dist/index.js +70 -63
  112. package/dist/lib/deck-access.d.ts +86 -0
  113. package/dist/lib/deck-access.d.ts.map +1 -0
  114. package/dist/lib/deck-access.js +58 -0
  115. package/dist/lib/deck-dexie.d.ts +88 -0
  116. package/dist/lib/deck-dexie.d.ts.map +1 -0
  117. package/dist/lib/deck-dexie.js +22 -0
  118. package/dist/lib/deck-edit.d.ts +67 -0
  119. package/dist/lib/deck-edit.d.ts.map +1 -0
  120. package/dist/lib/deck-media-assets.d.ts +27 -0
  121. package/dist/lib/deck-media-assets.d.ts.map +1 -0
  122. package/dist/lib/deck-media-assets.js +25 -0
  123. package/dist/lib/deck-media-refs.d.ts +35 -0
  124. package/dist/lib/deck-media-refs.d.ts.map +1 -0
  125. package/dist/lib/deck-media-refs.js +36 -0
  126. package/dist/lib/deck-tool.d.ts +25 -0
  127. package/dist/lib/deck-tool.d.ts.map +1 -0
  128. package/dist/lib/deck-tool.js +58 -0
  129. package/dist/lib/index-ops.js +2 -2
  130. package/dist/lib/index.js +17 -12
  131. package/dist/lib/nav.d.ts.map +1 -1
  132. package/dist/media/index.js +3 -3
  133. package/dist/media/resolve.d.ts +2 -0
  134. package/dist/media/resolve.d.ts.map +1 -1
  135. package/dist/schemas/deck-meta.d.ts +85 -0
  136. package/dist/schemas/deck-meta.d.ts.map +1 -0
  137. package/dist/schemas/deck-meta.js +64 -0
  138. package/dist/schemas/deck.d.ts +954 -0
  139. package/dist/schemas/deck.d.ts.map +1 -0
  140. package/dist/schemas/deck.js +65 -0
  141. package/dist/schemas/index.js +39 -37
  142. package/dist/schemas/site-config.d.ts +14 -1
  143. package/dist/schemas/site-config.d.ts.map +1 -1
  144. package/dist/types/database.d.ts +8 -0
  145. package/dist/types/database.d.ts.map +1 -1
  146. package/package.json +37 -1
  147. package/src/auth/types.ts +1 -0
  148. package/src/components/chat/ChatComposer.tsx +100 -0
  149. package/src/components/chat/ChatPanel.tsx +20 -69
  150. package/src/components/chat/ChatWidget.tsx +5 -4
  151. package/src/components/chat/markdown.ts +32 -0
  152. package/src/components/primitives/EditablePlainText.tsx +11 -1
  153. package/src/components/shared/Navigation.tsx +26 -18
  154. package/src/components/shell/AudienceChips.tsx +30 -0
  155. package/src/components/shell/EditorShell.tsx +12 -0
  156. package/src/components/shell/SiteSettingsChatbot.tsx +1 -18
  157. package/src/components/shell/SiteSettingsDecks.tsx +41 -0
  158. package/src/components/shell/SiteSettingsDisplay.tsx +10 -1
  159. package/src/components/shell/SiteSettingsModal.tsx +11 -0
  160. package/src/components/shell/SiteSettingsUsers.tsx +49 -2
  161. package/src/deck/DeckComponentView.tsx +28 -0
  162. package/src/deck/DeckPlayer.tsx +166 -0
  163. package/src/deck/DeckSlideView.tsx +38 -0
  164. package/src/deck/DeckStage.tsx +39 -0
  165. package/src/deck/components/DeckImage.tsx +38 -0
  166. package/src/deck/components/DeckImageGrid.tsx +254 -0
  167. package/src/deck/components/DeckRichText.tsx +111 -0
  168. package/src/deck/components/DeckVideo.tsx +81 -0
  169. package/src/deck/editing.tsx +201 -0
  170. package/src/deck/flip.ts +70 -0
  171. package/src/deck/index.ts +30 -0
  172. package/src/deck/layouts/CenteredTextLayout.tsx +29 -0
  173. package/src/deck/layouts/ColumnSplitLayout.tsx +69 -0
  174. package/src/deck/layouts/FullBleedImageLayout.tsx +58 -0
  175. package/src/deck/media.tsx +46 -0
  176. package/src/deck/tokens.ts +57 -0
  177. package/src/deck/useStageScale.ts +44 -0
  178. package/src/deck-builder/DeckBuilderApp.tsx +572 -0
  179. package/src/deck-builder/DeckBuilderTopBar.tsx +62 -0
  180. package/src/deck-builder/DeckChatPanel.tsx +334 -0
  181. package/src/deck-builder/DeckEditor.tsx +258 -0
  182. package/src/deck-builder/DeckLibrary.tsx +195 -0
  183. package/src/deck-builder/DeckSlideSurface.tsx +55 -0
  184. package/src/deck-builder/deck-api.ts +177 -0
  185. package/src/deck-builder/deck-chat-client.ts +129 -0
  186. package/src/deck-builder/deck-chat-history.ts +62 -0
  187. package/src/deck-builder/index.ts +12 -0
  188. package/src/deck-builder/starter-deck.ts +86 -0
  189. package/src/deck-builder/types.ts +24 -0
  190. package/src/deck-builder/useSortableSlide.ts +102 -0
  191. package/src/lib/deck-access.ts +132 -0
  192. package/src/lib/deck-dexie.ts +190 -0
  193. package/src/lib/deck-edit.ts +196 -0
  194. package/src/lib/deck-media-assets.ts +57 -0
  195. package/src/lib/deck-media-refs.ts +67 -0
  196. package/src/lib/deck-tool.ts +95 -0
  197. package/src/lib/nav.ts +21 -1
  198. package/src/media/resolve.ts +3 -1
  199. package/src/schemas/deck-meta.ts +88 -0
  200. package/src/schemas/deck.ts +478 -0
  201. package/src/schemas/index.ts +4 -0
  202. package/src/schemas/site-config.ts +27 -1
  203. package/src/types/database.ts +4 -0
@@ -0,0 +1,85 @@
1
+ // src/lib/insert-menu.ts
2
+ var PRESET_PREFIX = "__preset:";
3
+ var INSERT_MENU_ORDER = [
4
+ "container",
5
+ "link_heading",
6
+ "sub_heading",
7
+ "sub_sub_heading",
8
+ "prose",
9
+ "media",
10
+ "video",
11
+ "button",
12
+ "colors",
13
+ "document",
14
+ "icon_list",
15
+ `${PRESET_PREFIX}do_dont_list`,
16
+ "dodont_media"
17
+ ];
18
+
19
+ // src/lib/container-ops.ts
20
+ function getContainerChildren(block) {
21
+ const content = block.content;
22
+ return Array.isArray(content?.children) ? content.children : [];
23
+ }
24
+ function withChildren(container, children) {
25
+ return { ...container, content: { ...container.content, children } };
26
+ }
27
+ function containerColumns(container) {
28
+ const n = container.content?.columns;
29
+ return typeof n === "number" && n >= 1 ? n : 1;
30
+ }
31
+ function removeChildAt(container, index) {
32
+ const children = getContainerChildren(container);
33
+ if (index < 0 || index >= children.length) return { container, removed: null };
34
+ const next = [...children];
35
+ const [removed] = next.splice(index, 1);
36
+ return { container: withChildren(container, next), removed };
37
+ }
38
+ function duplicateChildAt(container, index, newId) {
39
+ const children = getContainerChildren(container);
40
+ if (index < 0 || index >= children.length) return container;
41
+ const clone = { ...JSON.parse(JSON.stringify(children[index])), id: newId() };
42
+ const next = [...children];
43
+ next.splice(index + 1, 0, clone);
44
+ return withChildren(container, next);
45
+ }
46
+ function setChildColSpan(container, index, span) {
47
+ const children = getContainerChildren(container);
48
+ if (index < 0 || index >= children.length) return container;
49
+ const clamped = Math.max(1, Math.min(span, containerColumns(container)));
50
+ const next = children.map((child, i) => {
51
+ if (i !== index) return child;
52
+ if (clamped <= 1) {
53
+ const { layout: _drop, ...rest } = child;
54
+ return rest;
55
+ }
56
+ return { ...child, layout: { ...child.layout, colSpan: clamped } };
57
+ });
58
+ return withChildren(container, next);
59
+ }
60
+ function effectiveSpan(child, columns) {
61
+ return isSpacer(child) ? 1 : Math.min(child.layout?.colSpan ?? 1, columns);
62
+ }
63
+ function isSpacer(child) {
64
+ return child.type === "spacer";
65
+ }
66
+ function occupiedColumns(children, columns) {
67
+ return children.reduce((sum, c) => sum + effectiveSpan(c, columns), 0);
68
+ }
69
+ function trimTrailingSpacers(container) {
70
+ const children = getContainerChildren(container);
71
+ let end = children.length;
72
+ while (end > 0 && isSpacer(children[end - 1])) end--;
73
+ if (end === children.length) return container;
74
+ return withChildren(container, children.slice(0, end));
75
+ }
76
+
77
+ export {
78
+ getContainerChildren,
79
+ removeChildAt,
80
+ duplicateChildAt,
81
+ setChildColSpan,
82
+ isSpacer,
83
+ occupiedColumns,
84
+ trimTrailingSpacers
85
+ };
@@ -1,3 +1,6 @@
1
+ import {
2
+ getContainerChildren
3
+ } from "./chunk-CXZ5YJG4.js";
1
4
  import {
2
5
  getSection
3
6
  } from "./chunk-FTPETWXE.js";
@@ -12,6 +15,17 @@ function toSectionId(text) {
12
15
  }
13
16
  return `section-${Math.abs(hash).toString(36)}`;
14
17
  }
18
+ function flattenForNav(sections) {
19
+ const flat = [];
20
+ const walk = (loaded) => {
21
+ flat.push(loaded);
22
+ for (const child of getContainerChildren(loaded.section)) {
23
+ walk({ section: child, meta: loaded.meta });
24
+ }
25
+ };
26
+ sections.forEach(walk);
27
+ return flat;
28
+ }
15
29
  function generateNavLinks(sections, registry) {
16
30
  const nav = [];
17
31
  let currentParent = null;
@@ -20,7 +34,7 @@ function generateNavLinks(sections, registry) {
20
34
  const def = registry ? registry.getSection(type) : getSection(type);
21
35
  return def?.navRole;
22
36
  };
23
- for (const { section, meta } of sections) {
37
+ for (const { section, meta } of flattenForNav(sections)) {
24
38
  const content = section.content;
25
39
  if (!content.heading) continue;
26
40
  const role = lookupRole(section.type);
@@ -66,43 +80,7 @@ function generateNavLinks(sections, registry) {
66
80
  return nav;
67
81
  }
68
82
 
69
- // src/lib/sanitize.ts
70
- var purifierPromise = null;
71
- var purifier = null;
72
- if (typeof window !== "undefined") {
73
- purifierPromise = import("dompurify").then((mod) => {
74
- const DOMPurify = mod.default ?? mod;
75
- purifier = (html) => DOMPurify.sanitize(html);
76
- return mod;
77
- });
78
- }
79
- var sanitizeCache = /* @__PURE__ */ new Map();
80
- var SANITIZE_CACHE_LIMIT = 500;
81
- function sanitizeHtml(html) {
82
- if (!html) return "";
83
- if (!purifier) return html;
84
- const cached = sanitizeCache.get(html);
85
- if (cached !== void 0) return cached;
86
- const clean = purifier(html);
87
- if (sanitizeCache.size >= SANITIZE_CACHE_LIMIT) {
88
- const oldest = sanitizeCache.keys().next().value;
89
- if (oldest !== void 0) sanitizeCache.delete(oldest);
90
- }
91
- sanitizeCache.set(html, clean);
92
- return clean;
93
- }
94
- function isSanitizerReady() {
95
- return typeof window === "undefined" || purifier !== null;
96
- }
97
- async function ensureSanitizer() {
98
- if (typeof window === "undefined") return;
99
- if (!purifier && purifierPromise) await purifierPromise;
100
- }
101
-
102
83
  export {
103
84
  toSectionId,
104
- generateNavLinks,
105
- sanitizeHtml,
106
- isSanitizerReady,
107
- ensureSanitizer
85
+ generateNavLinks
108
86
  };
@@ -2,14 +2,16 @@ import {
2
2
  AddIcon,
3
3
  DeleteIcon,
4
4
  DragHandle,
5
- IconButton,
6
5
  SettingsIcon,
7
6
  useMediaLibrary,
8
7
  useResolvedMedia
9
- } from "./chunk-ELQ4C677.js";
8
+ } from "./chunk-JH762VUH.js";
9
+ import {
10
+ IconButton
11
+ } from "./chunk-IIDKW7AV.js";
10
12
  import {
11
13
  rebindImageRef
12
- } from "./chunk-XQMDWXD6.js";
14
+ } from "./chunk-FRE2CCLV.js";
13
15
  import {
14
16
  cn
15
17
  } from "./chunk-7EOSDFS3.js";
@@ -74,6 +76,8 @@ function EditablePlainText({
74
76
  ...htmlAttrs
75
77
  }) {
76
78
  const ref = useRef2(null);
79
+ const uncontrolled = useRef2(value);
80
+ if (!isEditMode) uncontrolled.current = value;
77
81
  const {
78
82
  value: localValue,
79
83
  handleInput,
@@ -131,7 +135,7 @@ function EditablePlainText({
131
135
  "data-placeholder": isEditMode && placeholder ? placeholder : void 0,
132
136
  ...htmlAttrs
133
137
  },
134
- value
138
+ isEditMode ? uncontrolled.current : value
135
139
  );
136
140
  }
137
141
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  slugifyPageSlug
3
- } from "./chunk-OTDA6KWL.js";
3
+ } from "./chunk-NHN2LSIU.js";
4
4
 
5
5
  // src/media/utils.ts
6
6
  async function hashFileBuffer(buffer) {
@@ -248,7 +248,8 @@ function resolveLogo(logo, manifest, provider) {
248
248
  lightUrl: light.src,
249
249
  darkUrl,
250
250
  invertInDark: logo.invertInDark,
251
- showInNav: logo.showInNav
251
+ showInNav: logo.showInNav,
252
+ hideTitle: logo.hideTitle
252
253
  };
253
254
  }
254
255
 
@@ -0,0 +1,94 @@
1
+ import {
2
+ serializeDeck
3
+ } from "./chunk-VAYZJKVR.js";
4
+
5
+ // src/lib/deck-dexie.ts
6
+ import Dexie from "dexie";
7
+ var DeckDraftDatabase = class extends Dexie {
8
+ drafts;
9
+ constructor(name) {
10
+ super(name);
11
+ this.version(1).stores({ drafts: "deckId" });
12
+ }
13
+ };
14
+ function databaseName(siteId, scope) {
15
+ return `brand-portal-decks-${siteId}-${scope}`;
16
+ }
17
+ var db = null;
18
+ function initDeckDraftStore(siteId, scope) {
19
+ const name = databaseName(siteId, scope);
20
+ if (db?.name === name) return;
21
+ db?.close();
22
+ db = new DeckDraftDatabase(name);
23
+ }
24
+ function getDb() {
25
+ if (!db) throw new Error("Deck draft store not initialized. Call initDeckDraftStore first.");
26
+ return db;
27
+ }
28
+ async function listDeckDrafts() {
29
+ const rows = await getDb().drafts.toArray();
30
+ return rows.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
31
+ }
32
+ async function getDeckDraft(deckId) {
33
+ return await getDb().drafts.get(deckId) ?? null;
34
+ }
35
+ async function putDeckDraft(deck) {
36
+ const db2 = getDb();
37
+ return db2.transaction("rw", db2.drafts, async () => {
38
+ const existing = await db2.drafts.get(deck.id);
39
+ const row = {
40
+ deckId: deck.id,
41
+ deck,
42
+ baseVersion: existing?.baseVersion ?? null,
43
+ publishedText: existing?.publishedText ?? null,
44
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
45
+ };
46
+ await db2.drafts.put(row);
47
+ return row;
48
+ });
49
+ }
50
+ async function markDeckDraftPublished(deck, version, updatedAt) {
51
+ const row = {
52
+ deckId: deck.id,
53
+ deck,
54
+ baseVersion: version,
55
+ publishedText: serializeDeck(deck),
56
+ updatedAt: updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
57
+ };
58
+ await getDb().drafts.put(row);
59
+ return row;
60
+ }
61
+ async function recordDeckPublish(deckId, publishedDeck, version) {
62
+ const db2 = getDb();
63
+ return db2.transaction("rw", db2.drafts, async () => {
64
+ const existing = await db2.drafts.get(deckId);
65
+ if (!existing) return null;
66
+ const row = {
67
+ ...existing,
68
+ baseVersion: version,
69
+ publishedText: serializeDeck(publishedDeck),
70
+ // "Now", as the previous implementation stamped: a publish is activity the
71
+ // deck grid should sort on, even though it did not change the body.
72
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
73
+ };
74
+ await db2.drafts.put(row);
75
+ return row;
76
+ });
77
+ }
78
+ async function deleteDeckDraft(deckId) {
79
+ await getDb().drafts.delete(deckId);
80
+ }
81
+ function hasUnpublishedChanges(row) {
82
+ return row.publishedText === null || serializeDeck(row.deck) !== row.publishedText;
83
+ }
84
+
85
+ export {
86
+ initDeckDraftStore,
87
+ listDeckDrafts,
88
+ getDeckDraft,
89
+ putDeckDraft,
90
+ markDeckDraftPublished,
91
+ recordDeckPublish,
92
+ deleteDeckDraft,
93
+ hasUnpublishedChanges
94
+ };
@@ -0,0 +1,41 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-7EOSDFS3.js";
4
+
5
+ // src/components/shared/IconButton.tsx
6
+ import { forwardRef } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var sizeClasses = {
9
+ sm: "h-chrome-sm w-chrome-sm",
10
+ md: "h-chrome-md w-chrome-md",
11
+ lg: "h-chrome-lg w-chrome-lg"
12
+ };
13
+ var intentClasses = {
14
+ default: "text-base-contrast-light hover:text-base-contrast",
15
+ destructive: "text-destructive hover:text-destructive-hover",
16
+ primary: "text-primary/60 hover:text-primary"
17
+ };
18
+ var IconButton = forwardRef(
19
+ function IconButton2({ icon, label, size = "md", intent = "default", className, ...rest }, ref) {
20
+ return /* @__PURE__ */ jsx(
21
+ "button",
22
+ {
23
+ ref,
24
+ type: "button",
25
+ className: cn(
26
+ "cursor-pointer flex items-center justify-center rounded transition-colors",
27
+ sizeClasses[size],
28
+ intentClasses[intent],
29
+ className
30
+ ),
31
+ "aria-label": label,
32
+ ...rest,
33
+ children: icon
34
+ }
35
+ );
36
+ }
37
+ );
38
+
39
+ export {
40
+ IconButton
41
+ };
@@ -0,0 +1,204 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-7EOSDFS3.js";
4
+
5
+ // src/components/shared/Tooltip.tsx
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var sideClasses = {
8
+ bottom: "left-1/2 top-full mt-1.5 -translate-x-1/2",
9
+ right: "left-full top-1/2 ml-1.5 -translate-y-1/2",
10
+ "bottom-clamp": "top-full mt-1.5 left-1/2 -translate-x-1/2 max-xl:left-auto max-xl:right-0 max-xl:translate-x-0"
11
+ };
12
+ function Tooltip({ content, children, className, side = "bottom" }) {
13
+ return /* @__PURE__ */ jsxs("div", { className: cn("group/tooltip relative inline-flex", className), children: [
14
+ children,
15
+ /* @__PURE__ */ jsx(
16
+ "span",
17
+ {
18
+ className: cn(
19
+ "pointer-events-none absolute z-50 opacity-0 transition-opacity delay-300 group-hover/tooltip:opacity-100",
20
+ sideClasses[side]
21
+ ),
22
+ role: "tooltip",
23
+ 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 })
24
+ }
25
+ )
26
+ ] });
27
+ }
28
+ function Kbd({ children }) {
29
+ return /* @__PURE__ */ jsx("strong", { className: "text-tooltip-contrast", children });
30
+ }
31
+
32
+ // src/components/shared/Popover.tsx
33
+ import { useEffect as useEffect2, useLayoutEffect, useRef, useState } from "react";
34
+ import { createPortal } from "react-dom";
35
+
36
+ // src/hooks/useFocusTrap.ts
37
+ import { useEffect } from "react";
38
+ var FOCUSABLE_SELECTOR = [
39
+ "a[href]",
40
+ "button:not([disabled])",
41
+ "input:not([disabled])",
42
+ "select:not([disabled])",
43
+ "textarea:not([disabled])",
44
+ '[tabindex]:not([tabindex="-1"])'
45
+ ].join(", ");
46
+ function useInitialFocus(ref, active) {
47
+ useEffect(() => {
48
+ if (!active || !ref.current) return;
49
+ const previouslyFocused = document.activeElement;
50
+ ref.current.querySelector(FOCUSABLE_SELECTOR)?.focus();
51
+ return () => {
52
+ if (previouslyFocused && document.body.contains(previouslyFocused)) {
53
+ previouslyFocused.focus();
54
+ }
55
+ };
56
+ }, [active, ref]);
57
+ }
58
+
59
+ // src/components/shared/Popover.tsx
60
+ import { jsx as jsx2 } from "react/jsx-runtime";
61
+ function Popover({
62
+ isOpen,
63
+ onClose,
64
+ anchorRef,
65
+ children,
66
+ align = "auto",
67
+ className,
68
+ portal = false,
69
+ placement = "bottom"
70
+ }) {
71
+ const panelRef = useRef(null);
72
+ const [resolvedAlign, setResolvedAlign] = useState("start");
73
+ const [fixedPos, setFixedPos] = useState(null);
74
+ useInitialFocus(panelRef, isOpen && (!portal || fixedPos !== null));
75
+ useLayoutEffect(() => {
76
+ if (!portal) return;
77
+ if (!isOpen) {
78
+ setFixedPos(null);
79
+ return;
80
+ }
81
+ const anchor = anchorRef.current;
82
+ if (!anchor) return;
83
+ const update = () => {
84
+ const r = anchor.getBoundingClientRect();
85
+ const next = placement === "top" ? { left: r.left, bottom: window.innerHeight - r.top + 4 } : { left: r.left, top: r.bottom + 4 };
86
+ setFixedPos(
87
+ (prev) => prev && prev.left === next.left && prev.top === next.top && prev.bottom === next.bottom ? prev : next
88
+ );
89
+ };
90
+ update();
91
+ window.addEventListener("resize", update);
92
+ window.addEventListener("scroll", update, true);
93
+ return () => {
94
+ window.removeEventListener("resize", update);
95
+ window.removeEventListener("scroll", update, true);
96
+ };
97
+ }, [isOpen, portal, placement, anchorRef]);
98
+ useLayoutEffect(() => {
99
+ if (portal) return;
100
+ if (!isOpen || align !== "auto") {
101
+ if (!isOpen) setResolvedAlign("start");
102
+ return;
103
+ }
104
+ const panel = panelRef.current;
105
+ if (!panel) return;
106
+ panel.style.left = "0";
107
+ panel.style.right = "auto";
108
+ const rect = panel.getBoundingClientRect();
109
+ if (rect.right > window.innerWidth) {
110
+ setResolvedAlign("end");
111
+ } else {
112
+ setResolvedAlign("start");
113
+ }
114
+ panel.style.left = "";
115
+ panel.style.right = "";
116
+ }, [isOpen, align]);
117
+ useEffect2(() => {
118
+ if (!isOpen) return;
119
+ function onKeyDown(e) {
120
+ if (e.key !== "Escape") return;
121
+ if (document.querySelector("[data-modal-layer]")) return;
122
+ onClose();
123
+ }
124
+ function onMouseDown(e) {
125
+ const target = e.target;
126
+ if (panelRef.current?.contains(target)) return;
127
+ if (anchorRef.current?.contains(target)) return;
128
+ if (target instanceof Element && target.closest("[data-modal-layer]")) return;
129
+ onClose();
130
+ }
131
+ document.addEventListener("keydown", onKeyDown);
132
+ document.addEventListener("mousedown", onMouseDown);
133
+ return () => {
134
+ document.removeEventListener("keydown", onKeyDown);
135
+ document.removeEventListener("mousedown", onMouseDown);
136
+ };
137
+ }, [isOpen, onClose, anchorRef]);
138
+ if (!isOpen) return null;
139
+ const effectiveAlign = align === "auto" ? resolvedAlign : align;
140
+ if (portal) {
141
+ if (!fixedPos) return null;
142
+ return createPortal(
143
+ /* @__PURE__ */ jsx2(
144
+ "div",
145
+ {
146
+ ref: panelRef,
147
+ role: "dialog",
148
+ "aria-modal": "false",
149
+ style: {
150
+ position: "fixed",
151
+ left: `${fixedPos.left}px`,
152
+ ...fixedPos.top !== void 0 ? { top: `${fixedPos.top}px` } : {},
153
+ ...fixedPos.bottom !== void 0 ? { bottom: `${fixedPos.bottom}px` } : {}
154
+ },
155
+ className: cn("z-50 rounded-md border border-base-200 bg-base shadow-lg", className),
156
+ children
157
+ }
158
+ ),
159
+ document.body
160
+ );
161
+ }
162
+ return /* @__PURE__ */ jsx2(
163
+ "div",
164
+ {
165
+ ref: panelRef,
166
+ role: "dialog",
167
+ "aria-modal": "false",
168
+ className: cn(
169
+ "absolute top-full z-50 mt-1 rounded-md border border-base-200 bg-base shadow-lg",
170
+ effectiveAlign === "end" ? "right-0" : "left-0",
171
+ className
172
+ ),
173
+ children
174
+ }
175
+ );
176
+ }
177
+
178
+ // src/components/shared/PopoverItem.tsx
179
+ import { forwardRef } from "react";
180
+ import { jsx as jsx3 } from "react/jsx-runtime";
181
+ var PopoverItem = forwardRef(
182
+ function PopoverItem2({ className, children, ...rest }, ref) {
183
+ return /* @__PURE__ */ jsx3(
184
+ "button",
185
+ {
186
+ ref,
187
+ type: "button",
188
+ className: cn(
189
+ "flex w-full cursor-pointer items-center gap-3 px-3 py-1.5 text-left text-xs hover:bg-base-accent",
190
+ className
191
+ ),
192
+ ...rest,
193
+ children
194
+ }
195
+ );
196
+ }
197
+ );
198
+
199
+ export {
200
+ Tooltip,
201
+ Kbd,
202
+ Popover,
203
+ PopoverItem
204
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getMediaProvider
3
- } from "./chunk-XQMDWXD6.js";
3
+ } from "./chunk-FRE2CCLV.js";
4
4
  import {
5
5
  cn
6
6
  } from "./chunk-7EOSDFS3.js";
@@ -72,44 +72,10 @@ function SettingsIcon({ size = 14 }) {
72
72
  return /* @__PURE__ */ jsx(Settings, { size });
73
73
  }
74
74
 
75
- // src/components/shared/IconButton.tsx
76
- import { forwardRef } from "react";
77
- import { jsx as jsx2 } from "react/jsx-runtime";
78
- var sizeClasses = {
79
- sm: "h-chrome-sm w-chrome-sm",
80
- md: "h-chrome-md w-chrome-md",
81
- lg: "h-chrome-lg w-chrome-lg"
82
- };
83
- var intentClasses = {
84
- default: "text-base-contrast-light hover:text-base-contrast",
85
- destructive: "text-destructive hover:text-destructive-hover",
86
- primary: "text-primary/60 hover:text-primary"
87
- };
88
- var IconButton = forwardRef(
89
- function IconButton2({ icon, label, size = "md", intent = "default", className, ...rest }, ref) {
90
- return /* @__PURE__ */ jsx2(
91
- "button",
92
- {
93
- ref,
94
- type: "button",
95
- className: cn(
96
- "cursor-pointer flex items-center justify-center rounded transition-colors",
97
- sizeClasses[size],
98
- intentClasses[intent],
99
- className
100
- ),
101
- "aria-label": label,
102
- ...rest,
103
- children: icon
104
- }
105
- );
106
- }
107
- );
108
-
109
75
  // src/components/shared/FormLabel.tsx
110
- import { jsx as jsx3 } from "react/jsx-runtime";
76
+ import { jsx as jsx2 } from "react/jsx-runtime";
111
77
  function FormLabel({ htmlFor, children, className }) {
112
- return /* @__PURE__ */ jsx3(
78
+ return /* @__PURE__ */ jsx2(
113
79
  "label",
114
80
  {
115
81
  htmlFor,
@@ -120,14 +86,14 @@ function FormLabel({ htmlFor, children, className }) {
120
86
  }
121
87
 
122
88
  // src/components/shared/Input.tsx
123
- import { forwardRef as forwardRef2, useId } from "react";
124
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
125
- var Input = forwardRef2(function Input2({ label, value, onChange, type = "text", className, disabled, error, ...rest }, ref) {
89
+ import { forwardRef, useId } from "react";
90
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
91
+ var Input = forwardRef(function Input2({ label, value, onChange, type = "text", className, disabled, error, ...rest }, ref) {
126
92
  const id = useId();
127
93
  const errorId = `${id}-error`;
128
94
  return /* @__PURE__ */ jsxs("div", { className, children: [
129
- /* @__PURE__ */ jsx4(FormLabel, { htmlFor: id, children: label }),
130
- /* @__PURE__ */ jsx4(
95
+ /* @__PURE__ */ jsx3(FormLabel, { htmlFor: id, children: label }),
96
+ /* @__PURE__ */ jsx3(
131
97
  "input",
132
98
  {
133
99
  ref,
@@ -146,7 +112,7 @@ var Input = forwardRef2(function Input2({ label, value, onChange, type = "text",
146
112
  ...rest
147
113
  }
148
114
  ),
149
- error && /* @__PURE__ */ jsx4("p", { id: errorId, className: "mt-1 text-xs text-red-600", children: error })
115
+ error && /* @__PURE__ */ jsx3("p", { id: errorId, className: "mt-1 text-xs text-red-600", children: error })
150
116
  ] });
151
117
  });
152
118
 
@@ -157,7 +123,6 @@ export {
157
123
  AddIcon,
158
124
  DeleteIcon,
159
125
  SettingsIcon,
160
- IconButton,
161
126
  FormLabel,
162
127
  Input
163
128
  };