@evinvest/uikit 0.18.0 → 0.20.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 (277) hide show
  1. package/README.md +210 -13
  2. package/dist/chunk-3IPB4QQJ.js +306 -0
  3. package/dist/chunk-3IPB4QQJ.js.map +1 -0
  4. package/dist/components/accordion.js +136 -0
  5. package/dist/components/accordion.js.map +1 -0
  6. package/dist/components/alert-dialog.js +175 -0
  7. package/dist/components/alert-dialog.js.map +1 -0
  8. package/dist/components/alert.js +49 -0
  9. package/dist/components/alert.js.map +1 -0
  10. package/dist/components/avatar.js +54 -0
  11. package/dist/components/avatar.js.map +1 -0
  12. package/dist/components/badge.js +25 -0
  13. package/dist/components/badge.js.map +1 -0
  14. package/dist/components/band.js +75 -0
  15. package/dist/components/band.js.map +1 -0
  16. package/dist/components/breadcrumb.js +132 -0
  17. package/dist/components/breadcrumb.js.map +1 -0
  18. package/dist/components/button-group.js +61 -0
  19. package/dist/components/button-group.js.map +1 -0
  20. package/dist/components/button.js +55 -0
  21. package/dist/components/button.js.map +1 -0
  22. package/dist/components/calendar.js +226 -0
  23. package/dist/components/calendar.js.map +1 -0
  24. package/dist/components/card.js +98 -0
  25. package/dist/components/card.js.map +1 -0
  26. package/dist/components/carousel.js +262 -0
  27. package/dist/components/carousel.js.map +1 -0
  28. package/dist/components/chart.js +151 -0
  29. package/dist/components/chart.js.map +1 -0
  30. package/dist/components/checkbox.js +68 -0
  31. package/dist/components/checkbox.js.map +1 -0
  32. package/dist/components/collapsible.js +60 -0
  33. package/dist/components/collapsible.js.map +1 -0
  34. package/dist/components/command.js +274 -0
  35. package/dist/components/command.js.map +1 -0
  36. package/dist/components/container.js +18 -0
  37. package/dist/components/container.js.map +1 -0
  38. package/dist/components/context-menu.js +337 -0
  39. package/dist/components/context-menu.js.map +1 -0
  40. package/dist/components/date-time-picker.js +301 -0
  41. package/dist/components/date-time-picker.js.map +1 -0
  42. package/dist/components/dialog.js +207 -0
  43. package/dist/components/dialog.js.map +1 -0
  44. package/dist/components/drawer.js +351 -0
  45. package/dist/components/drawer.js.map +1 -0
  46. package/dist/components/dropdown-menu.js +356 -0
  47. package/dist/components/dropdown-menu.js.map +1 -0
  48. package/dist/components/empty.js +91 -0
  49. package/dist/components/empty.js.map +1 -0
  50. package/dist/components/field-context.js +30 -0
  51. package/dist/components/field-context.js.map +1 -0
  52. package/dist/components/field.js +216 -0
  53. package/dist/components/field.js.map +1 -0
  54. package/dist/components/footer.js +89 -0
  55. package/dist/components/footer.js.map +1 -0
  56. package/dist/components/form.js +101 -0
  57. package/dist/components/form.js.map +1 -0
  58. package/dist/components/hover-card.js +90 -0
  59. package/dist/components/hover-card.js.map +1 -0
  60. package/dist/components/info-tip.js +161 -0
  61. package/dist/components/info-tip.js.map +1 -0
  62. package/dist/components/input-group.js +124 -0
  63. package/dist/components/input-group.js.map +1 -0
  64. package/dist/components/input-otp.js +120 -0
  65. package/dist/components/input-otp.js.map +1 -0
  66. package/dist/components/input.js +26 -0
  67. package/dist/components/input.js.map +1 -0
  68. package/dist/components/item.js +167 -0
  69. package/dist/components/item.js.map +1 -0
  70. package/dist/components/kbd.js +29 -0
  71. package/dist/components/kbd.js.map +1 -0
  72. package/dist/components/label.js +11 -0
  73. package/dist/components/label.js.map +1 -0
  74. package/dist/components/logo.js +41 -0
  75. package/dist/components/logo.js.map +1 -0
  76. package/dist/components/menubar.js +330 -0
  77. package/dist/components/menubar.js.map +1 -0
  78. package/dist/components/native-select.js +62 -0
  79. package/dist/components/native-select.js.map +1 -0
  80. package/dist/components/navigation-menu.js +204 -0
  81. package/dist/components/navigation-menu.js.map +1 -0
  82. package/dist/components/pagination.js +138 -0
  83. package/dist/components/pagination.js.map +1 -0
  84. package/dist/components/popover.js +104 -0
  85. package/dist/components/popover.js.map +1 -0
  86. package/dist/components/progress.js +30 -0
  87. package/dist/components/progress.js.map +1 -0
  88. package/dist/components/radio-group.js +113 -0
  89. package/dist/components/radio-group.js.map +1 -0
  90. package/dist/components/resizable.js +193 -0
  91. package/dist/components/resizable.js.map +1 -0
  92. package/dist/components/scroll-area.js +40 -0
  93. package/dist/components/scroll-area.js.map +1 -0
  94. package/dist/components/section-descriptor.js +88 -0
  95. package/dist/components/section-descriptor.js.map +1 -0
  96. package/dist/components/select-chevron.js +25 -0
  97. package/dist/components/select-chevron.js.map +1 -0
  98. package/dist/components/select.js +288 -0
  99. package/dist/components/select.js.map +1 -0
  100. package/dist/components/separator.js +31 -0
  101. package/dist/components/separator.js.map +1 -0
  102. package/dist/components/sheet.js +200 -0
  103. package/dist/components/sheet.js.map +1 -0
  104. package/dist/components/sidebar.js +326 -0
  105. package/dist/components/sidebar.js.map +1 -0
  106. package/dist/components/skeleton.js +18 -0
  107. package/dist/components/skeleton.js.map +1 -0
  108. package/dist/components/slider.js +138 -0
  109. package/dist/components/slider.js.map +1 -0
  110. package/dist/components/sonner.js +319 -0
  111. package/dist/components/sonner.js.map +1 -0
  112. package/dist/components/spinner.js +23 -0
  113. package/dist/components/spinner.js.map +1 -0
  114. package/dist/components/status-screen.js +145 -0
  115. package/dist/components/status-screen.js.map +1 -0
  116. package/dist/components/switch.js +54 -0
  117. package/dist/components/switch.js.map +1 -0
  118. package/dist/components/table.js +124 -0
  119. package/dist/components/table.js.map +1 -0
  120. package/dist/components/tabs.js +133 -0
  121. package/dist/components/tabs.js.map +1 -0
  122. package/dist/components/terminal.js +206 -0
  123. package/dist/components/terminal.js.map +1 -0
  124. package/dist/components/textarea.js +22 -0
  125. package/dist/components/textarea.js.map +1 -0
  126. package/dist/components/toggle-group.js +105 -0
  127. package/dist/components/toggle-group.js.map +1 -0
  128. package/dist/components/toggle.js +53 -0
  129. package/dist/components/toggle.js.map +1 -0
  130. package/dist/components/tooltip.js +102 -0
  131. package/dist/components/tooltip.js.map +1 -0
  132. package/dist/generated/accent.js +27 -0
  133. package/dist/generated/accent.js.map +1 -0
  134. package/dist/generated/accordion.js +13 -0
  135. package/dist/generated/accordion.js.map +1 -0
  136. package/dist/generated/alert-dialog.js +15 -0
  137. package/dist/generated/alert-dialog.js.map +1 -0
  138. package/dist/generated/alert.js +16 -0
  139. package/dist/generated/alert.js.map +1 -0
  140. package/dist/generated/avatar.js +9 -0
  141. package/dist/generated/avatar.js.map +1 -0
  142. package/dist/generated/badge.js +13 -0
  143. package/dist/generated/badge.js.map +1 -0
  144. package/dist/generated/band.js +39 -0
  145. package/dist/generated/band.js.map +1 -0
  146. package/dist/generated/breadcrumb.js +15 -0
  147. package/dist/generated/breadcrumb.js.map +1 -0
  148. package/dist/generated/button-group.js +14 -0
  149. package/dist/generated/button-group.js.map +1 -0
  150. package/dist/generated/button.js +30 -0
  151. package/dist/generated/button.js.map +1 -0
  152. package/dist/generated/calendar.js +29 -0
  153. package/dist/generated/calendar.js.map +1 -0
  154. package/dist/generated/card.js +17 -0
  155. package/dist/generated/card.js.map +1 -0
  156. package/dist/generated/carousel.js +31 -0
  157. package/dist/generated/carousel.js.map +1 -0
  158. package/dist/generated/chart.js +9 -0
  159. package/dist/generated/chart.js.map +1 -0
  160. package/dist/generated/checkbox.js +7 -0
  161. package/dist/generated/checkbox.js.map +1 -0
  162. package/dist/generated/command.js +27 -0
  163. package/dist/generated/command.js.map +1 -0
  164. package/dist/generated/container.js +5 -0
  165. package/dist/generated/container.js.map +1 -0
  166. package/dist/generated/context-menu.js +19 -0
  167. package/dist/generated/context-menu.js.map +1 -0
  168. package/dist/generated/date-time-picker.js +15 -0
  169. package/dist/generated/date-time-picker.js.map +1 -0
  170. package/dist/generated/dialog.js +17 -0
  171. package/dist/generated/dialog.js.map +1 -0
  172. package/dist/generated/drawer.js +26 -0
  173. package/dist/generated/drawer.js.map +1 -0
  174. package/dist/generated/dropdown-menu.js +21 -0
  175. package/dist/generated/dropdown-menu.js.map +1 -0
  176. package/dist/generated/empty.js +20 -0
  177. package/dist/generated/empty.js.map +1 -0
  178. package/dist/generated/field.js +33 -0
  179. package/dist/generated/field.js.map +1 -0
  180. package/dist/generated/focus.js +5 -0
  181. package/dist/generated/focus.js.map +1 -0
  182. package/dist/generated/form.js +11 -0
  183. package/dist/generated/form.js.map +1 -0
  184. package/dist/generated/hover-card.js +5 -0
  185. package/dist/generated/hover-card.js.map +1 -0
  186. package/dist/generated/input-group.js +29 -0
  187. package/dist/generated/input-group.js.map +1 -0
  188. package/dist/generated/input-otp.js +15 -0
  189. package/dist/generated/input-otp.js.map +1 -0
  190. package/dist/generated/input.js +11 -0
  191. package/dist/generated/input.js.map +1 -0
  192. package/dist/generated/item.js +40 -0
  193. package/dist/generated/item.js.map +1 -0
  194. package/dist/generated/kbd.js +7 -0
  195. package/dist/generated/kbd.js.map +1 -0
  196. package/dist/generated/label.js +5 -0
  197. package/dist/generated/label.js.map +1 -0
  198. package/dist/generated/menubar.js +27 -0
  199. package/dist/generated/menubar.js.map +1 -0
  200. package/dist/generated/navigation-menu.js +19 -0
  201. package/dist/generated/navigation-menu.js.map +1 -0
  202. package/dist/generated/popover.js +5 -0
  203. package/dist/generated/popover.js.map +1 -0
  204. package/dist/generated/progress.js +7 -0
  205. package/dist/generated/progress.js.map +1 -0
  206. package/dist/generated/radio-group.js +7 -0
  207. package/dist/generated/radio-group.js.map +1 -0
  208. package/dist/generated/resizable.js +11 -0
  209. package/dist/generated/resizable.js.map +1 -0
  210. package/dist/generated/scroll-area.js +14 -0
  211. package/dist/generated/scroll-area.js.map +1 -0
  212. package/dist/generated/select.js +15 -0
  213. package/dist/generated/select.js.map +1 -0
  214. package/dist/generated/separator.js +10 -0
  215. package/dist/generated/separator.js.map +1 -0
  216. package/dist/generated/sheet.js +25 -0
  217. package/dist/generated/sheet.js.map +1 -0
  218. package/dist/generated/sidebar.js +41 -0
  219. package/dist/generated/sidebar.js.map +1 -0
  220. package/dist/generated/skeleton.js +5 -0
  221. package/dist/generated/skeleton.js.map +1 -0
  222. package/dist/generated/slider.js +11 -0
  223. package/dist/generated/slider.js.map +1 -0
  224. package/dist/generated/sonner.js +30 -0
  225. package/dist/generated/sonner.js.map +1 -0
  226. package/dist/generated/switch.js +7 -0
  227. package/dist/generated/switch.js.map +1 -0
  228. package/dist/generated/table.js +21 -0
  229. package/dist/generated/table.js.map +1 -0
  230. package/dist/generated/tabs.js +11 -0
  231. package/dist/generated/tabs.js.map +1 -0
  232. package/dist/generated/terminal.js +70 -0
  233. package/dist/generated/terminal.js.map +1 -0
  234. package/dist/generated/textarea.js +11 -0
  235. package/dist/generated/textarea.js.map +1 -0
  236. package/dist/generated/toggle.js +18 -0
  237. package/dist/generated/toggle.js.map +1 -0
  238. package/dist/generated/tooltip.js +5 -0
  239. package/dist/generated/tooltip.js.map +1 -0
  240. package/dist/index.d.ts +141 -34
  241. package/dist/index.js +73 -8623
  242. package/dist/lib/cn.js +12 -0
  243. package/dist/lib/cn.js.map +1 -0
  244. package/dist/lib/focus.js +5 -0
  245. package/dist/lib/focus.js.map +1 -0
  246. package/dist/palette-cli.js +41 -0
  247. package/dist/palette-cli.js.map +1 -0
  248. package/dist/palette.d.ts +80 -0
  249. package/dist/palette.js +25 -0
  250. package/dist/palette.js.map +1 -0
  251. package/dist/primitives/dismissable-layer.js +35 -0
  252. package/dist/primitives/dismissable-layer.js.map +1 -0
  253. package/dist/primitives/focus-scope.js +58 -0
  254. package/dist/primitives/focus-scope.js.map +1 -0
  255. package/dist/primitives/merge-refs.js +13 -0
  256. package/dist/primitives/merge-refs.js.map +1 -0
  257. package/dist/primitives/portal.js +19 -0
  258. package/dist/primitives/portal.js.map +1 -0
  259. package/dist/primitives/presence.js +45 -0
  260. package/dist/primitives/presence.js.map +1 -0
  261. package/dist/primitives/slot.js +39 -0
  262. package/dist/primitives/slot.js.map +1 -0
  263. package/dist/primitives/use-controllable-state.js +24 -0
  264. package/dist/primitives/use-controllable-state.js.map +1 -0
  265. package/dist/primitives/use-floating.js +128 -0
  266. package/dist/primitives/use-floating.js.map +1 -0
  267. package/dist/primitives/use-hover-intent.js +50 -0
  268. package/dist/primitives/use-hover-intent.js.map +1 -0
  269. package/dist/primitives/use-roving-focus.js +40 -0
  270. package/dist/primitives/use-roving-focus.js.map +1 -0
  271. package/dist/primitives/walk-elements.js +13 -0
  272. package/dist/primitives/walk-elements.js.map +1 -0
  273. package/package.json +15 -5
  274. package/styles/ev.css +73 -0
  275. package/styles/theme.css +470 -0
  276. package/styles/tokens.css +129 -90
  277. package/dist/index.js.map +0 -1
