@nibapp/ui 0.0.0-stage → 0.1.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 (292) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +124 -2
  4. package/TRADEMARKS.md +22 -0
  5. package/dist/_bundled/money/index.d.ts +98 -0
  6. package/dist/_bundled/money/index.js +152 -0
  7. package/dist/_bundled/tokens/index.d.ts +2 -0
  8. package/dist/_bundled/tokens/index.js +2 -0
  9. package/dist/_bundled/tokens/palette.gen.d.ts +51 -0
  10. package/dist/_bundled/tokens/palette.gen.js +206 -0
  11. package/dist/_bundled/tokens/spec.d.ts +291 -0
  12. package/dist/_bundled/tokens/spec.js +221 -0
  13. package/dist/components/accordion.d.ts +27 -0
  14. package/dist/components/accordion.js +66 -0
  15. package/dist/components/alert-dialog.d.ts +59 -0
  16. package/dist/components/alert-dialog.js +82 -0
  17. package/dist/components/alert.d.ts +141 -0
  18. package/dist/components/alert.js +55 -0
  19. package/dist/components/appear.d.ts +21 -0
  20. package/dist/components/appear.js +42 -0
  21. package/dist/components/avatar.d.ts +196 -0
  22. package/dist/components/avatar.js +135 -0
  23. package/dist/components/badge.d.ts +183 -0
  24. package/dist/components/badge.js +68 -0
  25. package/dist/components/block-stack.d.ts +33 -0
  26. package/dist/components/block-stack.js +197 -0
  27. package/dist/components/button.d.ts +327 -0
  28. package/dist/components/button.js +101 -0
  29. package/dist/components/card.d.ts +131 -0
  30. package/dist/components/card.js +100 -0
  31. package/dist/components/checkbox.d.ts +34 -0
  32. package/dist/components/checkbox.js +37 -0
  33. package/dist/components/choice-row.d.ts +104 -0
  34. package/dist/components/choice-row.js +56 -0
  35. package/dist/components/clamped-text.d.ts +17 -0
  36. package/dist/components/clamped-text.js +47 -0
  37. package/dist/components/code-block.d.ts +48 -0
  38. package/dist/components/code-block.js +124 -0
  39. package/dist/components/code-input.d.ts +49 -0
  40. package/dist/components/code-input.js +59 -0
  41. package/dist/components/combobox.d.ts +194 -0
  42. package/dist/components/combobox.js +176 -0
  43. package/dist/components/command-palette.d.ts +57 -0
  44. package/dist/components/command-palette.js +83 -0
  45. package/dist/components/context-menu.d.ts +24 -0
  46. package/dist/components/context-menu.js +30 -0
  47. package/dist/components/copy-button.d.ts +9 -0
  48. package/dist/components/copy-button.js +20 -0
  49. package/dist/components/copy-command.d.ts +18 -0
  50. package/dist/components/copy-command.js +19 -0
  51. package/dist/components/data-table.d.ts +240 -0
  52. package/dist/components/data-table.js +63 -0
  53. package/dist/components/dialog.d.ts +80 -0
  54. package/dist/components/dialog.js +85 -0
  55. package/dist/components/diff-lines.d.ts +20 -0
  56. package/dist/components/diff-lines.js +10 -0
  57. package/dist/components/diff-view.d.ts +128 -0
  58. package/dist/components/diff-view.js +675 -0
  59. package/dist/components/disclosure.d.ts +33 -0
  60. package/dist/components/disclosure.js +51 -0
  61. package/dist/components/drawn-check.d.ts +8 -0
  62. package/dist/components/drawn-check.js +16 -0
  63. package/dist/components/edge-resizer.d.ts +26 -0
  64. package/dist/components/edge-resizer.js +53 -0
  65. package/dist/components/empty-state.d.ts +112 -0
  66. package/dist/components/empty-state.js +56 -0
  67. package/dist/components/expand-toggle.d.ts +19 -0
  68. package/dist/components/expand-toggle.js +10 -0
  69. package/dist/components/field.d.ts +48 -0
  70. package/dist/components/field.js +60 -0
  71. package/dist/components/filter-chip.d.ts +47 -0
  72. package/dist/components/filter-chip.js +33 -0
  73. package/dist/components/group.d.ts +10 -0
  74. package/dist/components/group.js +9 -0
  75. package/dist/components/icon-text.d.ts +11 -0
  76. package/dist/components/icon-text.js +10 -0
  77. package/dist/components/image-sources.d.ts +57 -0
  78. package/dist/components/image-sources.js +58 -0
  79. package/dist/components/image-thumbnail.d.ts +34 -0
  80. package/dist/components/image-thumbnail.js +58 -0
  81. package/dist/components/image-viewer.d.ts +60 -0
  82. package/dist/components/image-viewer.js +411 -0
  83. package/dist/components/inline-edit.d.ts +22 -0
  84. package/dist/components/inline-edit.js +51 -0
  85. package/dist/components/input.d.ts +155 -0
  86. package/dist/components/input.js +109 -0
  87. package/dist/components/item-label.d.ts +11 -0
  88. package/dist/components/item-label.js +5 -0
  89. package/dist/components/item-row.d.ts +23 -0
  90. package/dist/components/item-row.js +19 -0
  91. package/dist/components/kbd.d.ts +55 -0
  92. package/dist/components/kbd.js +86 -0
  93. package/dist/components/link-policy.d.ts +24 -0
  94. package/dist/components/link-policy.js +32 -0
  95. package/dist/components/linked-text.d.ts +15 -0
  96. package/dist/components/linked-text.js +48 -0
  97. package/dist/components/loading-region.d.ts +18 -0
  98. package/dist/components/loading-region.js +17 -0
  99. package/dist/components/markdown.d.ts +34 -0
  100. package/dist/components/markdown.js +307 -0
  101. package/dist/components/menu.d.ts +132 -0
  102. package/dist/components/menu.js +102 -0
  103. package/dist/components/message-bubble.d.ts +57 -0
  104. package/dist/components/message-bubble.js +28 -0
  105. package/dist/components/meta-line.d.ts +28 -0
  106. package/dist/components/meta-line.js +36 -0
  107. package/dist/components/money-input.d.ts +23 -0
  108. package/dist/components/money-input.js +34 -0
  109. package/dist/components/native-select.d.ts +120 -0
  110. package/dist/components/native-select.js +85 -0
  111. package/dist/components/page-trail.d.ts +30 -0
  112. package/dist/components/page-trail.js +43 -0
  113. package/dist/components/page.d.ts +80 -0
  114. package/dist/components/page.js +60 -0
  115. package/dist/components/path-text.d.ts +8 -0
  116. package/dist/components/path-text.js +11 -0
  117. package/dist/components/percent-input.d.ts +20 -0
  118. package/dist/components/percent-input.js +32 -0
  119. package/dist/components/popover.d.ts +52 -0
  120. package/dist/components/popover.js +36 -0
  121. package/dist/components/progress.d.ts +389 -0
  122. package/dist/components/progress.js +142 -0
  123. package/dist/components/radio.d.ts +65 -0
  124. package/dist/components/radio.js +54 -0
  125. package/dist/components/remote-image-gate.d.ts +34 -0
  126. package/dist/components/remote-image-gate.js +60 -0
  127. package/dist/components/resizable.d.ts +23 -0
  128. package/dist/components/resizable.js +30 -0
  129. package/dist/components/rolling-number.d.ts +13 -0
  130. package/dist/components/rolling-number.js +69 -0
  131. package/dist/components/scroll-area.d.ts +17 -0
  132. package/dist/components/scroll-area.js +25 -0
  133. package/dist/components/section-label.d.ts +10 -0
  134. package/dist/components/section-label.js +21 -0
  135. package/dist/components/section-nav.d.ts +22 -0
  136. package/dist/components/section-nav.js +80 -0
  137. package/dist/components/segmented-control.d.ts +125 -0
  138. package/dist/components/segmented-control.js +101 -0
  139. package/dist/components/select.d.ts +120 -0
  140. package/dist/components/select.js +89 -0
  141. package/dist/components/separator.d.ts +10 -0
  142. package/dist/components/separator.js +12 -0
  143. package/dist/components/sheet.d.ts +101 -0
  144. package/dist/components/sheet.js +129 -0
  145. package/dist/components/shortcut-recorder.d.ts +31 -0
  146. package/dist/components/shortcut-recorder.js +67 -0
  147. package/dist/components/skeleton.d.ts +31 -0
  148. package/dist/components/skeleton.js +33 -0
  149. package/dist/components/slider.d.ts +138 -0
  150. package/dist/components/slider.js +98 -0
  151. package/dist/components/spinner.d.ts +37 -0
  152. package/dist/components/spinner.js +16 -0
  153. package/dist/components/switch.d.ts +59 -0
  154. package/dist/components/switch.js +56 -0
  155. package/dist/components/tab-strip.d.ts +29 -0
  156. package/dist/components/tab-strip.js +42 -0
  157. package/dist/components/tabs.d.ts +13 -0
  158. package/dist/components/tabs.js +68 -0
  159. package/dist/components/text-link.d.ts +54 -0
  160. package/dist/components/text-link.js +38 -0
  161. package/dist/components/textarea.d.ts +75 -0
  162. package/dist/components/textarea.js +90 -0
  163. package/dist/components/toast.d.ts +74 -0
  164. package/dist/components/toast.js +128 -0
  165. package/dist/components/tooltip.d.ts +50 -0
  166. package/dist/components/tooltip.js +46 -0
  167. package/dist/components/truncated-text.d.ts +15 -0
  168. package/dist/components/truncated-text.js +19 -0
  169. package/dist/i18n/defaults.d.ts +7 -0
  170. package/dist/i18n/defaults.js +7 -0
  171. package/dist/i18n/format.d.ts +84 -0
  172. package/dist/i18n/format.js +169 -0
  173. package/dist/i18n/index.d.ts +6 -0
  174. package/dist/i18n/index.js +8 -0
  175. package/dist/i18n/locales/ru-formatters.d.ts +3 -0
  176. package/dist/i18n/locales/ru-formatters.js +22 -0
  177. package/dist/i18n/locales/ru.d.ts +5 -0
  178. package/dist/i18n/locales/ru.js +185 -0
  179. package/dist/i18n/plural.d.ts +20 -0
  180. package/dist/i18n/plural.js +26 -0
  181. package/dist/i18n/provider.d.ts +36 -0
  182. package/dist/i18n/provider.js +39 -0
  183. package/dist/i18n/rich.d.ts +14 -0
  184. package/dist/i18n/rich.js +44 -0
  185. package/dist/i18n/ru-plural.d.ts +3 -0
  186. package/dist/i18n/ru-plural.js +4 -0
  187. package/dist/i18n/strings/alert-dialog.d.ts +5 -0
  188. package/dist/i18n/strings/alert-dialog.js +5 -0
  189. package/dist/i18n/strings/block-stack.d.ts +17 -0
  190. package/dist/i18n/strings/block-stack.js +11 -0
  191. package/dist/i18n/strings/code-block.d.ts +18 -0
  192. package/dist/i18n/strings/code-block.js +13 -0
  193. package/dist/i18n/strings/combobox.d.ts +12 -0
  194. package/dist/i18n/strings/combobox.js +10 -0
  195. package/dist/i18n/strings/command-palette.d.ts +17 -0
  196. package/dist/i18n/strings/command-palette.js +11 -0
  197. package/dist/i18n/strings/diff-view.d.ts +92 -0
  198. package/dist/i18n/strings/diff-view.js +68 -0
  199. package/dist/i18n/strings/image-viewer.d.ts +43 -0
  200. package/dist/i18n/strings/image-viewer.js +32 -0
  201. package/dist/i18n/strings/index.d.ts +314 -0
  202. package/dist/i18n/strings/index.js +43 -0
  203. package/dist/i18n/strings/kbd.d.ts +42 -0
  204. package/dist/i18n/strings/kbd.js +39 -0
  205. package/dist/i18n/strings/markdown.d.ts +23 -0
  206. package/dist/i18n/strings/markdown.js +18 -0
  207. package/dist/i18n/strings/money.d.ts +7 -0
  208. package/dist/i18n/strings/money.js +5 -0
  209. package/dist/i18n/strings/primitives.d.ts +25 -0
  210. package/dist/i18n/strings/primitives.js +18 -0
  211. package/dist/i18n/strings/progress.d.ts +4 -0
  212. package/dist/i18n/strings/progress.js +4 -0
  213. package/dist/i18n/strings/shortcut-recorder.d.ts +15 -0
  214. package/dist/i18n/strings/shortcut-recorder.js +11 -0
  215. package/dist/i18n/strings/time.d.ts +41 -0
  216. package/dist/i18n/strings/time.js +20 -0
  217. package/dist/i18n/strings/toast.d.ts +6 -0
  218. package/dist/i18n/strings/toast.js +6 -0
  219. package/dist/i18n/time-zones.d.ts +20 -0
  220. package/dist/i18n/time-zones.js +64 -0
  221. package/dist/index.d.ts +84 -0
  222. package/dist/index.js +88 -0
  223. package/dist/lib/ansi.d.ts +55 -0
  224. package/dist/lib/ansi.js +438 -0
  225. package/dist/lib/clock.d.ts +32 -0
  226. package/dist/lib/clock.js +106 -0
  227. package/dist/lib/cn.d.ts +12 -0
  228. package/dist/lib/cn.js +28 -0
  229. package/dist/lib/control-props.d.ts +21 -0
  230. package/dist/lib/control-props.js +14 -0
  231. package/dist/lib/direction.d.ts +13 -0
  232. package/dist/lib/direction.js +25 -0
  233. package/dist/lib/image-size.d.ts +12 -0
  234. package/dist/lib/image-size.js +66 -0
  235. package/dist/lib/keycaps.d.ts +21 -0
  236. package/dist/lib/keycaps.js +62 -0
  237. package/dist/lib/keyed.d.ts +9 -0
  238. package/dist/lib/keyed.js +14 -0
  239. package/dist/lib/keys.d.ts +49 -0
  240. package/dist/lib/keys.js +159 -0
  241. package/dist/lib/kit-motion.d.ts +9 -0
  242. package/dist/lib/kit-motion.js +12 -0
  243. package/dist/lib/link-check-dialog.d.ts +11 -0
  244. package/dist/lib/link-check-dialog.js +18 -0
  245. package/dist/lib/motion-features.d.ts +1 -0
  246. package/dist/lib/motion-features.js +4 -0
  247. package/dist/lib/motion-provider.d.ts +5 -0
  248. package/dist/lib/motion-provider.js +8 -0
  249. package/dist/lib/motion.d.ts +39 -0
  250. package/dist/lib/motion.js +49 -0
  251. package/dist/lib/number-format.d.ts +114 -0
  252. package/dist/lib/number-format.js +313 -0
  253. package/dist/lib/patch.d.ts +72 -0
  254. package/dist/lib/patch.js +344 -0
  255. package/dist/lib/path.d.ts +34 -0
  256. package/dist/lib/path.js +85 -0
  257. package/dist/lib/search.d.ts +10 -0
  258. package/dist/lib/search.js +20 -0
  259. package/dist/lib/shiki-themes.d.ts +7 -0
  260. package/dist/lib/shiki-themes.js +115 -0
  261. package/dist/lib/shiki.d.ts +101 -0
  262. package/dist/lib/shiki.js +353 -0
  263. package/dist/lib/shortcut-context.d.ts +23 -0
  264. package/dist/lib/shortcut-context.js +29 -0
  265. package/dist/lib/text.d.ts +56 -0
  266. package/dist/lib/text.js +149 -0
  267. package/dist/lib/theme-scope.d.ts +18 -0
  268. package/dist/lib/theme-scope.js +24 -0
  269. package/dist/lib/theme.d.ts +40 -0
  270. package/dist/lib/theme.js +96 -0
  271. package/dist/lib/tree-keys.d.ts +72 -0
  272. package/dist/lib/tree-keys.js +126 -0
  273. package/dist/lib/url.d.ts +17 -0
  274. package/dist/lib/url.js +38 -0
  275. package/dist/lib/use-copy-to-clipboard.d.ts +13 -0
  276. package/dist/lib/use-copy-to-clipboard.js +40 -0
  277. package/dist/lib/use-delayed-loading.d.ts +11 -0
  278. package/dist/lib/use-delayed-loading.js +31 -0
  279. package/dist/lib/use-file-picker.d.ts +26 -0
  280. package/dist/lib/use-file-picker.js +43 -0
  281. package/dist/lib/use-hydrated.d.ts +6 -0
  282. package/dist/lib/use-hydrated.js +11 -0
  283. package/dist/lib/use-latest.d.ts +12 -0
  284. package/dist/lib/use-latest.js +22 -0
  285. package/dist/lib/use-resolved-theme.d.ts +12 -0
  286. package/dist/lib/use-resolved-theme.js +25 -0
  287. package/dist/styles/base.css +393 -0
  288. package/dist/styles/code.css +35 -0
  289. package/dist/styles/fonts.css +12 -0
  290. package/dist/styles/index.css +13 -0
  291. package/dist/styles/tokens.gen.css +494 -0
  292. package/package.json +208 -4
