@burojs/ui-shadcn 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +26 -7
  2. package/dist/bulk-summary-CwSd6Yjb.cjs +108 -0
  3. package/dist/bulk-summary-CwSd6Yjb.cjs.map +1 -0
  4. package/dist/bulk-summary-GxMDP4bw.js +108 -0
  5. package/dist/bulk-summary-GxMDP4bw.js.map +1 -0
  6. package/dist/button-BxnnE28H.cjs +55 -0
  7. package/dist/button-BxnnE28H.cjs.map +1 -0
  8. package/dist/button-MWVeZaqj.js +44 -0
  9. package/dist/button-MWVeZaqj.js.map +1 -0
  10. package/dist/candidates-gate-CX2PBu6l.js +63 -0
  11. package/dist/candidates-gate-CX2PBu6l.js.map +1 -0
  12. package/dist/candidates-gate-DiFI52r7.cjs +70 -0
  13. package/dist/candidates-gate-DiFI52r7.cjs.map +1 -0
  14. package/dist/candidates-relation-picker-CBsVcnPR.js +82 -0
  15. package/dist/candidates-relation-picker-CBsVcnPR.js.map +1 -0
  16. package/dist/candidates-relation-picker-Dd_GVLlu.cjs +84 -0
  17. package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +1 -0
  18. package/dist/check-confirm-dialog-BRByTFpg.cjs +1534 -0
  19. package/dist/check-confirm-dialog-BRByTFpg.cjs.map +1 -0
  20. package/dist/check-confirm-dialog-CkaVTPT9.js +1232 -0
  21. package/dist/check-confirm-dialog-CkaVTPT9.js.map +1 -0
  22. package/dist/dialog-B_rt9S1c.js +63 -0
  23. package/dist/dialog-B_rt9S1c.js.map +1 -0
  24. package/dist/dialog-DRNUq9EA.cjs +112 -0
  25. package/dist/dialog-DRNUq9EA.cjs.map +1 -0
  26. package/dist/filter-builder-B3mZFL-K.cjs +1761 -0
  27. package/dist/filter-builder-B3mZFL-K.cjs.map +1 -0
  28. package/dist/filter-builder-DOnaXNfP.js +1605 -0
  29. package/dist/filter-builder-DOnaXNfP.js.map +1 -0
  30. package/dist/flow-runtime-B6zOHYqI.cjs +255 -0
  31. package/dist/flow-runtime-B6zOHYqI.cjs.map +1 -0
  32. package/dist/flow-runtime-D8h99FSg.js +252 -0
  33. package/dist/flow-runtime-D8h99FSg.js.map +1 -0
  34. package/dist/flow-screen-CX05b3G9.cjs +705 -0
  35. package/dist/flow-screen-CX05b3G9.cjs.map +1 -0
  36. package/dist/flow-screen-CmbARSqg.js +701 -0
  37. package/dist/flow-screen-CmbARSqg.js.map +1 -0
  38. package/dist/index.cjs +6453 -17494
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +3754 -3621
  41. package/dist/index.d.ts +3754 -3621
  42. package/dist/index.js +6217 -17219
  43. package/dist/index.js.map +1 -1
  44. package/dist/inline-flow-host-B7U1OJUe.cjs +112 -0
  45. package/dist/inline-flow-host-B7U1OJUe.cjs.map +1 -0
  46. package/dist/inline-flow-host-C3H2UpSA.js +112 -0
  47. package/dist/inline-flow-host-C3H2UpSA.js.map +1 -0
  48. package/dist/input-BI0czGyI.cjs +23 -0
  49. package/dist/input-BI0czGyI.cjs.map +1 -0
  50. package/dist/input-CUPStzZO.js +17 -0
  51. package/dist/input-CUPStzZO.js.map +1 -0
  52. package/dist/label-66W1qbke.js +29 -0
  53. package/dist/label-66W1qbke.js.map +1 -0
  54. package/dist/label-w6JYf2cF.cjs +36 -0
  55. package/dist/label-w6JYf2cF.cjs.map +1 -0
  56. package/dist/layout/theme-storage-key.cjs +5 -10
  57. package/dist/layout/theme-storage-key.cjs.map +1 -1
  58. package/dist/layout/theme-storage-key.d.cts +2 -23
  59. package/dist/layout/theme-storage-key.d.ts +2 -23
  60. package/dist/layout/theme-storage-key.js +5 -2
  61. package/dist/layout/theme-storage-key.js.map +1 -1
  62. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +14 -0
  63. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +1 -0
  64. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +19 -0
  65. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +1 -0
  66. package/dist/markdown/index.cjs +424 -511
  67. package/dist/markdown/index.cjs.map +1 -1
  68. package/dist/markdown/index.d.cts +1072 -34
  69. package/dist/markdown/index.d.ts +1072 -34
  70. package/dist/markdown/index.js +411 -459
  71. package/dist/markdown/index.js.map +1 -1
  72. package/dist/page-flow-route-CQ7HsbBm.cjs +277 -0
  73. package/dist/page-flow-route-CQ7HsbBm.cjs.map +1 -0
  74. package/dist/page-flow-route-CyXSvXSE.js +277 -0
  75. package/dist/page-flow-route-CyXSvXSE.js.map +1 -0
  76. package/dist/peek-filter-CnqHwTMU.cjs +51 -0
  77. package/dist/peek-filter-CnqHwTMU.cjs.map +1 -0
  78. package/dist/peek-filter-ErLfO153.js +28 -0
  79. package/dist/peek-filter-ErLfO153.js.map +1 -0
  80. package/dist/place-node--GdsP688.js +7315 -0
  81. package/dist/place-node--GdsP688.js.map +1 -0
  82. package/dist/place-node-SIp4XYQH.cjs +7833 -0
  83. package/dist/place-node-SIp4XYQH.cjs.map +1 -0
  84. package/dist/related-list-cards-B6x9HQQp.js +42 -0
  85. package/dist/related-list-cards-B6x9HQQp.js.map +1 -0
  86. package/dist/related-list-cards-nxuODlIA.cjs +42 -0
  87. package/dist/related-list-cards-nxuODlIA.cjs.map +1 -0
  88. package/dist/related-list-timeline-BYJKEbmA.cjs +142 -0
  89. package/dist/related-list-timeline-BYJKEbmA.cjs.map +1 -0
  90. package/dist/related-list-timeline-DEdfd-rr.js +142 -0
  91. package/dist/related-list-timeline-DEdfd-rr.js.map +1 -0
  92. package/dist/related-rows-B4levHcR.js +186 -0
  93. package/dist/related-rows-B4levHcR.js.map +1 -0
  94. package/dist/related-rows-zh5f_4QR.cjs +209 -0
  95. package/dist/related-rows-zh5f_4QR.cjs.map +1 -0
  96. package/dist/relation-create-BOaUe12G.cjs +362 -0
  97. package/dist/relation-create-BOaUe12G.cjs.map +1 -0
  98. package/dist/relation-create-Bg-eEJLs.js +337 -0
  99. package/dist/relation-create-Bg-eEJLs.js.map +1 -0
  100. package/dist/relation-many-picker-B-NCEUgX.cjs +465 -0
  101. package/dist/relation-many-picker-B-NCEUgX.cjs.map +1 -0
  102. package/dist/relation-many-picker-gdAOD-qK.js +464 -0
  103. package/dist/relation-many-picker-gdAOD-qK.js.map +1 -0
  104. package/dist/relation-picker-BAY77ivO.js +462 -0
  105. package/dist/relation-picker-BAY77ivO.js.map +1 -0
  106. package/dist/relation-picker-Da_uhFvA.cjs +479 -0
  107. package/dist/relation-picker-Da_uhFvA.cjs.map +1 -0
  108. package/dist/relation-picker-dialog-M0pQaWlp.cjs +300 -0
  109. package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +1 -0
  110. package/dist/relation-picker-dialog-hA9rYrZB.js +299 -0
  111. package/dist/relation-picker-dialog-hA9rYrZB.js.map +1 -0
  112. package/dist/resource-page-router-OSQyRA5b.cjs +1779 -0
  113. package/dist/resource-page-router-OSQyRA5b.cjs.map +1 -0
  114. package/dist/resource-page-router-T4vS5gbJ.js +1582 -0
  115. package/dist/resource-page-router-T4vS5gbJ.js.map +1 -0
  116. package/dist/rich-text-DFerywgz.js +457 -0
  117. package/dist/rich-text-DFerywgz.js.map +1 -0
  118. package/dist/rich-text-vTyVjDoa.cjs +460 -0
  119. package/dist/rich-text-vTyVjDoa.cjs.map +1 -0
  120. package/dist/search-access-B7G5oR8n.cjs +812 -0
  121. package/dist/search-access-B7G5oR8n.cjs.map +1 -0
  122. package/dist/search-access-CURsWn-U.js +669 -0
  123. package/dist/search-access-CURsWn-U.js.map +1 -0
  124. package/dist/shell-BPlk8hEa.js +205 -0
  125. package/dist/shell-BPlk8hEa.js.map +1 -0
  126. package/dist/shell-DxhlsPwm.cjs +271 -0
  127. package/dist/shell-DxhlsPwm.cjs.map +1 -0
  128. package/dist/skeleton-U2noefj5.js +13 -0
  129. package/dist/skeleton-U2noefj5.js.map +1 -0
  130. package/dist/skeleton-_1gP7wan.cjs +18 -0
  131. package/dist/skeleton-_1gP7wan.cjs.map +1 -0
  132. package/dist/styles.css +255 -17
  133. package/dist/theme/theme-css.cjs +95 -96
  134. package/dist/theme/theme-css.cjs.map +1 -1
  135. package/dist/theme/theme-css.d.cts +10 -8
  136. package/dist/theme/theme-css.d.ts +10 -8
  137. package/dist/theme/theme-css.js +94 -94
  138. package/dist/theme/theme-css.js.map +1 -1
  139. package/dist/theme/theme-storage.cjs +34 -39
  140. package/dist/theme/theme-storage.cjs.map +1 -1
  141. package/dist/theme/theme-storage.d.cts +3 -1
  142. package/dist/theme/theme-storage.d.ts +3 -1
  143. package/dist/theme/theme-storage.js +33 -37
  144. package/dist/theme/theme-storage.js.map +1 -1
  145. package/dist/theme-storage-key-pYlp4aDS.d.cts +31 -0
  146. package/dist/theme-storage-key-pYlp4aDS.d.ts +31 -0
  147. package/dist/toaster-C7CiYA4Z.cjs +248 -0
  148. package/dist/toaster-C7CiYA4Z.cjs.map +1 -0
  149. package/dist/toaster-CfCRLgR-.js +164 -0
  150. package/dist/toaster-CfCRLgR-.js.map +1 -0
  151. package/dist/use-dialog-filter-state-BBCC0OEK.cjs +92 -0
  152. package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +1 -0
  153. package/dist/use-dialog-filter-state-BDcgS5Oz.js +87 -0
  154. package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +1 -0
  155. package/dist/use-theme-DFlpXils.cjs +257 -0
  156. package/dist/use-theme-DFlpXils.cjs.map +1 -0
  157. package/dist/use-theme-dnE3Od3I.js +175 -0
  158. package/dist/use-theme-dnE3Od3I.js.map +1 -0
  159. package/package.json +18 -17
  160. package/dist/chunk-ABUQPOOE.js +0 -11
  161. package/dist/chunk-ABUQPOOE.js.map +0 -1
  162. package/dist/chunk-DBGGWBFG.cjs +0 -199
  163. package/dist/chunk-DBGGWBFG.cjs.map +0 -1
  164. package/dist/chunk-FMX4GLCQ.js +0 -6
  165. package/dist/chunk-FMX4GLCQ.js.map +0 -1
  166. package/dist/chunk-UC57Z243.js +0 -143
  167. package/dist/chunk-UC57Z243.js.map +0 -1
  168. package/dist/chunk-UPGPO75B.js +0 -164
  169. package/dist/chunk-UPGPO75B.js.map +0 -1
  170. package/dist/chunk-VXJAXJ3U.cjs +0 -8
  171. package/dist/chunk-VXJAXJ3U.cjs.map +0 -1
  172. package/dist/chunk-W7AEYE7A.cjs +0 -13
  173. package/dist/chunk-W7AEYE7A.cjs.map +0 -1
  174. package/dist/chunk-YF5IW525.cjs +0 -155
  175. package/dist/chunk-YF5IW525.cjs.map +0 -1
  176. package/dist/rich-text-6V7WZ3WV.cjs +0 -518
  177. package/dist/rich-text-6V7WZ3WV.cjs.map +0 -1
  178. package/dist/rich-text-N2FZEJJV.js +0 -491
  179. package/dist/rich-text-N2FZEJJV.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ //#region src/theme/theme-css.d.ts