package/dist/lib/cn.js ADDED
@@ -0,0 +1,12 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ function cn(...inputs) {
4
+ if (inputs.length === 2 && typeof inputs[0] === "string" && !inputs[1]) {
5
+ return inputs[0];
6
+ }
7
+ return twMerge(clsx(inputs));
8
+ }
9
+ export {
10
+ cn
11
+ };
12
+ //# sourceMappingURL=cn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Fuses class fragments into one string with real Tailwind conflict resolution:\n * `clsx` drops falsy fragments and joins the rest, `tailwind-merge` resolves\n * conflicting utilities so the rightmost wins (`cn(\"p-4\", \"p-2\") === \"p-2\"`). A\n * caller's `className` override, passed last, therefore beats the base classes.\n *\n * `twMerge` parses every utility on every render, and the overwhelmingly common\n * call is one kit constant plus a `className` override that is absent — so that\n * call returns the constant untouched.\n *\n * The Rust mirror is the `cn!` macro in `ev_lib::uikit` (`tailwind_fuse::tw_merge!`).\n */\nexport function cn(...inputs: ClassValue[]): string {\n if (inputs.length === 2 && typeof inputs[0] === \"string\" && !inputs[1]) {\n return inputs[0];\n }\n return twMerge(clsx(inputs));\n}\n"],"mappings":"AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAcjB,SAAS,MAAM,QAA8B;AAClD,MAAI,OAAO,WAAW,KAAK,OAAO,OAAO,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG;AACtE,WAAO,OAAO,CAAC;AAAA,EACjB;AACA,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
@@ -0,0 +1,5 @@
1
+ import { FILLED_FOCUS_RING } from "../generated/focus.js";
2
+ export {
3
+ FILLED_FOCUS_RING
4
+ };
5
+ //# sourceMappingURL=focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/focus.ts"],"sourcesContent":["/**\n * The focus indicator for a control that paints a fill (lib#129): the solid\n * ring standing 2px off the control instead of the kit's 50 % halo. The class\n * tables that paint a fill already carry it (`rust/classes/src/focus.rs` says\n * why); this is for a filled control of the caller's own.\n */\nexport { FILLED_FOCUS_RING } from \"../generated/focus\";\n"],"mappings":"AAMA,SAAS,yBAAyB;","names":[]}
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PaletteError,
4
+ brandFromToml,
5
+ readContract,
6
+ renderPalette
7
+ } from "./chunk-3IPB4QQJ.js";
8
+
9
+ // src/palette/cli.ts
10
+ import { readFileSync, writeFileSync } from "fs";
11
+ import { basename } from "path";
12
+ import { parseArgs } from "util";
13
+ var USAGE = `usage: evinvest-palette --brand <slug> [--out <file.css>] <brand.toml>
14
+
15
+ Writes the brand's palette as a flat sheet scoped to [data-brand="<slug>"],
16
+ after checking both polarities against the contract in @evinvest/uikit's
17
+ styles/theme.css. Prints to stdout without --out.`;
18
+ function main() {
19
+ const { values, positionals } = parseArgs({
20
+ options: { brand: { type: "string" }, out: { type: "string" }, help: { type: "boolean", short: "h" } },
21
+ allowPositionals: true
22
+ });
23
+ const [input] = positionals;
24
+ if (values.help || !values.brand || !input || positionals.length > 1) {
25
+ console.error(USAGE);
26
+ return values.help ? 0 : 2;
27
+ }
28
+ const contract = readContract(readFileSync(new URL("../styles/theme.css", import.meta.url), "utf8"));
29
+ const sheet = renderPalette(values.brand, brandFromToml(readFileSync(input, "utf8")), contract, basename(input));
30
+ if (values.out) writeFileSync(values.out, sheet);
31
+ else process.stdout.write(sheet);
32
+ return 0;
33
+ }
34
+ try {
35
+ process.exitCode = main();
36
+ } catch (error) {
37
+ if (error instanceof PaletteError) console.error(error.message);
38
+ else throw error;
39
+ process.exitCode = 1;
40
+ }
41
+ //# sourceMappingURL=palette-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/palette/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { brandFromToml, PaletteError, renderPalette } from \"./brand\";\nimport { readContract } from \"./contract\";\n\nconst USAGE = `usage: evinvest-palette --brand <slug> [--out <file.css>] <brand.toml>\n\nWrites the brand's palette as a flat sheet scoped to [data-brand=\"<slug>\"],\nafter checking both polarities against the contract in @evinvest/uikit's\nstyles/theme.css. Prints to stdout without --out.`;\n\nfunction main(): number {\n const { values, positionals } = parseArgs({\n options: { brand: { type: \"string\" }, out: { type: \"string\" }, help: { type: \"boolean\", short: \"h\" } },\n allowPositionals: true,\n });\n const [input] = positionals;\n if (values.help || !values.brand || !input || positionals.length > 1) {\n console.error(USAGE);\n return values.help ? 0 : 2;\n }\n\n // Shipped beside `dist/`, so the contract is always the installed kit's own.\n const contract = readContract(readFileSync(new URL(\"../styles/theme.css\", import.meta.url), \"utf8\"));\n const sheet = renderPalette(values.brand, brandFromToml(readFileSync(input, \"utf8\")), contract, basename(input));\n if (values.out) writeFileSync(values.out, sheet);\n else process.stdout.write(sheet);\n return 0;\n}\n\ntry {\n process.exitCode = main();\n} catch (error) {\n // A bad brand file is the user's input, not a crash: the message, no stack.\n if (error instanceof PaletteError) console.error(error.message);\n else throw error;\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;;;;AACA,SAAS,cAAc,qBAAqB;AAC5C,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAK1B,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAMd,SAAS,OAAe;AACtB,QAAM,EAAE,QAAQ,YAAY,IAAI,UAAU;AAAA,IACxC,SAAS,EAAE,OAAO,EAAE,MAAM,SAAS,GAAG,KAAK,EAAE,MAAM,SAAS,GAAG,MAAM,EAAE,MAAM,WAAW,OAAO,IAAI,EAAE;AAAA,IACrG,kBAAkB;AAAA,EACpB,CAAC;AACD,QAAM,CAAC,KAAK,IAAI;AAChB,MAAI,OAAO,QAAQ,CAAC,OAAO,SAAS,CAAC,SAAS,YAAY,SAAS,GAAG;AACpE,YAAQ,MAAM,KAAK;AACnB,WAAO,OAAO,OAAO,IAAI;AAAA,EAC3B;AAGA,QAAM,WAAW,aAAa,aAAa,IAAI,IAAI,uBAAuB,YAAY,GAAG,GAAG,MAAM,CAAC;AACnG,QAAM,QAAQ,cAAc,OAAO,OAAO,cAAc,aAAa,OAAO,MAAM,CAAC,GAAG,UAAU,SAAS,KAAK,CAAC;AAC/G,MAAI,OAAO,IAAK,eAAc,OAAO,KAAK,KAAK;AAAA,MAC1C,SAAQ,OAAO,MAAM,KAAK;AAC/B,SAAO;AACT;AAEA,IAAI;AACF,UAAQ,WAAW,KAAK;AAC1B,SAAS,OAAO;AAEd,MAAI,iBAAiB,aAAc,SAAQ,MAAM,MAAM,OAAO;AAAA,MACzD,OAAM;AACX,UAAQ,WAAW;AACrB;","names":[]}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The colour contract, read out of the kit's own token sheet rather than
3
+ * restated here: `@theme inline` maps every name the class tables can paint
4
+ * (`--color-X: var(--X)`), and the derived rule lists the ones the contract
5
+ * computes from the rest. A palette owes the difference.
6
+ */
7
+ interface Contract {
8
+ /** Names every palette scope must declare. */
9
+ required: readonly string[];
10
+ /** Names the contract derives from the palette; a palette may override them. */
11
+ derived: readonly string[];
12
+ /** The tokens each derived default reads, so a palette's references can be checked for cycles through them. */
13
+ derivedFrom: Readonly<Record<string, readonly string[]>>;
14
+ }
15
+ /** The rule the derived tokens live on — see `theme.css`, the contract half of `tokens.css`. */
16
+ declare const DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand])";
17
+ interface CssRule {
18
+ selector: string;
19
+ /** Custom properties declared directly in the rule, in source order. */
20
+ declarations: Map<string, string>;
21
+ }
22
+ /**
23
+ * Top-level and `@media`-nested rules of a flat sheet, comments stripped. Only
24
+ * as much CSS as a token sheet is made of: no strings containing braces.
25
+ */
26
+ declare function readRules(css: string): CssRule[];
27
+ declare function readContract(css: string): Contract;
28
+
29
+ type PolarityName = "light" | "dark";
30
+ declare const POLARITIES: readonly PolarityName[];
31
+ /** A brand's palette: one value per contract name, for each polarity. */
32
+ interface BrandConfig {
33
+ colors: Record<PolarityName, Record<string, string>>;
34
+ /** `--brand-mark`: the URL `<Logo>` masks. */
35
+ mark?: string;
36
+ /** `--brand-aspect`: the mark's width / height, e.g. `"387 / 335"`. */
37
+ markAspect?: string;
38
+ /** `--brand-font-<role>`: a family name per role; unset roles keep the kit's. */
39
+ fonts?: Partial<Record<FontRole, string>>;
40
+ }
41
+ type FontRole = "sans" | "display" | "mono";
42
+ declare const FONT_ROLES: readonly FontRole[];
43
+ /** Every problem with a brand file at once, so one run fixes them all. */
44
+ declare class PaletteError extends Error {
45
+ readonly problems: readonly string[];
46
+ constructor(problems: readonly string[]);
47
+ }
48
+ /**
49
+ * Reads the aquafix `brand.toml` shape: `[colors.light]` and `[colors.dark]`
50
+ * keyed by contract name, optional top-level `mark` / `mark-aspect`, and the
51
+ * family names in `[fonts]` (`sans` or `text`, `display`, `mono`). The rest of
52
+ * `[fonts]` and every other table (`[print]`, …) belong to other tools and are
53
+ * ignored — loading the font files is the site's job, not the palette's.
54
+ */
55
+ declare function brandFromToml(text: string): BrandConfig;
56
+ /** The problems with `config` against `contract`; empty when it is complete. */
57
+ declare function validateBrand(config: BrandConfig, contract: Contract): string[];
58
+ /**
59
+ * A flat sheet scoping `config` under `[data-brand="<brand>"]`: light is the
60
+ * brand's default and `.light` / `.dark` beneath it pick a polarity. Selectors
61
+ * outrank the zero-specificity EV palette, so import order does not matter.
62
+ * Throws a {@link PaletteError} when a scope is incomplete — Tailwind answers an
63
+ * undefined token with no rule at all, so a hole would be silent on the page.
64
+ */
65
+ declare function renderPalette(brand: string, config: BrandConfig, contract: Contract, source?: string): string;
66
+
67
+ /**
68
+ * The subset of TOML a brand file is written in: `[table.headers]`, bare keys,
69
+ * and single-line values that are a basic string, a number, a boolean, or a
70
+ * flat array of those. Anything else is an error naming the line, rather than a
71
+ * silent misread — the kit stays dependency-free, and a brand file has no use
72
+ * for the rest of the language.
73
+ */
74
+ type TomlValue = string | number | boolean | TomlValue[];
75
+ interface TomlTable {
76
+ [key: string]: TomlValue | TomlTable;
77
+ }
78
+ declare function parseToml(text: string): TomlTable;
79
+
80
+ export { type BrandConfig, type Contract, type CssRule, DERIVED_SCOPE, FONT_ROLES, type FontRole, POLARITIES, PaletteError, type PolarityName, type TomlTable, type TomlValue, brandFromToml, parseToml, readContract, readRules, renderPalette, validateBrand };
@@ -0,0 +1,25 @@
1
+ import {
2
+ DERIVED_SCOPE,
3
+ FONT_ROLES,
4
+ POLARITIES,
5
+ PaletteError,
6
+ brandFromToml,
7
+ parseToml,
8
+ readContract,
9
+ readRules,
10
+ renderPalette,
11
+ validateBrand
12
+ } from "./chunk-3IPB4QQJ.js";
13
+ export {
14
+ DERIVED_SCOPE,
15
+ FONT_ROLES,
16
+ POLARITIES,
17
+ PaletteError,
18
+ brandFromToml,
19
+ parseToml,
20
+ readContract,
21
+ readRules,
22
+ renderPalette,
23
+ validateBrand
24
+ };
25
+ //# sourceMappingURL=palette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ function useDismissableLayer(opts) {
4
+ const { enabled, onDismiss, exclude } = opts;
5
+ const ref = React.useRef(null);
6
+ const onDismissRef = React.useRef(onDismiss);
7
+ React.useEffect(() => {
8
+ onDismissRef.current = onDismiss;
9
+ });
10
+ React.useEffect(() => {
11
+ if (!enabled) return;
12
+ function onPointerDown(event) {
13
+ const target = event.target;
14
+ if (ref.current && target && ref.current.contains(target)) return;
15
+ for (const r of exclude ?? []) {
16
+ if (r.current && target && r.current.contains(target)) return;
17
+ }
18
+ onDismissRef.current();
19
+ }
20
+ function onKeyDown(event) {
21
+ if (event.key === "Escape") onDismissRef.current();
22
+ }
23
+ document.addEventListener("pointerdown", onPointerDown, true);
24
+ document.addEventListener("keydown", onKeyDown);
25
+ return () => {
26
+ document.removeEventListener("pointerdown", onPointerDown, true);
27
+ document.removeEventListener("keydown", onKeyDown);
28
+ };
29
+ }, [enabled, exclude]);
30
+ return ref;
31
+ }
32
+ export {
33
+ useDismissableLayer
34
+ };
35
+ //# sourceMappingURL=dismissable-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/dismissable-layer.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Calls `onDismiss` on Escape or on a pointer-down outside the layer — the\n * dep-light core of every overlay's \"click away to close\". Pass refs to any\n * nodes that should NOT count as outside (e.g. the trigger), via `exclude`.\n *\n * Mirrors the dismiss behaviour Rust expresses with a full-screen backdrop.\n */\nexport function useDismissableLayer(opts: {\n enabled: boolean;\n onDismiss: () => void;\n exclude?: Array<React.RefObject<Element | null>>;\n}): React.RefObject<HTMLDivElement | null> {\n const { enabled, onDismiss, exclude } = opts;\n const ref = React.useRef<HTMLDivElement | null>(null);\n const onDismissRef = React.useRef(onDismiss);\n React.useEffect(() => {\n onDismissRef.current = onDismiss;\n });\n\n React.useEffect(() => {\n if (!enabled) return;\n\n function onPointerDown(event: PointerEvent) {\n const target = event.target as Node | null;\n if (ref.current && target && ref.current.contains(target)) return;\n for (const r of exclude ?? []) {\n if (r.current && target && r.current.contains(target)) return;\n }\n onDismissRef.current();\n }\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key === \"Escape\") onDismissRef.current();\n }\n\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [enabled, exclude]);\n\n return ref;\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAShB,SAAS,oBAAoB,MAIO;AACzC,QAAM,EAAE,SAAS,WAAW,QAAQ,IAAI;AACxC,QAAM,MAAM,MAAM,OAA8B,IAAI;AACpD,QAAM,eAAe,MAAM,OAAO,SAAS;AAC3C,QAAM,UAAU,MAAM;AACpB,iBAAa,UAAU;AAAA,EACzB,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS;AAEd,aAAS,cAAc,OAAqB;AAC1C,YAAM,SAAS,MAAM;AACrB,UAAI,IAAI,WAAW,UAAU,IAAI,QAAQ,SAAS,MAAM,EAAG;AAC3D,iBAAW,KAAK,WAAW,CAAC,GAAG;AAC7B,YAAI,EAAE,WAAW,UAAU,EAAE,QAAQ,SAAS,MAAM,EAAG;AAAA,MACzD;AACA,mBAAa,QAAQ;AAAA,IACvB;AAEA,aAAS,UAAU,OAAsB;AACvC,UAAI,MAAM,QAAQ,SAAU,cAAa,QAAQ;AAAA,IACnD;AAEA,aAAS,iBAAiB,eAAe,eAAe,IAAI;AAC5D,aAAS,iBAAiB,WAAW,SAAS;AAC9C,WAAO,MAAM;AACX,eAAS,oBAAoB,eAAe,eAAe,IAAI;AAC/D,eAAS,oBAAoB,WAAW,SAAS;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,SAAS,OAAO,CAAC;AAErB,SAAO;AACT;","names":[]}
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ const FOCUSABLE = [
4
+ "a[href]",
5
+ "button:not([disabled])",
6
+ "textarea:not([disabled])",
7
+ "input:not([disabled])",
8
+ "select:not([disabled])",
9
+ "[tabindex]"
10
+ ].map((sel) => `${sel}:not([tabindex="-1"])`).join(",");
11
+ function focusCandidates(root) {
12
+ return Array.from(root.querySelectorAll(FOCUSABLE));
13
+ }
14
+ function focusable(root) {
15
+ return focusCandidates(root).filter(
16
+ (el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement
17
+ );
18
+ }
19
+ function useFocusScope(enabled) {
20
+ const ref = React.useRef(null);
21
+ React.useEffect(() => {
22
+ if (!enabled) return;
23
+ const root = ref.current;
24
+ if (!root) return;
25
+ const previouslyFocused = document.activeElement;
26
+ const first = focusable(root)[0];
27
+ (first ?? root).focus();
28
+ function onKeyDown(event) {
29
+ if (event.key !== "Tab" || !root) return;
30
+ const items = focusable(root);
31
+ if (items.length === 0) {
32
+ event.preventDefault();
33
+ return;
34
+ }
35
+ const firstEl = items[0];
36
+ const lastEl = items[items.length - 1];
37
+ const active = document.activeElement;
38
+ if (event.shiftKey && active === firstEl) {
39
+ event.preventDefault();
40
+ lastEl.focus();
41
+ } else if (!event.shiftKey && active === lastEl) {
42
+ event.preventDefault();
43
+ firstEl.focus();
44
+ }
45
+ }
46
+ root.addEventListener("keydown", onKeyDown);
47
+ return () => {
48
+ root.removeEventListener("keydown", onKeyDown);
49
+ previouslyFocused?.focus?.();
50
+ };
51
+ }, [enabled]);
52
+ return ref;
53
+ }
54
+ export {
55
+ focusCandidates,
56
+ useFocusScope
57
+ };
58
+ //# sourceMappingURL=focus-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/focus-scope.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n// `tabindex=\"-1\"` opts an element out of Tab order whatever its tag, so the\n// exclusion goes on every branch — otherwise a roving-tabindex grid's parked\n// cells would count as tab stops.\nconst FOCUSABLE = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]\",\n]\n .map(sel => `${sel}:not([tabindex=\"-1\"])`)\n .join(\",\");\n\n/**\n * Every enabled, Tab-reachable candidate under `root` in DOM (= Tab) order,\n * before the layout-based visibility filter; `tabindex=\"-1\"` is skipped on any\n * tag. For content that is fully visible while it exists (a popover), this is\n * the tab order itself. Internal to the kit.\n */\nexport function focusCandidates(root: HTMLElement): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE));\n}\n\nfunction focusable(root: HTMLElement): HTMLElement[] {\n return focusCandidates(root).filter(\n (el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement,\n );\n}\n\n/**\n * Traps Tab focus inside the returned ref's subtree and restores focus to the\n * previously-focused element on unmount — the dep-light core of dialogs/menus,\n * replacing `@radix-ui/react-focus-scope`. On mount, focuses the first\n * focusable (or the container itself).\n *\n * Rust dialogs rely on the browser's native focus order within a fixed overlay;\n * see the README \"Limitations\".\n */\nexport function useFocusScope(enabled: boolean): React.RefObject<HTMLDivElement | null> {\n const ref = React.useRef<HTMLDivElement | null>(null);\n\n React.useEffect(() => {\n if (!enabled) return;\n const root = ref.current;\n if (!root) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const first = focusable(root)[0];\n (first ?? root).focus();\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key !== \"Tab\" || !root) return;\n const items = focusable(root);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const firstEl = items[0]!;\n const lastEl = items[items.length - 1]!;\n const active = document.activeElement;\n if (event.shiftKey && active === firstEl) {\n event.preventDefault();\n lastEl.focus();\n } else if (!event.shiftKey && active === lastEl) {\n event.preventDefault();\n firstEl.focus();\n }\n }\n\n root.addEventListener(\"keydown\", onKeyDown);\n return () => {\n root.removeEventListener(\"keydown\", onKeyDown);\n previouslyFocused?.focus?.();\n };\n }, [enabled]);\n\n return ref;\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAKvB,MAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EACG,IAAI,SAAO,GAAG,GAAG,uBAAuB,EACxC,KAAK,GAAG;AAQJ,SAAS,gBAAgB,MAAkC;AAChE,SAAO,MAAM,KAAK,KAAK,iBAA8B,SAAS,CAAC;AACjE;AAEA,SAAS,UAAU,MAAkC;AACnD,SAAO,gBAAgB,IAAI,EAAE;AAAA,IAC3B,CAAC,OAAO,GAAG,cAAc,KAAK,GAAG,eAAe,KAAK,OAAO,SAAS;AAAA,EACvE;AACF;AAWO,SAAS,cAAc,SAA0D;AACtF,QAAM,MAAM,MAAM,OAA8B,IAAI;AAEpD,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,KAAM;AAEX,UAAM,oBAAoB,SAAS;AACnC,UAAM,QAAQ,UAAU,IAAI,EAAE,CAAC;AAC/B,KAAC,SAAS,MAAM,MAAM;AAEtB,aAAS,UAAU,OAAsB;AACvC,UAAI,MAAM,QAAQ,SAAS,CAAC,KAAM;AAClC,YAAM,QAAQ,UAAU,IAAI;AAC5B,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,eAAe;AACrB;AAAA,MACF;AACA,YAAM,UAAU,MAAM,CAAC;AACvB,YAAM,SAAS,MAAM,MAAM,SAAS,CAAC;AACrC,YAAM,SAAS,SAAS;AACxB,UAAI,MAAM,YAAY,WAAW,SAAS;AACxC,cAAM,eAAe;AACrB,eAAO,MAAM;AAAA,MACf,WAAW,CAAC,MAAM,YAAY,WAAW,QAAQ;AAC/C,cAAM,eAAe;AACrB,gBAAQ,MAAM;AAAA,MAChB;AAAA,IACF;AAEA,SAAK,iBAAiB,WAAW,SAAS;AAC1C,WAAO,MAAM;AACX,WAAK,oBAAoB,WAAW,SAAS;AAC7C,yBAAmB,QAAQ;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO;AACT;","names":[]}
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ function mergeRefs(...refs) {
3
+ return (node) => {
4
+ for (const ref of refs) {
5
+ if (typeof ref === "function") ref(node);
6
+ else if (ref != null) ref.current = node;
7
+ }
8
+ };
9
+ }
10
+ export {
11
+ mergeRefs
12
+ };
13
+ //# sourceMappingURL=merge-refs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/merge-refs.ts"],"sourcesContent":["import * as React from \"react\";\n\n/**\n * Combines several refs into one callback ref so a node can satisfy multiple\n * consumers at once (e.g. an overlay that is both the floating element and the\n * dismissable layer). Used throughout the overlay components.\n */\nexport function mergeRefs<T>(\n ...refs: Array<React.Ref<T> | undefined>\n): React.RefCallback<T> {\n return (node) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") ref(node);\n else if (ref != null) (ref as React.MutableRefObject<T | null>).current = node;\n }\n };\n}\n"],"mappings":"AAAA,YAAY,WAAW;AAOhB,SAAS,aACX,MACmB;AACtB,SAAO,CAAC,SAAS;AACf,eAAW,OAAO,MAAM;AACtB,UAAI,OAAO,QAAQ,WAAY,KAAI,IAAI;AAAA,eAC9B,OAAO,KAAM,CAAC,IAAyC,UAAU;AAAA,IAC5E;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { createPortal } from "react-dom";
5
+ const PortalContext = React.createContext(null);
6
+ function PortalProvider({ container, children }) {
7
+ return /* @__PURE__ */ jsx(PortalContext.Provider, { value: container, children });
8
+ }
9
+ function Portal({ children, container }) {
10
+ const provided = React.useContext(PortalContext);
11
+ if (typeof document === "undefined") return null;
12
+ const target = container ?? provided ?? document.body;
13
+ return createPortal(children, target);
14
+ }
15
+ export {
16
+ Portal,
17
+ PortalProvider
18
+ };
19
+ //# sourceMappingURL=portal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/portal.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\n/**\n * Renders its children into `container` (default: the nearest\n * {@link PortalProvider}'s, else `document.body`) via `react-dom`'s\n * `createPortal` — the dep-light stand-in for `@radix-ui/react-portal`.\n * SSR-safe: renders nothing until mounted.\n *\n * Rust has no portal counterpart (see the README \"Limitations\"): `ev_lib::uikit`\n * overlays render inline with fixed positioning instead.\n */\nexport interface PortalProps {\n children: React.ReactNode;\n container?: Element | null;\n}\n\nconst PortalContext = React.createContext<Element | null>(null);\n\nexport interface PortalProviderProps {\n /** Where every overlay beneath this provider mounts; `null` falls back to `document.body`. */\n container: Element | null;\n children?: React.ReactNode;\n}\n\n/**\n * Re-targets every overlay beneath it (Dialog, Select, Popover, Tooltip, …)\n * into `container`. Tokens are inherited custom properties, so an overlay\n * portaled to `document.body` leaves its `[data-brand]` scope and paints in\n * the root palette; mounted inside the scope, it keeps the brand's.\n */\nexport function PortalProvider({ container, children }: PortalProviderProps) {\n return <PortalContext.Provider value={container}>{children}</PortalContext.Provider>;\n}\n\nexport function Portal({ children, container }: PortalProps) {\n const provided = React.useContext(PortalContext);\n // Render the portal synchronously on the client so a consumer's ref (e.g. a\n // floating element measured in `useLayoutEffect`) is attached in the same\n // commit. Overlays only render this when open — i.e. after a client event —\n // so there is no server render to mismatch.\n if (typeof document === \"undefined\") return null;\n const target = container ?? provided ?? document.body;\n return createPortal(children, target);\n}\n"],"mappings":";AAkCS;AAhCT,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAgB7B,MAAM,gBAAgB,MAAM,cAA8B,IAAI;AAcvD,SAAS,eAAe,EAAE,WAAW,SAAS,GAAwB;AAC3E,SAAO,oBAAC,cAAc,UAAd,EAAuB,OAAO,WAAY,UAAS;AAC7D;AAEO,SAAS,OAAO,EAAE,UAAU,UAAU,GAAgB;AAC3D,QAAM,WAAW,MAAM,WAAW,aAAa;AAK/C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,SAAS,aAAa,YAAY,SAAS;AACjD,SAAO,aAAa,UAAU,MAAM;AACtC;","names":[]}
@@ -0,0 +1,45 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { flushSync } from "react-dom";
4
+ const EXIT_FALLBACK_MS = 1e3;
5
+ function usePresence(present) {
6
+ const ref = React.useRef(null);
7
+ const [mounted, setMounted] = React.useState(present);
8
+ React.useLayoutEffect(() => {
9
+ if (present) {
10
+ setMounted(true);
11
+ return;
12
+ }
13
+ const node = ref.current;
14
+ if (!node) {
15
+ setMounted(false);
16
+ return;
17
+ }
18
+ const name = getComputedStyle(node).animationName;
19
+ if (name === "" || name === "none") {
20
+ setMounted(false);
21
+ return;
22
+ }
23
+ const prevPointerEvents = node.style.pointerEvents;
24
+ node.style.pointerEvents = "none";
25
+ const onEnd = (e) => {
26
+ if (e.target !== node) return;
27
+ if (e.animationName && !name.includes(e.animationName)) return;
28
+ flushSync(() => setMounted(false));
29
+ };
30
+ node.addEventListener("animationend", onEnd);
31
+ node.addEventListener("animationcancel", onEnd);
32
+ const timer = setTimeout(() => setMounted(false), EXIT_FALLBACK_MS);
33
+ return () => {
34
+ clearTimeout(timer);
35
+ node.removeEventListener("animationend", onEnd);
36
+ node.removeEventListener("animationcancel", onEnd);
37
+ node.style.pointerEvents = prevPointerEvents;
38
+ };
39
+ }, [present]);
40
+ return { isPresent: present || mounted, ref };
41
+ }
42
+ export {
43
+ usePresence
44
+ };
45
+ //# sourceMappingURL=presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/presence.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { flushSync } from \"react-dom\";\n\n// Safety net for a missed exit `animationend` (display:none ancestor, tab\n// hidden, animations disabled by a stylesheet after the check). Well past the\n// longest exit in tw-animate-css.\nconst EXIT_FALLBACK_MS = 1000;\n\n/**\n * Keeps an overlay mounted through its exit animation. Enter animations\n * (`data-[state=open]:animate-in`) play on mount; once `present` turns false\n * the consumer flips the node to `data-state=\"closed\"` in the same render and\n * the node stays until its exit animation ends (or the fallback timer fires).\n * Re-opening mid-exit just flips the state back; the node is never remounted.\n *\n * The exit was dropped once (5728b40) because closing flickered: tw-animate's\n * `animate-out` runs with `animation-fill-mode: none`, so after `animationend`\n * the node snaps back to full opacity/scale for the frame React needs to\n * process an async unmount. Hence `flushSync` inside the native listener —\n * the unmount commits before the browser paints that frame (Radix Presence\n * does the same). With no animation on the node (jsdom, overlays without an\n * exit class) unmount is immediate, as before. The Rust mirror still does not\n * defer unmount.\n *\n * Pass `ref` to the animated node — the one carrying `data-state`.\n */\nexport function usePresence(present: boolean): {\n isPresent: boolean;\n ref: React.RefObject<HTMLElement | null>;\n} {\n const ref = React.useRef<HTMLElement | null>(null);\n const [mounted, setMounted] = React.useState(present);\n\n React.useLayoutEffect(() => {\n if (present) {\n setMounted(true);\n return;\n }\n const node = ref.current;\n if (!node) {\n setMounted(false);\n return;\n }\n // `data-state=\"closed\"` is already in the DOM, so this resolves to the\n // exit animation (if any) — reading it forces the style recalc.\n const name = getComputedStyle(node).animationName;\n if (name === \"\" || name === \"none\") {\n setMounted(false);\n return;\n }\n const prevPointerEvents = node.style.pointerEvents;\n node.style.pointerEvents = \"none\";\n // Animation events bubble from descendants, and a close mid-enter cancels\n // the enter keyframe on this very node — only the exit's own end counts.\n const onEnd = (e: AnimationEvent) => {\n if (e.target !== node) return;\n if (e.animationName && !name.includes(e.animationName)) return;\n flushSync(() => setMounted(false));\n };\n node.addEventListener(\"animationend\", onEnd);\n node.addEventListener(\"animationcancel\", onEnd);\n const timer = setTimeout(() => setMounted(false), EXIT_FALLBACK_MS);\n return () => {\n clearTimeout(timer);\n node.removeEventListener(\"animationend\", onEnd);\n node.removeEventListener(\"animationcancel\", onEnd);\n node.style.pointerEvents = prevPointerEvents;\n };\n }, [present]);\n\n return { isPresent: present || mounted, ref };\n}\n"],"mappings":";AAEA,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAK1B,MAAM,mBAAmB;AAoBlB,SAAS,YAAY,SAG1B;AACA,QAAM,MAAM,MAAM,OAA2B,IAAI;AACjD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,OAAO;AAEpD,QAAM,gBAAgB,MAAM;AAC1B,QAAI,SAAS;AACX,iBAAW,IAAI;AACf;AAAA,IACF;AACA,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,MAAM;AACT,iBAAW,KAAK;AAChB;AAAA,IACF;AAGA,UAAM,OAAO,iBAAiB,IAAI,EAAE;AACpC,QAAI,SAAS,MAAM,SAAS,QAAQ;AAClC,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,UAAM,oBAAoB,KAAK,MAAM;AACrC,SAAK,MAAM,gBAAgB;AAG3B,UAAM,QAAQ,CAAC,MAAsB;AACnC,UAAI,EAAE,WAAW,KAAM;AACvB,UAAI,EAAE,iBAAiB,CAAC,KAAK,SAAS,EAAE,aAAa,EAAG;AACxD,gBAAU,MAAM,WAAW,KAAK,CAAC;AAAA,IACnC;AACA,SAAK,iBAAiB,gBAAgB,KAAK;AAC3C,SAAK,iBAAiB,mBAAmB,KAAK;AAC9C,UAAM,QAAQ,WAAW,MAAM,WAAW,KAAK,GAAG,gBAAgB;AAClE,WAAO,MAAM;AACX,mBAAa,KAAK;AAClB,WAAK,oBAAoB,gBAAgB,KAAK;AAC9C,WAAK,oBAAoB,mBAAmB,KAAK;AACjD,WAAK,MAAM,gBAAgB;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,WAAW,WAAW,SAAS,IAAI;AAC9C;","names":[]}
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/cn.js";
3
+ const Slot = React.forwardRef(function Slot2({ children, ...slotProps }, forwardedRef) {
4
+ if (!React.isValidElement(children)) {
5
+ return React.Children.count(children) > 1 ? React.Children.only(null) : null;
6
+ }
7
+ const child = children;
8
+ const childProps = child.props;
9
+ const merged = { ...slotProps, ...childProps };
10
+ for (const key of Object.keys(slotProps)) {
11
+ const slotValue = slotProps[key];
12
+ const childValue = childProps[key];
13
+ if (/^on[A-Z]/.test(key) && typeof slotValue === "function") {
14
+ merged[key] = typeof childValue === "function" ? (...args) => {
15
+ childValue(...args);
16
+ slotValue(...args);
17
+ } : slotValue;
18
+ } else if (key === "style") {
19
+ merged[key] = { ...slotValue, ...childValue };
20
+ } else if (key === "className") {
21
+ merged[key] = cn(slotValue, childValue);
22
+ }
23
+ }
24
+ const childRef = child.ref;
25
+ if (forwardedRef || childRef) merged["ref"] = composeRefs(forwardedRef, childRef);
26
+ return React.cloneElement(child, merged);
27
+ });
28
+ function composeRefs(...refs) {
29
+ return (node) => {
30
+ for (const ref of refs) {
31
+ if (typeof ref === "function") ref(node);
32
+ else if (ref != null) ref.current = node;
33
+ }
34
+ };
35
+ }
36
+ export {
37
+ Slot
38
+ };
39
+ //# sourceMappingURL=slot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/slot.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/**\n * Minimal `asChild` mechanism — the dep-light replacement for\n * `@radix-ui/react-slot`. Renders by cloning its single child, merging the\n * Slot's own props onto it: `className` is fused with `cn`, event handlers are\n * chained (child's runs first, then ours), and `style` objects are shallow\n * merged. Refs are composed so both the caller's and the child's ref fire.\n *\n * There is no Rust counterpart: Dioxus composition is expressed with `children`\n * directly, so `asChild` is a TS-only ergonomic.\n */\nexport interface SlotProps extends React.HTMLAttributes<HTMLElement> {\n children?: React.ReactNode;\n}\n\nexport const Slot = React.forwardRef<HTMLElement, SlotProps>(function Slot(\n { children, ...slotProps },\n forwardedRef,\n) {\n if (!React.isValidElement(children)) {\n return React.Children.count(children) > 1\n ? React.Children.only(null)\n : null;\n }\n\n const child = children as React.ReactElement<Record<string, unknown>>;\n const childProps = child.props;\n\n const merged: Record<string, unknown> = { ...slotProps, ...childProps };\n\n for (const key of Object.keys(slotProps)) {\n const slotValue = (slotProps as Record<string, unknown>)[key];\n const childValue = childProps[key];\n if (/^on[A-Z]/.test(key) && typeof slotValue === \"function\") {\n merged[key] =\n typeof childValue === \"function\"\n ? (...args: unknown[]) => {\n (childValue as (...a: unknown[]) => void)(...args);\n (slotValue as (...a: unknown[]) => void)(...args);\n }\n : slotValue;\n } else if (key === \"style\") {\n merged[key] = { ...(slotValue as object), ...(childValue as object) };\n } else if (key === \"className\") {\n merged[key] = cn(slotValue as string, childValue as string);\n }\n }\n\n // Only when there is a ref to compose. `Slot` renders inside server\n // components too (`<Button asChild>` has no \"use client\"), and a function\n // ref in a Server Component's output fails the render (\"Refs cannot be used\n // in Server Components\") even when it would call nothing.\n const childRef = (child as { ref?: React.Ref<unknown> }).ref;\n if (forwardedRef || childRef) merged[\"ref\"] = composeRefs(forwardedRef, childRef);\n\n return React.cloneElement(child, merged);\n});\n\nfunction composeRefs<T>(...refs: Array<React.Ref<T> | undefined>): React.RefCallback<T> {\n return (node) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") ref(node);\n else if (ref != null) (ref as React.MutableRefObject<T | null>).current = node;\n }\n };\n}\n"],"mappings":"AAAA,YAAY,WAAW;AACvB,SAAS,UAAU;AAgBZ,MAAM,OAAO,MAAM,WAAmC,SAASA,MACpE,EAAE,UAAU,GAAG,UAAU,GACzB,cACA;AACA,MAAI,CAAC,MAAM,eAAe,QAAQ,GAAG;AACnC,WAAO,MAAM,SAAS,MAAM,QAAQ,IAAI,IACpC,MAAM,SAAS,KAAK,IAAI,IACxB;AAAA,EACN;AAEA,QAAM,QAAQ;AACd,QAAM,aAAa,MAAM;AAEzB,QAAM,SAAkC,EAAE,GAAG,WAAW,GAAG,WAAW;AAEtE,aAAW,OAAO,OAAO,KAAK,SAAS,GAAG;AACxC,UAAM,YAAa,UAAsC,GAAG;AAC5D,UAAM,aAAa,WAAW,GAAG;AACjC,QAAI,WAAW,KAAK,GAAG,KAAK,OAAO,cAAc,YAAY;AAC3D,aAAO,GAAG,IACR,OAAO,eAAe,aAClB,IAAI,SAAoB;AACtB,QAAC,WAAyC,GAAG,IAAI;AACjD,QAAC,UAAwC,GAAG,IAAI;AAAA,MAClD,IACA;AAAA,IACR,WAAW,QAAQ,SAAS;AAC1B,aAAO,GAAG,IAAI,EAAE,GAAI,WAAsB,GAAI,WAAsB;AAAA,IACtE,WAAW,QAAQ,aAAa;AAC9B,aAAO,GAAG,IAAI,GAAG,WAAqB,UAAoB;AAAA,IAC5D;AAAA,EACF;AAMA,QAAM,WAAY,MAAuC;AACzD,MAAI,gBAAgB,SAAU,QAAO,KAAK,IAAI,YAAY,cAAc,QAAQ;AAEhF,SAAO,MAAM,aAAa,OAAO,MAAM;AACzC,CAAC;AAED,SAAS,eAAkB,MAA6D;AACtF,SAAO,CAAC,SAAS;AACf,eAAW,OAAO,MAAM;AACtB,UAAI,OAAO,QAAQ,WAAY,KAAI,IAAI;AAAA,eAC9B,OAAO,KAAM,CAAC,IAAyC,UAAU;AAAA,IAC5E;AAAA,EACF;AACF;","names":["Slot"]}
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ function useControllableState(opts) {
4
+ const { value, defaultValue, onChange } = opts;
5
+ const isControlled = value !== void 0;
6
+ const [uncontrolled, setUncontrolled] = React.useState(defaultValue);
7
+ const current = isControlled ? value : uncontrolled;
8
+ const onChangeRef = React.useRef(onChange);
9
+ React.useEffect(() => {
10
+ onChangeRef.current = onChange;
11
+ });
12
+ const setValue = React.useCallback(
13
+ (next) => {
14
+ if (!isControlled) setUncontrolled(next);
15
+ onChangeRef.current?.(next);
16
+ },
17
+ [isControlled]
18
+ );
19
+ return [current, setValue];
20
+ }
21
+ export {
22
+ useControllableState
23
+ };
24
+ //# sourceMappingURL=use-controllable-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/use-controllable-state.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Controlled/uncontrolled state, the dep-light equivalent of Radix's\n * `useControllableState`. When `value` is provided the component is controlled\n * (the hook returns it and routes writes through `onChange`); otherwise it owns\n * an internal `useState` seeded from `defaultValue`.\n *\n * The Rust mirror is `use_controllable_signal` in `ev_lib::uikit`.\n */\nexport function useControllableState<T>(opts: {\n value?: T | undefined;\n defaultValue: T;\n onChange?: ((value: T) => void) | undefined;\n}): [T, (next: T) => void] {\n const { value, defaultValue, onChange } = opts;\n const isControlled = value !== undefined;\n const [uncontrolled, setUncontrolled] = React.useState<T>(defaultValue);\n\n const current = isControlled ? value : uncontrolled;\n\n const onChangeRef = React.useRef(onChange);\n React.useEffect(() => {\n onChangeRef.current = onChange;\n });\n\n const setValue = React.useCallback(\n (next: T) => {\n if (!isControlled) setUncontrolled(next);\n onChangeRef.current?.(next);\n },\n [isControlled],\n );\n\n return [current, setValue];\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAUhB,SAAS,qBAAwB,MAIb;AACzB,QAAM,EAAE,OAAO,cAAc,SAAS,IAAI;AAC1C,QAAM,eAAe,UAAU;AAC/B,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAY,YAAY;AAEtE,QAAM,UAAU,eAAe,QAAQ;AAEvC,QAAM,cAAc,MAAM,OAAO,QAAQ;AACzC,QAAM,UAAU,MAAM;AACpB,gBAAY,UAAU;AAAA,EACxB,CAAC;AAED,QAAM,WAAW,MAAM;AAAA,IACrB,CAAC,SAAY;AACX,UAAI,CAAC,aAAc,iBAAgB,IAAI;AACvC,kBAAY,UAAU,IAAI;AAAA,IAC5B;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SAAO,CAAC,SAAS,QAAQ;AAC3B;","names":[]}
@@ -0,0 +1,128 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ const ABSOLUTE = Object.freeze({ position: "absolute", top: 0, left: 0 });
4
+ const POSITIONED = /* @__PURE__ */ new Set(["relative", "absolute", "fixed", "sticky"]);
5
+ function toContainingBlock(floating, x, y) {
6
+ const parent = floating.offsetParent;
7
+ const doc = floating.ownerDocument;
8
+ const root = parent === doc.body || parent === doc.documentElement || parent === null;
9
+ const positioned = parent instanceof HTMLElement && (!root || POSITIONED.has(doc.defaultView?.getComputedStyle(parent).position ?? ""));
10
+ if (!positioned) {
11
+ const win = doc.defaultView;
12
+ return { x: x + (win?.scrollX ?? 0), y: y + (win?.scrollY ?? 0), root: true };
13
+ }
14
+ const p = parent.getBoundingClientRect();
15
+ return {
16
+ x: x - p.left - parent.clientLeft + parent.scrollLeft,
17
+ y: y - p.top - parent.clientTop + parent.scrollTop,
18
+ root: false
19
+ };
20
+ }
21
+ function layoutSize(el) {
22
+ return { width: el.offsetWidth, height: el.offsetHeight };
23
+ }
24
+ function viewport() {
25
+ const el = document.documentElement;
26
+ return {
27
+ vw: el.clientWidth || window.innerWidth,
28
+ vh: el.clientHeight || window.innerHeight
29
+ };
30
+ }
31
+ function useFloating(opts) {
32
+ const { anchorRef, open, side = "bottom", align = "center", offset = 4 } = opts;
33
+ const floatingRef = React.useRef(null);
34
+ const [placedSide, setPlacedSide] = React.useState(side);
35
+ const placement = React.useRef({
36
+ side,
37
+ shiftX: 0,
38
+ shiftY: 0
39
+ });
40
+ React.useLayoutEffect(() => {
41
+ if (!open) return;
42
+ function ideal(a, f, placed) {
43
+ let top = 0;
44
+ let left = 0;
45
+ const vertical = placed === "top" || placed === "bottom";
46
+ if (placed === "bottom") top = a.bottom + offset;
47
+ else if (placed === "top") top = a.top - offset - f.height;
48
+ else if (placed === "right") left = a.right + offset;
49
+ else left = a.left - offset - f.width;
50
+ if (vertical) {
51
+ if (align === "start") left = a.left;
52
+ else if (align === "end") left = a.right - f.width;
53
+ else left = a.left + a.width / 2 - f.width / 2;
54
+ } else {
55
+ if (align === "start") top = a.top;
56
+ else if (align === "end") top = a.bottom - f.height;
57
+ else top = a.top + a.height / 2 - f.height / 2;
58
+ }
59
+ return { top, left };
60
+ }
61
+ function write(floating, f, top, left, contain) {
62
+ const p = toContainingBlock(floating, left, top);
63
+ let { x, y } = p;
64
+ if (contain && p.root) {
65
+ const icbWidth = floating.ownerDocument.documentElement.clientWidth || window.innerWidth;
66
+ x = Math.max(0, Math.min(x, icbWidth - f.width));
67
+ y = Math.max(0, y);
68
+ }
69
+ floating.style.translate = `${Math.round(x)}px ${Math.round(y)}px`;
70
+ }
71
+ function place() {
72
+ const anchor = anchorRef.current;
73
+ const floating = floatingRef.current;
74
+ if (!anchor || !floating) return;
75
+ const a = anchor.getBoundingClientRect();
76
+ const f = layoutSize(floating);
77
+ const { vw, vh } = viewport();
78
+ let placed = side;
79
+ if (side === "bottom" && a.bottom + offset + f.height > vh && a.top - offset - f.height > 0)
80
+ placed = "top";
81
+ else if (side === "top" && a.top - offset - f.height < 0 && a.bottom + offset + f.height < vh)
82
+ placed = "bottom";
83
+ else if (side === "right" && a.right + offset + f.width > vw && a.left - offset - f.width > 0)
84
+ placed = "left";
85
+ else if (side === "left" && a.left - offset - f.width < 0 && a.right + offset + f.width < vw)
86
+ placed = "right";
87
+ const { top, left } = ideal(a, f, placed);
88
+ const clampedLeft = Math.max(offset, Math.min(left, vw - f.width - offset));
89
+ const clampedTop = Math.max(offset, Math.min(top, vh - f.height - offset));
90
+ placement.current = {
91
+ side: placed,
92
+ shiftX: clampedLeft - left,
93
+ shiftY: clampedTop - top
94
+ };
95
+ write(floating, f, clampedTop, clampedLeft, false);
96
+ setPlacedSide((prev) => prev === placed ? prev : placed);
97
+ }
98
+ function sync() {
99
+ const anchor = anchorRef.current;
100
+ const floating = floatingRef.current;
101
+ if (!anchor || !floating) return;
102
+ const a = anchor.getBoundingClientRect();
103
+ const f = layoutSize(floating);
104
+ const { side: placed, shiftX, shiftY } = placement.current;
105
+ const { top, left } = ideal(a, f, placed);
106
+ write(floating, f, top + shiftY, left + shiftX, true);
107
+ }
108
+ place();
109
+ window.addEventListener("scroll", sync, true);
110
+ window.addEventListener("resize", place);
111
+ let observer;
112
+ if (typeof ResizeObserver !== "undefined") {
113
+ observer = new ResizeObserver(place);
114
+ if (anchorRef.current) observer.observe(anchorRef.current);
115
+ if (floatingRef.current) observer.observe(floatingRef.current);
116
+ }
117
+ return () => {
118
+ window.removeEventListener("scroll", sync, true);
119
+ window.removeEventListener("resize", place);
120
+ observer?.disconnect();
121
+ };
122
+ }, [open, side, align, offset, anchorRef]);
123
+ return { floatingRef, style: ABSOLUTE, side: placedSide, align };
124
+ }
125
+ export {
126
+ useFloating
127
+ };
128
+ //# sourceMappingURL=use-floating.js.map