@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,114 @@
1
+ /**
2
+ * The one count and money formatter: counts and money render the same in every view that uses it, at
3
+ * most one decimal in a compact count, cents below 100 of a currency and whole units above, and
4
+ * tabular figures where they are shown (wrap them in `.tabular`, or use `<Count>` / `<Money>`).
5
+ *
6
+ * Every locale gets its own digits, separators, unit words and currency pattern from `Intl`
7
+ * ("58.7k", "58,7 тыс.", "$0.42", "0,42 $"); nothing is special-cased per language. Components get
8
+ * the provider's locale through `useFormatters()` (`@nibapp/ui/i18n`).
9
+ */
10
+ import { type CurrencyCode, type Money, type ShowableMoney } from "../_bundled/money/index.js";
11
+ /**
12
+ * An amount to show: `Money`, in its own currency, or a number in the formatters' currency (major
13
+ * units). Never a usage cost (`UsageCost`): what work cost reaches a person only through the usage
14
+ * display policy (`shownUsage`, `<UsageAmount>`), which decides by who paid.
15
+ */
16
+ export type Amount = ShowableMoney | number;
17
+ /** A whole number as two digits ("04"), for a clock's minutes and seconds. */
18
+ export declare const pad2: (n: number) => string;
19
+ /**
20
+ * The value a count shows: 950 → 950, 1001 → 1000, 58_740 → 58_700, 1_250_000 → 1_300_000. Pass it
21
+ * as the `count` (plural selector) of a message whose noun follows a formatted count, so "1 тыс."
22
+ * agrees as a thousand ("1 тыс. токенов"), not as one.
23
+ */
24
+ export declare function shownCount(n: number): number;
25
+ export interface NumberFormatters {
26
+ /** `formatCount` for the locale. */
27
+ readonly count: (n: number) => string;
28
+ /** `formatMoney` for the locale. */
29
+ readonly money: (amount: Amount) => string;
30
+ /** Rounded to whole units, for amounts people set or prices that are round: "$25", "25 $". */
31
+ readonly wholeMoney: (amount: Amount) => string;
32
+ /**
33
+ * An amount exactly as set or billed: whole units when it's whole ("$25"), else with the currency's
34
+ * minor digits ("$2.50", "$1,204.50"); never rounded to whole units like `money` above 100.
35
+ */
36
+ readonly exactMoney: (amount: Amount) => string;
37
+ /** An amount in the currency's minor units (an invoice line): 1240 → "$12.40"; 500 JPY → "¥500". */
38
+ readonly minorUnits: (minor: number) => string;
39
+ /** A low–high range in one pattern: "$0.05–$0.20", "0,05–0,20 $"; one amount when they match. */
40
+ readonly moneyRange: (low: Amount, high: Amount) => string;
41
+ /** An amount in words, for meters and screen readers: "1.84 US dollars", "1,84 доллара США". */
42
+ readonly spokenMoney: (amount: Amount) => string;
43
+ /**
44
+ * The smallest amount its currency shows when `amount` is less than that (dust: "<$0.01"), in the
45
+ * same currency; `undefined` when it shows as it is.
46
+ */
47
+ readonly dustFloor: (amount: Amount) => Amount | undefined;
48
+ /** A unit price, sub-cent digits kept: "$4", "$2.50", "$0.075" (per million tokens, per hour). */
49
+ readonly unitPrice: (amount: Amount) => string;
50
+ /**
51
+ * A currency's symbol and which side of an amount the locale writes it on ("$12", "12 $"): `currency`'s,
52
+ * else the formatters' own.
53
+ */
54
+ readonly currencySymbol: (currency?: string) => {
55
+ readonly symbol: string;
56
+ readonly leading: boolean;
57
+ };
58
+ }
59
+ /**
60
+ * Count and money formatters for one locale. The `Intl` objects are built on first use and owned
61
+ * by the returned object, never cached at module scope (one process may render several locales):
62
+ * keep one per locale, e.g. in a provider's memo. `Money` is shown in its own currency; a number in
63
+ * `currency`, the host's. There's no default currency: pass `undefined` for formatters that show only
64
+ * `Money`, and formatting a number as money with them fails.
65
+ */
66
+ export declare function createNumberFormatters(locale: string, currency: string | undefined): NumberFormatters;
67
+ /**
68
+ * 950 → "950", 1234 → "1.2k", 58_700 → "58.7k", 202_400 → "202k", 1_250_000 → "1.3m" in en-GB;
69
+ * "58,7\u00A0тыс.", "1,3\u00A0млн" in ru. Values that round up to the next unit move to it
70
+ * (999_960 → "1m", never "1000k").
71
+ */
72
+ export declare function formatCount(n: number, locale: string): string;
73
+ /**
74
+ * "$0", "<$0.01", "$0.42", "$12.30", "$140", "$1,204" in en-GB; "0 $", "<0,01 $", "12,30 $",
75
+ * "1 204 $" in ru (Intl's no-break spaces). Cents below 100, whole units (with the locale's
76
+ * thousands separators) from 100. Never colored: money is information, not brand. `Money` carries
77
+ * its currency; a number needs the `currency` it's in.
78
+ */
79
+ export declare function formatMoney(amount: ShowableMoney, locale: string): string;
80
+ export declare function formatMoney(amount: number, locale: string, currency: string): string;
81
+ /**
82
+ * An amount as typed in the locale: its own digits (Arabic-Indic in ar-EG, Devanagari in hi with
83
+ * `nu-deva`) or ASCII ones, its group separator and decimal mark ("1,250.50", "1 250,50",
84
+ * "1.250,50", "1'234.5", "١٬٢٣٤٫٥"), its minus sign; any currency symbol, spaces and direction marks
85
+ * ignored. Where the locale doesn't group with "." or ",", either reads as the decimal mark too
86
+ * ("12.5" in ru, "1234.5" in ar-EG). Where it does, its group mark counts only where it groups
87
+ * ("1,234" in en); one that doesn't is the decimal mark typed the other way ("12,34" is 12.34 in en,
88
+ * "12.5" is 12.5 in de), never a figure 100 times larger. Undefined when it isn't a number.
89
+ */
90
+ export declare function parseAmount(text: string, locale: string): number | undefined;
91
+ /**
92
+ * An amount typed in major units (`parseAmount`, `readAmount`, a `MoneyInput`'s `onAmountChange`) as
93
+ * `Money` of `currency`, rounded half to even at the micro; `undefined` past what `Money` holds (its
94
+ * micros are safe integers).
95
+ */
96
+ export declare function typedMoney(units: number, currency: CurrencyCode): Money | undefined;
97
+ /** What an amount field accepts. */
98
+ export interface AmountBounds {
99
+ /** The smallest amount, inclusive. */
100
+ readonly min?: number | undefined;
101
+ /** The largest amount, inclusive. */
102
+ readonly max?: number | undefined;
103
+ /** Amounts are whole multiples of it: 1 for whole units. Any precision when omitted. */
104
+ readonly step?: number | undefined;
105
+ /** Zero isn't an amount (with `min: 0`, only amounts above zero are). */
106
+ readonly nonZero?: boolean | undefined;
107
+ /** An empty field is `null` ("no limit", "the default"); otherwise it isn't an amount. */
108
+ readonly allowEmpty?: boolean | undefined;
109
+ }
110
+ /**
111
+ * The amount typed in the locale (`parseAmount`) within `bounds`: the number, `null` for an empty
112
+ * field that `allowEmpty`, `undefined` for anything else.
113
+ */
114
+ export declare function readAmount(text: string, locale: string, bounds?: AmountBounds): number | null | undefined;
@@ -0,0 +1,313 @@
1
+ /**
2
+ * The one count and money formatter: counts and money render the same in every view that uses it, at
3
+ * most one decimal in a compact count, cents below 100 of a currency and whole units above, and
4
+ * tabular figures where they are shown (wrap them in `.tabular`, or use `<Count>` / `<Money>`).
5
+ *
6
+ * Every locale gets its own digits, separators, unit words and currency pattern from `Intl`
7
+ * ("58.7k", "58,7 тыс.", "$0.42", "0,42 $"); nothing is special-cased per language. Components get
8
+ * the provider's locale through `useFormatters()` (`@nibapp/ui/i18n`).
9
+ */
10
+ import { CurrencyMismatchError, currencyDigits, MICROS_PER_UNIT, moneyFormat, moneyFromUnits, moneyToUnits, } from "../_bundled/money/index.js";
11
+ /** A whole number as two digits ("04"), for a clock's minutes and seconds. */
12
+ export const pad2 = (n) => String(n).padStart(2, "0");
13
+ const UNITS = [1e3, 1e6, 1e9];
14
+ /** One decimal below 100 of a unit (58.7 thousand), whole above it (202 thousand). */
15
+ function scaled(value) {
16
+ return value < 99.95 ? Number(value.toFixed(1)) : Math.round(value);
17
+ }
18
+ /**
19
+ * The value a count shows: 950 → 950, 1001 → 1000, 58_740 → 58_700, 1_250_000 → 1_300_000. Pass it
20
+ * as the `count` (plural selector) of a message whose noun follows a formatted count, so "1 тыс."
21
+ * agrees as a thousand ("1 тыс. токенов"), not as one.
22
+ */
23
+ export function shownCount(n) {
24
+ if (!Number.isFinite(n))
25
+ return 0;
26
+ const sign = n < 0 ? -1 : 1;
27
+ const abs = Math.abs(n);
28
+ if (Math.round(abs) < 1000)
29
+ return sign * Math.round(abs);
30
+ for (const [index, unit] of UNITS.entries()) {
31
+ const value = scaled(abs / unit);
32
+ if (index === UNITS.length - 1 || value < 1000)
33
+ return sign * Math.round(value * unit);
34
+ }
35
+ return sign * Math.round(abs);
36
+ }
37
+ function currencyFormatters(locale, currency) {
38
+ let spoken;
39
+ let price;
40
+ let digits;
41
+ const moneyFormats = new Map();
42
+ // The currency in the locale's pattern ("$0.42", "0,42 $"), with exactly `fraction` decimals.
43
+ const currencyFormat = (fraction) => {
44
+ let format = moneyFormats.get(fraction);
45
+ if (format === undefined) {
46
+ format = moneyFormat(locale, currency, fraction);
47
+ moneyFormats.set(fraction, format);
48
+ }
49
+ return format;
50
+ };
51
+ const inCurrency = (value, fraction) => currencyFormat(fraction).format(value);
52
+ // The currency's minor digits: 2 for USD ("cents"), 0 for JPY.
53
+ const minor = () => {
54
+ digits ??= currencyDigits(currency, locale);
55
+ return digits;
56
+ };
57
+ const dustFloor = (amount) => {
58
+ const smallest = 10 ** -minor();
59
+ const abs = Math.abs(amount);
60
+ return abs > 0 && abs < smallest ? smallest : undefined;
61
+ };
62
+ const money = (amount) => {
63
+ if (!Number.isFinite(amount))
64
+ return "—";
65
+ const abs = Math.abs(amount);
66
+ if (abs === 0)
67
+ return inCurrency(0, 0);
68
+ // Dust: less than the smallest amount the minor unit shows.
69
+ const floor = dustFloor(amount);
70
+ if (floor !== undefined)
71
+ return `<${inCurrency(floor, minor())}`;
72
+ return abs < 100 ? inCurrency(amount, minor()) : inCurrency(Math.round(amount), 0);
73
+ };
74
+ const wholeMoney = (amount) => Number.isFinite(amount) ? inCurrency(Math.round(amount), 0) : "—";
75
+ const exactMoney = (amount) => {
76
+ if (!Number.isFinite(amount))
77
+ return "—";
78
+ return Number.isInteger(amount) ? inCurrency(amount, 0) : inCurrency(amount, minor());
79
+ };
80
+ const minorUnits = (amount) => Number.isFinite(amount) ? inCurrency(amount / 10 ** minor(), minor()) : "—";
81
+ const moneyRange = (low, high) => {
82
+ if (!Number.isFinite(low) || !Number.isFinite(high))
83
+ return "—";
84
+ if (money(low) === money(high))
85
+ return money(low);
86
+ return Math.max(Math.abs(low), Math.abs(high)) < 100
87
+ ? currencyFormat(minor()).formatRange(low, high)
88
+ : currencyFormat(0).formatRange(Math.round(low), Math.round(high));
89
+ };
90
+ const spokenMoney = (amount) => {
91
+ spoken ??= new Intl.NumberFormat(locale, {
92
+ style: "currency",
93
+ currency,
94
+ currencyDisplay: "name",
95
+ trailingZeroDisplay: "stripIfInteger",
96
+ });
97
+ const scale = 10 ** minor();
98
+ return spoken.format(Math.round(amount * scale) / scale);
99
+ };
100
+ const unitPrice = (amount) => {
101
+ if (Number.isInteger(amount))
102
+ return inCurrency(amount, 0);
103
+ price ??= new Intl.NumberFormat(locale, {
104
+ style: "currency",
105
+ currency,
106
+ currencyDisplay: "narrowSymbol",
107
+ minimumFractionDigits: 2,
108
+ maximumFractionDigits: 3,
109
+ });
110
+ return price.format(amount);
111
+ };
112
+ const currencySymbol = () => {
113
+ const parts = currencyFormat(0).formatToParts(1);
114
+ const at = parts.findIndex((part) => part.type === "currency");
115
+ const number = parts.findIndex((part) => part.type === "integer");
116
+ return { symbol: parts[at]?.value ?? currency, leading: at < number };
117
+ };
118
+ return {
119
+ money,
120
+ wholeMoney,
121
+ exactMoney,
122
+ minorUnits,
123
+ moneyRange,
124
+ spokenMoney,
125
+ dustFloor,
126
+ unitPrice,
127
+ currencySymbol,
128
+ };
129
+ }
130
+ /**
131
+ * Count and money formatters for one locale. The `Intl` objects are built on first use and owned
132
+ * by the returned object, never cached at module scope (one process may render several locales):
133
+ * keep one per locale, e.g. in a provider's memo. `Money` is shown in its own currency; a number in
134
+ * `currency`, the host's. There's no default currency: pass `undefined` for formatters that show only
135
+ * `Money`, and formatting a number as money with them fails.
136
+ */
137
+ export function createNumberFormatters(locale, currency) {
138
+ let compact;
139
+ const byCurrency = new Map();
140
+ const formattersFor = (code) => {
141
+ let formatters = byCurrency.get(code);
142
+ if (formatters === undefined) {
143
+ formatters = currencyFormatters(locale, code);
144
+ byCurrency.set(code, formatters);
145
+ }
146
+ return formatters;
147
+ };
148
+ // A number is money in the host's currency, which only the host knows.
149
+ const hostCurrency = () => {
150
+ if (currency === undefined)
151
+ throw new Error("showing a number as money needs a currency: give UiI18nProvider one, or createNumberFormatters its second argument");
152
+ return currency;
153
+ };
154
+ // An amount's major units and the formatters of its currency.
155
+ const split = (amount) => typeof amount === "number"
156
+ ? [amount, formattersFor(hostCurrency())]
157
+ : [moneyToUnits(amount), formattersFor(amount.currency)];
158
+ const count = (n) => {
159
+ if (!Number.isFinite(n))
160
+ return "—";
161
+ // One decimal or three significant digits, whichever is coarser: "58.7k", "202k", "1.3m".
162
+ compact ??= new Intl.NumberFormat(locale, {
163
+ notation: "compact",
164
+ maximumFractionDigits: 1,
165
+ maximumSignificantDigits: 3,
166
+ roundingPriority: "lessPrecision",
167
+ });
168
+ return compact.format(Math.round(n));
169
+ };
170
+ const via = (format) => (amount) => {
171
+ const [units, formatters] = split(amount);
172
+ return format(formatters, units);
173
+ };
174
+ return {
175
+ count,
176
+ money: via((f, units) => f.money(units)),
177
+ wholeMoney: via((f, units) => f.wholeMoney(units)),
178
+ exactMoney: via((f, units) => f.exactMoney(units)),
179
+ minorUnits: (minor) => formattersFor(hostCurrency()).minorUnits(minor),
180
+ moneyRange: (low, high) => {
181
+ const lowCurrency = typeof low === "number" ? hostCurrency() : low.currency;
182
+ const highCurrency = typeof high === "number" ? hostCurrency() : high.currency;
183
+ if (lowCurrency !== highCurrency)
184
+ throw new CurrencyMismatchError(lowCurrency, highCurrency);
185
+ const [from, formatters] = split(low);
186
+ return formatters.moneyRange(from, split(high)[0]);
187
+ },
188
+ spokenMoney: via((f, units) => f.spokenMoney(units)),
189
+ dustFloor: (amount) => {
190
+ const [units, formatters] = split(amount);
191
+ const floor = formatters.dustFloor(units);
192
+ if (floor === undefined)
193
+ return undefined;
194
+ return typeof amount === "number" ? floor : moneyFromUnits(floor, amount.currency);
195
+ },
196
+ unitPrice: via((f, units) => f.unitPrice(units)),
197
+ currencySymbol: (code) => formattersFor(code ?? hostCurrency()).currencySymbol(),
198
+ };
199
+ }
200
+ /**
201
+ * 950 → "950", 1234 → "1.2k", 58_700 → "58.7k", 202_400 → "202k", 1_250_000 → "1.3m" in en-GB;
202
+ * "58,7\u00A0тыс.", "1,3\u00A0млн" in ru. Values that round up to the next unit move to it
203
+ * (999_960 → "1m", never "1000k").
204
+ */
205
+ export function formatCount(n, locale) {
206
+ return createNumberFormatters(locale, undefined).count(n);
207
+ }
208
+ export function formatMoney(amount, locale, currency) {
209
+ return createNumberFormatters(locale, currency).money(amount);
210
+ }
211
+ const NUMBER_MARKS = new Map();
212
+ function numberMarks(locale) {
213
+ let marks = NUMBER_MARKS.get(locale);
214
+ if (marks === undefined) {
215
+ const format = new Intl.NumberFormat(locale, { useGrouping: false });
216
+ const parts = new Intl.NumberFormat(locale).formatToParts(-12_345.6);
217
+ marks = {
218
+ digits: Array.from({ length: 10 }, (_, digit) => format.format(digit)),
219
+ group: parts.find((part) => part.type === "group")?.value ?? ",",
220
+ decimal: parts.find((part) => part.type === "decimal")?.value ?? ".",
221
+ minus: parts.find((part) => part.type === "minusSign")?.value ?? "-",
222
+ };
223
+ NUMBER_MARKS.set(locale, marks);
224
+ }
225
+ return marks;
226
+ }
227
+ /** Thousands grouped by "," or ".": one to three digits, then groups of three. */
228
+ const WELL_GROUPED = {
229
+ ",": /^\d{1,3}(?:,\d{3})+$/u,
230
+ ".": /^\d{1,3}(?:\.\d{3})+$/u,
231
+ };
232
+ /**
233
+ * An amount in a locale that groups with "," or "." as plain "-1234.5": the group mark where it
234
+ * groups thousands, else (once, with no decimal mark) the decimal mark typed the other way.
235
+ */
236
+ function groupedAmount(text, group, decimal) {
237
+ const parts = text.split(decimal);
238
+ if (parts.length > 2)
239
+ return undefined;
240
+ const [whole = "", fraction] = parts;
241
+ const sign = whole.startsWith("-") ? "-" : "";
242
+ const integer = whole.slice(sign.length);
243
+ const tail = fraction === undefined ? "" : `.${fraction}`;
244
+ if (!integer.includes(group))
245
+ return `${sign}${integer}${tail}`;
246
+ if (WELL_GROUPED[group].test(integer))
247
+ return `${sign}${integer.replaceAll(group, "")}${tail}`;
248
+ return fraction === undefined && integer.split(group).length === 2
249
+ ? `${sign}${integer.replace(group, ".")}`
250
+ : undefined;
251
+ }
252
+ /** Apostrophe-like group marks, typed for one another (de-CH: 1’234.5 or 1'234.5). */
253
+ const APOSTROPHES = /['’]/gu;
254
+ const isApostrophe = (mark) => mark === "'" || mark === "’";
255
+ /**
256
+ * An amount as typed in the locale: its own digits (Arabic-Indic in ar-EG, Devanagari in hi with
257
+ * `nu-deva`) or ASCII ones, its group separator and decimal mark ("1,250.50", "1 250,50",
258
+ * "1.250,50", "1'234.5", "١٬٢٣٤٫٥"), its minus sign; any currency symbol, spaces and direction marks
259
+ * ignored. Where the locale doesn't group with "." or ",", either reads as the decimal mark too
260
+ * ("12.5" in ru, "1234.5" in ar-EG). Where it does, its group mark counts only where it groups
261
+ * ("1,234" in en); one that doesn't is the decimal mark typed the other way ("12,34" is 12.34 in en,
262
+ * "12.5" is 12.5 in de), never a figure 100 times larger. Undefined when it isn't a number.
263
+ */
264
+ export function parseAmount(text, locale) {
265
+ const { digits, group, decimal, minus } = numberMarks(locale);
266
+ let clean = text.replaceAll(/[\p{Sc}\s\p{Cf}]/gu, "");
267
+ for (const [value, digit] of digits.entries())
268
+ clean = clean.replaceAll(digit, String(value));
269
+ clean = clean.replaceAll(minus, "-").replaceAll("\u2212", "-");
270
+ if (group === "." || group === ",") {
271
+ const read = groupedAmount(clean, group, decimal);
272
+ if (read === undefined)
273
+ return undefined;
274
+ clean = read;
275
+ }
276
+ else {
277
+ clean = isApostrophe(group) ? clean.replaceAll(APOSTROPHES, "") : clean.replaceAll(group, "");
278
+ clean = clean.replaceAll(decimal, ".").replaceAll(",", ".");
279
+ }
280
+ if (!/^-?(?:\d+\.?\d*|\.\d+)$/u.test(clean))
281
+ return undefined;
282
+ const value = Number(clean);
283
+ return Number.isFinite(value) ? value : undefined;
284
+ }
285
+ /**
286
+ * An amount typed in major units (`parseAmount`, `readAmount`, a `MoneyInput`'s `onAmountChange`) as
287
+ * `Money` of `currency`, rounded half to even at the micro; `undefined` past what `Money` holds (its
288
+ * micros are safe integers).
289
+ */
290
+ export function typedMoney(units, currency) {
291
+ return Math.abs(units) * MICROS_PER_UNIT <= Number.MAX_SAFE_INTEGER
292
+ ? moneyFromUnits(units, currency)
293
+ : undefined;
294
+ }
295
+ /**
296
+ * The amount typed in the locale (`parseAmount`) within `bounds`: the number, `null` for an empty
297
+ * field that `allowEmpty`, `undefined` for anything else.
298
+ */
299
+ export function readAmount(text, locale, bounds = {}) {
300
+ if (text.trim() === "")
301
+ return bounds.allowEmpty === true ? null : undefined;
302
+ const value = parseAmount(text, locale);
303
+ if (value === undefined)
304
+ return undefined;
305
+ const { min, max, step, nonZero } = bounds;
306
+ if ((min !== undefined && value < min) || (max !== undefined && value > max))
307
+ return undefined;
308
+ if (nonZero === true && value === 0)
309
+ return undefined;
310
+ if (step !== undefined && Math.abs(value / step - Math.round(value / step)) > 1e-9)
311
+ return undefined;
312
+ return value;
313
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Unified diffs as `git diff`, other diff tools and models write them: split into files, cut into
3
+ * hunks, counted and built. Every reader takes CRLF as LF, leaves `` out
4
+ * of the counts and decodes git's C-quoted paths, so every view of a change, DiffView included,
5
+ * reads one patch the same way.
6
+ */
7
+ import type { FileDiffMetadata } from "@pierre/diffs";
8
+ export interface DiffStats {
9
+ additions: number;
10
+ deletions: number;
11
+ }
12
+ /** What a file's patch does to it; a rename or copy is a modification that also has `oldPath`. */
13
+ export type PatchChange = "added" | "deleted" | "modified";
14
+ /** One file's slice of a patch. */
15
+ export interface FilePatch {
16
+ /** Repo-relative path on the new side (old side for deletions); undefined when no header names it. */
17
+ readonly path: string | undefined;
18
+ /** Previous path when the file was renamed or copied. */
19
+ readonly oldPath: string | undefined;
20
+ readonly change: PatchChange;
21
+ /** The file's section of the patch, from its first header line, with LF line ends. */
22
+ readonly patch: string;
23
+ readonly additions: number;
24
+ readonly deletions: number;
25
+ readonly binary: boolean;
26
+ }
27
+ /** One `@@` hunk of a file's patch. */
28
+ export interface PatchHunk {
29
+ /** First line on each side, from the `@@` line (0 on the side a new or deleted file lacks). */
30
+ readonly oldStart: number;
31
+ readonly newStart: number;
32
+ /** The hunk as unified-diff text: its `@@` line, then its body. */
33
+ readonly text: string;
34
+ /** The body alone: the same change wherever other hunks shifted its line numbers. */
35
+ readonly key: string;
36
+ }
37
+ /** Whether `text` holds at least one `@@ -a,b +c,d @@` hunk. */
38
+ export declare function isUnifiedDiff(text: string): boolean;
39
+ /**
40
+ * Git C-quotes paths with special characters (`"a/caf\303\251.txt"`). Octal escapes are UTF-8
41
+ * bytes, so they are collected and decoded together; anything else is written raw (`core.quotePath`
42
+ * off), taken a code point at a time so a character outside the BMP stays whole.
43
+ */
44
+ export declare function unquoteGitPath(raw: string): string;
45
+ /**
46
+ * Splits a patch (`git diff`, `diff -u`, or bare hunks) into one entry per file, with line counts.
47
+ * Text before the first file is ignored; text without hunks or file headers has no files.
48
+ */
49
+ export declare function splitPatch(patch: string): FilePatch[];
50
+ /** `+`/`-` line counts across every file of a patch; file headers never count. */
51
+ export declare function patchStats(patch: string): DiffStats;
52
+ /** The `@@` hunks of a patch, in order (the order DiffView indexes them). */
53
+ export declare function parseHunks(patch: string): PatchHunk[];
54
+ /** The text of `line` on one side of a patch, when the patch shows it. */
55
+ export declare function lineText(patch: string, side: "old" | "new", line: number): string | undefined;
56
+ /**
57
+ * Makes hand-written / model-written patches parse cleanly: CRLF → LF, no trailing newline, and
58
+ * bare empty lines inside hunks become the " " context lines they were meant to be (editors and
59
+ * LLMs routinely strip that leading space).
60
+ */
61
+ export declare function normalizePatch(patch: string): string;
62
+ /** A git-style patch of one file from its hunks' unified-diff text, so DiffView can render it. */
63
+ export declare function buildFilePatch(path: string, change: PatchChange, hunks: readonly string[]): string;
64
+ /** A patch that adds `content` as a new file, so new files render like any other diff. */
65
+ export declare function newFilePatch(path: string, content: string): string;
66
+ /** Counts `+` / `−` lines across all hunks. */
67
+ export declare function getDiffStats(diff: FileDiffMetadata): DiffStats;
68
+ /**
69
+ * One hunk as unified-diff text (`@@ … @@` header plus ` `/`-`/`+` lines and the no-newline
70
+ * marker), e.g. to quote in a message or to compare with a patch's hunk.
71
+ */
72
+ export declare function getHunkExcerpt(diff: FileDiffMetadata, hunkIndex: number): string;