1
2
  /**
2
3
  * Pure functions for handling a pasted tweakcn theme.
3
4
  *
@@ -20,15 +21,15 @@
20
21
  * silently stops working, with no error anywhere.
21
22
  */
22
23
  type ThemeRecord = {
23
- id: string;
24
- name: string;
25
- css: string;
24
+ id: string;
25
+ name: string;
26
+ css: string;
26
27
  };
27
28
  type ThemeValidationResult = {
28
- ok: true;
29
+ ok: true;
29
30
  } | {
30
- ok: false;
31
- reason: string;
31
+ ok: false;
32
+ reason: string;
32
33
  };
33
34
  /**
34
35
  * Reduces a pasted tweakcn theme to just its `:root { ... }` and
@@ -45,5 +46,6 @@ declare function stripThemeCss(raw: string): string;
45
46
  * no `:root` block at all.
46
47
  */
47
48
  declare function validateThemeCss(css: string): ThemeValidationResult;
48
-
49
- export { type ThemeRecord, type ThemeValidationResult, stripThemeCss, validateThemeCss };
49
+ //#endregion
50
+ export { ThemeRecord, ThemeValidationResult, stripThemeCss, validateThemeCss };
51
+ //# sourceMappingURL=theme-css.d.ts.map
@@ -1,102 +1,102 @@
1
- // src/theme/theme-css.ts
1
+ //#region src/theme/theme-css.ts
2
2
  function parseTopLevelBlocks(src) {
3
- const n = src.length;
4
- const blocks = [];
5
- let i = 0;
6
- const skipComment = (pos) => {
7
- const end = src.indexOf("*/", pos + 2);
8
- return end === -1 ? n : end + 2;
9
- };
10
- const skipString = (pos) => {
11
- const quote = src[pos];
12
- let j = pos + 1;
13
- while (j < n && src[j] !== quote) {
14
- if (src[j] === "\\") j++;
15
- j++;
16
- }
17
- return j < n ? j + 1 : n;
18
- };
19
- while (i < n) {
20
- while (i < n) {
21
- const ch = src[i];
22
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
23
- i++;
24
- continue;
25
- }
26
- if (src.startsWith("/*", i)) {
27
- i = skipComment(i);
28
- continue;
29
- }
30
- break;
31
- }
32
- if (i >= n) break;
33
- const statementStart = i;
34
- let head = "";
35
- while (i < n && src[i] !== "{" && src[i] !== ";") {
36
- if (src.startsWith("/*", i)) {
37
- i = skipComment(i);
38
- continue;
39
- }
40
- if (src[i] === '"' || src[i] === "'") {
41
- const strStart = i;
42
- i = skipString(i);
43
- head += src.slice(strStart, i);
44
- continue;
45
- }
46
- head += src[i];
47
- i++;
48
- }
49
- if (i >= n) break;
50
- if (src[i] === ";") {
51
- i++;
52
- continue;
53
- }
54
- let depth = 1;
55
- let j = i + 1;
56
- while (j < n && depth > 0) {
57
- if (src.startsWith("/*", j)) {
58
- j = skipComment(j);
59
- continue;
60
- }
61
- if (src[j] === '"' || src[j] === "'") {
62
- j = skipString(j);
63
- continue;
64
- }
65
- if (src[j] === "{") depth++;
66
- else if (src[j] === "}") depth--;
67
- j++;
68
- }
69
- blocks.push({ selector: head.trim(), raw: src.slice(statementStart, j) });
70
- i = j;
71
- }
72
- return blocks;
3
+ const n = src.length;
4
+ const blocks = [];
5
+ let i = 0;
6
+ const skipComment = (pos) => {
7
+ const end = src.indexOf("*/", pos + 2);
8
+ return end === -1 ? n : end + 2;
9
+ };
10
+ const skipString = (pos) => {
11
+ const quote = src[pos];
12
+ let j = pos + 1;
13
+ while (j < n && src[j] !== quote) {
14
+ if (src[j] === "\\") j++;
15
+ j++;
16
+ }
17
+ return j < n ? j + 1 : n;
18
+ };
19
+ while (i < n) {
20
+ while (i < n) {
21
+ const ch = src[i];
22
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
23
+ i++;
24
+ continue;
25
+ }
26
+ if (src.startsWith("/*", i)) {
27
+ i = skipComment(i);
28
+ continue;
29
+ }
30
+ break;
31
+ }
32
+ if (i >= n) break;
33
+ const statementStart = i;
34
+ let head = "";
35
+ while (i < n && src[i] !== "{" && src[i] !== ";") {
36
+ if (src.startsWith("/*", i)) {
37
+ i = skipComment(i);
38
+ continue;
39
+ }
40
+ if (src[i] === "\"" || src[i] === "'") {
41
+ const strStart = i;
42
+ i = skipString(i);
43
+ head += src.slice(strStart, i);
44
+ continue;
45
+ }
46
+ head += src[i];
47
+ i++;
48
+ }
49
+ if (i >= n) break;
50
+ if (src[i] === ";") {
51
+ i++;
52
+ continue;
53
+ }
54
+ let depth = 1;
55
+ let j = i + 1;
56
+ while (j < n && depth > 0) {
57
+ if (src.startsWith("/*", j)) {
58
+ j = skipComment(j);
59
+ continue;
60
+ }
61
+ if (src[j] === "\"" || src[j] === "'") {
62
+ j = skipString(j);
63
+ continue;
64
+ }
65
+ if (src[j] === "{") depth++;
66
+ else if (src[j] === "}") depth--;
67
+ j++;
68
+ }
69
+ blocks.push({
70
+ selector: head.trim(),
71
+ raw: src.slice(statementStart, j)
72
+ });
73
+ i = j;
74
+ }
75
+ return blocks;
73
76
  }
74
77
  function stripThemeCss(raw) {
75
- const normalized = raw.replace(/\r\n/g, "\n");
76
- const blocks = parseTopLevelBlocks(normalized);
77
- const kept = blocks.filter((b) => b.selector === ":root" || b.selector === ".dark");
78
- return kept.map((b) => b.raw).join("\n\n");
78
+ return parseTopLevelBlocks(raw.replace(/\r\n/g, "\n")).filter((b) => b.selector === ":root" || b.selector === ".dark").map((b) => b.raw).join("\n\n");
79
79
  }
