@inkra/react 0.2.0 → 0.4.3

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 (246) hide show
  1. package/README.md +101 -7
  2. package/dist/Button.js +3 -2
  3. package/dist/PencilIcon.d.ts +10 -0
  4. package/dist/PencilIcon.js +83 -39
  5. package/dist/PencilLine.js +9 -1
  6. package/dist/PencilText.js +15 -9
  7. package/dist/accordion/Accordion.js +65 -5
  8. package/dist/alert-dialog/index.d.ts +1 -1
  9. package/dist/alert-dialog/index.js +18 -6
  10. package/dist/audio-player/index.d.ts +2 -0
  11. package/dist/audio-player/index.js +126 -31
  12. package/dist/calendar/index.d.ts +4 -1
  13. package/dist/calendar/index.js +31 -12
  14. package/dist/carousel/index.js +13 -6
  15. package/dist/choice-grid/index.d.ts +6 -1
  16. package/dist/choice-grid/index.js +61 -33
  17. package/dist/color-picker/GradientEditor.d.ts +39 -0
  18. package/dist/color-picker/GradientEditor.js +63 -0
  19. package/dist/color-picker/gradient.d.ts +25 -0
  20. package/dist/color-picker/gradient.js +60 -0
  21. package/dist/color-picker/index.d.ts +27 -7
  22. package/dist/color-picker/index.js +134 -32
  23. package/dist/color-swatch/index.d.ts +10 -0
  24. package/dist/color-swatch/index.js +9 -0
  25. package/dist/command/index.d.ts +24 -2
  26. package/dist/command/index.js +29 -6
  27. package/dist/content/index.d.ts +5 -0
  28. package/dist/content/index.js +12 -3
  29. package/dist/context-menu/index.d.ts +2 -2
  30. package/dist/context-menu/index.js +4 -3
  31. package/dist/data-table/index.d.ts +7 -1
  32. package/dist/data-table/index.js +64 -16
  33. package/dist/data-table/model.d.ts +2 -0
  34. package/dist/data-table/model.js +7 -0
  35. package/dist/date-picker/index.d.ts +2 -0
  36. package/dist/date-picker/index.js +10 -9
  37. package/dist/day-agenda/index.d.ts +6 -1
  38. package/dist/day-agenda/index.js +46 -21
  39. package/dist/day-agenda/layout.js +65 -9
  40. package/dist/display/ProgressRing.js +7 -3
  41. package/dist/display/index.d.ts +4 -1
  42. package/dist/display/index.js +3 -2
  43. package/dist/dockable-panel/index.js +5 -5
  44. package/dist/drawer/index.d.ts +1 -1
  45. package/dist/drawer/index.js +9 -6
  46. package/dist/dropdown-menu/index.d.ts +1 -1
  47. package/dist/dropdown-menu/index.js +3 -2
  48. package/dist/editable/index.d.ts +24 -2
  49. package/dist/editable/index.js +142 -37
  50. package/dist/empty/illustrations.d.ts +8 -0
  51. package/dist/empty/illustrations.js +78 -0
  52. package/dist/empty/index.d.ts +6 -1
  53. package/dist/empty/index.js +11 -3
  54. package/dist/flip/index.js +28 -8
  55. package/dist/form/EditablePencilText.js +34 -0
  56. package/dist/form/Field.js +11 -10
  57. package/dist/form/Input.js +1 -1
  58. package/dist/form/Textarea.d.ts +3 -1
  59. package/dist/form/Textarea.js +6 -4
  60. package/dist/hotkey/index.d.ts +1 -0
  61. package/dist/hotkey/index.js +6 -4
  62. package/dist/hover-card/index.js +1 -1
  63. package/dist/image-cropper/index.js +4 -2
  64. package/dist/image-viewer/VideoResource.d.ts +21 -0
  65. package/dist/image-viewer/VideoResource.js +194 -0
  66. package/dist/image-viewer/index.d.ts +14 -0
  67. package/dist/image-viewer/index.js +118 -65
  68. package/dist/image-viewer/lucideGeometry.d.ts +3 -0
  69. package/dist/image-viewer/lucideGeometry.js +19 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +2 -0
  72. package/dist/input-number/index.d.ts +6 -0
  73. package/dist/input-number/index.js +131 -15
  74. package/dist/input-number/model.d.ts +13 -0
  75. package/dist/input-number/model.js +50 -0
  76. package/dist/internal/cache.d.ts +4 -0
  77. package/dist/internal/cache.js +18 -1
  78. package/dist/internal/iconGeometry.d.ts +2 -0
  79. package/dist/internal/iconGeometry.js +32 -0
  80. package/dist/internal/renderTokens.d.ts +4 -0
  81. package/dist/internal/renderTokens.js +4 -0
  82. package/dist/internal/stroke.d.ts +7 -0
  83. package/dist/internal/stroke.js +63 -7
  84. package/dist/internal/surfaceBudget.d.ts +20 -0
  85. package/dist/internal/surfaceBudget.js +69 -0
  86. package/dist/internal/surfaceRenderer.js +87 -28
  87. package/dist/internal/surfaceVisibility.d.ts +3 -0
  88. package/dist/internal/surfaceVisibility.js +60 -0
  89. package/dist/internal/textRenderer.d.ts +2 -0
  90. package/dist/internal/textRenderer.js +3 -3
  91. package/dist/internal/usePencilSurface.js +17 -1
  92. package/dist/internal/wash.js +4 -3
  93. package/dist/journey-map/terrain.js +10 -5
  94. package/dist/layout/index.js +1 -1
  95. package/dist/log/index.js +29 -22
  96. package/dist/markdown/CodeBlock.d.ts +19 -0
  97. package/dist/markdown/CodeBlock.js +86 -0
  98. package/dist/markdown/Deletion.d.ts +2 -0
  99. package/dist/markdown/Deletion.js +69 -0
  100. package/dist/markdown/icons.d.ts +14 -0
  101. package/dist/markdown/icons.js +16 -0
  102. package/dist/markdown/index.d.ts +17 -0
  103. package/dist/markdown/index.js +175 -0
  104. package/dist/node-graph/index.d.ts +2 -0
  105. package/dist/node-graph/index.js +49 -25
  106. package/dist/number-ticker/index.d.ts +11 -9
  107. package/dist/number-ticker/index.js +14 -11
  108. package/dist/number-ticker/model.d.ts +38 -5
  109. package/dist/number-ticker/model.js +139 -10
  110. package/dist/number-ticker/precision.d.ts +7 -0
  111. package/dist/number-ticker/precision.js +141 -0
  112. package/dist/overflow-list/index.js +5 -2
  113. package/dist/overlay/Overlay.d.ts +1 -1
  114. package/dist/overlay/Overlay.js +5 -3
  115. package/dist/overlay/Popover.d.ts +1 -1
  116. package/dist/overlay/Popover.js +6 -5
  117. package/dist/overlay/themeBridge.d.ts +12 -1
  118. package/dist/overlay/themeBridge.js +41 -3
  119. package/dist/pagination/index.d.ts +10 -1
  120. package/dist/pagination/index.js +6 -4
  121. package/dist/pan-zoom/index.d.ts +2 -0
  122. package/dist/pan-zoom/index.js +7 -6
  123. package/dist/pencil.d.ts +1 -0
  124. package/dist/pencil.js +4 -4
  125. package/dist/radar/index.d.ts +4 -0
  126. package/dist/radar/index.js +41 -21
  127. package/dist/rating/index.d.ts +4 -0
  128. package/dist/rating/index.js +128 -24
  129. package/dist/resizable/index.js +1 -1
  130. package/dist/scroll-area/index.d.ts +1 -1
  131. package/dist/scroll-area/index.js +5 -2
  132. package/dist/select/MultipleSelect.js +5 -4
  133. package/dist/select/SearchableSelect.js +61 -6
  134. package/dist/select/Select.d.ts +1 -0
  135. package/dist/select/Select.js +3 -1
  136. package/dist/select/StepperSelect.d.ts +29 -0
  137. package/dist/select/StepperSelect.js +119 -0
  138. package/dist/select/index.d.ts +2 -0
  139. package/dist/select/index.js +1 -0
  140. package/dist/select/stepperModel.d.ts +7 -0
  141. package/dist/select/stepperModel.js +29 -0
  142. package/dist/selection/Checkbox.js +4 -3
  143. package/dist/selection/RadioGroup.js +29 -13
  144. package/dist/selection/Switch.js +4 -3
  145. package/dist/selection/useNativeChecked.d.ts +2 -1
  146. package/dist/selection/useNativeChecked.js +26 -8
  147. package/dist/slider/index.js +50 -6
  148. package/dist/sortable/index.js +15 -6
  149. package/dist/sparkline/index.js +17 -2
  150. package/dist/sparkline/model.js +11 -4
  151. package/dist/styles/accordion.css +228 -0
  152. package/dist/styles/alert-dialog.css +139 -0
  153. package/dist/styles/audio-player.css +22 -0
  154. package/dist/styles/base.css +638 -0
  155. package/dist/styles/breadcrumb.css +7 -0
  156. package/dist/styles/calendar.css +62 -0
  157. package/dist/styles/carousel.css +173 -0
  158. package/dist/styles/choice-grid.css +226 -0
  159. package/dist/styles/color-picker.css +281 -0
  160. package/dist/styles/color-swatch.css +47 -0
  161. package/dist/styles/command.css +52 -0
  162. package/dist/styles/content.css +79 -0
  163. package/dist/styles/context-menu.css +35 -0
  164. package/dist/styles/data-table.css +31 -0
  165. package/dist/styles/date-picker.css +107 -0
  166. package/dist/styles/day-agenda.css +30 -0
  167. package/dist/styles/display.css +304 -0
  168. package/dist/styles/dockable-panel.css +110 -0
  169. package/dist/styles/drawer.css +133 -0
  170. package/dist/styles/dropdown-menu.css +68 -0
  171. package/dist/styles/editable.css +145 -0
  172. package/dist/styles/empty.css +108 -0
  173. package/dist/styles/flip.css +101 -0
  174. package/dist/styles/form.css +634 -0
  175. package/dist/styles/heatmap.css +256 -0
  176. package/dist/styles/hotkey.css +189 -0
  177. package/dist/styles/hover-card.css +27 -0
  178. package/dist/styles/image-cropper.css +205 -0
  179. package/dist/styles/image-viewer.css +422 -0
  180. package/dist/styles/input-number.css +21 -0
  181. package/dist/styles/journey-map.css +19 -0
  182. package/dist/styles/keypad.css +92 -0
  183. package/dist/styles/layout.css +72 -0
  184. package/dist/styles/leaflet.css +661 -0
  185. package/dist/styles/log.css +203 -0
  186. package/dist/styles/markdown.css +379 -0
  187. package/dist/styles/node-graph.css +142 -0
  188. package/dist/styles/number-ticker.css +83 -0
  189. package/dist/styles/overflow-list.css +51 -0
  190. package/dist/styles/overlay.css +284 -0
  191. package/dist/styles/pagination.css +21 -0
  192. package/dist/styles/pan-zoom.css +136 -0
  193. package/dist/styles/radar.css +1 -0
  194. package/dist/styles/rating.css +121 -0
  195. package/dist/styles/resizable.css +108 -0
  196. package/dist/styles/scroll-area.css +122 -0
  197. package/dist/styles/select.css +557 -0
  198. package/dist/styles/selection.css +264 -0
  199. package/dist/styles/slider.css +145 -0
  200. package/dist/styles/slot.css +109 -0
  201. package/dist/styles/sortable.css +159 -0
  202. package/dist/styles/sparkline.css +24 -0
  203. package/dist/styles/steps.css +27 -0
  204. package/dist/styles/tabs.css +79 -0
  205. package/dist/styles/tag-input.css +213 -0
  206. package/dist/styles/time-picker.css +66 -0
  207. package/dist/styles/timeline.css +24 -0
  208. package/dist/styles/toast.css +160 -0
  209. package/dist/styles/toggle-group.css +107 -0
  210. package/dist/styles/tooltip.css +78 -0
  211. package/dist/styles/transfer.css +148 -0
  212. package/dist/styles/tree-select.css +131 -0
  213. package/dist/styles/tree.css +59 -0
  214. package/dist/styles/typewriter.css +62 -0
  215. package/dist/styles/upload.css +60 -0
  216. package/dist/styles/virtual-list.css +112 -0
  217. package/dist/styles.css +2241 -443
  218. package/dist/tabs/index.d.ts +9 -0
  219. package/dist/tabs/index.js +66 -16
  220. package/dist/tabs/useOverflow.d.ts +20 -0
  221. package/dist/tabs/useOverflow.js +145 -0
  222. package/dist/tag-input/index.d.ts +7 -2
  223. package/dist/tag-input/index.js +70 -26
  224. package/dist/time-picker/index.js +38 -20
  225. package/dist/toast/index.d.ts +19 -11
  226. package/dist/toast/index.js +48 -8
  227. package/dist/toast/manager.d.ts +42 -0
  228. package/dist/toast/manager.js +85 -0
  229. package/dist/toast/useToastNavigation.d.ts +3 -0
  230. package/dist/toast/useToastNavigation.js +75 -0
  231. package/dist/tokens.css +4 -0
  232. package/dist/tooltip/Tooltip.d.ts +1 -1
  233. package/dist/tooltip/Tooltip.js +6 -5
  234. package/dist/transfer/index.d.ts +5 -0
  235. package/dist/transfer/index.js +93 -29
  236. package/dist/transfer/model.d.ts +4 -0
  237. package/dist/transfer/model.js +37 -11
  238. package/dist/tree/index.d.ts +5 -1
  239. package/dist/tree/index.js +5 -5
  240. package/dist/tree-select/index.js +67 -30
  241. package/dist/tree-select/model.d.ts +11 -5
  242. package/dist/tree-select/model.js +111 -83
  243. package/dist/typewriter/index.js +10 -10
  244. package/dist/upload/index.js +30 -16
  245. package/dist/virtual-list/index.js +10 -7
  246. package/package.json +416 -5
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from 'react';
3
+ import { PencilLine } from '../PencilLine.js';
4
+ import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
5
+ export function Deletion({ children, className, ...props }) {
6
+ const ref = useRef(null);
7
+ const [lines, setLines] = useState([]);
8
+ useBrowserLayoutEffect(() => {
9
+ const node = ref.current;
10
+ if (!node || !document.createElement('canvas').getContext('2d'))
11
+ return;
12
+ let disposed = false;
13
+ let frame = 0;
14
+ const measure = () => {
15
+ frame = 0;
16
+ if (disposed)
17
+ return;
18
+ const box = node.getBoundingClientRect();
19
+ const scaleX = node.offsetWidth ? box.width / node.offsetWidth : 1;
20
+ const scaleY = node.offsetHeight ? box.height / node.offsetHeight : 1;
21
+ const fragments = [];
22
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
23
+ const range = document.createRange();
24
+ // Use native line fragments, not one line through the whole wrapped block.
25
+ for (let text = walker.nextNode(); text; text = walker.nextNode()) {
26
+ if (!text.textContent?.trim() || text.parentElement?.closest('[aria-hidden="true"]'))
27
+ continue;
28
+ range.selectNodeContents(text);
29
+ for (const rect of range.getClientRects()) {
30
+ if (!rect.width || !rect.height)
31
+ continue;
32
+ const top = (rect.top - box.top + rect.height * 0.52) / (scaleY || 1) - 3;
33
+ const left = (rect.left - box.left) / (scaleX || 1);
34
+ const width = rect.width / (scaleX || 1);
35
+ const line = fragments.find(item => Math.abs(item.top - top) < 3);
36
+ if (line) {
37
+ const end = Math.max(line.left + line.width, left + width);
38
+ line.left = Math.min(line.left, left);
39
+ line.width = end - line.left;
40
+ }
41
+ else
42
+ fragments.push({ left, top, width });
43
+ }
44
+ }
45
+ const next = fragments.map(line => ({
46
+ left: Math.round(line.left * 2) / 2,
47
+ top: Math.round(line.top * 2) / 2,
48
+ width: Math.round(line.width * 2) / 2,
49
+ }));
50
+ setLines(previous => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
51
+ };
52
+ const schedule = () => {
53
+ if (!disposed && !frame)
54
+ frame = requestAnimationFrame(measure);
55
+ };
56
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
57
+ observer?.observe(node);
58
+ document.fonts?.addEventListener('loadingdone', schedule);
59
+ document.fonts?.ready.then(schedule);
60
+ measure();
61
+ return () => {
62
+ disposed = true;
63
+ observer?.disconnect();
64
+ document.fonts?.removeEventListener('loadingdone', schedule);
65
+ cancelAnimationFrame(frame);
66
+ };
67
+ }, [children]);
68
+ return _jsxs("del", { ...props, className: ['inkra-markdown__deleted', className].filter(Boolean).join(' '), ref: ref, "data-pencil-strike-ready": lines.length > 0 || undefined, children: [children, lines.map((line, index) => _jsx(PencilLine, { className: "inkra-markdown__strike", seed: `markdown-strike-${index}`, weight: 0.7, roughness: 0.4, style: { left: line.left, top: line.top, width: line.width } }, index))] });
69
+ }
@@ -0,0 +1,14 @@
1
+ export declare const codeIcons: {
2
+ copy: {
3
+ d: string;
4
+ }[];
5
+ check: {
6
+ d: string;
7
+ }[];
8
+ terminal: {
9
+ d: string;
10
+ }[];
11
+ code: {
12
+ d: string;
13
+ }[];
14
+ };
@@ -0,0 +1,16 @@
1
+ import { __iconData as copy } from 'lucide-react/dist/esm/icons/copy.mjs';
2
+ import { __iconData as check } from 'lucide-react/dist/esm/icons/check.mjs';
3
+ import { __iconData as terminal } from 'lucide-react/dist/esm/icons/terminal.mjs';
4
+ import { __iconData as code } from 'lucide-react/dist/esm/icons/code-xml.mjs';
5
+ function paths(data) {
6
+ return data.node.flatMap(([tag, attributes]) => {
7
+ if (tag === 'path')
8
+ return [{ d: attributes.d }];
9
+ if (tag !== 'rect')
10
+ return [];
11
+ const { x = 0, y = 0, width = 0, height = 0, rx = 0 } = Object.fromEntries(Object.entries(attributes).map(([key, value]) => [key, Number(value)]));
12
+ const r = Math.min(rx, width / 2, height / 2);
13
+ return [{ d: `M${x + r} ${y}H${x + width - r}Q${x + width} ${y} ${x + width} ${y + r}V${y + height - r}Q${x + width} ${y + height} ${x + width - r} ${y + height}H${x + r}Q${x} ${y + height} ${x} ${y + height - r}V${y + r}Q${x} ${y} ${x + r} ${y}Z` }];
14
+ });
15
+ }
16
+ export const codeIcons = { copy: paths(copy), check: paths(check), terminal: paths(terminal), code: paths(code) };
@@ -0,0 +1,17 @@
1
+ import type { Components } from 'react-markdown';
2
+ import { type ComponentPropsWithoutRef } from 'react';
3
+ import { type MarkdownCodeLabels } from './CodeBlock.js';
4
+ export interface MarkdownLabels extends Partial<MarkdownCodeLabels> {
5
+ code?: string;
6
+ table?: string;
7
+ taskChecked?: string;
8
+ taskUnchecked?: string;
9
+ }
10
+ export interface MarkdownProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
11
+ children: string;
12
+ components?: Components;
13
+ headingLevelOffset?: number;
14
+ labels?: MarkdownLabels;
15
+ transformUrl?: (url: string, kind: 'link' | 'image') => string | null | undefined;
16
+ }
17
+ export declare const Markdown: import("react").ForwardRefExoticComponent<MarkdownProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,175 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useMemo, } from 'react';
5
+ import { PencilIcon } from '../PencilIcon.js';
6
+ import { PencilLine } from '../PencilLine.js';
7
+ import { PencilSurface } from '../PencilSurface.js';
8
+ import { PencilText } from '../PencilText.js';
9
+ import { Table } from '../content/index.js';
10
+ import { Checkbox } from '../selection/Checkbox.js';
11
+ import { MarkdownCodeBlock } from './CodeBlock.js';
12
+ import { Deletion } from './Deletion.js';
13
+ const listContext = createContext('unordered');
14
+ const codeContext = createContext('inline');
15
+ const bulletPaths = [{ d: 'M12 4.5a7.5 7.5 0 1 0 0 15a7.5 7.5 0 1 0 0-15' }];
16
+ const markdownPlugins = [remarkGfm];
17
+ function classes(...values) {
18
+ return values.filter(Boolean).join(' ');
19
+ }
20
+ function pencilChildren(children, seed) {
21
+ return Children.map(children, (child, index) => {
22
+ if (typeof child !== 'string' && typeof child !== 'number')
23
+ return child;
24
+ const value = String(child);
25
+ if (!/\S/.test(value))
26
+ return value;
27
+ const [, leading, text, trailing] = /^(\s*)([\s\S]*?)(\s*)$/.exec(value);
28
+ return _jsxs(_Fragment, { children: [leading, _jsx(PencilText, { seed: `${seed}-${index}`, children: text }), trailing] });
29
+ });
30
+ }
31
+ function taskContent(children) {
32
+ let checkbox;
33
+ const extract = (content) => Children.map(content, child => {
34
+ if (!isValidElement(child))
35
+ return child;
36
+ if (!checkbox && child.props.node?.tagName === 'input' && child.props.type === 'checkbox') {
37
+ checkbox = child;
38
+ return null;
39
+ }
40
+ if (!checkbox && child.props.node?.tagName === 'p')
41
+ return cloneElement(child, {}, extract(child.props.children));
42
+ return child;
43
+ });
44
+ const content = extract(children);
45
+ return { checkbox, content };
46
+ }
47
+ function textChildren(children) {
48
+ let value = '';
49
+ Children.forEach(children, child => {
50
+ if (typeof child === 'string' || typeof child === 'number')
51
+ value += String(child);
52
+ });
53
+ return value;
54
+ }
55
+ function codeBlockContent(children) {
56
+ if (isValidElement(children)) {
57
+ return {
58
+ className: children.props.className,
59
+ code: textChildren(children.props.children),
60
+ };
61
+ }
62
+ return { className: undefined, code: textChildren(children) };
63
+ }
64
+ function safeUrl(url, kind) {
65
+ const trimmed = url.trim();
66
+ if (!trimmed)
67
+ return null;
68
+ if (/^(?:[#/?]|\.\.?\/)/.test(trimmed))
69
+ return trimmed;
70
+ const scheme = /^([a-z][a-z\d+.-]*):/i.exec(trimmed.replace(/[\u0000-\u001f\u007f]/g, ''))?.[1]?.toLowerCase();
71
+ if (!scheme)
72
+ return trimmed;
73
+ if (scheme === 'http' || scheme === 'https')
74
+ return trimmed;
75
+ if (kind === 'link' && (scheme === 'mailto' || scheme === 'tel'))
76
+ return trimmed;
77
+ return null;
78
+ }
79
+ function headingTag(level, offset) {
80
+ return `h${Math.max(1, Math.min(6, level + offset))}`;
81
+ }
82
+ function MarkdownTaskCheckbox({ checked = false, className, label, ...props }) {
83
+ const selected = Boolean(checked);
84
+ return _jsx(Checkbox, { ...props, "aria-label": props['aria-label'] ?? label, checked: selected, className: classes('inkra-markdown__task-checkbox', className), disabled: true, label: _jsx("span", { "aria-hidden": "true" }), seed: `markdown-task-${selected ? 'checked' : 'unchecked'}` });
85
+ }
86
+ function createMarkdownComponents(headingLevelOffset, labels) {
87
+ const heading = (level) => {
88
+ const Heading = headingTag(level, headingLevelOffset);
89
+ return ({ node: _node, children, className, ...props }) => (_jsx(Heading, { ...props, className: classes('inkra-markdown__heading', className), children: pencilChildren(children, `markdown-heading-${level}`) }));
90
+ };
91
+ return {
92
+ h1: heading(1),
93
+ h2: heading(2),
94
+ h3: heading(3),
95
+ h4: heading(4),
96
+ h5: heading(5),
97
+ h6: heading(6),
98
+ p: ({ node: _node, children, className, ...props }) => (_jsx("p", { ...props, className: classes('inkra-markdown__paragraph', className), children: pencilChildren(children, 'markdown-paragraph') })),
99
+ strong: ({ node: _node, children, ...props }) => _jsx("strong", { ...props, children: pencilChildren(children, 'markdown-strong') }),
100
+ em: ({ node: _node, children, ...props }) => _jsx("em", { ...props, children: pencilChildren(children, 'markdown-emphasis') }),
101
+ del: ({ node: _node, children, ...props }) => _jsx(Deletion, { ...props, children: pencilChildren(children, 'markdown-deleted') }),
102
+ a: ({ node: _node, children, className, href, ...props }) => href ? (_jsxs("a", { ...props, className: classes('inkra-markdown__link', className), href: href, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-markdown__link-focus", radius: 3, seed: `markdown-link-focus-${href}`, variant: "outline" }), _jsx("span", { children: pencilChildren(children, 'markdown-link') }), _jsx(PencilLine, { className: "inkra-markdown__link-line", seed: `markdown-link-${href}` })] })) : _jsx("span", { className: classes('inkra-markdown__link-text', className), children: pencilChildren(children, 'markdown-link-text') }),
103
+ blockquote: ({ node: _node, children, className, ...props }) => (_jsxs("blockquote", { ...props, className: classes('inkra-markdown__quote', className), children: [_jsx(PencilLine, { className: "inkra-markdown__quote-line", orientation: "vertical", seed: "markdown-quote" }), _jsx("div", { children: children })] })),
104
+ hr: ({ node: _node, className, ...props }) => (_jsx("div", { ...props, className: classes('inkra-markdown__divider', className), role: "separator", children: _jsx(PencilLine, { seed: "markdown-divider" }) })),
105
+ pre: ({ node: _node, children, ...props }) => {
106
+ const block = codeBlockContent(children);
107
+ return _jsx(codeContext.Provider, { value: "block", children: _jsx(MarkdownCodeBlock, { ...props, "aria-label": labels.code, labels: labels, code: block.code, codeClassName: block.className }) });
108
+ },
109
+ code: ({ node: _node, children, className, ...props }) => {
110
+ const kind = useContext(codeContext);
111
+ const value = textChildren(children);
112
+ return _jsx("code", { ...props, className: classes('inkra-markdown__code', className), "data-inline": kind === 'inline' || undefined, children: kind === 'inline'
113
+ ? _jsxs(_Fragment, { children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-markdown__code-wash", radius: 3, seed: `markdown-inline-code-wash-${value}`, variant: "wash" }), _jsx(PencilText, { className: "inkra-markdown__code-text", seed: `markdown-inline-code-${value}`, children: value })] })
114
+ : children });
115
+ },
116
+ ul: ({ node: _node, children, className, ...props }) => (_jsx(listContext.Provider, { value: "unordered", children: _jsx("ul", { ...props, className: classes('inkra-markdown__list', 'inkra-markdown__list--unordered', className), children: children }) })),
117
+ ol: ({ node: _node, children, className, start, style, ...props }) => {
118
+ const first = typeof start === 'number' && Number.isFinite(start) ? Math.trunc(start) : 1;
119
+ let itemIndex = 0;
120
+ const numberedChildren = Children.map(children, child => {
121
+ if (!isValidElement(child))
122
+ return child;
123
+ const marker = first + itemIndex;
124
+ itemIndex += 1;
125
+ return cloneElement(child, { 'data-inkra-ordered-index': marker });
126
+ });
127
+ const last = first + Math.max(0, itemIndex - 1);
128
+ const markerCharacters = Math.max(`${first}.`.length, `${last}.`.length);
129
+ const listStyle = {
130
+ ...style,
131
+ '--inkra-markdown-ordered-marker-width': `max(1.75rem, ${markerCharacters}ch)`,
132
+ };
133
+ return _jsx(listContext.Provider, { value: "ordered", children: _jsx("ol", { ...props, className: classes('inkra-markdown__list', 'inkra-markdown__list--ordered', className), start: first, style: listStyle, children: numberedChildren }) });
134
+ },
135
+ li: ({ node: _node, children, className, 'data-inkra-ordered-index': orderedIndex, ...props }) => {
136
+ const kind = useContext(listContext);
137
+ const task = className?.split(/\s+/).includes('task-list-item');
138
+ const extracted = task ? taskContent(children) : undefined;
139
+ return _jsxs("li", { ...props, className: classes('inkra-markdown__list-item', className), "data-inkra-list-kind": kind, children: [kind === 'unordered' && !task && _jsx(PencilIcon, { className: "inkra-markdown__bullet", paths: bulletPaths, size: 12, sourceSize: 24, strokeWidth: 1.2, intensity: 0.78 }), kind === 'ordered' && orderedIndex != null && _jsx(PencilText, { "aria-hidden": "true", className: "inkra-markdown__ordered-marker", seed: `markdown-order-${orderedIndex}`, children: `${orderedIndex}.` }), _jsx("div", { className: "inkra-markdown__list-content", children: extracted?.checkbox
140
+ ? _jsxs("div", { className: "inkra-markdown__task-row", children: [extracted.checkbox, _jsx("div", { className: "inkra-markdown__task-content", children: pencilChildren(extracted.content, 'markdown-list-item') })] })
141
+ : pencilChildren(children, 'markdown-list-item') })] });
142
+ },
143
+ input: ({ node: _node, checked, type, ...props }) => type === 'checkbox'
144
+ ? _jsx(MarkdownTaskCheckbox, { ...props, checked: checked, label: checked ? labels.taskChecked : labels.taskUnchecked })
145
+ : _jsx("input", { ...props, checked: checked, type: type }),
146
+ table: ({ node: _node, children, className, ...props }) => (_jsxs("div", { className: "inkra-markdown__table-frame", children: [_jsx(Table.Container, { "aria-label": labels.table, className: "inkra-markdown__table", role: "region", children: _jsx(Table.Root, { ...props, className: className, children: children }) }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-markdown__table-focus", radius: 6, seed: "markdown-table-focus", variant: "outline" })] })),
147
+ thead: ({ node: _node, ...props }) => _jsx(Table.Header, { ...props }),
148
+ tbody: ({ node: _node, ...props }) => _jsx(Table.Body, { ...props }),
149
+ tr: ({ node: _node, ...props }) => _jsx(Table.Row, { ...props }),
150
+ th: ({ node: _node, children, align, ...props }) => _jsx(Table.Head, { ...props, align: align, children: pencilChildren(children, 'markdown-table-heading') }),
151
+ td: ({ node: _node, children, align, ...props }) => _jsx(Table.Cell, { ...props, align: align, verticalAlign: "top", children: pencilChildren(children, 'markdown-table-cell') }),
152
+ img: ({ node: _node, className, alt = '', ...props }) => props.src ? (_jsx(PencilSurface, { as: "span", className: "inkra-markdown__image-frame", radius: 6, seed: `markdown-image-${props.src ?? ''}`, variant: "outline", children: _jsx("img", { ...props, alt: alt, className: classes('inkra-markdown__image', className), decoding: "async", loading: "lazy" }) })) : _jsx("span", { className: classes('inkra-markdown__image-alt', className), children: pencilChildren(alt, 'markdown-image-alt') }),
153
+ };
154
+ }
155
+ export const Markdown = forwardRef(function Markdown({ children, className, components, headingLevelOffset = 0, labels, transformUrl, ...props }, ref) {
156
+ const offset = Number.isFinite(headingLevelOffset) ? Math.trunc(headingLevelOffset) : 0;
157
+ const resolvedLabels = useMemo(() => ({
158
+ code: labels?.code ?? 'Code block',
159
+ plainText: labels?.plainText ?? 'Plain text',
160
+ copy: labels?.copy ?? 'Copy code',
161
+ copied: labels?.copied ?? 'Code copied',
162
+ copyFailed: labels?.copyFailed ?? 'Copy failed. Select the code to copy it manually.',
163
+ table: labels?.table ?? 'Markdown table',
164
+ taskChecked: labels?.taskChecked ?? 'Completed task',
165
+ taskUnchecked: labels?.taskUnchecked ?? 'Incomplete task',
166
+ }), [labels?.code, labels?.plainText, labels?.copy, labels?.copied, labels?.copyFailed, labels?.table, labels?.taskChecked, labels?.taskUnchecked]);
167
+ const builtIns = useMemo(() => createMarkdownComponents(offset, resolvedLabels), [offset, resolvedLabels]);
168
+ const resolvedComponents = useMemo(() => ({ ...builtIns, ...components }), [builtIns, components]);
169
+ const urlTransform = useCallback((url, key) => {
170
+ const kind = key === 'src' ? 'image' : 'link';
171
+ const transformed = transformUrl ? transformUrl(url, kind) : url;
172
+ return transformed == null ? null : safeUrl(transformed, kind);
173
+ }, [transformUrl]);
174
+ return _jsx("div", { ...props, ref: ref, className: classes('inkra-markdown', className), children: _jsx(ReactMarkdown, { components: resolvedComponents, remarkPlugins: markdownPlugins, skipHtml: true, urlTransform: urlTransform, children: children }) });
175
+ });
@@ -50,7 +50,9 @@ export interface NodeGraphControlLabels {
50
50
  fitView: string;
51
51
  }
52
52
  export interface NodeGraphRootProps<NodeType extends NodeGraphNode = NodeGraphNode, EdgeType extends NodeGraphEdge = NodeGraphEdge> extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onError'> {
53
+ /** Immutable snapshots: replace the array and each changed node (including changed nested data). */
53
54
  nodes: NodeType[];
55
+ /** Immutable snapshots: replace the array and each changed edge (including changed nested data). */
54
56
  edges: EdgeType[];
55
57
  onNodesChange?: (changes: NodeGraphNodeChange<NodeType>[]) => void;
56
58
  onEdgesChange?: (changes: NodeGraphEdgeChange<EdgeType>[]) => void;
@@ -132,9 +132,6 @@ function PencilEdgeCanvas({ id, path, className, selected = false }) {
132
132
  if (disposed)
133
133
  return;
134
134
  try {
135
- const points = sampleSvgPath(pathElement, bounds);
136
- if (points.length < 2)
137
- throw new Error('NodeGraph edge has no drawable path');
138
135
  const style = getComputedStyle(canvas);
139
136
  const width = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
140
137
  const roughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
@@ -150,6 +147,9 @@ function PencilEdgeCanvas({ id, path, className, selected = false }) {
150
147
  image = undefined;
151
148
  }
152
149
  if (!image) {
150
+ const points = sampleSvgPath(pathElement, bounds);
151
+ if (points.length < 2)
152
+ throw new Error('NodeGraph edge has no drawable path');
153
153
  image = document.createElement('canvas');
154
154
  image.width = budget.width;
155
155
  image.height = budget.height;
@@ -260,28 +260,52 @@ function NodeGraphRootInner({ nodes, edges, onNodesChange, onEdgesChange, onConn
260
260
  zoomOut: () => instanceRef.current?.zoomOut() ?? Promise.resolve(false),
261
261
  getViewport: () => instanceRef.current?.getViewport() ?? defaultViewport,
262
262
  }), [defaultViewport]);
263
- const internalNodes = useMemo(() => nodes.map(node => ({
264
- ...node,
265
- type: 'inkra',
266
- ariaLabel: node.ariaLabel ?? readableLabel(node.data.label, node.id),
267
- data: {
268
- __inkraRenderNode: renderNode,
269
- __inkraReadOnly: readOnly,
270
- __inkraData: node.data,
271
- },
272
- })), [nodes, readOnly, renderNode]);
273
- const internalEdges = useMemo(() => edges.map(edge => ({
274
- ...edge,
275
- type: 'inkra',
276
- ariaLabel: edge.ariaLabel ?? `Edge from ${edge.source} to ${edge.target}`,
277
- label: undefined,
278
- data: {
279
- ...edge.data,
280
- __inkraEdge: edge,
281
- __inkraRenderEdge: renderEdge,
282
- __inkraRenderEdgeLabel: renderEdgeLabel,
283
- },
284
- })), [edges, renderEdge, renderEdgeLabel]);
263
+ // Immutable node/edge updates should invalidate only the changed records. Weak
264
+ // keys release removed records and changing render configuration resets the cache.
265
+ const adaptNode = useMemo(() => {
266
+ const cache = new WeakMap();
267
+ return (node) => {
268
+ const cached = cache.get(node);
269
+ if (cached)
270
+ return cached;
271
+ const adapted = {
272
+ ...node,
273
+ type: 'inkra',
274
+ ariaLabel: node.ariaLabel ?? readableLabel(node.data.label, node.id),
275
+ data: {
276
+ __inkraRenderNode: renderNode,
277
+ __inkraReadOnly: readOnly,
278
+ __inkraData: node.data,
279
+ },
280
+ };
281
+ cache.set(node, adapted);
282
+ return adapted;
283
+ };
284
+ }, [readOnly, renderNode]);
285
+ const adaptEdge = useMemo(() => {
286
+ const cache = new WeakMap();
287
+ return (edge) => {
288
+ const cached = cache.get(edge);
289
+ if (cached)
290
+ return cached;
291
+ const adapted = {
292
+ ...edge,
293
+ type: 'inkra',
294
+ ariaLabel: edge.ariaLabel ?? `Edge from ${edge.source} to ${edge.target}`,
295
+ label: undefined,
296
+ data: {
297
+ ...edge.data,
298
+ __inkraEdge: edge,
299
+ __inkraRenderEdge: renderEdge,
300
+ __inkraRenderEdgeLabel: renderEdgeLabel,
301
+ },
302
+ };
303
+ cache.set(edge, adapted);
304
+ return adapted;
305
+ };
306
+ }, [renderEdge, renderEdgeLabel]);
307
+ const internalNodes = useMemo(() => nodes.map(adaptNode), [nodes, adaptNode]);
308
+ const internalEdges = useMemo(() => edges.map(adaptEdge), [edges, adaptEdge]);
285
309
  const rootStyle = { minHeight: 320, ...style };
286
310
  return _jsx("div", { ...props, "aria-label": ariaLabel, className: ['inkra-node-graph', className].filter(Boolean).join(' '), "data-empty": nodes.length === 0 || undefined, "data-read-only": readOnly || undefined, style: rootStyle, children: !mounted ? _jsx(SsrFallback, { nodes: nodes, emptyState: emptyState, label: ariaLabel }) : _jsxs(ReactFlowProvider, { children: [_jsx(ReactFlow, { "aria-label": ariaLabel, nodes: internalNodes, edges: internalEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onInit: instance => { instanceRef.current = instance; }, onNodesChange: changes => onNodesChange?.(changes), onEdgesChange: changes => onEdgesChange?.(changes), onConnect: readOnly ? undefined : onConnect, isValidConnection: isValidConnection, nodesDraggable: !readOnly, nodesConnectable: !readOnly, elementsSelectable: !readOnly, edgesReconnectable: !readOnly, nodesFocusable: true, edgesFocusable: true, deleteKeyCode: readOnly ? null : ['Backspace', 'Delete'], fitView: fitView, fitViewOptions: fitViewOptions, defaultViewport: defaultViewport, viewport: viewport, onViewportChange: onViewportChange, minZoom: minZoom, maxZoom: maxZoom, panOnDrag: panOnDrag, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, connectOnClick: connectOnClick && !readOnly, connectionLineComponent: NodeGraphConnectionLine, onlyRenderVisibleElements: onlyRenderVisibleElements, noDragClassName: "nodrag", noPanClassName: "nopan", children: controls && _jsx(Panel, { position: "bottom-left", children: _jsx(NodeGraphControls, { fitViewLabel: controlLabels?.fitView, fitViewOptions: fitViewOptions, zoomInLabel: controlLabels?.zoomIn, zoomOutLabel: controlLabels?.zoomOut }) }) }), nodes.length === 0 && _jsx("div", { className: "inkra-node-graph__empty", role: "status", children: emptyState })] }) });
287
311
  }
@@ -1,5 +1,5 @@
1
1
  import type { ComponentPropsWithoutRef, ReactNode } from 'react';
2
- import type { NumberTickerLocale } from './model.js';
2
+ import type { NumberTickerCompact, NumberTickerCompactOptions, NumberTickerFormatOptions, NumberTickerLocale, NumberTickerValue } from './model.js';
3
3
  export interface NumberTickerRenderDetails {
4
4
  animating: boolean;
5
5
  formattedValue: string;
@@ -9,21 +9,23 @@ export interface NumberTickerDeltaDetails {
9
9
  formattedDelta: string;
10
10
  }
11
11
  export interface NumberTickerProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'prefix'> {
12
- value: number;
12
+ value: NumberTickerValue;
13
13
  locale?: NumberTickerLocale;
14
- formatOptions?: Intl.NumberFormatOptions;
14
+ formatOptions?: NumberTickerFormatOptions;
15
+ compact?: NumberTickerCompact;
16
+ compactOptions?: NumberTickerCompactOptions;
15
17
  prefix?: ReactNode;
16
18
  suffix?: ReactNode;
17
- renderValue?: (value: number, details: NumberTickerRenderDetails) => ReactNode;
18
- getValueLabel?: (value: number, formattedValue: string) => string;
19
+ renderValue?: (value: NumberTickerValue, details: NumberTickerRenderDetails) => ReactNode;
20
+ getValueLabel?: (value: NumberTickerValue, formattedValue: string) => string;
19
21
  animate?: boolean;
20
22
  duration?: number;
21
23
  easing?: (progress: number) => number;
22
24
  showDelta?: boolean;
23
25
  deltaDuration?: number;
24
- deltaFormatOptions?: Intl.NumberFormatOptions;
25
- renderDelta?: (delta: number, details: NumberTickerDeltaDetails) => ReactNode;
26
+ deltaFormatOptions?: NumberTickerFormatOptions;
27
+ renderDelta?: (delta: NumberTickerValue, details: NumberTickerDeltaDetails) => ReactNode;
26
28
  }
27
29
  export declare const NumberTicker: import("react").ForwardRefExoticComponent<NumberTickerProps & import("react").RefAttributes<HTMLSpanElement>>;
28
- export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, } from './model.js';
29
- export type { NumberTickerLocale } from './model.js';
30
+ export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, numberTickerUnits, } from './model.js';
31
+ export type { NumberTickerCompact, NumberTickerCompactOptions, NumberTickerFormatOptions, NumberTickerLocale, NumberTickerUnit, NumberTickerValue } from './model.js';
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
3
3
  import { PencilText } from '../PencilText.js';
4
4
  import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
5
- import { canAnimateTickerValue, createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, tickerValueChanged, } from './model.js';
5
+ import { canAnimateTickerValue, createNumberFormatter, createTickerFormatter, easeOutCubic, formatDerivedTickerDelta, formatTickerValue, interpolateTickerValue, tickerValueChanged, subtractTickerValues, tickerValueKey, } from './model.js';
6
+ import { ExactDecimal } from './precision.js';
6
7
  function usePrefersReducedMotion() {
7
8
  const [reduced, setReduced] = useState(false);
8
9
  useBrowserLayoutEffect(() => {
@@ -22,7 +23,7 @@ function lettering(content, seed) {
22
23
  function accessibleAffix(content) {
23
24
  return typeof content === 'string' || typeof content === 'number' ? String(content) : '';
24
25
  }
25
- export const NumberTicker = forwardRef(function NumberTicker({ animate = true, className, deltaDuration = 1600, deltaFormatOptions, duration = 420, easing = easeOutCubic, formatOptions, getValueLabel, locale, prefix, renderDelta, renderValue, showDelta = false, suffix, value, ...props }, forwardedRef) {
26
+ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, className, compact = false, compactOptions, deltaDuration = 1600, deltaFormatOptions, duration = 420, easing = easeOutCubic, formatOptions, getValueLabel, locale, prefix, renderDelta, renderValue, showDelta = false, suffix, value, ...props }, forwardedRef) {
26
27
  const rootRef = useRef(null);
27
28
  const frameRef = useRef(null);
28
29
  const deltaTimerRef = useRef(null);
@@ -35,15 +36,17 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
35
36
  const reducedMotion = usePrefersReducedMotion();
36
37
  const localeKey = Array.isArray(locale) ? locale.join('\u0000') : locale;
37
38
  const formatOptionsKey = JSON.stringify(formatOptions ?? {});
39
+ const compactOptionsKey = tickerValueKey(compactOptions ?? {});
38
40
  const deltaFormatOptionsKey = JSON.stringify(deltaFormatOptions ?? formatOptions ?? {});
39
- const formatter = useMemo(() => createNumberFormatter(locale, formatOptions), [localeKey, formatOptionsKey]);
40
- const deltaFormatter = useMemo(() => createNumberFormatter(locale, deltaFormatOptions ?? formatOptions), [localeKey, deltaFormatOptionsKey]);
41
+ const formatter = useMemo(() => createTickerFormatter(locale, formatOptions, compact, compactOptions), [localeKey, formatOptionsKey, compact, compactOptionsKey]);
42
+ const deltaFormatter = useMemo(() => createTickerFormatter(locale, deltaFormatOptions ?? formatOptions, compact, compactOptions), [localeKey, deltaFormatOptionsKey, compact, compactOptionsKey]);
43
+ const announcementFormatter = useMemo(() => createNumberFormatter(locale, compact ? { ...formatOptions, notation: 'standard' } : formatOptions), [localeKey, formatOptionsKey, compact]);
41
44
  useImperativeHandle(forwardedRef, () => rootRef.current);
42
45
  useBrowserLayoutEffect(() => {
43
46
  const previousTarget = targetRef.current;
44
47
  targetRef.current = value;
45
- if (showDelta && tickerValueChanged(previousTarget, value) && Number.isFinite(previousTarget) && Number.isFinite(value)) {
46
- const nextDelta = value - previousTarget;
48
+ if (showDelta && tickerValueChanged(previousTarget, value)) {
49
+ const nextDelta = subtractTickerValues(value, previousTarget);
47
50
  setDelta(nextDelta);
48
51
  if (deltaTimerRef.current !== null)
49
52
  clearTimeout(deltaTimerRef.current);
@@ -109,7 +112,7 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
109
112
  }, []);
110
113
  const formattedValue = formatTickerValue(formatter, displayedValue);
111
114
  const finalFormattedValue = formatTickerValue(formatter, value);
112
- const announcedFormattedValue = formatTickerValue(formatter, announcedValue);
115
+ const announcedFormattedValue = formatTickerValue(announcementFormatter, announcedValue);
113
116
  const defaultAccessibleLabel = [accessibleAffix(prefix), announcedFormattedValue, accessibleAffix(suffix)]
114
117
  .filter(Boolean).join(' ');
115
118
  const accessibleLabel = getValueLabel?.(announcedValue, announcedFormattedValue) ?? defaultAccessibleLabel;
@@ -118,15 +121,15 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
118
121
  ? _jsx("span", { className: "inkra-number-ticker__native-value", children: formattedValue })
119
122
  : _jsx(PencilText, { seed: `number-ticker-${finalFormattedValue}`, children: finalFormattedValue }));
120
123
  const deltaContent = delta === null ? null : (() => {
121
- const formattedDelta = formatTickerDelta(deltaFormatter, delta);
124
+ const formattedDelta = formatDerivedTickerDelta(deltaFormatter, delta);
122
125
  const details = {
123
- direction: delta < 0 ? 'decrease' : 'increase',
126
+ direction: new ExactDecimal(String(delta)).lt(0) ? 'decrease' : 'increase',
124
127
  formattedDelta,
125
128
  };
126
129
  return renderDelta?.(delta, details) ?? _jsx(PencilText, { seed: `number-ticker-delta-${formattedDelta}`, children: formattedDelta });
127
130
  })();
128
131
  const classes = ['inkra-number-ticker', animating && 'inkra-number-ticker--animating', className]
129
132
  .filter(Boolean).join(' ');
130
- return _jsxs("span", { ...props, className: classes, ref: rootRef, "data-animating": animating || undefined, children: [_jsxs("span", { "aria-hidden": "true", className: "inkra-number-ticker__visual", children: [prefix !== undefined && _jsx("span", { className: "inkra-number-ticker__prefix", children: lettering(prefix, 'number-ticker-prefix') }), _jsx("span", { className: "inkra-number-ticker__value", children: valueContent }), suffix !== undefined && _jsx("span", { className: "inkra-number-ticker__suffix", children: lettering(suffix, 'number-ticker-suffix') }), deltaContent !== null && _jsx("span", { className: "inkra-number-ticker__delta", "data-direction": delta < 0 ? 'decrease' : 'increase', children: deltaContent })] }), _jsx("span", { className: "inkra-number-ticker__announcement", "aria-atomic": "true", "aria-live": "polite", children: accessibleLabel })] });
133
+ return _jsxs("span", { ...props, className: classes, ref: rootRef, "data-animating": animating || undefined, children: [_jsxs("span", { "aria-hidden": "true", className: "inkra-number-ticker__visual", children: [prefix !== undefined && _jsx("span", { className: "inkra-number-ticker__prefix", children: lettering(prefix, 'number-ticker-prefix') }), _jsx("span", { className: "inkra-number-ticker__value", children: valueContent }), suffix !== undefined && _jsx("span", { className: "inkra-number-ticker__suffix", children: lettering(suffix, 'number-ticker-suffix') }), deltaContent !== null && _jsx("span", { className: "inkra-number-ticker__delta", "data-direction": new ExactDecimal(String(delta)).lt(0) ? 'decrease' : 'increase', children: deltaContent })] }), _jsx("span", { className: "inkra-number-ticker__announcement", "aria-atomic": "true", "aria-live": "polite", children: accessibleLabel })] });
131
134
  });
132
- export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, } from './model.js';
135
+ export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, numberTickerUnits, } from './model.js';
@@ -1,8 +1,41 @@
1
+ import { tickerDecimal } from './precision.js';
2
+ import type { NumberTickerValue } from './precision.js';
3
+ export { subtractTickerValues, tickerValueKey } from './precision.js';
4
+ export type { NumberTickerValue } from './precision.js';
1
5
  export type NumberTickerLocale = string | readonly string[];
2
- export declare function createNumberFormatter(locale?: NumberTickerLocale, options?: Intl.NumberFormatOptions): Intl.NumberFormat;
3
- export declare function formatTickerValue(formatter: Intl.NumberFormat, value: number): string;
4
- export declare function formatTickerDelta(formatter: Intl.NumberFormat, delta: number): string;
6
+ export type NumberTickerCompact = boolean | 'en' | 'zh';
7
+ export interface NumberTickerFormatOptions extends Intl.NumberFormatOptions {
8
+ roundingMode?: 'ceil' | 'floor' | 'expand' | 'trunc' | 'halfCeil' | 'halfFloor' | 'halfExpand' | 'halfTrunc' | 'halfEven';
9
+ }
10
+ export interface NumberTickerUnit {
11
+ factor: NumberTickerValue;
12
+ label: string;
13
+ /** Absolute display magnitude required to enter this unit. */
14
+ threshold?: NumberTickerValue;
15
+ }
16
+ export interface NumberTickerCompactOptions {
17
+ units?: readonly NumberTickerUnit[];
18
+ minUnit?: string;
19
+ maxUnit?: string;
20
+ separator?: string;
21
+ promotionThreshold?: NumberTickerValue;
22
+ promoteAfterRounding?: boolean;
23
+ overflow?: 'unit' | 'scientific';
24
+ }
25
+ export declare const numberTickerUnits: Readonly<Record<'en' | 'zh', readonly NumberTickerUnit[]>>;
26
+ type TickerFormatter = {
27
+ format(value: NumberTickerValue): string;
28
+ formatToParts(value: NumberTickerValue): Intl.NumberFormatPart[];
29
+ formatDecimal(value: ReturnType<typeof tickerDecimal>): Intl.NumberFormatPart[];
30
+ };
31
+ export declare function createNumberFormatter(locale?: NumberTickerLocale, options?: NumberTickerFormatOptions): Intl.NumberFormat;
32
+ export declare function createTickerFormatter(locale?: NumberTickerLocale, options?: NumberTickerFormatOptions, compact?: NumberTickerCompact, compactOptions?: NumberTickerCompactOptions): TickerFormatter;
33
+ export declare function formatTickerValue(formatter: TickerFormatter | Intl.NumberFormat, value: NumberTickerValue): string;
34
+ export declare function formatTickerDelta(formatter: TickerFormatter | Intl.NumberFormat, delta: NumberTickerValue): string;
35
+ export declare function formatDerivedTickerDelta(formatter: TickerFormatter, delta: NumberTickerValue): string;
5
36
  export declare function interpolateTickerValue(from: number, to: number, progress: number): number;
6
- export declare function tickerValueChanged(previous: number, next: number): boolean;
7
- export declare function canAnimateTickerValue(from: number, to: number, duration: number): boolean;
37
+ export declare function interpolateTickerValue(from: bigint, to: bigint, progress: number): bigint;
38
+ export declare function interpolateTickerValue(from: NumberTickerValue, to: NumberTickerValue, progress: number): NumberTickerValue;
39
+ export declare function tickerValueChanged(previous: NumberTickerValue, next: NumberTickerValue): boolean;
40
+ export declare function canAnimateTickerValue(from: NumberTickerValue, to: NumberTickerValue, duration: number): boolean;
8
41
  export declare function easeOutCubic(progress: number): number;