@@ -0,0 +1,438 @@
1
+ /**
2
+ * ANSI SGR → styled segments, for terminal-ish text rendered in the UI (status lines, tool output).
3
+ *
4
+ * Only SGR (`ESC [ … m`) is interpreted; every other escape sequence (cursor moves, OSC titles and
5
+ * hyperlinks, DCS/APC/PM/SOS strings, C1 controls) is removed, and so are raw control characters and
6
+ * bidi overrides. The output is plain text plus a style object — render it with React text nodes,
7
+ * never as HTML.
8
+ */
9
+ import { palette } from "../_bundled/tokens/index.js";
10
+ const ESC = 0x1b;
11
+ const BEL = 0x07;
12
+ /** C1 introducers (8-bit forms of ESC [, ESC ], ESC P, ESC X, ESC ^, ESC _). */
13
+ const C1_CSI = 0x9b;
14
+ const C1_OSC = 0x9d;
15
+ const C1_ST = 0x9c;
16
+ const C1_STRING_INTRODUCERS = new Set([0x90, 0x98, 0x9e, 0x9f]);
17
+ /** After ESC: `]` OSC, `P` DCS, `X` SOS, `^` PM, `_` APC — all run until ST (or BEL for OSC). */
18
+ const STRING_FINALS = new Set(["]", "P", "X", "^", "_"]);
19
+ /** Unicode bidi embeddings/overrides/isolates: they can visually reorder text, so they never render. */
20
+ const isBidiControl = (code) => (code >= 0x202a && code <= 0x202e) ||
21
+ (code >= 0x2066 && code <= 0x2069) ||
22
+ code === 0x200e ||
23
+ code === 0x200f;
24
+ /** Printable for our purposes: not C0 (except tab/newline), not DEL, not C1, not a bidi control. */
25
+ const isPrintable = (code) => code === 0x09 ||
26
+ code === 0x0a ||
27
+ (code >= 0x20 && code !== 0x7f && (code < 0x80 || code > 0x9f) && !isBidiControl(code));
28
+ /** Index just past a string sequence's terminator (ST `ESC \`, C1 ST or — for OSC — BEL), or the end. */
29
+ function skipString(input, from) {
30
+ for (let i = from; i < input.length; i++) {
31
+ const code = input.charCodeAt(i);
32
+ if (code === BEL || code === C1_ST)
33
+ return i + 1;
34
+ if (code === ESC)
35
+ return input[i + 1] === "\\" ? i + 2 : i;
36
+ }
37
+ return input.length;
38
+ }
39
+ /**
40
+ * Reads a CSI body starting after `ESC [`. Returns the end index and, for well-formed SGR, its
41
+ * parameters. Malformed sequences end at the first byte outside the CSI grammar (that byte is kept).
42
+ */
43
+ function readCsi(input, from) {
44
+ let i = from;
45
+ while (i < input.length) {
46
+ const code = input.charCodeAt(i);
47
+ if (code >= 0x30 && code <= 0x3f)
48
+ i++;
49
+ else
50
+ break;
51
+ }
52
+ const params = input.slice(from, i);
53
+ while (i < input.length) {
54
+ const code = input.charCodeAt(i);
55
+ if (code >= 0x20 && code <= 0x2f)
56
+ i++;
57
+ else
58
+ break;
59
+ }
60
+ const final = input.charCodeAt(i);
61
+ if (Number.isNaN(final) || final < 0x40 || final > 0x7e)
62
+ return { end: i, sgr: undefined };
63
+ const isSgr = final === 0x6d && i === from + params.length && /^[\d;:]*$/.test(params);
64
+ return { end: i + 1, sgr: isSgr ? params : undefined };
65
+ }
66
+ /** Index just past a non-CSI, non-string escape: `ESC` + intermediates (0x20–0x2F) + one final byte. */
67
+ function skipEscape(input, from) {
68
+ let i = from;
69
+ while (i < input.length) {
70
+ const code = input.charCodeAt(i);
71
+ if (code >= 0x20 && code <= 0x2f)
72
+ i++;
73
+ else
74
+ break;
75
+ }
76
+ const final = input.charCodeAt(i);
77
+ return final >= 0x30 && final <= 0x7e ? i + 1 : i;
78
+ }
79
+ /** Handles one escape introducer at `i`; returns the next index and an SGR token when there is one. */
80
+ function readEscape(input, i) {
81
+ const code = input.charCodeAt(i);
82
+ if (code === C1_CSI) {
83
+ const csi = readCsi(input, i + 1);
84
+ return { next: csi.end, sgr: csi.sgr };
85
+ }
86
+ if (code === C1_OSC || C1_STRING_INTRODUCERS.has(code))
87
+ return { next: skipString(input, i + 1), sgr: undefined };
88
+ // code === ESC
89
+ const after = input[i + 1];
90
+ if (after === "[") {
91
+ const csi = readCsi(input, i + 2);
92
+ return { next: csi.end, sgr: csi.sgr };
93
+ }
94
+ if (after !== undefined && STRING_FINALS.has(after))
95
+ return { next: skipString(input, i + 2), sgr: undefined };
96
+ return { next: skipEscape(input, i + 1), sgr: undefined };
97
+ }
98
+ function tokenize(input) {
99
+ const tokens = [];
100
+ let text = "";
101
+ let i = 0;
102
+ while (i < input.length) {
103
+ const code = input.charCodeAt(i);
104
+ if (code === ESC || code === C1_CSI || code === C1_OSC || C1_STRING_INTRODUCERS.has(code)) {
105
+ const { next, sgr } = readEscape(input, i);
106
+ if (sgr !== undefined) {
107
+ if (text !== "")
108
+ tokens.push({ kind: "text", text });
109
+ text = "";
110
+ tokens.push({ kind: "sgr", params: sgr });
111
+ }
112
+ i = Math.max(next, i + 1);
113
+ continue;
114
+ }
115
+ if (isPrintable(code))
116
+ text += input[i];
117
+ i++;
118
+ }
119
+ if (text !== "")
120
+ tokens.push({ kind: "text", text });
121
+ return tokens;
122
+ }
123
+ const STYLE_KEYS = [
124
+ "bold",
125
+ "dim",
126
+ "italic",
127
+ "underline",
128
+ "strikethrough",
129
+ "inverse",
130
+ "fg",
131
+ "bg",
132
+ ];
133
+ const byte = (value) => {
134
+ if (value === undefined || value === "")
135
+ return undefined;
136
+ const n = Number(value);
137
+ return Number.isInteger(n) && n >= 0 && n <= 255 ? n : undefined;
138
+ };
139
+ /**
140
+ * `38;5;n` / `38;2;r;g;b` (semicolon form, consuming following params) or `38:5:n` /
141
+ * `38:2:[cs]:r:g:b` (colon form, one param). Returns the color (if valid) and how many params it used.
142
+ */
143
+ function readExtendedColor(params, at) {
144
+ const head = params[at] ?? "";
145
+ if (head.includes(":")) {
146
+ const parts = head.split(":");
147
+ if (parts[1] === "5")
148
+ return { color: paletteColor(byte(parts[2])), used: 1 };
149
+ if (parts[1] === "2") {
150
+ const rgb = parts.length >= 6 ? parts.slice(-3) : parts.slice(2, 5);
151
+ return { color: rgbColor(rgb), used: 1 };
152
+ }
153
+ return { color: undefined, used: 1 };
154
+ }
155
+ const mode = params[at + 1];
156
+ if (mode === "5")
157
+ return { color: paletteColor(byte(params[at + 2])), used: 3 };
158
+ if (mode === "2")
159
+ return { color: rgbColor(params.slice(at + 2, at + 5)), used: 5 };
160
+ return { color: undefined, used: mode === undefined ? 1 : 2 };
161
+ }
162
+ const paletteColor = (index) => index === undefined ? undefined : { type: "palette", index };
163
+ function rgbColor(parts) {
164
+ const [r, g, b] = parts.map(byte);
165
+ return r === undefined || g === undefined || b === undefined ? undefined : { type: "rgb", r, g, b };
166
+ }
167
+ /** Flags and their resets: code → [property, on]. */
168
+ const FLAGS = {
169
+ 1: [["bold", true]],
170
+ 2: [["dim", true]],
171
+ 3: [["italic", true]],
172
+ 4: [["underline", true]],
173
+ 7: [["inverse", true]],
174
+ 9: [["strikethrough", true]],
175
+ 21: [["underline", true]],
176
+ 22: [
177
+ ["bold", false],
178
+ ["dim", false],
179
+ ],
180
+ 23: [["italic", false]],
181
+ 24: [["underline", false]],
182
+ 27: [["inverse", false]],
183
+ 29: [["strikethrough", false]],
184
+ };
185
+ function setFlag(style, key, on) {
186
+ if (key === "fg" || key === "bg")
187
+ return;
188
+ if (on)
189
+ style[key] = true;
190
+ else
191
+ delete style[key];
192
+ }
193
+ /** Basic colors: 30–37 / 90–97 fg, 40–47 / 100–107 bg, 39 / 49 defaults. */
194
+ function applyBasicColor(style, code) {
195
+ if (code >= 30 && code <= 37)
196
+ style.fg = { type: "palette", index: code - 30 };
197
+ else if (code >= 90 && code <= 97)
198
+ style.fg = { type: "palette", index: code - 90 + 8 };
199
+ else if (code >= 40 && code <= 47)
200
+ style.bg = { type: "palette", index: code - 40 };
201
+ else if (code >= 100 && code <= 107)
202
+ style.bg = { type: "palette", index: code - 100 + 8 };
203
+ else if (code === 39)
204
+ delete style.fg;
205
+ else if (code === 49)
206
+ delete style.bg;
207
+ else
208
+ return false;
209
+ return true;
210
+ }
211
+ /** Applies one simple (non-extended-color) SGR parameter to `style`. Unknown codes are ignored. */
212
+ function applyCode(style, code, param) {
213
+ if (code === 0) {
214
+ for (const key of STYLE_KEYS)
215
+ delete style[key];
216
+ return;
217
+ }
218
+ if (code === 4 && param.startsWith("4:")) {
219
+ setFlag(style, "underline", param !== "4:0");
220
+ return;
221
+ }
222
+ const flags = FLAGS[code];
223
+ if (flags)
224
+ for (const [key, on] of flags)
225
+ setFlag(style, key, on);
226
+ else
227
+ applyBasicColor(style, code);
228
+ }
229
+ /** Applies one SGR parameter list to `style` (mutated). */
230
+ function applySgr(style, raw) {
231
+ const params = raw === "" ? ["0"] : raw.split(";");
232
+ let i = 0;
233
+ while (i < params.length) {
234
+ const param = params[i] ?? "";
235
+ const code = Number(param.split(":")[0] || "0");
236
+ if (code === 38 || code === 48 || code === 58) {
237
+ const { color, used } = readExtendedColor(params, i);
238
+ if (color && code === 38)
239
+ style.fg = color;
240
+ if (color && code === 48)
241
+ style.bg = color;
242
+ i += used;
243
+ }
244
+ else {
245
+ applyCode(style, code, param);
246
+ i++;
247
+ }
248
+ }
249
+ }
250
+ const sameColor = (a, b) => JSON.stringify(a) === JSON.stringify(b);
251
+ const sameStyle = (a, b) => a.bold === b.bold &&
252
+ a.dim === b.dim &&
253
+ a.italic === b.italic &&
254
+ a.underline === b.underline &&
255
+ a.strikethrough === b.strikethrough &&
256
+ a.inverse === b.inverse &&
257
+ sameColor(a.fg, b.fg) &&
258
+ sameColor(a.bg, b.bg);
259
+ /** Parses text with ANSI escapes into styled segments (adjacent equal styles merged, empty dropped). */
260
+ export function parseAnsi(input) {
261
+ const segments = [];
262
+ const style = {};
263
+ for (const token of tokenize(input)) {
264
+ if (token.kind === "sgr") {
265
+ applySgr(style, token.params);
266
+ continue;
267
+ }
268
+ const snapshot = { ...style };
269
+ const last = segments.at(-1);
270
+ if (last && sameStyle(last.style, snapshot))
271
+ segments[segments.length - 1] = { ...last, text: last.text + token.text };
272
+ else
273
+ segments.push({ text: token.text, style: snapshot });
274
+ }
275
+ return segments;
276
+ }
277
+ /** The visible text only: every escape sequence and control character removed. */
278
+ export function stripAnsi(input) {
279
+ return tokenize(input)
280
+ .map((token) => (token.kind === "text" ? token.text : ""))
281
+ .join("");
282
+ }
283
+ /* ------------------------------------------------------------------------------------------ */
284
+ /* Colors → CSS */
285
+ /* ------------------------------------------------------------------------------------------ */
286
+ /**
287
+ * The 16 named colors on theme tokens (ramp step 11 = legible text in both themes). Black and
288
+ * white follow the theme instead of being literal, so text never vanishes on the page background.
289
+ */
290
+ const NAMED_FG = [
291
+ "var(--fg-muted)", // black
292
+ "var(--red-11)",
293
+ "var(--green-11)",
294
+ "var(--amber-11)",
295
+ "var(--blue-11)",
296
+ "var(--violet-11)",
297
+ "var(--persona-teal-fg)", // cyan
298
+ "var(--fg)", // white
299
+ "var(--fg-subtle)", // bright black (gray)
300
+ "var(--red-10)",
301
+ "var(--green-10)",
302
+ "var(--amber-11)",
303
+ "var(--blue-10)",
304
+ "var(--violet-10)",
305
+ "var(--persona-teal-fg)",
306
+ "var(--fg)", // bright white
307
+ ];
308
+ const tint = (token, percent) => `color-mix(in oklab, var(${token}) ${percent}%, transparent)`;
309
+ /** Backgrounds are tints, like the semantic `*-subtle` tokens, so text on them stays readable. */
310
+ const NAMED_BG = [
311
+ tint("--gray-12", 14),
312
+ tint("--red-9", 16),
313
+ tint("--green-9", 16),
314
+ tint("--amber-9", 18),
315
+ tint("--blue-9", 16),
316
+ tint("--violet-9", 16),
317
+ tint("--persona-teal-solid", 18),
318
+ tint("--gray-12", 7),
319
+ tint("--gray-12", 10),
320
+ tint("--red-9", 22),
321
+ tint("--green-9", 22),
322
+ tint("--amber-9", 24),
323
+ tint("--blue-9", 22),
324
+ tint("--violet-9", 22),
325
+ tint("--persona-teal-solid", 24),
326
+ tint("--gray-12", 4),
327
+ ];
328
+ const CUBE_LEVELS = [0, 95, 135, 175, 215, 255];
329
+ const cubeLevel = (i) => CUBE_LEVELS[i] ?? 0;
330
+ /** xterm 256-color cube (16–231) as rgb. */
331
+ function cubeRgb(index) {
332
+ const n = index - 16;
333
+ return {
334
+ type: "rgb",
335
+ r: cubeLevel(Math.floor(n / 36)),
336
+ g: cubeLevel(Math.floor(n / 6) % 6),
337
+ b: cubeLevel(n % 6),
338
+ };
339
+ }
340
+ const hexRgb = (hex) => {
341
+ const n = Number.parseInt(hex.slice(1, 7), 16);
342
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
343
+ };
344
+ const toHex = (rgb) => `#${rgb.map((c) => c.toString(16).padStart(2, "0")).join("")}`;
345
+ /** WCAG 2 relative luminance. */
346
+ const luminance = (rgb) => {
347
+ const [r, g, b] = rgb.map((value) => {
348
+ const s = value / 255;
349
+ return s <= 0.040_45 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
350
+ });
351
+ return 0.2126 * (r ?? 0) + 0.7152 * (g ?? 0) + 0.0722 * (b ?? 0);
352
+ };
353
+ const contrast = (a, b) => {
354
+ const la = luminance(a);
355
+ const lb = luminance(b);
356
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
357
+ };
358
+ const mixRgb = (a, b, t) => [
359
+ Math.round(a[0] + (b[0] - a[0]) * t),
360
+ Math.round(a[1] + (b[1] - a[1]) * t),
361
+ Math.round(a[2] + (b[2] - a[2]) * t),
362
+ ];
363
+ /** WCAG AA for text. */
364
+ const MIN_CONTRAST = 4.5;
365
+ const SCHEMES = {
366
+ light: { paper: hexRgb(palette.light.gray[1]), ink: hexRgb(palette.light.gray[11]) },
367
+ dark: { paper: hexRgb(palette.dark.gray[1]), ink: hexRgb(palette.dark.gray[11]) },
368
+ };
369
+ /** The colour moved toward the ink just far enough to read at 4.5:1 on the paper. */
370
+ function readable(color, { paper, ink }) {
371
+ const steps = 20;
372
+ for (let step = 0; step < steps; step += 1) {
373
+ const mixed = mixRgb(color, ink, step / steps);
374
+ if (contrast(mixed, paper) >= MIN_CONTRAST)
375
+ return mixed;
376
+ }
377
+ return ink;
378
+ }
379
+ /**
380
+ * A script's own colour (the 256-colour cube or truecolor) as text in this UI. It was picked for
381
+ * some terminal's background, not ours, so it keeps its hue but never reads under 4.5:1 in either
382
+ * scheme: terminals hold the same floor (xterm.js, VS Code's `minimumContrastRatio`).
383
+ * `light-dark()` picks by the element's colour scheme, so nested theme scopes work.
384
+ */
385
+ const readableCss = (color) => {
386
+ const rgb = [color.r, color.g, color.b];
387
+ return `light-dark(${toHex(readable(rgb, SCHEMES.light))}, ${toHex(readable(rgb, SCHEMES.dark))})`;
388
+ };
389
+ /**
390
+ * CSS color for an ANSI color. Named colors and the 24-step grayscale ramp map onto theme tokens
391
+ * (grays become a mix of `--fg` and `--bg`, brighter = more prominent, as on a dark terminal).
392
+ * Cube and truecolor keep their hue: as text, held to 4.5:1 (`readableCss`); as a background, a
393
+ * tint like the named ones, so text on it stays readable.
394
+ */
395
+ export function ansiColorToCss(color, layer) {
396
+ if (color.type === "rgb") {
397
+ return layer === "fg"
398
+ ? readableCss(color)
399
+ : `color-mix(in oklab, rgb(${color.r} ${color.g} ${color.b}) 22%, transparent)`;
400
+ }
401
+ const { index } = color;
402
+ if (index < 16)
403
+ return (layer === "fg" ? NAMED_FG : NAMED_BG)[index] ?? "currentColor";
404
+ if (index >= 232) {
405
+ const step = index - 232; // 0 (near black) … 23 (near white)
406
+ const percent = layer === "fg" ? Math.round(35 + (step / 23) * 65) : Math.round(4 + (step / 23) * 14);
407
+ return layer === "fg"
408
+ ? `color-mix(in oklab, var(--fg) ${percent}%, var(--bg))`
409
+ : `color-mix(in oklab, var(--gray-12) ${percent}%, transparent)`;
410
+ }
411
+ return ansiColorToCss(cubeRgb(index), layer);
412
+ }
413
+ /** Inline style for a segment (inverse swaps fg/bg, falling back to the theme's fg/bg). */
414
+ export function ansiStyleToCss(style) {
415
+ const css = {};
416
+ let fg = style.fg === undefined ? undefined : ansiColorToCss(style.fg, "fg");
417
+ let bg = style.bg === undefined ? undefined : ansiColorToCss(style.bg, "bg");
418
+ if (style.inverse) {
419
+ const swappedFg = style.bg === undefined ? "var(--bg)" : ansiColorToCss(style.bg, "fg");
420
+ bg = style.fg === undefined ? "var(--fg)" : ansiColorToCss(style.fg, "fg");
421
+ fg = swappedFg;
422
+ }
423
+ // Dim reads quieter but never under 4.5:1: plain text takes fg-subtle, coloured text keeps its colour.
424
+ if (fg !== undefined)
425
+ css.color = fg;
426
+ else if (style.dim)
427
+ css.color = "var(--fg-subtle)";
428
+ if (bg !== undefined)
429
+ css.backgroundColor = bg;
430
+ if (style.bold)
431
+ css.fontWeight = 600;
432
+ if (style.italic)
433
+ css.fontStyle = "italic";
434
+ const lines = [style.underline && "underline", style.strikethrough && "line-through"].filter((line) => typeof line === "string");
435
+ if (lines.length > 0)
436
+ css.textDecorationLine = lines.join(" ");
437
+ return css;
438
+ }
@@ -0,0 +1,32 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * The time source for every countdown, elapsed time and relative time the kit and its hosts show.
4
+ * The system clock is the default; tests and the catalog pass a {@link ManualClock} (the same idea
5
+ * as the orchestrator's injected `Clock`), so a fake-clock-driven screen renders consistent times.
6
+ */
7
+ export interface Clock {
8
+ now(): number;
9
+ /** Calls `tick` roughly every `ms` until the returned function is called (idempotent). */
10
+ every(ms: number, tick: () => void): () => void;
11
+ }
12
+ export interface ManualClock extends Clock {
13
+ /** Moves time forward and notifies every subscriber once. */
14
+ advance(ms: number): void;
15
+ set(now: number): void;
16
+ }
17
+ /** A clock that only moves when told to. */
18
+ export declare function createManualClock(start?: number): ManualClock;
19
+ /** Provides the clock to everything below it that reads the time. */
20
+ export declare function ClockProvider({ clock, children }: {
21
+ clock: Clock;
22
+ children: ReactNode;
23
+ }): import("react").JSX.Element;
24
+ export declare function useClock(): Clock;
25
+ /**
26
+ * Milliseconds left until `until` (epoch ms), re-rendering while it counts down and once more when
27
+ * it reaches zero. `undefined` when there's nothing to count down to. Stays subscribed (without
28
+ * re-rendering) after zero, so a clock that is set back — a test or replayed fixture — resumes.
29
+ */
30
+ export declare function useCountdown(until: number | undefined): number | undefined;
31
+ /** The current time, refreshed every `everyMs` while `live` (elapsed timers on running runs). */
32
+ export declare function useNow(live: boolean, everyMs?: number): number;
@@ -0,0 +1,106 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useEffect, useState } from "react";
4
+ /** One interval per period, shared by every subscriber and running only while one listens. */
5
+ const periods = new Map();
6
+ const systemClock = {
7
+ now: () => Date.now(),
8
+ every: (ms, tick) => {
9
+ let period = periods.get(ms);
10
+ if (period === undefined) {
11
+ const ticks = new Set();
12
+ const timer = setInterval(() => {
13
+ for (const notify of ticks)
14
+ notify();
15
+ }, ms);
16
+ period = { ticks, timer };
17
+ periods.set(ms, period);
18
+ }
19
+ const { ticks, timer } = period;
20
+ // Wrap so the same function subscribed twice stays two subscriptions.
21
+ const entry = () => tick();
22
+ ticks.add(entry);
23
+ return () => {
24
+ ticks.delete(entry);
25
+ if (ticks.size === 0 && periods.get(ms)?.timer === timer) {
26
+ clearInterval(timer);
27
+ periods.delete(ms);
28
+ }
29
+ };
30
+ },
31
+ };
32
+ /** A clock that only moves when told to. */
33
+ export function createManualClock(start = 0) {
34
+ let now = start;
35
+ const ticks = new Set();
36
+ const notify = () => {
37
+ for (const tick of ticks)
38
+ tick();
39
+ };
40
+ return {
41
+ now: () => now,
42
+ every: (_ms, tick) => {
43
+ // Wrap so the same function subscribed twice stays two subscriptions.
44
+ const entry = () => tick();
45
+ ticks.add(entry);
46
+ return () => {
47
+ ticks.delete(entry);
48
+ };
49
+ },
50
+ advance: (ms) => {
51
+ now += ms;
52
+ notify();
53
+ },
54
+ set: (value) => {
55
+ now = value;
56
+ notify();
57
+ },
58
+ };
59
+ }
60
+ const ClockContext = createContext(systemClock);
61
+ /** Provides the clock to everything below it that reads the time. */
62
+ export function ClockProvider({ clock, children }) {
63
+ return _jsx(ClockContext, { value: clock, children: children });
64
+ }
65
+ export function useClock() {
66
+ return useContext(ClockContext);
67
+ }
68
+ /** Half a second keeps a ceil-rounded seconds display from skipping or stalling a digit. */
69
+ const TICK_MS = 500;
70
+ /**
71
+ * Milliseconds left until `until` (epoch ms), re-rendering while it counts down and once more when
72
+ * it reaches zero. `undefined` when there's nothing to count down to. Stays subscribed (without
73
+ * re-rendering) after zero, so a clock that is set back — a test or replayed fixture — resumes.
74
+ */
75
+ export function useCountdown(until) {
76
+ const clock = useClock();
77
+ const [now, setNow] = useState(() => clock.now());
78
+ useEffect(() => {
79
+ if (until === undefined)
80
+ return undefined;
81
+ let settled = false;
82
+ const sync = () => {
83
+ const t = clock.now();
84
+ if (t < until || !settled)
85
+ setNow(t);
86
+ settled = t >= until;
87
+ };
88
+ sync();
89
+ return clock.every(TICK_MS, sync);
90
+ }, [clock, until]);
91
+ if (until === undefined)
92
+ return undefined;
93
+ return Math.max(0, until - now);
94
+ }
95
+ /** The current time, refreshed every `everyMs` while `live` (elapsed timers on running runs). */
96
+ export function useNow(live, everyMs = 1000) {
97
+ const clock = useClock();
98
+ const [now, setNow] = useState(() => clock.now());
99
+ useEffect(() => {
100
+ setNow(clock.now());
101
+ if (!live)
102
+ return undefined;
103
+ return clock.every(everyMs, () => setNow(clock.now()));
104
+ }, [clock, live, everyMs]);
105
+ return now;
106
+ }
@@ -0,0 +1,12 @@
1
+ import { type ClassValue } from "clsx";
2
+ /** Compose class names; later Tailwind classes win over conflicting earlier ones. */
3
+ export declare function cn(...inputs: ClassValue[]): string;
4
+ /** Base UI's `className`: a string, or a function of the part's state. */
5
+ export type StateClassName<S> = string | ((state: S) => string | undefined) | undefined;
6
+ /** Base UI's `className` for a part's state: the function's answer, or the string as it is. */
7
+ export declare function resolveClassName<S>(className: StateClassName<S>, state: S): string | undefined;
8
+ /** Merges a base class with Base UI's string-or-function `className`. */
9
+ export declare function withBase<S>(base: string, className: StateClassName<S>): (state: S) => string;
10
+ /** `tailwind-variants` configured with the kit's merge rules. */
11
+ export declare const tv: import("tailwind-variants").TV;
12
+ export type { VariantProps } from "tailwind-variants";
package/dist/lib/cn.js ADDED
@@ -0,0 +1,28 @@
1
+ import { clsx } from "clsx";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
+ import { createTV } from "tailwind-variants";
4
+ /** Teach the class merger about the kit's custom theme keys so conflicts resolve correctly. */
5
+ const twMergeConfig = {
6
+ extend: {
7
+ theme: {
8
+ text: ["2xs", "code", "xs", "sm", "base", "md", "lg", "xl", "2xl", "3xl", "4xl", "5xl", "6xl"],
9
+ shadow: ["xs", "sm", "md", "lg", "xl", "inset-edge"],
10
+ radius: ["xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl"],
11
+ },
12
+ },
13
+ };
14
+ const twMerge = extendTailwindMerge(twMergeConfig);
15
+ /** Compose class names; later Tailwind classes win over conflicting earlier ones. */
16
+ export function cn(...inputs) {
17
+ return twMerge(clsx(inputs));
18
+ }
19
+ /** Base UI's `className` for a part's state: the function's answer, or the string as it is. */
20
+ export function resolveClassName(className, state) {
21
+ return typeof className === "function" ? className(state) : className;
22
+ }
23
+ /** Merges a base class with Base UI's string-or-function `className`. */
24
+ export function withBase(base, className) {
25
+ return (state) => cn(base, resolveClassName(className, state));
26
+ }
27
+ /** `tailwind-variants` configured with the kit's merge rules. */
28
+ export const tv = createTV({ twMergeConfig });
@@ -0,0 +1,21 @@
1
+ /** A text control's own props, as a caller may set them. */
2
+ export interface ControlProps {
3
+ readonly value?: string | undefined;
4
+ readonly defaultValue?: string | undefined;
5
+ readonly disabled?: boolean | undefined;
6
+ readonly id?: string | undefined;
7
+ readonly name?: string | undefined;
8
+ readonly autoFocus?: boolean | undefined;
9
+ }
10
+ /**
11
+ * Only the props the caller set, for Base UI's field control: one passed as `undefined` would override
12
+ * what a surrounding `<Field>` gives the control (its `id`, its `disabled`).
13
+ */
14
+ export declare function givenControlProps({ value, defaultValue, disabled, id, name, autoFocus }: ControlProps): {
15
+ value?: string;
16
+ defaultValue?: string;
17
+ disabled?: boolean;
18
+ id?: string;
19
+ name?: string;
20
+ autoFocus?: boolean;
21
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Only the props the caller set, for Base UI's field control: one passed as `undefined` would override
3
+ * what a surrounding `<Field>` gives the control (its `id`, its `disabled`).
4
+ */
5
+ export function givenControlProps({ value, defaultValue, disabled, id, name, autoFocus }) {
6
+ return {
7
+ ...(value === undefined ? {} : { value }),
8
+ ...(defaultValue === undefined ? {} : { defaultValue }),
9
+ ...(disabled === undefined ? {} : { disabled }),
10
+ ...(id === undefined ? {} : { id }),
11
+ ...(name === undefined ? {} : { name }),
12
+ ...(autoFocus === undefined ? {} : { autoFocus }),
13
+ };
14
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * +1 left to right, -1 right to left: multiply a horizontal motion offset by it so things slide in
3
+ * from the side they would in the person's language (a step forward comes from the end edge). The
4
+ * direction is the one `UiI18nProvider` gives Base UI.
5
+ */
6
+ export declare function useInlineSign(): 1 | -1;
7
+ /**
8
+ * A horizontal arrow key as a left-to-right reader means it, given `useInlineSign()`'s sign: right to
9
+ * left, ArrowRight points toward the start of the line and ArrowLeft toward its end (WAI-ARIA APG), so
10
+ * they swap. A handler written for left to right (ArrowRight opens a tree node, moves to the next
11
+ * column) then follows the reading direction. Other keys pass through.
12
+ */
13
+ export declare function logicalArrow(key: string, sign: 1 | -1): string;