80
80
  function validateThemeCss(css) {
81
- const normalized = css.replace(/\r\n/g, "\n");
82
- if (normalized.trim().length === 0) {
83
- return { ok: false, reason: "Theme CSS is empty." };
84
- }
85
- const blocks = parseTopLevelBlocks(normalized);
86
- const hasDark = blocks.some((b) => b.selector === ".dark");
87
- const hasRoot = blocks.some((b) => b.selector === ":root");
88
- if (!hasDark) {
89
- return {
90
- ok: false,
91
- reason: `Theme has no ".dark" block. Without it, the theme's ":root" values override the app's ".dark" styles at equal CSS specificity, and dark mode silently stops working.`
92
- };
93
- }
94
- if (!hasRoot) {
95
- return { ok: false, reason: 'Theme has no ":root" block \u2014 light-mode values are missing.' };
96
- }
97
- return { ok: true };
81
+ const normalized = css.replace(/\r\n/g, "\n");
82
+ if (normalized.trim().length === 0) return {
83
+ ok: false,
84
+ reason: "Theme CSS is empty."
85
+ };
86
+ const blocks = parseTopLevelBlocks(normalized);
87
+ const hasDark = blocks.some((b) => b.selector === ".dark");
88
+ const hasRoot = blocks.some((b) => b.selector === ":root");
89
+ if (!hasDark) return {
90
+ ok: false,
91
+ reason: "Theme has no \".dark\" block. Without it, the theme's \":root\" values override the app's \".dark\" styles at equal CSS specificity, and dark mode silently stops working."
92
+ };
93
+ if (!hasRoot) return {
94
+ ok: false,
95
+ reason: "Theme has no \":root\" block — light-mode values are missing."
96
+ };
97
+ return { ok: true };
98
98
  }
99
-
99
+ //#endregion
100
100
  export { stripThemeCss, validateThemeCss };
101
- //# sourceMappingURL=theme-css.js.map
101
+
102
102
  //# sourceMappingURL=theme-css.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/theme-css.ts"],"names":[],"mappings":"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","file":"theme-css.js","sourcesContent":["/**\n * Pure functions for handling a pasted tweakcn theme.\n *\n * tweakcn's \"Copy code\" button emits six constructs. Only two of them mean\n * anything once dropped into a browser at runtime (not build time, since\n * there is no Tailwind build step for pasted CSS):\n *\n * @import \"tailwindcss\"; -> 404s if kept (no such route at runtime)\n * @custom-variant dark (...); -> meaningless outside a Tailwind build\n * :root { ... } -> survives, sets light-mode custom properties\n * .dark { ... } -> survives, sets dark-mode custom properties\n * @theme inline { ... } -> meaningless outside a Tailwind build\n * @layer base { ...; @apply ...; } -> @apply is not real CSS, dead weight\n *\n * `stripThemeCss` throws away everything except the `:root` and `.dark`\n * blocks. `validateThemeCss` catches the one failure mode that is otherwise\n * invisible: a theme with no `.dark` block. `.dark` and `:root` share equal\n * specificity, so injecting a `:root`-only theme *after* the app's own\n * stylesheet makes its `:root` win over `html.dark`'s rules too — dark mode\n * silently stops working, with no error anywhere.\n */\n\nexport type ThemeRecord = { id: string; name: string; css: string };\n\nexport type ThemeValidationResult = { ok: true } | { ok: false; reason: string };\n\ninterface TopLevelBlock {\n /** The selector/at-rule head, trimmed (e.g. \":root\", \".dark\", \"@theme inline\"). */\n selector: string;\n /** The full source slice for this block, selector through closing brace. */\n raw: string;\n}\n\n/**\n * Walks the source once, splitting it into top-level statements. Any\n * statement terminated by `;` (e.g. `@import \"tailwindcss\";`,\n * `@custom-variant dark (&:is(.dark *));`) is dropped outright — it never\n * produces a block. Any statement with a `{ ... }` body is returned as a\n * block, with brace depth tracked through the body so a nested block (e.g.\n * `@layer base { * { ... } body { ... } }`) is consumed as a single unit\n * instead of stopping at its first inner `}`.\n *\n * Comments and quoted strings are skipped transparently everywhere braces\n * are counted, so a brace inside `/* ... *\\/` or inside a string value never\n * perturbs the depth count.\n *\n * Unclosed input (an opened `{`, `/*`, or quote that never finds its\n * closer) is NOT dropped: the depth/comment/string scan simply runs off\n * the end of the string, and the block gets pushed with its `raw` spanning\n * from the opening selector all the way to EOF — swallowing whatever\n * well-formed blocks would otherwise have followed it. Concretely: an\n * unclosed `:root {` earlier in the source will absorb a perfectly valid\n * `.dark { ... }` that comes after it, so `.dark` never becomes its own\n * top-level block. This is a deliberately-left gap, not a bug: the\n * consequence is caught downstream by `validateThemeCss`'s \"no .dark\"\n * check, so truncated/corrupt input fails closed rather than corrupting\n * silently. The one sharp edge is the *reason string* it fails with — a\n * clipboard read that came back truncated will be reported as \"no .dark\n * block\", not \"your paste was cut off\", which is worth remembering when\n * debugging a fetch step that feeds this function.\n */\nfunction parseTopLevelBlocks(src: string): TopLevelBlock[] {\n const n = src.length;\n const blocks: TopLevelBlock[] = [];\n let i = 0;\n\n const skipComment = (pos: number): number => {\n const end = src.indexOf('*/', pos + 2);\n return end === -1 ? n : end + 2;\n };\n\n const skipString = (pos: number): number => {\n const quote = src[pos];\n let j = pos + 1;\n while (j < n && src[j] !== quote) {\n if (src[j] === '\\\\') j++;\n j++;\n }\n return j < n ? j + 1 : n;\n };\n\n while (i < n) {\n // Skip whitespace and comments between statements.\n while (i < n) {\n const ch = src[i];\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n i++;\n continue;\n }\n if (src.startsWith('/*', i)) {\n i = skipComment(i);\n continue;\n }\n break;\n }\n if (i >= n) break;\n\n const statementStart = i;\n let head = '';\n\n // Scan the head (selector / at-rule prelude) up to a top-level `{` or `;`.\n while (i < n && src[i] !== '{' && src[i] !== ';') {\n if (src.startsWith('/*', i)) {\n i = skipComment(i);\n continue;\n }\n if (src[i] === '\"' || src[i] === \"'\") {\n const strStart = i;\n i = skipString(i);\n head += src.slice(strStart, i);\n continue;\n }\n head += src[i];\n i++;\n }\n\n // Ran off the end while scanning the head itself (no `{` or `;` ever\n // showed up) — this fragment genuinely IS dropped, since there is no\n // `{` to even start a block from. Contrast with an opened-but-unclosed\n // `{ ... }` body below, which is NOT dropped — see the block comment\n // on this function.\n if (i >= n) break;\n\n if (src[i] === ';') {\n i++; // e.g. `@import \"tailwindcss\";` — drop, no block produced.\n continue;\n }\n\n // src[i] === '{' — consume the whole block, tracking nested brace depth.\n let depth = 1;\n let j = i + 1;\n while (j < n && depth > 0) {\n if (src.startsWith('/*', j)) {\n j = skipComment(j);\n continue;\n }\n if (src[j] === '\"' || src[j] === \"'\") {\n j = skipString(j);\n continue;\n }\n if (src[j] === '{') depth++;\n else if (src[j] === '}') depth--;\n j++;\n }\n\n // Pushed unconditionally — if `depth` never reached 0 the loop above\n // only stopped because `j` hit `n` (EOF). That's intentional: see the\n // \"Unclosed input\" paragraph on this function's doc comment.\n blocks.push({ selector: head.trim(), raw: src.slice(statementStart, j) });\n i = j;\n }\n\n return blocks;\n}\n\n/**\n * Reduces a pasted tweakcn theme to just its `:root { ... }` and\n * `.dark { ... }` blocks, in source order. Everything else (imports,\n * `@custom-variant`, `@theme inline`, `@layer base`) is dropped. Idempotent:\n * running it again on its own output is a no-op.\n */\nexport function stripThemeCss(raw: string): string {\n const normalized = raw.replace(/\\r\\n/g, '\\n');\n const blocks = parseTopLevelBlocks(normalized);\n const kept = blocks.filter((b) => b.selector === ':root' || b.selector === '.dark');\n return kept.map((b) => b.raw).join('\\n\\n');\n}\n\n/**\n * Validates that a (normally already-stripped) theme is safe to inject.\n * The load-bearing check is `.dark`: without it, a theme's `:root` values\n * override the app's `.dark` rules at equal specificity, and dark mode\n * breaks silently. Also rejects empty input and (defensively) a theme with\n * no `:root` block at all.\n */\nexport function validateThemeCss(css: string): ThemeValidationResult {\n const normalized = css.replace(/\\r\\n/g, '\\n');\n if (normalized.trim().length === 0) {\n return { ok: false, reason: 'Theme CSS is empty.' };\n }\n\n const blocks = parseTopLevelBlocks(normalized);\n const hasDark = blocks.some((b) => b.selector === '.dark');\n const hasRoot = blocks.some((b) => b.selector === ':root');\n\n if (!hasDark) {\n return {\n ok: false,\n reason:\n 'Theme has no \".dark\" block. Without it, the theme\\'s \":root\" values ' +\n 'override the app\\'s \".dark\" styles at equal CSS specificity, and dark ' +\n 'mode silently stops working.',\n };\n }\n\n if (!hasRoot) {\n return { ok: false, reason: 'Theme has no \":root\" block — light-mode values are missing.' };\n }\n\n return { ok: true };\n}\n"]}
1
+ {"version":3,"file":"theme-css.js","names":[],"sources":["../../src/theme/theme-css.ts"],"sourcesContent":["/**\n * Pure functions for handling a pasted tweakcn theme.\n *\n * tweakcn's \"Copy code\" button emits six constructs. Only two of them mean\n * anything once dropped into a browser at runtime (not build time, since\n * there is no Tailwind build step for pasted CSS):\n *\n * @import \"tailwindcss\"; -> 404s if kept (no such route at runtime)\n * @custom-variant dark (...); -> meaningless outside a Tailwind build\n * :root { ... } -> survives, sets light-mode custom properties\n * .dark { ... } -> survives, sets dark-mode custom properties\n * @theme inline { ... } -> meaningless outside a Tailwind build\n * @layer base { ...; @apply ...; } -> @apply is not real CSS, dead weight\n *\n * `stripThemeCss` throws away everything except the `:root` and `.dark`\n * blocks. `validateThemeCss` catches the one failure mode that is otherwise\n * invisible: a theme with no `.dark` block. `.dark` and `:root` share equal\n * specificity, so injecting a `:root`-only theme *after* the app's own\n * stylesheet makes its `:root` win over `html.dark`'s rules too — dark mode\n * silently stops working, with no error anywhere.\n */\n\nexport type ThemeRecord = { id: string; name: string; css: string };\n\nexport type ThemeValidationResult = { ok: true } | { ok: false; reason: string };\n\ninterface TopLevelBlock {\n /** The selector/at-rule head, trimmed (e.g. \":root\", \".dark\", \"@theme inline\"). */\n selector: string;\n /** The full source slice for this block, selector through closing brace. */\n raw: string;\n}\n\n/**\n * Walks the source once, splitting it into top-level statements. Any\n * statement terminated by `;` (e.g. `@import \"tailwindcss\";`,\n * `@custom-variant dark (&:is(.dark *));`) is dropped outright — it never\n * produces a block. Any statement with a `{ ... }` body is returned as a\n * block, with brace depth tracked through the body so a nested block (e.g.\n * `@layer base { * { ... } body { ... } }`) is consumed as a single unit\n * instead of stopping at its first inner `}`.\n *\n * Comments and quoted strings are skipped transparently everywhere braces\n * are counted, so a brace inside `/* ... *\\/` or inside a string value never\n * perturbs the depth count.\n *\n * Unclosed input (an opened `{`, `/*`, or quote that never finds its\n * closer) is NOT dropped: the depth/comment/string scan simply runs off\n * the end of the string, and the block gets pushed with its `raw` spanning\n * from the opening selector all the way to EOF — swallowing whatever\n * well-formed blocks would otherwise have followed it. Concretely: an\n * unclosed `:root {` earlier in the source will absorb a perfectly valid\n * `.dark { ... }` that comes after it, so `.dark` never becomes its own\n * top-level block. This is a deliberately-left gap, not a bug: the\n * consequence is caught downstream by `validateThemeCss`'s \"no .dark\"\n * check, so truncated/corrupt input fails closed rather than corrupting\n * silently. The one sharp edge is the *reason string* it fails with — a\n * clipboard read that came back truncated will be reported as \"no .dark\n * block\", not \"your paste was cut off\", which is worth remembering when\n * debugging a fetch step that feeds this function.\n */\nfunction parseTopLevelBlocks(src: string): TopLevelBlock[] {\n const n = src.length;\n const blocks: TopLevelBlock[] = [];\n let i = 0;\n\n const skipComment = (pos: number): number => {\n const end = src.indexOf('*/', pos + 2);\n return end === -1 ? n : end + 2;\n };\n\n const skipString = (pos: number): number => {\n const quote = src[pos];\n let j = pos + 1;\n while (j < n && src[j] !== quote) {\n if (src[j] === '\\\\') j++;\n j++;\n }\n return j < n ? j + 1 : n;\n };\n\n while (i < n) {\n // Skip whitespace and comments between statements.\n while (i < n) {\n const ch = src[i];\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n i++;\n continue;\n }\n if (src.startsWith('/*', i)) {\n i = skipComment(i);\n continue;\n }\n break;\n }\n if (i >= n) break;\n\n const statementStart = i;\n let head = '';\n\n // Scan the head (selector / at-rule prelude) up to a top-level `{` or `;`.\n while (i < n && src[i] !== '{' && src[i] !== ';') {\n if (src.startsWith('/*', i)) {\n i = skipComment(i);\n continue;\n }\n if (src[i] === '\"' || src[i] === \"'\") {\n const strStart = i;\n i = skipString(i);\n head += src.slice(strStart, i);\n continue;\n }\n head += src[i];\n i++;\n }\n\n // Ran off the end while scanning the head itself (no `{` or `;` ever\n // showed up) — this fragment genuinely IS dropped, since there is no\n // `{` to even start a block from. Contrast with an opened-but-unclosed\n // `{ ... }` body below, which is NOT dropped — see the block comment\n // on this function.\n if (i >= n) break;\n\n if (src[i] === ';') {\n i++; // e.g. `@import \"tailwindcss\";` — drop, no block produced.\n continue;\n }\n\n // src[i] === '{' — consume the whole block, tracking nested brace depth.\n let depth = 1;\n let j = i + 1;\n while (j < n && depth > 0) {\n if (src.startsWith('/*', j)) {\n j = skipComment(j);\n continue;\n }\n if (src[j] === '\"' || src[j] === \"'\") {\n j = skipString(j);\n continue;\n }\n if (src[j] === '{') depth++;\n else if (src[j] === '}') depth--;\n j++;\n }\n\n // Pushed unconditionally — if `depth` never reached 0 the loop above\n // only stopped because `j` hit `n` (EOF). That's intentional: see the\n // \"Unclosed input\" paragraph on this function's doc comment.\n blocks.push({ selector: head.trim(), raw: src.slice(statementStart, j) });\n i = j;\n }\n\n return blocks;\n}\n\n/**\n * Reduces a pasted tweakcn theme to just its `:root { ... }` and\n * `.dark { ... }` blocks, in source order. Everything else (imports,\n * `@custom-variant`, `@theme inline`, `@layer base`) is dropped. Idempotent:\n * running it again on its own output is a no-op.\n */\nexport function stripThemeCss(raw: string): string {\n const normalized = raw.replace(/\\r\\n/g, '\\n');\n const blocks = parseTopLevelBlocks(normalized);\n const kept = blocks.filter((b) => b.selector === ':root' || b.selector === '.dark');\n return kept.map((b) => b.raw).join('\\n\\n');\n}\n\n/**\n * Validates that a (normally already-stripped) theme is safe to inject.\n * The load-bearing check is `.dark`: without it, a theme's `:root` values\n * override the app's `.dark` rules at equal specificity, and dark mode\n * breaks silently. Also rejects empty input and (defensively) a theme with\n * no `:root` block at all.\n */\nexport function validateThemeCss(css: string): ThemeValidationResult {\n const normalized = css.replace(/\\r\\n/g, '\\n');\n if (normalized.trim().length === 0) {\n return { ok: false, reason: 'Theme CSS is empty.' };\n }\n\n const blocks = parseTopLevelBlocks(normalized);\n const hasDark = blocks.some((b) => b.selector === '.dark');\n const hasRoot = blocks.some((b) => b.selector === ':root');\n\n if (!hasDark) {\n return {\n ok: false,\n reason:\n 'Theme has no \".dark\" block. Without it, the theme\\'s \":root\" values ' +\n 'override the app\\'s \".dark\" styles at equal CSS specificity, and dark ' +\n 'mode silently stops working.',\n };\n }\n\n if (!hasRoot) {\n return { ok: false, reason: 'Theme has no \":root\" block — light-mode values are missing.' };\n }\n\n return { ok: true };\n}\n"],"mappings":";AA6DA,SAAS,oBAAoB,KAA8B;CACzD,MAAM,IAAI,IAAI;CACd,MAAM,SAA0B,CAAC;CACjC,IAAI,IAAI;CAER,MAAM,eAAe,QAAwB;EAC3C,MAAM,MAAM,IAAI,QAAQ,MAAM,MAAM,CAAC;EACrC,OAAO,QAAQ,KAAK,IAAI,MAAM;CAChC;CAEA,MAAM,cAAc,QAAwB;EAC1C,MAAM,QAAQ,IAAI;EAClB,IAAI,IAAI,MAAM;EACd,OAAO,IAAI,KAAK,IAAI,OAAO,OAAO;GAChC,IAAI,IAAI,OAAO,MAAM;GACrB;EACF;EACA,OAAO,IAAI,IAAI,IAAI,IAAI;CACzB;CAEA,OAAO,IAAI,GAAG;EAEZ,OAAO,IAAI,GAAG;GACZ,MAAM,KAAK,IAAI;GACf,IAAI,OAAO,OAAO,OAAO,OAAQ,OAAO,QAAQ,OAAO,MAAM;IAC3D;IACA;GACF;GACA,IAAI,IAAI,WAAW,MAAM,CAAC,GAAG;IAC3B,IAAI,YAAY,CAAC;IACjB;GACF;GACA;EACF;EACA,IAAI,KAAK,GAAG;EAEZ,MAAM,iBAAiB;EACvB,IAAI,OAAO;EAGX,OAAO,IAAI,KAAK,IAAI,OAAO,OAAO,IAAI,OAAO,KAAK;GAChD,IAAI,IAAI,WAAW,MAAM,CAAC,GAAG;IAC3B,IAAI,YAAY,CAAC;IACjB;GACF;GACA,IAAI,IAAI,OAAO,QAAO,IAAI,OAAO,KAAK;IACpC,MAAM,WAAW;IACjB,IAAI,WAAW,CAAC;IAChB,QAAQ,IAAI,MAAM,UAAU,CAAC;IAC7B;GACF;GACA,QAAQ,IAAI;GACZ;EACF;EAOA,IAAI,KAAK,GAAG;EAEZ,IAAI,IAAI,OAAO,KAAK;GAClB;GACA;EACF;EAGA,IAAI,QAAQ;EACZ,IAAI,IAAI,IAAI;EACZ,OAAO,IAAI,KAAK,QAAQ,GAAG;GACzB,IAAI,IAAI,WAAW,MAAM,CAAC,GAAG;IAC3B,IAAI,YAAY,CAAC;IACjB;GACF;GACA,IAAI,IAAI,OAAO,QAAO,IAAI,OAAO,KAAK;IACpC,IAAI,WAAW,CAAC;IAChB;GACF;GACA,IAAI,IAAI,OAAO,KAAK;QACf,IAAI,IAAI,OAAO,KAAK;GACzB;EACF;EAKA,OAAO,KAAK;GAAE,UAAU,KAAK,KAAK;GAAG,KAAK,IAAI,MAAM,gBAAgB,CAAC;EAAE,CAAC;EACxE,IAAI;CACN;CAEA,OAAO;AACT;AAQA,SAAgB,cAAc,KAAqB;CAIjD,OAFe,oBADI,IAAI,QAAQ,SAAS,IACI,CAC1B,CAAC,CAAC,QAAQ,MAAM,EAAE,aAAa,WAAW,EAAE,aAAa,OACjE,CAAC,CAAC,KAAK,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,MAAM;AAC3C;AASA,SAAgB,iBAAiB,KAAoC;CACnE,MAAM,aAAa,IAAI,QAAQ,SAAS,IAAI;CAC5C,IAAI,WAAW,KAAK,CAAC,CAAC,WAAW,GAC/B,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAsB;CAGpD,MAAM,SAAS,oBAAoB,UAAU;CAC7C,MAAM,UAAU,OAAO,MAAM,MAAM,EAAE,aAAa,OAAO;CACzD,MAAM,UAAU,OAAO,MAAM,MAAM,EAAE,aAAa,OAAO;CAEzD,IAAI,CAAC,SACH,OAAO;EACL,IAAI;EACJ,QACE;CAGJ;CAGF,IAAI,CAAC,SACH,OAAO;EAAE,IAAI;EAAO,QAAQ;CAA8D;CAG5F,OAAO,EAAE,IAAI,KAAK;AACpB"}
@@ -1,51 +1,46 @@
1
- 'use strict';
2
-
3
- // src/theme/theme-storage.ts
4
- var THEME_CSS_STORAGE_KEY = "buro-theme-css";
5
- var THEME_CSS_STYLE_ELEMENT_ID = "buro-theme-css";
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/theme/theme-storage.ts
3
+ const THEME_CSS_STORAGE_KEY = "buro-theme-css";
4
+ const THEME_CSS_STYLE_ELEMENT_ID = "buro-theme-css";
6
5
  function readThemeCss() {
7
- if (typeof window === "undefined") return null;
8
- try {
9
- const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);
10
- return css && css.length > 0 ? css : null;
11
- } catch {
12
- return null;
13
- }
6
+ if (typeof window === "undefined") return null;
7
+ try {
8
+ const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);
9
+ return css && css.length > 0 ? css : null;
10
+ } catch {
11
+ return null;
12
+ }
14
13
  }
15
14
  function writeThemeCss(css) {
16
- if (typeof window === "undefined") return;
17
- try {
18
- if (css === null || css.length === 0) {
19
- window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);
20
- } else {
21
- window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);
22
- }
23
- } catch {
24
- }
15
+ if (typeof window === "undefined") return;
16
+ try {
17
+ if (css === null || css.length === 0) window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);
18
+ else window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);
19
+ } catch {}
25
20
  }
26
21
  function applyThemeCss(css) {
27
- if (typeof document === "undefined") return;
28
- const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);
29
- if (css === null || css.length === 0) {
30
- existing?.remove();
31
- return;
32
- }
33
- if (existing instanceof HTMLStyleElement) {
34
- existing.textContent = css;
35
- document.head.appendChild(existing);
36
- return;
37
- }
38
- existing?.remove();
39
- const style = document.createElement("style");
40
- style.id = THEME_CSS_STYLE_ELEMENT_ID;
41
- style.textContent = css;
42
- document.head.appendChild(style);
22
+ if (typeof document === "undefined") return;
23
+ const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);
24
+ if (css === null || css.length === 0) {
25
+ existing?.remove();
26
+ return;
27
+ }
28
+ if (existing instanceof HTMLStyleElement) {
29
+ existing.textContent = css;
30
+ document.head.appendChild(existing);
31
+ return;
32
+ }
33
+ existing?.remove();
34
+ const style = document.createElement("style");
35
+ style.id = THEME_CSS_STYLE_ELEMENT_ID;
36
+ style.textContent = css;
37
+ document.head.appendChild(style);
43
38
  }
44
-
39
+ //#endregion
45
40
  exports.THEME_CSS_STORAGE_KEY = THEME_CSS_STORAGE_KEY;
46
41
  exports.THEME_CSS_STYLE_ELEMENT_ID = THEME_CSS_STYLE_ELEMENT_ID;
47
42
  exports.applyThemeCss = applyThemeCss;
48
43
  exports.readThemeCss = readThemeCss;
49
44
  exports.writeThemeCss = writeThemeCss;
50
- //# sourceMappingURL=theme-storage.cjs.map
45
+
51
46
  //# sourceMappingURL=theme-storage.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/theme-storage.ts"],"names":[],"mappings":";;;AAuCO,IAAM,qBAAA,GAAwB;AAK9B,IAAM,0BAAA,GAA6B;AAQnC,SAAS,YAAA,GAA8B;AAC5C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,qBAAqB,CAAA;AAC7D,IAAA,OAAO,GAAA,IAAO,GAAA,CAAI,MAAA,GAAS,CAAA,GAAI,GAAA,GAAM,IAAA;AAAA,EACvC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAWO,SAAS,cAAc,GAAA,EAA0B;AACtD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG;AACpC,MAAA,MAAA,CAAO,YAAA,CAAa,WAAW,qBAAqB,CAAA;AAAA,IACtD,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,qBAAA,EAAuB,GAAG,CAAA;AAAA,IACxD;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAGR;AACF;AAcO,SAAS,cAAc,GAAA,EAA0B;AACtD,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,cAAA,CAAe,0BAA0B,CAAA;AAEnE,EAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG;AACpC,IAAA,QAAA,EAAU,MAAA,EAAO;AACjB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,oBAAoB,gBAAA,EAAkB;AACxC,IAAA,QAAA,CAAS,WAAA,GAAc,GAAA;AACvB,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,QAAQ,CAAA;AAClC,IAAA;AAAA,EACF;AACA,EAAA,QAAA,EAAU,MAAA,EAAO;AAEjB,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,EAAA,KAAA,CAAM,EAAA,GAAK,0BAAA;AACX,EAAA,KAAA,CAAM,WAAA,GAAc,GAAA;AACpB,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AACjC","file":"theme-storage.cjs","sourcesContent":["/**\n * Persists and applies the active PALETTE theme's CSS — separate concern\n * from `THEME_STORAGE_KEY` (`packages/ui-shadcn/src/layout/theme-storage-key.ts`),\n * which holds light/dark/system. That key answers \"which class goes on\n * `<html>`\"; this one answers \"what custom-property values does `:root`/\n * `.dark` carry\". A theme picker calling `writeThemeCss` must never touch\n * `THEME_STORAGE_KEY`, and the light/dark toggle must never touch this one —\n * each write path only ever calls its own key's functions, so there is no\n * shared code path where a bug could cross-contaminate them.\n *\n * The stored value is the theme's CSS **in full** (already reduced to just\n * `:root`/`.dark` by `stripThemeCss`), not a theme id. That is what makes a\n * pasted (tweakcn) theme and a built-in theme indistinguishable to this\n * module and to the bootstrap script: neither has to know the built-in\n * theme catalogue, ship it in the bundle, or fetch it over the network\n * before paint. The cost is that this module does NOT validate — a caller\n * that wants the \".dark block present\" guarantee must run `validateThemeCss`\n * itself before calling `writeThemeCss` (the theme picker does this; see its\n * task). `applyThemeCss` and the bootstrap script just inject whatever\n * string is there. That is deliberate, not an oversight: CSS parsing is\n * forgiving by construction — unknown/malformed declarations are dropped\n * rule-by-rule by the browser's own parser, not fatal — so blindly injecting\n * a corrupt or half-written string degrades to \"some rules don't apply\", not\n * a crash or a blank page. Re-validating on every read would also block\n * first paint on running `parseTopLevelBlocks` over an arbitrarily large\n * string, which is exactly the cost the bootstrap script exists to avoid.\n *\n * Deliberately its own module, no React import, same reasoning as\n * `theme-storage-key.ts`: the ONE consumer that needs `THEME_CSS_STORAGE_KEY`\n * and `applyThemeCss` outside a hook is the bootstrap `<script>` in the\n * playground's root `app/layout.tsx`, a Server Component that cannot import\n * anything carrying `'use client'`. This file has no hooks, so it can be\n * imported there directly via the `@burojs/ui-shadcn/theme-storage` subpath.\n */\n\n/**\n * The localStorage key for the active theme's CSS text. Kept apart from\n * `THEME_STORAGE_KEY` on purpose — see the module doc comment above.\n */\nexport const THEME_CSS_STORAGE_KEY = 'buro-theme-css';\n\n/** The id of the `<style>` element the bootstrap script and `applyThemeCss`\n * both create/update. Exported so a test can assert on it without\n * retyping the literal. */\nexport const THEME_CSS_STYLE_ELEMENT_ID = 'buro-theme-css';\n\n/**\n * Reads the persisted theme CSS, or `null` if nothing is stored, storage is\n * unavailable (SSR, or a browser with it disabled — e.g. some private-mode\n * configurations throw on access rather than just being empty), or the\n * stored value is an empty string (treated the same as absent).\n */\nexport function readThemeCss(): string | null {\n if (typeof window === 'undefined') return null;\n try {\n const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);\n return css && css.length > 0 ? css : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists (or, given `null`, clears) the theme CSS. Does not validate —\n * callers that need the \".dark present\" guarantee run `validateThemeCss`\n * first (see module doc comment). Swallows storage errors (quota exceeded\n * from an oversized paste, storage disabled, etc.): the in-memory\n * application via `applyThemeCss` already happened by the time a caller\n * persists, so a failed write loses the choice on next reload but does not\n * throw or break the current page.\n */\nexport function writeThemeCss(css: string | null): void {\n if (typeof window === 'undefined') return;\n try {\n if (css === null || css.length === 0) {\n window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);\n } else {\n window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);\n }\n } catch {\n // Quota exceeded, storage disabled, or a similar DOMException — nothing\n // persisted. The caller's in-memory `applyThemeCss` call still stands.\n }\n}\n\n/**\n * Creates, updates, or removes the `<style id=\"buro-theme-css\">`\n * element that carries the active theme's `:root`/`.dark` custom\n * properties. Given `null` (or an empty string), removes the element\n * entirely rather than leaving an empty tag behind.\n *\n * Order matters: this function always places the element at the END of\n * `<head>`, including when updating a style created by a prepaint bootstrap.\n * That lets an app call it after its own stylesheet has loaded so the theme's\n * equal-specificity `:root`/`.dark` rules win the cascade without creating a\n * duplicate element.\n */\nexport function applyThemeCss(css: string | null): void {\n if (typeof document === 'undefined') return;\n const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);\n\n if (css === null || css.length === 0) {\n existing?.remove();\n return;\n }\n\n if (existing instanceof HTMLStyleElement) {\n existing.textContent = css;\n document.head.appendChild(existing);\n return;\n }\n existing?.remove(); // defensive: same id but somehow not a <style>\n\n const style = document.createElement('style');\n style.id = THEME_CSS_STYLE_ELEMENT_ID;\n style.textContent = css;\n document.head.appendChild(style);\n}\n"]}
1
+ {"version":3,"file":"theme-storage.cjs","names":[],"sources":["../../src/theme/theme-storage.ts"],"sourcesContent":["/**\n * Persists and applies the active PALETTE theme's CSS — separate concern\n * from `THEME_STORAGE_KEY` (`packages/ui-shadcn/src/layout/theme-storage-key.ts`),\n * which holds light/dark/system. That key answers \"which class goes on\n * `<html>`\"; this one answers \"what custom-property values does `:root`/\n * `.dark` carry\". A theme picker calling `writeThemeCss` must never touch\n * `THEME_STORAGE_KEY`, and the light/dark toggle must never touch this one —\n * each write path only ever calls its own key's functions, so there is no\n * shared code path where a bug could cross-contaminate them.\n *\n * The stored value is the theme's CSS **in full** (already reduced to just\n * `:root`/`.dark` by `stripThemeCss`), not a theme id. That is what makes a\n * pasted (tweakcn) theme and a built-in theme indistinguishable to this\n * module and to the bootstrap script: neither has to know the built-in\n * theme catalogue, ship it in the bundle, or fetch it over the network\n * before paint. The cost is that this module does NOT validate — a caller\n * that wants the \".dark block present\" guarantee must run `validateThemeCss`\n * itself before calling `writeThemeCss` (the theme picker does this; see its\n * task). `applyThemeCss` and the bootstrap script just inject whatever\n * string is there. That is deliberate, not an oversight: CSS parsing is\n * forgiving by construction — unknown/malformed declarations are dropped\n * rule-by-rule by the browser's own parser, not fatal — so blindly injecting\n * a corrupt or half-written string degrades to \"some rules don't apply\", not\n * a crash or a blank page. Re-validating on every read would also block\n * first paint on running `parseTopLevelBlocks` over an arbitrarily large\n * string, which is exactly the cost the bootstrap script exists to avoid.\n *\n * Deliberately its own module, no React import, same reasoning as\n * `theme-storage-key.ts`: the ONE consumer that needs `THEME_CSS_STORAGE_KEY`\n * and `applyThemeCss` outside a hook is the bootstrap `<script>` in the\n * playground's root `app/layout.tsx`, a Server Component that cannot import\n * anything carrying `'use client'`. This file has no hooks, so it can be\n * imported there directly via the `@burojs/ui-shadcn/theme-storage` subpath.\n */\n\n/**\n * The localStorage key for the active theme's CSS text. Kept apart from\n * `THEME_STORAGE_KEY` on purpose — see the module doc comment above.\n */\nexport const THEME_CSS_STORAGE_KEY = 'buro-theme-css';\n\n/** The id of the `<style>` element the bootstrap script and `applyThemeCss`\n * both create/update. Exported so a test can assert on it without\n * retyping the literal. */\nexport const THEME_CSS_STYLE_ELEMENT_ID = 'buro-theme-css';\n\n/**\n * Reads the persisted theme CSS, or `null` if nothing is stored, storage is\n * unavailable (SSR, or a browser with it disabled — e.g. some private-mode\n * configurations throw on access rather than just being empty), or the\n * stored value is an empty string (treated the same as absent).\n */\nexport function readThemeCss(): string | null {\n if (typeof window === 'undefined') return null;\n try {\n const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);\n return css && css.length > 0 ? css : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists (or, given `null`, clears) the theme CSS. Does not validate —\n * callers that need the \".dark present\" guarantee run `validateThemeCss`\n * first (see module doc comment). Swallows storage errors (quota exceeded\n * from an oversized paste, storage disabled, etc.): the in-memory\n * application via `applyThemeCss` already happened by the time a caller\n * persists, so a failed write loses the choice on next reload but does not\n * throw or break the current page.\n */\nexport function writeThemeCss(css: string | null): void {\n if (typeof window === 'undefined') return;\n try {\n if (css === null || css.length === 0) {\n window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);\n } else {\n window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);\n }\n } catch {\n // Quota exceeded, storage disabled, or a similar DOMException — nothing\n // persisted. The caller's in-memory `applyThemeCss` call still stands.\n }\n}\n\n/**\n * Creates, updates, or removes the `<style id=\"buro-theme-css\">`\n * element that carries the active theme's `:root`/`.dark` custom\n * properties. Given `null` (or an empty string), removes the element\n * entirely rather than leaving an empty tag behind.\n *\n * Order matters: this function always places the element at the END of\n * `<head>`, including when updating a style created by a prepaint bootstrap.\n * That lets an app call it after its own stylesheet has loaded so the theme's\n * equal-specificity `:root`/`.dark` rules win the cascade without creating a\n * duplicate element.\n */\nexport function applyThemeCss(css: string | null): void {\n if (typeof document === 'undefined') return;\n const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);\n\n if (css === null || css.length === 0) {\n existing?.remove();\n return;\n }\n\n if (existing instanceof HTMLStyleElement) {\n existing.textContent = css;\n document.head.appendChild(existing);\n return;\n }\n existing?.remove(); // defensive: same id but somehow not a <style>\n\n const style = document.createElement('style');\n style.id = THEME_CSS_STYLE_ELEMENT_ID;\n style.textContent = css;\n document.head.appendChild(style);\n}\n"],"mappings":";;AAuCA,MAAa,wBAAwB;AAKrC,MAAa,6BAA6B;AAQ1C,SAAgB,eAA8B;CAC5C,IAAI,OAAO,WAAW,aAAa,OAAO;CAC1C,IAAI;EACF,MAAM,MAAM,OAAO,aAAa,QAAQ,qBAAqB;EAC7D,OAAO,OAAO,IAAI,SAAS,IAAI,MAAM;CACvC,QAAQ;EACN,OAAO;CACT;AACF;AAWA,SAAgB,cAAc,KAA0B;CACtD,IAAI,OAAO,WAAW,aAAa;CACnC,IAAI;EACF,IAAI,QAAQ,QAAQ,IAAI,WAAW,GACjC,OAAO,aAAa,WAAW,qBAAqB;OAEpD,OAAO,aAAa,QAAQ,uBAAuB,GAAG;CAE1D,QAAQ,CAGR;AACF;AAcA,SAAgB,cAAc,KAA0B;CACtD,IAAI,OAAO,aAAa,aAAa;CACrC,MAAM,WAAW,SAAS,eAAe,0BAA0B;CAEnE,IAAI,QAAQ,QAAQ,IAAI,WAAW,GAAG;EACpC,UAAU,OAAO;EACjB;CACF;CAEA,IAAI,oBAAoB,kBAAkB;EACxC,SAAS,cAAc;EACvB,SAAS,KAAK,YAAY,QAAQ;EAClC;CACF;CACA,UAAU,OAAO;CAEjB,MAAM,QAAQ,SAAS,cAAc,OAAO;CAC5C,MAAM,KAAK;CACX,MAAM,cAAc;CACpB,SAAS,KAAK,YAAY,KAAK;AACjC"}
@@ -1,3 +1,4 @@
1
+ //#region src/theme/theme-storage.d.ts
1
2
  /**
2
3
  * Persists and applies the active PALETTE theme's CSS — separate concern
3
4
  * from `THEME_STORAGE_KEY` (`packages/ui-shadcn/src/layout/theme-storage-key.ts`),
@@ -71,5 +72,6 @@ declare function writeThemeCss(css: string | null): void;
71
72
  * duplicate element.
72
73
  */
73
74
  declare function applyThemeCss(css: string | null): void;
74
-
75
+ //#endregion
75
76
  export { THEME_CSS_STORAGE_KEY, THEME_CSS_STYLE_ELEMENT_ID, applyThemeCss, readThemeCss, writeThemeCss };
77
+ //# sourceMappingURL=theme-storage.d.cts.map
@@ -1,3 +1,4 @@
1
+ //#region src/theme/theme-storage.d.ts
1
2
  /**
2
3
  * Persists and applies the active PALETTE theme's CSS — separate concern
3
4
  * from `THEME_STORAGE_KEY` (`packages/ui-shadcn/src/layout/theme-storage-key.ts`),
@@ -71,5 +72,6 @@ declare function writeThemeCss(css: string | null): void;
71
72
  * duplicate element.
72
73
  */
73
74
  declare function applyThemeCss(css: string | null): void;
74
-
75
+ //#endregion
75
76
  export { THEME_CSS_STORAGE_KEY, THEME_CSS_STYLE_ELEMENT_ID, applyThemeCss, readThemeCss, writeThemeCss };
77
+ //# sourceMappingURL=theme-storage.d.ts.map
@@ -1,45 +1,41 @@
1
- // src/theme/theme-storage.ts
2
- var THEME_CSS_STORAGE_KEY = "buro-theme-css";
3
- var THEME_CSS_STYLE_ELEMENT_ID = "buro-theme-css";
1
+ //#region src/theme/theme-storage.ts
2
+ const THEME_CSS_STORAGE_KEY = "buro-theme-css";
3
+ const THEME_CSS_STYLE_ELEMENT_ID = "buro-theme-css";
4
4
  function readThemeCss() {
5
- if (typeof window === "undefined") return null;
6
- try {
7
- const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);
8
- return css && css.length > 0 ? css : null;
9
- } catch {
10
- return null;
11
- }
5
+ if (typeof window === "undefined") return null;
6
+ try {
7
+ const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);
8
+ return css && css.length > 0 ? css : null;
9
+ } catch {
10
+ return null;
11
+ }
12
12
  }
13
13
  function writeThemeCss(css) {
14
- if (typeof window === "undefined") return;
15
- try {
16
- if (css === null || css.length === 0) {
17
- window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);
18
- } else {
19
- window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);
20
- }
21
- } catch {
22
- }
14
+ if (typeof window === "undefined") return;
15
+ try {
16
+ if (css === null || css.length === 0) window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);
17
+ else window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);
18
+ } catch {}
23
19
  }
24
20
  function applyThemeCss(css) {
25
- if (typeof document === "undefined") return;
26
- const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);
27
- if (css === null || css.length === 0) {
28
- existing?.remove();
29
- return;
30
- }
31
- if (existing instanceof HTMLStyleElement) {
32
- existing.textContent = css;
33
- document.head.appendChild(existing);
34
- return;
35
- }
36
- existing?.remove();
37
- const style = document.createElement("style");
38
- style.id = THEME_CSS_STYLE_ELEMENT_ID;
39
- style.textContent = css;
40
- document.head.appendChild(style);
21
+ if (typeof document === "undefined") return;
22
+ const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);
23
+ if (css === null || css.length === 0) {
24
+ existing?.remove();
25
+ return;
26
+ }
27
+ if (existing instanceof HTMLStyleElement) {
28
+ existing.textContent = css;
29
+ document.head.appendChild(existing);
30
+ return;
31
+ }
32
+ existing?.remove();
33
+ const style = document.createElement("style");
34
+ style.id = THEME_CSS_STYLE_ELEMENT_ID;
35
+ style.textContent = css;
36
+ document.head.appendChild(style);
41
37
  }
42
-
38
+ //#endregion
43
39
  export { THEME_CSS_STORAGE_KEY, THEME_CSS_STYLE_ELEMENT_ID, applyThemeCss, readThemeCss, writeThemeCss };
44
- //# sourceMappingURL=theme-storage.js.map
40
+
45
41
  //# sourceMappingURL=theme-storage.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/theme-storage.ts"],"names":[],"mappings":";AAuCO,IAAM,qBAAA,GAAwB;AAK9B,IAAM,0BAAA,GAA6B;AAQnC,SAAS,YAAA,GAA8B;AAC5C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,qBAAqB,CAAA;AAC7D,IAAA,OAAO,GAAA,IAAO,GAAA,CAAI,MAAA,GAAS,CAAA,GAAI,GAAA,GAAM,IAAA;AAAA,EACvC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAWO,SAAS,cAAc,GAAA,EAA0B;AACtD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG;AACpC,MAAA,MAAA,CAAO,YAAA,CAAa,WAAW,qBAAqB,CAAA;AAAA,IACtD,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,qBAAA,EAAuB,GAAG,CAAA;AAAA,IACxD;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAGR;AACF;AAcO,SAAS,cAAc,GAAA,EAA0B;AACtD,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,cAAA,CAAe,0BAA0B,CAAA;AAEnE,EAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG;AACpC,IAAA,QAAA,EAAU,MAAA,EAAO;AACjB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,oBAAoB,gBAAA,EAAkB;AACxC,IAAA,QAAA,CAAS,WAAA,GAAc,GAAA;AACvB,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,QAAQ,CAAA;AAClC,IAAA;AAAA,EACF;AACA,EAAA,QAAA,EAAU,MAAA,EAAO;AAEjB,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,EAAA,KAAA,CAAM,EAAA,GAAK,0BAAA;AACX,EAAA,KAAA,CAAM,WAAA,GAAc,GAAA;AACpB,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AACjC","file":"theme-storage.js","sourcesContent":["/**\n * Persists and applies the active PALETTE theme's CSS — separate concern\n * from `THEME_STORAGE_KEY` (`packages/ui-shadcn/src/layout/theme-storage-key.ts`),\n * which holds light/dark/system. That key answers \"which class goes on\n * `<html>`\"; this one answers \"what custom-property values does `:root`/\n * `.dark` carry\". A theme picker calling `writeThemeCss` must never touch\n * `THEME_STORAGE_KEY`, and the light/dark toggle must never touch this one —\n * each write path only ever calls its own key's functions, so there is no\n * shared code path where a bug could cross-contaminate them.\n *\n * The stored value is the theme's CSS **in full** (already reduced to just\n * `:root`/`.dark` by `stripThemeCss`), not a theme id. That is what makes a\n * pasted (tweakcn) theme and a built-in theme indistinguishable to this\n * module and to the bootstrap script: neither has to know the built-in\n * theme catalogue, ship it in the bundle, or fetch it over the network\n * before paint. The cost is that this module does NOT validate — a caller\n * that wants the \".dark block present\" guarantee must run `validateThemeCss`\n * itself before calling `writeThemeCss` (the theme picker does this; see its\n * task). `applyThemeCss` and the bootstrap script just inject whatever\n * string is there. That is deliberate, not an oversight: CSS parsing is\n * forgiving by construction — unknown/malformed declarations are dropped\n * rule-by-rule by the browser's own parser, not fatal — so blindly injecting\n * a corrupt or half-written string degrades to \"some rules don't apply\", not\n * a crash or a blank page. Re-validating on every read would also block\n * first paint on running `parseTopLevelBlocks` over an arbitrarily large\n * string, which is exactly the cost the bootstrap script exists to avoid.\n *\n * Deliberately its own module, no React import, same reasoning as\n * `theme-storage-key.ts`: the ONE consumer that needs `THEME_CSS_STORAGE_KEY`\n * and `applyThemeCss` outside a hook is the bootstrap `<script>` in the\n * playground's root `app/layout.tsx`, a Server Component that cannot import\n * anything carrying `'use client'`. This file has no hooks, so it can be\n * imported there directly via the `@burojs/ui-shadcn/theme-storage` subpath.\n */\n\n/**\n * The localStorage key for the active theme's CSS text. Kept apart from\n * `THEME_STORAGE_KEY` on purpose — see the module doc comment above.\n */\nexport const THEME_CSS_STORAGE_KEY = 'buro-theme-css';\n\n/** The id of the `<style>` element the bootstrap script and `applyThemeCss`\n * both create/update. Exported so a test can assert on it without\n * retyping the literal. */\nexport const THEME_CSS_STYLE_ELEMENT_ID = 'buro-theme-css';\n\n/**\n * Reads the persisted theme CSS, or `null` if nothing is stored, storage is\n * unavailable (SSR, or a browser with it disabled — e.g. some private-mode\n * configurations throw on access rather than just being empty), or the\n * stored value is an empty string (treated the same as absent).\n */\nexport function readThemeCss(): string | null {\n if (typeof window === 'undefined') return null;\n try {\n const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);\n return css && css.length > 0 ? css : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists (or, given `null`, clears) the theme CSS. Does not validate —\n * callers that need the \".dark present\" guarantee run `validateThemeCss`\n * first (see module doc comment). Swallows storage errors (quota exceeded\n * from an oversized paste, storage disabled, etc.): the in-memory\n * application via `applyThemeCss` already happened by the time a caller\n * persists, so a failed write loses the choice on next reload but does not\n * throw or break the current page.\n */\nexport function writeThemeCss(css: string | null): void {\n if (typeof window === 'undefined') return;\n try {\n if (css === null || css.length === 0) {\n window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);\n } else {\n window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);\n }\n } catch {\n // Quota exceeded, storage disabled, or a similar DOMException — nothing\n // persisted. The caller's in-memory `applyThemeCss` call still stands.\n }\n}\n\n/**\n * Creates, updates, or removes the `<style id=\"buro-theme-css\">`\n * element that carries the active theme's `:root`/`.dark` custom\n * properties. Given `null` (or an empty string), removes the element\n * entirely rather than leaving an empty tag behind.\n *\n * Order matters: this function always places the element at the END of\n * `<head>`, including when updating a style created by a prepaint bootstrap.\n * That lets an app call it after its own stylesheet has loaded so the theme's\n * equal-specificity `:root`/`.dark` rules win the cascade without creating a\n * duplicate element.\n */\nexport function applyThemeCss(css: string | null): void {\n if (typeof document === 'undefined') return;\n const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);\n\n if (css === null || css.length === 0) {\n existing?.remove();\n return;\n }\n\n if (existing instanceof HTMLStyleElement) {\n existing.textContent = css;\n document.head.appendChild(existing);\n return;\n }\n existing?.remove(); // defensive: same id but somehow not a <style>\n\n const style = document.createElement('style');\n style.id = THEME_CSS_STYLE_ELEMENT_ID;\n style.textContent = css;\n document.head.appendChild(style);\n}\n"]}
1
+ {"version":3,"file":"theme-storage.js","names":[],"sources":["../../src/theme/theme-storage.ts"],"sourcesContent":["/**\n * Persists and applies the active PALETTE theme's CSS — separate concern\n * from `THEME_STORAGE_KEY` (`packages/ui-shadcn/src/layout/theme-storage-key.ts`),\n * which holds light/dark/system. That key answers \"which class goes on\n * `<html>`\"; this one answers \"what custom-property values does `:root`/\n * `.dark` carry\". A theme picker calling `writeThemeCss` must never touch\n * `THEME_STORAGE_KEY`, and the light/dark toggle must never touch this one —\n * each write path only ever calls its own key's functions, so there is no\n * shared code path where a bug could cross-contaminate them.\n *\n * The stored value is the theme's CSS **in full** (already reduced to just\n * `:root`/`.dark` by `stripThemeCss`), not a theme id. That is what makes a\n * pasted (tweakcn) theme and a built-in theme indistinguishable to this\n * module and to the bootstrap script: neither has to know the built-in\n * theme catalogue, ship it in the bundle, or fetch it over the network\n * before paint. The cost is that this module does NOT validate — a caller\n * that wants the \".dark block present\" guarantee must run `validateThemeCss`\n * itself before calling `writeThemeCss` (the theme picker does this; see its\n * task). `applyThemeCss` and the bootstrap script just inject whatever\n * string is there. That is deliberate, not an oversight: CSS parsing is\n * forgiving by construction — unknown/malformed declarations are dropped\n * rule-by-rule by the browser's own parser, not fatal — so blindly injecting\n * a corrupt or half-written string degrades to \"some rules don't apply\", not\n * a crash or a blank page. Re-validating on every read would also block\n * first paint on running `parseTopLevelBlocks` over an arbitrarily large\n * string, which is exactly the cost the bootstrap script exists to avoid.\n *\n * Deliberately its own module, no React import, same reasoning as\n * `theme-storage-key.ts`: the ONE consumer that needs `THEME_CSS_STORAGE_KEY`\n * and `applyThemeCss` outside a hook is the bootstrap `<script>` in the\n * playground's root `app/layout.tsx`, a Server Component that cannot import\n * anything carrying `'use client'`. This file has no hooks, so it can be\n * imported there directly via the `@burojs/ui-shadcn/theme-storage` subpath.\n */\n\n/**\n * The localStorage key for the active theme's CSS text. Kept apart from\n * `THEME_STORAGE_KEY` on purpose — see the module doc comment above.\n */\nexport const THEME_CSS_STORAGE_KEY = 'buro-theme-css';\n\n/** The id of the `<style>` element the bootstrap script and `applyThemeCss`\n * both create/update. Exported so a test can assert on it without\n * retyping the literal. */\nexport const THEME_CSS_STYLE_ELEMENT_ID = 'buro-theme-css';\n\n/**\n * Reads the persisted theme CSS, or `null` if nothing is stored, storage is\n * unavailable (SSR, or a browser with it disabled — e.g. some private-mode\n * configurations throw on access rather than just being empty), or the\n * stored value is an empty string (treated the same as absent).\n */\nexport function readThemeCss(): string | null {\n if (typeof window === 'undefined') return null;\n try {\n const css = window.localStorage.getItem(THEME_CSS_STORAGE_KEY);\n return css && css.length > 0 ? css : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists (or, given `null`, clears) the theme CSS. Does not validate —\n * callers that need the \".dark present\" guarantee run `validateThemeCss`\n * first (see module doc comment). Swallows storage errors (quota exceeded\n * from an oversized paste, storage disabled, etc.): the in-memory\n * application via `applyThemeCss` already happened by the time a caller\n * persists, so a failed write loses the choice on next reload but does not\n * throw or break the current page.\n */\nexport function writeThemeCss(css: string | null): void {\n if (typeof window === 'undefined') return;\n try {\n if (css === null || css.length === 0) {\n window.localStorage.removeItem(THEME_CSS_STORAGE_KEY);\n } else {\n window.localStorage.setItem(THEME_CSS_STORAGE_KEY, css);\n }\n } catch {\n // Quota exceeded, storage disabled, or a similar DOMException — nothing\n // persisted. The caller's in-memory `applyThemeCss` call still stands.\n }\n}\n\n/**\n * Creates, updates, or removes the `<style id=\"buro-theme-css\">`\n * element that carries the active theme's `:root`/`.dark` custom\n * properties. Given `null` (or an empty string), removes the element\n * entirely rather than leaving an empty tag behind.\n *\n * Order matters: this function always places the element at the END of\n * `<head>`, including when updating a style created by a prepaint bootstrap.\n * That lets an app call it after its own stylesheet has loaded so the theme's\n * equal-specificity `:root`/`.dark` rules win the cascade without creating a\n * duplicate element.\n */\nexport function applyThemeCss(css: string | null): void {\n if (typeof document === 'undefined') return;\n const existing = document.getElementById(THEME_CSS_STYLE_ELEMENT_ID);\n\n if (css === null || css.length === 0) {\n existing?.remove();\n return;\n }\n\n if (existing instanceof HTMLStyleElement) {\n existing.textContent = css;\n document.head.appendChild(existing);\n return;\n }\n existing?.remove(); // defensive: same id but somehow not a <style>\n\n const style = document.createElement('style');\n style.id = THEME_CSS_STYLE_ELEMENT_ID;\n style.textContent = css;\n document.head.appendChild(style);\n}\n"],"mappings":";AAuCA,MAAa,wBAAwB;AAKrC,MAAa,6BAA6B;AAQ1C,SAAgB,eAA8B;CAC5C,IAAI,OAAO,WAAW,aAAa,OAAO;CAC1C,IAAI;EACF,MAAM,MAAM,OAAO,aAAa,QAAQ,qBAAqB;EAC7D,OAAO,OAAO,IAAI,SAAS,IAAI,MAAM;CACvC,QAAQ;EACN,OAAO;CACT;AACF;AAWA,SAAgB,cAAc,KAA0B;CACtD,IAAI,OAAO,WAAW,aAAa;CACnC,IAAI;EACF,IAAI,QAAQ,QAAQ,IAAI,WAAW,GACjC,OAAO,aAAa,WAAW,qBAAqB;OAEpD,OAAO,aAAa,QAAQ,uBAAuB,GAAG;CAE1D,QAAQ,CAGR;AACF;AAcA,SAAgB,cAAc,KAA0B;CACtD,IAAI,OAAO,aAAa,aAAa;CACrC,MAAM,WAAW,SAAS,eAAe,0BAA0B;CAEnE,IAAI,QAAQ,QAAQ,IAAI,WAAW,GAAG;EACpC,UAAU,OAAO;EACjB;CACF;CAEA,IAAI,oBAAoB,kBAAkB;EACxC,SAAS,cAAc;EACvB,SAAS,KAAK,YAAY,QAAQ;EAClC;CACF;CACA,UAAU,OAAO;CAEjB,MAAM,QAAQ,SAAS,cAAc,OAAO;CAC5C,MAAM,KAAK;CACX,MAAM,cAAc;CACpB,SAAS,KAAK,YAAY,KAAK;AACjC"}
@@ -0,0 +1,31 @@
1
+ //#region src/layout/theme-storage-key.d.ts
2
+ /**
3
+ * The ONE localStorage key every theme-aware place in the app must agree on.
4
+ * Exported so nothing re-types the string — two files agreeing on a literal
5
+ * is how they drift.
6
+ *
7
+ * Deliberately its own module: no `'use client'`, no React import, nothing
8
+ * that would pull in `use-theme.ts`'s hook (or the rest of the kit's client
9
+ * component tree). That matters because the consumer that needs this
10
+ * constant outside a hook is a pre-paint bootstrap `<script>` in a Next root
11
+ * `app/layout.tsx` — a Server Component, which cannot itself carry
12
+ * `'use client'`. `examples/next/app/layout.tsx` is that consumer, and after
13
+ * SP9 deleted `apps/playground` it is the only one; a plain-HTML host has the
14
+ * same need but no module graph to satisfy it from, which is why
15
+ * `examples/marketplace-ops/index.html` spells the key as a literal and pins
16
+ * it against this constant in a test instead.
17
+ * Importing from the package's main entry (`@burojs/ui-shadcn`)
18
+ * there fails Next's server/client boundary check, because that entry
19
+ * bundles the whole kit — including many components that call hooks —
20
+ * into one file without a top-level `'use client'` directive (the bundler
21
+ * drops it when merging multiple client modules together — true of tsdown,
22
+ * as it was of tsup). Importing this file
23
+ * directly via the `@burojs/ui-shadcn/theme-storage-key` subpath sidesteps
24
+ * that: it has no hooks, so a Server Component can import the real string
25
+ * value at build/SSR time. `use-theme.ts` re-exports the same constant for
26
+ * everyone else, so there is exactly one literal in the whole codebase.
27
+ */
28
+ declare const THEME_STORAGE_KEY = "buro-theme";
29
+ //#endregion
30
+ export { THEME_STORAGE_KEY as t };
31
+ //# sourceMappingURL=theme-storage-key-pYlp4aDS.d.cts.map
@@ -0,0 +1,31 @@
1
+ //#region src/layout/theme-storage-key.d.ts
2
+ /**
3
+ * The ONE localStorage key every theme-aware place in the app must agree on.
4
+ * Exported so nothing re-types the string — two files agreeing on a literal
5
+ * is how they drift.
6
+ *
7
+ * Deliberately its own module: no `'use client'`, no React import, nothing
8
+ * that would pull in `use-theme.ts`'s hook (or the rest of the kit's client
9
+ * component tree). That matters because the consumer that needs this
10
+ * constant outside a hook is a pre-paint bootstrap `<script>` in a Next root
11
+ * `app/layout.tsx` — a Server Component, which cannot itself carry
12
+ * `'use client'`. `examples/next/app/layout.tsx` is that consumer, and after
13
+ * SP9 deleted `apps/playground` it is the only one; a plain-HTML host has the
14
+ * same need but no module graph to satisfy it from, which is why
15
+ * `examples/marketplace-ops/index.html` spells the key as a literal and pins
16
+ * it against this constant in a test instead.
17
+ * Importing from the package's main entry (`@burojs/ui-shadcn`)
18
+ * there fails Next's server/client boundary check, because that entry
19
+ * bundles the whole kit — including many components that call hooks —
20
+ * into one file without a top-level `'use client'` directive (the bundler
21
+ * drops it when merging multiple client modules together — true of tsdown,
22
+ * as it was of tsup). Importing this file
23
+ * directly via the `@burojs/ui-shadcn/theme-storage-key` subpath sidesteps
24
+ * that: it has no hooks, so a Server Component can import the real string
25
+ * value at build/SSR time. `use-theme.ts` re-exports the same constant for
26
+ * everyone else, so there is exactly one literal in the whole codebase.
27
+ */
28
+ declare const THEME_STORAGE_KEY = "buro-theme";
29
+ //#endregion
30
+ export { THEME_STORAGE_KEY as t };
31
+ //# sourceMappingURL=theme-storage-key-pYlp4aDS.d.ts.map