@inkra/react 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +113 -3
  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.js +14 -4
  9. package/dist/audio-player/index.d.ts +2 -0
  10. package/dist/audio-player/index.js +131 -34
  11. package/dist/breadcrumb/index.js +4 -1
  12. package/dist/calendar/index.d.ts +4 -1
  13. package/dist/calendar/index.js +37 -12
  14. package/dist/calendar/locales.d.ts +7 -0
  15. package/dist/calendar/locales.js +2 -0
  16. package/dist/carousel/index.js +20 -10
  17. package/dist/choice-grid/index.d.ts +6 -1
  18. package/dist/choice-grid/index.js +63 -33
  19. package/dist/color-picker/GradientEditor.d.ts +40 -0
  20. package/dist/color-picker/GradientEditor.js +64 -0
  21. package/dist/color-picker/gradient.d.ts +25 -0
  22. package/dist/color-picker/gradient.js +60 -0
  23. package/dist/color-picker/index.d.ts +27 -7
  24. package/dist/color-picker/index.js +144 -40
  25. package/dist/color-swatch/index.d.ts +10 -0
  26. package/dist/color-swatch/index.js +9 -0
  27. package/dist/command/index.js +27 -4
  28. package/dist/content/index.d.ts +5 -0
  29. package/dist/content/index.js +15 -3
  30. package/dist/context-menu/index.js +1 -1
  31. package/dist/data-table/index.d.ts +7 -1
  32. package/dist/data-table/index.js +67 -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 +20 -21
  37. package/dist/day-agenda/index.d.ts +6 -1
  38. package/dist/day-agenda/index.js +49 -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 +15 -13
  44. package/dist/drawer/index.js +11 -5
  45. package/dist/editable/index.d.ts +24 -2
  46. package/dist/editable/index.js +148 -40
  47. package/dist/empty/illustrations.d.ts +8 -0
  48. package/dist/empty/illustrations.js +78 -0
  49. package/dist/empty/index.d.ts +6 -1
  50. package/dist/empty/index.js +11 -3
  51. package/dist/flip/index.js +28 -8
  52. package/dist/form/Field.js +11 -10
  53. package/dist/form/Input.js +4 -1
  54. package/dist/form/Textarea.d.ts +3 -1
  55. package/dist/form/Textarea.js +9 -5
  56. package/dist/heatmap/index.js +4 -1
  57. package/dist/hotkey/index.d.ts +1 -0
  58. package/dist/hotkey/index.js +9 -4
  59. package/dist/hover-card/index.js +1 -1
  60. package/dist/i18n/catalog.d.ts +5 -0
  61. package/dist/i18n/catalog.js +289 -0
  62. package/dist/i18n/index.d.ts +23 -0
  63. package/dist/i18n/index.js +50 -0
  64. package/dist/image-cropper/index.js +21 -17
  65. package/dist/image-viewer/VideoResource.d.ts +21 -0
  66. package/dist/image-viewer/VideoResource.js +196 -0
  67. package/dist/image-viewer/index.d.ts +14 -0
  68. package/dist/image-viewer/index.js +141 -86
  69. package/dist/image-viewer/lucideGeometry.d.ts +3 -0
  70. package/dist/image-viewer/lucideGeometry.js +19 -0
  71. package/dist/index.d.ts +4 -0
  72. package/dist/index.js +4 -0
  73. package/dist/input-number/index.js +13 -5
  74. package/dist/internal/cache.d.ts +4 -0
  75. package/dist/internal/cache.js +18 -1
  76. package/dist/internal/iconGeometry.d.ts +2 -0
  77. package/dist/internal/iconGeometry.js +32 -0
  78. package/dist/internal/renderTokens.d.ts +4 -0
  79. package/dist/internal/renderTokens.js +4 -0
  80. package/dist/internal/stroke.d.ts +7 -0
  81. package/dist/internal/stroke.js +63 -7
  82. package/dist/internal/surfaceBudget.d.ts +20 -0
  83. package/dist/internal/surfaceBudget.js +69 -0
  84. package/dist/internal/surfaceRenderer.js +87 -28
  85. package/dist/internal/surfaceVisibility.d.ts +3 -0
  86. package/dist/internal/surfaceVisibility.js +60 -0
  87. package/dist/internal/textRenderer.d.ts +3 -0
  88. package/dist/internal/textRenderer.js +13 -5
  89. package/dist/internal/usePencilSurface.js +17 -1
  90. package/dist/internal/wash.js +4 -3
  91. package/dist/journey-map/index.d.ts +1 -1
  92. package/dist/journey-map/index.js +6 -3
  93. package/dist/journey-map/terrain.js +10 -5
  94. package/dist/keypad/index.js +4 -1
  95. package/dist/layout/index.js +1 -1
  96. package/dist/log/index.js +42 -33
  97. package/dist/markdown/CodeBlock.d.ts +19 -0
  98. package/dist/markdown/CodeBlock.js +86 -0
  99. package/dist/markdown/Deletion.d.ts +2 -0
  100. package/dist/markdown/Deletion.js +69 -0
  101. package/dist/markdown/icons.d.ts +14 -0
  102. package/dist/markdown/icons.js +16 -0
  103. package/dist/markdown/index.d.ts +17 -0
  104. package/dist/markdown/index.js +177 -0
  105. package/dist/node-graph/index.d.ts +3 -1
  106. package/dist/node-graph/index.js +63 -28
  107. package/dist/number-ticker/index.js +4 -1
  108. package/dist/number-ticker/precision.js +9 -2
  109. package/dist/overflow-list/index.js +9 -6
  110. package/dist/overlay/Overlay.js +8 -3
  111. package/dist/overlay/Popover.js +3 -3
  112. package/dist/overlay/themeBridge.js +14 -2
  113. package/dist/pagination/index.d.ts +10 -1
  114. package/dist/pagination/index.js +13 -9
  115. package/dist/pan-zoom/index.d.ts +2 -0
  116. package/dist/pan-zoom/index.js +12 -8
  117. package/dist/pencil.d.ts +1 -0
  118. package/dist/pencil.js +4 -4
  119. package/dist/radar/index.d.ts +2 -0
  120. package/dist/radar/index.js +44 -21
  121. package/dist/range-slider/index.js +4 -3
  122. package/dist/rating/index.d.ts +4 -0
  123. package/dist/rating/index.js +131 -24
  124. package/dist/resizable/index.d.ts +1 -1
  125. package/dist/resizable/index.js +5 -2
  126. package/dist/scroll-area/index.d.ts +1 -1
  127. package/dist/scroll-area/index.js +5 -2
  128. package/dist/select/MultipleSelect.js +5 -4
  129. package/dist/select/SearchableSelect.js +64 -7
  130. package/dist/select/Select.d.ts +1 -0
  131. package/dist/select/Select.js +6 -2
  132. package/dist/select/StepperSelect.d.ts +29 -0
  133. package/dist/select/StepperSelect.js +122 -0
  134. package/dist/select/index.d.ts +2 -0
  135. package/dist/select/index.js +1 -0
  136. package/dist/select/stepperModel.d.ts +7 -0
  137. package/dist/select/stepperModel.js +29 -0
  138. package/dist/selection/Checkbox.js +4 -3
  139. package/dist/selection/RadioGroup.js +29 -13
  140. package/dist/selection/Switch.js +4 -3
  141. package/dist/selection/useNativeChecked.d.ts +2 -1
  142. package/dist/selection/useNativeChecked.js +26 -8
  143. package/dist/slider/index.js +50 -6
  144. package/dist/sortable/index.js +17 -11
  145. package/dist/sparkline/index.js +21 -6
  146. package/dist/sparkline/model.js +11 -4
  147. package/dist/steps/index.js +4 -1
  148. package/dist/styles/accordion.css +228 -0
  149. package/dist/styles/alert-dialog.css +139 -0
  150. package/dist/styles/audio-player.css +22 -0
  151. package/dist/styles/base.css +638 -0
  152. package/dist/styles/breadcrumb.css +7 -0
  153. package/dist/styles/calendar.css +62 -0
  154. package/dist/styles/carousel.css +173 -0
  155. package/dist/styles/choice-grid.css +226 -0
  156. package/dist/styles/color-picker.css +281 -0
  157. package/dist/styles/color-swatch.css +47 -0
  158. package/dist/styles/command.css +52 -0
  159. package/dist/styles/content.css +79 -0
  160. package/dist/styles/context-menu.css +35 -0
  161. package/dist/styles/data-table.css +31 -0
  162. package/dist/styles/date-picker.css +107 -0
  163. package/dist/styles/day-agenda.css +30 -0
  164. package/dist/styles/display.css +304 -0
  165. package/dist/styles/dockable-panel.css +110 -0
  166. package/dist/styles/drawer.css +133 -0
  167. package/dist/styles/dropdown-menu.css +68 -0
  168. package/dist/styles/editable.css +145 -0
  169. package/dist/styles/empty.css +108 -0
  170. package/dist/styles/flip.css +101 -0
  171. package/dist/styles/form.css +634 -0
  172. package/dist/styles/heatmap.css +256 -0
  173. package/dist/styles/hotkey.css +189 -0
  174. package/dist/styles/hover-card.css +27 -0
  175. package/dist/styles/image-cropper.css +205 -0
  176. package/dist/styles/image-viewer.css +422 -0
  177. package/dist/styles/input-number.css +21 -0
  178. package/dist/styles/journey-map.css +19 -0
  179. package/dist/styles/keypad.css +92 -0
  180. package/dist/styles/layout.css +72 -0
  181. package/dist/styles/leaflet.css +661 -0
  182. package/dist/styles/log.css +203 -0
  183. package/dist/styles/markdown.css +379 -0
  184. package/dist/styles/node-graph.css +142 -0
  185. package/dist/styles/number-ticker.css +83 -0
  186. package/dist/styles/overflow-list.css +51 -0
  187. package/dist/styles/overlay.css +284 -0
  188. package/dist/styles/pagination.css +21 -0
  189. package/dist/styles/pan-zoom.css +136 -0
  190. package/dist/styles/radar.css +1 -0
  191. package/dist/styles/rating.css +121 -0
  192. package/dist/styles/resizable.css +108 -0
  193. package/dist/styles/scroll-area.css +122 -0
  194. package/dist/styles/select.css +554 -0
  195. package/dist/styles/selection.css +264 -0
  196. package/dist/styles/slider.css +145 -0
  197. package/dist/styles/slot.css +109 -0
  198. package/dist/styles/sortable.css +159 -0
  199. package/dist/styles/sparkline.css +24 -0
  200. package/dist/styles/steps.css +27 -0
  201. package/dist/styles/tabs.css +79 -0
  202. package/dist/styles/tag-input.css +213 -0
  203. package/dist/styles/time-picker.css +66 -0
  204. package/dist/styles/timeline.css +24 -0
  205. package/dist/styles/toast.css +160 -0
  206. package/dist/styles/toggle-group.css +107 -0
  207. package/dist/styles/tooltip.css +78 -0
  208. package/dist/styles/transfer.css +148 -0
  209. package/dist/styles/tree-select.css +131 -0
  210. package/dist/styles/tree.css +59 -0
  211. package/dist/styles/typewriter.css +62 -0
  212. package/dist/styles/upload.css +60 -0
  213. package/dist/styles/virtual-list.css +112 -0
  214. package/dist/styles.css +2091 -421
  215. package/dist/tabs/index.d.ts +9 -0
  216. package/dist/tabs/index.js +69 -16
  217. package/dist/tabs/useOverflow.d.ts +20 -0
  218. package/dist/tabs/useOverflow.js +145 -0
  219. package/dist/tag-input/index.d.ts +7 -2
  220. package/dist/tag-input/index.js +81 -42
  221. package/dist/theme/index.d.ts +11 -0
  222. package/dist/theme/index.js +23 -0
  223. package/dist/time-picker/index.js +45 -27
  224. package/dist/timeline/index.js +4 -1
  225. package/dist/toast/index.d.ts +19 -11
  226. package/dist/toast/index.js +51 -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.js +3 -3
  233. package/dist/transfer/index.d.ts +5 -0
  234. package/dist/transfer/index.js +120 -53
  235. package/dist/transfer/model.d.ts +4 -0
  236. package/dist/transfer/model.js +37 -11
  237. package/dist/tree/index.d.ts +5 -1
  238. package/dist/tree/index.js +11 -6
  239. package/dist/tree-select/index.js +70 -31
  240. package/dist/tree-select/model.d.ts +11 -5
  241. package/dist/tree-select/model.js +111 -83
  242. package/dist/typewriter/index.js +10 -10
  243. package/dist/upload/index.js +50 -33
  244. package/dist/virtual-list/index.js +11 -6
  245. package/package.json +423 -3
@@ -0,0 +1,177 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
3
+ import ReactMarkdown from 'react-markdown';
4
+ import remarkGfm from 'remark-gfm';
5
+ import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useMemo, } from 'react';
6
+ import { PencilIcon } from '../PencilIcon.js';
7
+ import { PencilLine } from '../PencilLine.js';
8
+ import { PencilSurface } from '../PencilSurface.js';
9
+ import { PencilText } from '../PencilText.js';
10
+ import { Table } from '../content/index.js';
11
+ import { Checkbox } from '../selection/Checkbox.js';
12
+ import { MarkdownCodeBlock } from './CodeBlock.js';
13
+ import { Deletion } from './Deletion.js';
14
+ const listContext = createContext('unordered');
15
+ const codeContext = createContext('inline');
16
+ const bulletPaths = [{ d: 'M12 4.5a7.5 7.5 0 1 0 0 15a7.5 7.5 0 1 0 0-15' }];
17
+ const markdownPlugins = [remarkGfm];
18
+ function classes(...values) {
19
+ return values.filter(Boolean).join(' ');
20
+ }
21
+ function pencilChildren(children, seed) {
22
+ return Children.map(children, (child, index) => {
23
+ if (typeof child !== 'string' && typeof child !== 'number')
24
+ return child;
25
+ const value = String(child);
26
+ if (!/\S/.test(value))
27
+ return value;
28
+ const [, leading, text, trailing] = /^(\s*)([\s\S]*?)(\s*)$/.exec(value);
29
+ return _jsxs(_Fragment, { children: [leading, _jsx(PencilText, { seed: `${seed}-${index}`, children: text }), trailing] });
30
+ });
31
+ }
32
+ function taskContent(children) {
33
+ let checkbox;
34
+ const extract = (content) => Children.map(content, child => {
35
+ if (!isValidElement(child))
36
+ return child;
37
+ if (!checkbox && child.props.node?.tagName === 'input' && child.props.type === 'checkbox') {
38
+ checkbox = child;
39
+ return null;
40
+ }
41
+ if (!checkbox && child.props.node?.tagName === 'p')
42
+ return cloneElement(child, {}, extract(child.props.children));
43
+ return child;
44
+ });
45
+ const content = extract(children);
46
+ return { checkbox, content };
47
+ }
48
+ function textChildren(children) {
49
+ let value = '';
50
+ Children.forEach(children, child => {
51
+ if (typeof child === 'string' || typeof child === 'number')
52
+ value += String(child);
53
+ });
54
+ return value;
55
+ }
56
+ function codeBlockContent(children) {
57
+ if (isValidElement(children)) {
58
+ return {
59
+ className: children.props.className,
60
+ code: textChildren(children.props.children),
61
+ };
62
+ }
63
+ return { className: undefined, code: textChildren(children) };
64
+ }
65
+ function safeUrl(url, kind) {
66
+ const trimmed = url.trim();
67
+ if (!trimmed)
68
+ return null;
69
+ if (/^(?:[#/?]|\.\.?\/)/.test(trimmed))
70
+ return trimmed;
71
+ const scheme = /^([a-z][a-z\d+.-]*):/i.exec(trimmed.replace(/[\u0000-\u001f\u007f]/g, ''))?.[1]?.toLowerCase();
72
+ if (!scheme)
73
+ return trimmed;
74
+ if (scheme === 'http' || scheme === 'https')
75
+ return trimmed;
76
+ if (kind === 'link' && (scheme === 'mailto' || scheme === 'tel'))
77
+ return trimmed;
78
+ return null;
79
+ }
80
+ function headingTag(level, offset) {
81
+ return `h${Math.max(1, Math.min(6, level + offset))}`;
82
+ }
83
+ function MarkdownTaskCheckbox({ checked = false, className, label, ...props }) {
84
+ const selected = Boolean(checked);
85
+ 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'}` });
86
+ }
87
+ function createMarkdownComponents(headingLevelOffset, labels) {
88
+ const heading = (level) => {
89
+ const Heading = headingTag(level, headingLevelOffset);
90
+ return ({ node: _node, children, className, ...props }) => (_jsx(Heading, { ...props, className: classes('inkra-markdown__heading', className), children: pencilChildren(children, `markdown-heading-${level}`) }));
91
+ };
92
+ return {
93
+ h1: heading(1),
94
+ h2: heading(2),
95
+ h3: heading(3),
96
+ h4: heading(4),
97
+ h5: heading(5),
98
+ h6: heading(6),
99
+ p: ({ node: _node, children, className, ...props }) => (_jsx("p", { ...props, className: classes('inkra-markdown__paragraph', className), children: pencilChildren(children, 'markdown-paragraph') })),
100
+ strong: ({ node: _node, children, ...props }) => _jsx("strong", { ...props, children: pencilChildren(children, 'markdown-strong') }),
101
+ em: ({ node: _node, children, ...props }) => _jsx("em", { ...props, children: pencilChildren(children, 'markdown-emphasis') }),
102
+ del: ({ node: _node, children, ...props }) => _jsx(Deletion, { ...props, children: pencilChildren(children, 'markdown-deleted') }),
103
+ 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') }),
104
+ 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 })] })),
105
+ hr: ({ node: _node, className, ...props }) => (_jsx("div", { ...props, className: classes('inkra-markdown__divider', className), role: "separator", children: _jsx(PencilLine, { seed: "markdown-divider" }) })),
106
+ pre: ({ node: _node, children, ...props }) => {
107
+ const block = codeBlockContent(children);
108
+ return _jsx(codeContext.Provider, { value: "block", children: _jsx(MarkdownCodeBlock, { ...props, "aria-label": labels.code, labels: labels, code: block.code, codeClassName: block.className }) });
109
+ },
110
+ code: ({ node: _node, children, className, ...props }) => {
111
+ const kind = useContext(codeContext);
112
+ const value = textChildren(children);
113
+ return _jsx("code", { ...props, className: classes('inkra-markdown__code', className), "data-inline": kind === 'inline' || undefined, children: kind === 'inline'
114
+ ? _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 })] })
115
+ : children });
116
+ },
117
+ 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 }) })),
118
+ ol: ({ node: _node, children, className, start, style, ...props }) => {
119
+ const first = typeof start === 'number' && Number.isFinite(start) ? Math.trunc(start) : 1;
120
+ let itemIndex = 0;
121
+ const numberedChildren = Children.map(children, child => {
122
+ if (!isValidElement(child))
123
+ return child;
124
+ const marker = first + itemIndex;
125
+ itemIndex += 1;
126
+ return cloneElement(child, { 'data-inkra-ordered-index': marker });
127
+ });
128
+ const last = first + Math.max(0, itemIndex - 1);
129
+ const markerCharacters = Math.max(`${first}.`.length, `${last}.`.length);
130
+ const listStyle = {
131
+ ...style,
132
+ '--inkra-markdown-ordered-marker-width': `max(1.75rem, ${markerCharacters}ch)`,
133
+ };
134
+ 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 }) });
135
+ },
136
+ li: ({ node: _node, children, className, 'data-inkra-ordered-index': orderedIndex, ...props }) => {
137
+ const kind = useContext(listContext);
138
+ const task = className?.split(/\s+/).includes('task-list-item');
139
+ const extracted = task ? taskContent(children) : undefined;
140
+ 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
141
+ ? _jsxs("div", { className: "inkra-markdown__task-row", children: [extracted.checkbox, _jsx("div", { className: "inkra-markdown__task-content", children: pencilChildren(extracted.content, 'markdown-list-item') })] })
142
+ : pencilChildren(children, 'markdown-list-item') })] });
143
+ },
144
+ input: ({ node: _node, checked, type, ...props }) => type === 'checkbox'
145
+ ? _jsx(MarkdownTaskCheckbox, { ...props, checked: checked, label: checked ? labels.taskChecked : labels.taskUnchecked })
146
+ : _jsx("input", { ...props, checked: checked, type: type }),
147
+ 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" })] })),
148
+ thead: ({ node: _node, ...props }) => _jsx(Table.Header, { ...props }),
149
+ tbody: ({ node: _node, ...props }) => _jsx(Table.Body, { ...props }),
150
+ tr: ({ node: _node, ...props }) => _jsx(Table.Row, { ...props }),
151
+ th: ({ node: _node, children, align, ...props }) => _jsx(Table.Head, { ...props, align: align, children: pencilChildren(children, 'markdown-table-heading') }),
152
+ td: ({ node: _node, children, align, ...props }) => _jsx(Table.Cell, { ...props, align: align, verticalAlign: "top", children: pencilChildren(children, 'markdown-table-cell') }),
153
+ 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') }),
154
+ };
155
+ }
156
+ export const Markdown = forwardRef(function Markdown({ children, className, components, headingLevelOffset = 0, labels, transformUrl, ...props }, ref) {
157
+ const { t } = useInkraLocale();
158
+ const offset = Number.isFinite(headingLevelOffset) ? Math.trunc(headingLevelOffset) : 0;
159
+ const resolvedLabels = useMemo(() => ({
160
+ code: labels?.code ?? t('markdown.code'),
161
+ plainText: labels?.plainText ?? t('markdown.plain'),
162
+ copy: labels?.copy ?? t('markdown.copy'),
163
+ copied: labels?.copied ?? t('markdown.copied'),
164
+ copyFailed: labels?.copyFailed ?? t('markdown.copyFailed'),
165
+ table: labels?.table ?? t('markdown.table'),
166
+ taskChecked: labels?.taskChecked ?? t('markdown.checked'),
167
+ taskUnchecked: labels?.taskUnchecked ?? t('markdown.unchecked'),
168
+ }), [t, labels?.code, labels?.plainText, labels?.copy, labels?.copied, labels?.copyFailed, labels?.table, labels?.taskChecked, labels?.taskUnchecked]);
169
+ const builtIns = useMemo(() => createMarkdownComponents(offset, resolvedLabels), [offset, resolvedLabels]);
170
+ const resolvedComponents = useMemo(() => ({ ...builtIns, ...components }), [builtIns, components]);
171
+ const urlTransform = useCallback((url, key) => {
172
+ const kind = key === 'src' ? 'image' : 'link';
173
+ const transformed = transformUrl ? transformUrl(url, kind) : url;
174
+ return transformed == null ? null : safeUrl(transformed, kind);
175
+ }, [transformUrl]);
176
+ return _jsx("div", { ...props, ref: ref, className: classes('inkra-markdown', className), children: _jsx(ReactMarkdown, { components: resolvedComponents, remarkPlugins: markdownPlugins, skipHtml: true, urlTransform: urlTransform, children: children }) });
177
+ });
@@ -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;
@@ -109,7 +111,7 @@ export interface NodeGraphControlsProps extends HTMLAttributes<HTMLDivElement> {
109
111
  zoomOutLabel?: string;
110
112
  fitViewLabel?: string;
111
113
  }
112
- declare function NodeGraphControls({ className, fitViewOptions, zoomInLabel, zoomOutLabel, fitViewLabel, ...props }: NodeGraphControlsProps): import("react").JSX.Element;
114
+ declare function NodeGraphControls(inkraProps: NodeGraphControlsProps): import("react").JSX.Element;
113
115
  export declare function applyNodeGraphNodeChanges<NodeType extends NodeGraphNode>(changes: NodeGraphNodeChange<NodeType>[], nodes: NodeType[]): NodeType[];
114
116
  export declare function applyNodeGraphEdgeChanges<EdgeType extends NodeGraphEdge>(changes: NodeGraphEdgeChange<EdgeType>[], edges: EdgeType[]): EdgeType[];
115
117
  export declare function addNodeGraphEdge<EdgeType extends NodeGraphEdge>(connection: NodeGraphConnection | EdgeType, edges: EdgeType[]): EdgeType[];
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { EdgeLabelRenderer, Handle, Panel, Position, ReactFlow, ReactFlowProvider, addEdge as addFlowEdge, applyEdgeChanges as applyFlowEdgeChanges, applyNodeChanges as applyFlowNodeChanges, getBezierPath, useReactFlow, useViewport, } from '@xyflow/react';
3
4
  import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from 'react';
4
5
  import { PencilIcon } from '../PencilIcon.js';
@@ -132,9 +133,6 @@ function PencilEdgeCanvas({ id, path, className, selected = false }) {
132
133
  if (disposed)
133
134
  return;
134
135
  try {
135
- const points = sampleSvgPath(pathElement, bounds);
136
- if (points.length < 2)
137
- throw new Error('NodeGraph edge has no drawable path');
138
136
  const style = getComputedStyle(canvas);
139
137
  const width = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
140
138
  const roughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
@@ -150,6 +148,9 @@ function PencilEdgeCanvas({ id, path, className, selected = false }) {
150
148
  image = undefined;
151
149
  }
152
150
  if (!image) {
151
+ const points = sampleSvgPath(pathElement, bounds);
152
+ if (points.length < 2)
153
+ throw new Error('NodeGraph edge has no drawable path');
153
154
  image = document.createElement('canvas');
154
155
  image.width = budget.width;
155
156
  image.height = budget.height;
@@ -238,7 +239,9 @@ function NodeGraphConnectionLine({ fromX, fromY, toX, toY, fromPosition, toPosit
238
239
  });
239
240
  return _jsx(PencilEdgeCanvas, { id: "connection-preview", path: path, className: "inkra-node-graph__connection-line" });
240
241
  }
241
- function NodeGraphControls({ className, fitViewOptions, zoomInLabel = 'Zoom in', zoomOutLabel = 'Zoom out', fitViewLabel = 'Fit graph to view', ...props }) {
242
+ function NodeGraphControls(inkraProps) {
243
+ const { t } = useInkraLocale();
244
+ const { className, fitViewOptions, zoomInLabel = t('common.zoomIn'), zoomOutLabel = t('common.zoomOut'), fitViewLabel = t('graph.fit'), ...props } = inkraProps;
242
245
  const flow = useReactFlow();
243
246
  const actions = [
244
247
  { label: zoomInLabel, paths: zoomInPaths, run: () => flow.zoomIn() },
@@ -250,7 +253,9 @@ function NodeGraphControls({ className, fitViewOptions, zoomInLabel = 'Zoom in',
250
253
  function SsrFallback({ nodes, emptyState, label, }) {
251
254
  return _jsx("div", { className: "inkra-node-graph__ssr", role: "group", "aria-label": label, children: nodes.length === 0 ? _jsx("div", { className: "inkra-node-graph__empty", children: emptyState }) : _jsx("ul", { children: nodes.map(node => _jsx("li", { children: readableLabel(node.data.label, node.id) }, node.id)) }) });
252
255
  }
253
- function NodeGraphRootInner({ nodes, edges, onNodesChange, onEdgesChange, onConnect, isValidConnection, renderNode, renderEdge, renderEdgeLabel, readOnly = false, emptyState = 'No nodes yet', controls = true, controlLabels, fitView = false, fitViewOptions, defaultViewport = { x: 0, y: 0, zoom: 1 }, viewport, onViewportChange, minZoom = 0.25, maxZoom = 2, panOnDrag = true, zoomOnScroll = true, zoomOnPinch = true, connectOnClick = true, onlyRenderVisibleElements = false, className, style, 'aria-label': ariaLabel = 'Node graph', ...props }, forwardedRef) {
256
+ function NodeGraphRootInner(inkraProps, forwardedRef) {
257
+ const { t } = useInkraLocale();
258
+ const { nodes, edges, onNodesChange, onEdgesChange, onConnect, isValidConnection, renderNode, renderEdge, renderEdgeLabel, readOnly = false, emptyState = t('graph.empty'), controls = true, controlLabels, fitView = false, fitViewOptions, defaultViewport = { x: 0, y: 0, zoom: 1 }, viewport, onViewportChange, minZoom = 0.25, maxZoom = 2, panOnDrag = true, zoomOnScroll = true, zoomOnPinch = true, connectOnClick = true, onlyRenderVisibleElements = false, className, style, 'aria-label': ariaLabel = t('graph.label'), ...props } = inkraProps;
254
259
  const [mounted, setMounted] = useState(false);
255
260
  const instanceRef = useRef(null);
256
261
  useEffect(() => setMounted(true), []);
@@ -260,30 +265,60 @@ function NodeGraphRootInner({ nodes, edges, onNodesChange, onEdgesChange, onConn
260
265
  zoomOut: () => instanceRef.current?.zoomOut() ?? Promise.resolve(false),
261
266
  getViewport: () => instanceRef.current?.getViewport() ?? defaultViewport,
262
267
  }), [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]);
268
+ // Immutable node/edge updates should invalidate only the changed records. Weak
269
+ // keys release removed records and changing render configuration resets the cache.
270
+ const adaptNode = useMemo(() => {
271
+ const cache = new WeakMap();
272
+ return (node) => {
273
+ const cached = cache.get(node);
274
+ if (cached)
275
+ return cached;
276
+ const adapted = {
277
+ ...node,
278
+ type: 'inkra',
279
+ ariaLabel: node.ariaLabel ?? readableLabel(node.data.label, node.id),
280
+ data: {
281
+ __inkraRenderNode: renderNode,
282
+ __inkraReadOnly: readOnly,
283
+ __inkraData: node.data,
284
+ },
285
+ };
286
+ cache.set(node, adapted);
287
+ return adapted;
288
+ };
289
+ }, [readOnly, renderNode]);
290
+ const adaptEdge = useMemo(() => {
291
+ const cache = new WeakMap();
292
+ return (edge) => {
293
+ const cached = cache.get(edge);
294
+ if (cached)
295
+ return cached;
296
+ const adapted = {
297
+ ...edge,
298
+ type: 'inkra',
299
+ ariaLabel: edge.ariaLabel ?? `Edge from ${edge.source} to ${edge.target}`,
300
+ label: undefined,
301
+ data: {
302
+ ...edge.data,
303
+ __inkraEdge: edge,
304
+ __inkraRenderEdge: renderEdge,
305
+ __inkraRenderEdgeLabel: renderEdgeLabel,
306
+ },
307
+ };
308
+ cache.set(edge, adapted);
309
+ return adapted;
310
+ };
311
+ }, [renderEdge, renderEdgeLabel]);
312
+ const internalNodes = useMemo(() => nodes.map(adaptNode), [nodes, adaptNode]);
313
+ const internalEdges = useMemo(() => edges.map(adaptEdge), [edges, adaptEdge]);
285
314
  const rootStyle = { minHeight: 320, ...style };
286
- 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 })] }) });
315
+ 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, ariaLabelConfig: {
316
+ 'node.a11yDescription.default': t('graph.nodeInstructions'),
317
+ 'node.a11yDescription.keyboardDisabled': t('graph.nodeMoveInstructions'),
318
+ 'node.a11yDescription.ariaLiveMessage': ({ x, y }) => t('graph.nodeMoved', { x, y }),
319
+ 'edge.a11yDescription.default': t('graph.edgeInstructions'),
320
+ 'handle.ariaLabel': t('graph.handle'),
321
+ }, 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
322
  }
288
323
  const NodeGraphRoot = forwardRef(NodeGraphRootInner);
289
324
  export function applyNodeGraphNodeChanges(changes, nodes) {
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
3
4
  import { PencilText } from '../PencilText.js';
4
5
  import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
@@ -23,7 +24,9 @@ function lettering(content, seed) {
23
24
  function accessibleAffix(content) {
24
25
  return typeof content === 'string' || typeof content === 'number' ? String(content) : '';
25
26
  }
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) {
27
+ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, className, compact = false, compactOptions, deltaDuration = 1600, deltaFormatOptions, duration = 420, easing = easeOutCubic, formatOptions, getValueLabel, locale: localeProp, prefix, renderDelta, renderValue, showDelta = false, suffix, value, ...props }, forwardedRef) {
28
+ const { locale: inheritedLocale } = useInkraLocale();
29
+ const locale = localeProp ?? inheritedLocale;
27
30
  const rootRef = useRef(null);
28
31
  const frameRef = useRef(null);
29
32
  const deltaTimerRef = useRef(null);
@@ -46,6 +46,14 @@ function roundedDecimal(value, options) {
46
46
  : options.minimumFractionDigits ?? 0;
47
47
  return { value: rounded, decimals };
48
48
  }
49
+ let exactDecimalStringsSupported;
50
+ function supportsExactDecimalStrings() {
51
+ if (exactDecimalStringsSupported === undefined) {
52
+ const probe = new Intl.NumberFormat('en-US', { useGrouping: false });
53
+ exactDecimalStringsSupported = probe.format('9007199254740993') === '9007199254740993';
54
+ }
55
+ return exactDecimalStringsSupported;
56
+ }
49
57
  // Intl accepts exact decimal strings in modern engines, but some engines still
50
58
  // turn strings beyond Number.MAX_VALUE into Infinity. BigInt bypasses that path.
51
59
  export function formatExactParts(formatter, value) {
@@ -60,8 +68,7 @@ export function formatExactParts(formatter, value) {
60
68
  return formatter.formatToParts(BigInt(decimal.toFixed(0)));
61
69
  if (decimal.e < 308) {
62
70
  const exactFormat = formatter.formatToParts;
63
- const probe = new Intl.NumberFormat('en-US', { useGrouping: false });
64
- if (probe.format('9007199254740993') !== '9007199254740993') {
71
+ if (!supportsExactDecimalStrings()) {
65
72
  throw new RangeError('NumberTicker decimal strings require exact Intl.NumberFormat string support');
66
73
  }
67
74
  return exactFormat.call(formatter, decimal.toString());
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState, } from 'react';
3
4
  import { Button } from '../Button.js';
4
5
  import { DropdownMenu } from '../dropdown-menu/index.js';
@@ -27,10 +28,9 @@ function assignRef(ref, value) {
27
28
  else if (ref)
28
29
  ref.current = value;
29
30
  }
30
- function defaultOverflowLabel(count) {
31
- return `More items (${count})`;
32
- }
33
- function OverflowListInner({ 'aria-label': ariaLabel, className, gap, getOverflowItemIcon, items, measurementKey = '', onOverflowChange, onOverflowItemSelect, overflowButtonProps, overflowLabel = defaultOverflowLabel, renderItem, renderOverflow, renderOverflowItem, role, strategy = 'end', style, ...props }, forwardedRef) {
31
+ function OverflowListInner({ 'aria-label': ariaLabel, className, gap, getOverflowItemIcon, items, measurementKey = '', onOverflowChange, onOverflowItemSelect, overflowButtonProps, overflowLabel: overflowLabelProp, renderItem, renderOverflow, renderOverflowItem, role, strategy = 'end', style, ...props }, forwardedRef) {
32
+ const { t } = useInkraLocale();
33
+ const overflowLabel = overflowLabelProp ?? ((count) => t('overflow.more', { count }));
34
34
  const generatedId = useId();
35
35
  const rootRef = useRef(null);
36
36
  const triggerRef = useRef(null);
@@ -157,6 +157,9 @@ function OverflowListInner({ 'aria-label': ariaLabel, className, gap, getOverflo
157
157
  widthCache.current.clear();
158
158
  setLayout({ signature, visibleKeys: new Set(allKeys), needsOverflow: false, measuring: true });
159
159
  }, [allKeys, layout.signature, signature]);
160
+ // Keep observer targets in sync when a resize reveals or hides items without
161
+ // changing whether an overflow trigger is needed. Memoized visibleEntries
162
+ // leaves the observer intact during unrelated parent updates.
160
163
  useEffect(() => {
161
164
  const root = rootRef.current;
162
165
  if (!root)
@@ -177,7 +180,7 @@ function OverflowListInner({ 'aria-label': ariaLabel, className, gap, getOverflo
177
180
  frameRef.current = null;
178
181
  }
179
182
  };
180
- }, [effectiveLayout.measuring, effectiveLayout.needsOverflow, scheduleLayout, signature]);
183
+ }, [effectiveLayout.measuring, effectiveLayout.needsOverflow, scheduleLayout, signature, visibleEntries]);
181
184
  useEffect(() => {
182
185
  const fonts = typeof document === 'undefined' ? undefined : document.fonts;
183
186
  if (!fonts?.addEventListener)
@@ -198,7 +201,7 @@ function OverflowListInner({ 'aria-label': ariaLabel, className, gap, getOverflo
198
201
  useEffect(() => {
199
202
  if (effectiveLayout.measuring)
200
203
  return;
201
- const changeKey = `${signature}:${visibleEntries.map(entry => entry.key).join('|')}`;
204
+ const changeKey = JSON.stringify([signature, visibleEntries.map(entry => entry.key)]);
202
205
  if (lastChangeRef.current === changeKey)
203
206
  return;
204
207
  lastChangeRef.current = changeKey;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import * as DialogPrimitive from '@radix-ui/react-dialog';
3
4
  import { forwardRef, useId } from 'react';
4
5
  import { __iconData as closeIcon } from 'lucide-react/dist/esm/icons/x.mjs';
@@ -43,7 +44,9 @@ export const OverlayClose = forwardRef(function OverlayClose(props, ref) {
43
44
  return _jsx(DialogPrimitive.Close, { ...props, ref: ref });
44
45
  });
45
46
  const closePaths = closeIcon.node.map(([, attributes]) => ({ d: attributes.d }));
46
- export const OverlayCloseButton = forwardRef(function OverlayCloseButton({ label = 'Close', className, onFocus, ...props }, ref) {
47
+ export const OverlayCloseButton = forwardRef(function OverlayCloseButton(inkraProps, ref) {
48
+ const { t } = useInkraLocale();
49
+ const { label = t('common.close'), className, onFocus, ...props } = inkraProps;
47
50
  return _jsx(Tooltip.Provider, { children: _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(DialogPrimitive.Close, { asChild: true, children: _jsx(Button, { ...props, ref: ref, type: "button", size: "icon", variant: "ghost", className: classes('inkra-overlay__close-button', className), "aria-label": label, onFocus: event => {
48
51
  onFocus?.(event);
49
52
  const dialog = event.currentTarget.closest('[role="dialog"]');
@@ -52,7 +55,9 @@ export const OverlayCloseButton = forwardRef(function OverlayCloseButton({ label
52
55
  event.preventDefault();
53
56
  }, children: _jsx(PencilIcon, { paths: closePaths, size: 30, sourceSize: 24, decorative: true }) }) }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Content, { side: "bottom", align: "end", children: label }) })] }) });
54
57
  });
55
- export const OverlayHeader = forwardRef(function OverlayHeader({ children, className, divided = true, showClose = true, closeLabel = 'Close', action, ...props }, ref) {
58
+ export const OverlayHeader = forwardRef(function OverlayHeader(inkraProps, ref) {
59
+ const { t } = useInkraLocale();
60
+ const { children, className, divided = true, showClose = true, closeLabel = t('common.close'), action, ...props } = inkraProps;
56
61
  const id = useId();
57
62
  return _jsxs("div", { ...props, ref: ref, className: classes('inkra-overlay__header', className), "data-divided": divided || undefined, children: [_jsx("div", { className: "inkra-overlay__heading", children: children }), (action || showClose) && _jsxs("div", { className: "inkra-overlay__header-actions", children: [action, showClose && _jsx(OverlayCloseButton, { label: closeLabel })] }), divided && _jsx(PencilLine, { className: "inkra-overlay__section-divider", seed: `overlay-header-${id}` })] });
58
63
  });
@@ -63,7 +68,7 @@ export const OverlayFooter = forwardRef(function OverlayFooter({ children, class
63
68
  const id = useId();
64
69
  return _jsxs("div", { ...props, ref: ref, className: classes('inkra-overlay__footer', className), "data-divided": divided || undefined, "data-align": align, children: [divided && _jsx(PencilLine, { className: "inkra-overlay__section-divider", seed: `overlay-footer-${id}` }), children] });
65
70
  });
66
- export const Overlay = Object.freeze({
71
+ export const Overlay = /* @__PURE__ */ Object.freeze({
67
72
  Root: OverlayRoot,
68
73
  Trigger: OverlayTrigger,
69
74
  Portal: OverlayPortal,
@@ -18,10 +18,10 @@ export const PopoverTrigger = forwardRef(function PopoverTrigger(props, forwarde
18
18
  export function PopoverPortal({ children, ...props }) {
19
19
  return mapPortalChildren(children, child => _jsx(PopoverPrimitive.Portal, { ...props, children: child }));
20
20
  }
21
- export const PopoverContent = forwardRef(function PopoverContent({ arrowProps, children, className, seed, showArrow = false, surfaceClassName, surfaceRadius = 8, surfaceVariant = 'paper', ...props }, ref) {
21
+ export const PopoverContent = forwardRef(function PopoverContent({ arrowProps, children, className, collisionPadding = 12, seed, showArrow = false, surfaceClassName, surfaceRadius = 8, surfaceVariant = 'paper', ...props }, ref) {
22
22
  const id = useId();
23
23
  props = usePortalThemeProps(props);
24
- return (_jsxs(PopoverPrimitive.Content, { ...props, className: classes('inkra-popover__content', className), ref: ref, children: [_jsx(PencilSurface, { className: classes('inkra-popover__surface', surfaceClassName), radius: surfaceRadius, seed: seed ?? `inkra-popover-${id}`, variant: surfaceVariant, children: _jsx("div", { className: "inkra-popover__content-body", children: children }) }), showArrow ? (_jsx(PopoverPrimitive.Arrow, { ...arrowProps, className: classes('inkra-popover__arrow', arrowProps?.className) })) : null] }));
24
+ return (_jsxs(PopoverPrimitive.Content, { ...props, className: classes('inkra-popover__content', className), collisionPadding: collisionPadding, ref: ref, children: [_jsx(PencilSurface, { className: classes('inkra-popover__surface', surfaceClassName), radius: surfaceRadius, seed: seed ?? `inkra-popover-${id}`, variant: surfaceVariant, children: _jsx("div", { className: "inkra-popover__content-body", children: children }) }), showArrow ? (_jsx(PopoverPrimitive.Arrow, { ...arrowProps, className: classes('inkra-popover__arrow', arrowProps?.className) })) : null] }));
25
25
  });
26
26
  export const PopoverTitle = forwardRef(function PopoverTitle({ children, className, seed, ...props }, ref) {
27
27
  const id = useId();
@@ -34,7 +34,7 @@ export const PopoverDescription = forwardRef(function PopoverDescription({ child
34
34
  export const PopoverClose = forwardRef(function PopoverClose(props, ref) {
35
35
  return _jsx(PopoverPrimitive.Close, { ...props, ref: ref });
36
36
  });
37
- export const Popover = Object.freeze({
37
+ export const Popover = /* @__PURE__ */ Object.freeze({
38
38
  Root: PopoverRoot,
39
39
  Anchor: PopoverAnchor,
40
40
  Trigger: PopoverTrigger,
@@ -45,6 +45,14 @@ function readThemeSnapshot(node, forcedColors = isForcedColorsActive()) {
45
45
  style,
46
46
  };
47
47
  }
48
+ function sameThemeSnapshot(left, right) {
49
+ if (left.theme !== right.theme || left.inkraTheme !== right.inkraTheme
50
+ || left.direction !== right.direction || left.language !== right.language)
51
+ return false;
52
+ const entries = Object.entries(left.style);
53
+ return entries.length === Object.keys(right.style).length
54
+ && entries.every(([property, value]) => right.style[property] === value);
55
+ }
48
56
  export function PortalThemeProvider({ children }) {
49
57
  const parentDepth = useContext(PortalLayerDepthContext);
50
58
  const depth = parentDepth + 1;
@@ -52,12 +60,16 @@ export function PortalThemeProvider({ children }) {
52
60
  const [snapshot, setSnapshot] = useState(emptySnapshot);
53
61
  const captureTrigger = useCallback((node) => {
54
62
  setTrigger(node);
55
- setSnapshot(readThemeSnapshot(node));
63
+ const next = readThemeSnapshot(node);
64
+ setSnapshot(current => sameThemeSnapshot(current, next) ? current : next);
56
65
  }, []);
57
66
  useEffect(() => {
58
67
  if (!trigger || typeof MutationObserver === 'undefined')
59
68
  return undefined;
60
- const refresh = () => setSnapshot(readThemeSnapshot(trigger));
69
+ const refresh = () => {
70
+ const next = readThemeSnapshot(trigger);
71
+ setSnapshot(current => sameThemeSnapshot(current, next) ? current : next);
72
+ };
61
73
  const observer = new MutationObserver(refresh);
62
74
  for (let target = trigger; target; target = target.parentElement) {
63
75
  observer.observe(target, {
@@ -1,4 +1,4 @@
1
- import type { HTMLAttributes } from 'react';
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
2
  export interface PaginationLabels {
3
3
  nav: string;
4
4
  first: string;
@@ -16,6 +16,15 @@ export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'chil
16
16
  siblingCount?: number;
17
17
  boundaryCount?: number;
18
18
  showFirstLast?: boolean;
19
+ showPreviousNext?: boolean;
20
+ hideOnSinglePage?: boolean;
21
+ simple?: boolean;
22
+ /** Non-interactive content inside each numbered page button. */
23
+ renderPage?: (page: number, state: {
24
+ isCurrent: boolean;
25
+ }) => ReactNode;
26
+ /** Non-interactive current/total content in simple mode. */
27
+ renderSummary?: (page: number, totalPages: number) => ReactNode;
19
28
  disabled?: boolean;
20
29
  size?: 'sm' | 'md' | 'lg';
21
30
  labels?: Partial<PaginationLabels>;
@@ -1,19 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { forwardRef, useEffect, useState } from 'react';
3
4
  import { Button } from '../Button.js';
4
5
  import { PencilIcon } from '../PencilIcon.js';
5
6
  import { PencilSurface } from '../PencilSurface.js';
6
7
  import { PencilText } from '../PencilText.js';
7
8
  import { clampPage, getPaginationEntries, normalizeTotal } from './model.js';
8
- const defaultLabels = {
9
- nav: 'Pagination', first: 'First page', previous: 'Previous page', next: 'Next page', last: 'Last page',
10
- page: page => `Page ${page}`, ellipsis: 'More pages',
11
- };
12
- export const Pagination = forwardRef(function Pagination({ totalPages, page, defaultPage = 1, onPageChange, siblingCount = 1, boundaryCount = 1, showFirstLast = false, disabled = false, size = 'md', labels, className, ...props }, ref) {
9
+ const defaultLabels = (t) => ({
10
+ nav: t('pagination.nav'), first: t('pagination.first'), previous: t('common.previousPage'), next: t('common.nextPage'), last: t('pagination.last'),
11
+ page: page => t('pagination.page', { page }), ellipsis: t('pagination.more'),
12
+ });
13
+ export const Pagination = forwardRef(function Pagination({ totalPages, page, defaultPage = 1, onPageChange, siblingCount = 1, boundaryCount = 1, showFirstLast = false, showPreviousNext = true, hideOnSinglePage = false, simple = false, renderPage, renderSummary, disabled = false, size = 'md', labels, className, ...props }, ref) {
14
+ const { t } = useInkraLocale();
13
15
  const total = normalizeTotal(totalPages);
14
16
  const [internalPage, setInternalPage] = useState(() => clampPage(defaultPage, total));
15
17
  const current = clampPage(page ?? internalPage, total);
16
- const text = { ...defaultLabels, ...labels };
18
+ const text = { ...defaultLabels(t), ...labels };
17
19
  useEffect(() => {
18
20
  if (page === undefined)
19
21
  setInternalPage(value => clampPage(value, total));
@@ -35,7 +37,9 @@ export const Pagination = forwardRef(function Pagination({ totalPages, page, def
35
37
  paths.push({ d: 'M19 6v12' });
36
38
  return _jsx("li", { children: _jsx(Button, { variant: "ghost", size: size === 'md' ? 'icon' : `icon-${size}`, "aria-label": text[kind], title: text[kind], disabled: disabled || unavailable, onClick: () => select(target), children: _jsx(PencilIcon, { className: "inkra-pagination__arrow", paths: paths, size: 20, sourceSize: 24 }) }) }, kind);
37
39
  };
38
- return _jsx("nav", { "aria-label": text.nav, ...props, ref: ref, className: ['inkra-pagination', className].filter(Boolean).join(' '), "data-empty": !total || undefined, children: _jsxs("ul", { className: "inkra-pagination__list", children: [showFirstLast && arrow('first', 1, current <= 1), arrow('previous', current - 1, current <= 1), getPaginationEntries(total, current, siblingCount, boundaryCount).map(entry => typeof entry === 'number'
39
- ? _jsx("li", { children: _jsxs(Button, { size: size, variant: "ghost", className: "inkra-pagination__page", "aria-label": text.page(entry), "aria-current": entry === current ? 'page' : undefined, disabled: disabled, onClick: () => select(entry), children: [entry === current && _jsx(PencilSurface, { as: "span", "aria-hidden": "true", variant: "selection", className: "inkra-pagination__selection", seed: `page-${entry}` }), _jsx(PencilText, { children: String(entry) })] }) }, entry)
40
- : _jsxs("li", { className: "inkra-pagination__ellipsis", children: [_jsx("span", { className: "inkra-pagination__sr-only", children: text.ellipsis }), _jsx(PencilText, { "aria-hidden": "true", children: "..." })] }, entry)), arrow('next', current + 1, !total || current >= total), showFirstLast && arrow('last', total, !total || current >= total)] }) });
40
+ if (hideOnSinglePage && total <= 1)
41
+ return null;
42
+ return _jsx("nav", { "aria-label": text.nav, ...props, ref: ref, className: ['inkra-pagination', className].filter(Boolean).join(' '), "data-empty": !total || undefined, "data-simple": simple || undefined, children: _jsxs("ul", { className: "inkra-pagination__list", children: [showFirstLast && arrow('first', 1, current <= 1), showPreviousNext && arrow('previous', current - 1, current <= 1), simple ? _jsx("li", { className: "inkra-pagination__summary", "aria-live": "polite", "aria-atomic": "true", children: renderSummary ? renderSummary(current, total) : _jsx(PencilText, { children: `${current} / ${total}` }) }) : getPaginationEntries(total, current, siblingCount, boundaryCount).map(entry => typeof entry === 'number'
43
+ ? _jsx("li", { className: "inkra-pagination__page-item", children: _jsxs(Button, { size: size, variant: "ghost", className: "inkra-pagination__page", "aria-label": text.page(entry), "aria-current": entry === current ? 'page' : undefined, disabled: disabled, onClick: () => select(entry), children: [entry === current && _jsx(PencilSurface, { as: "span", "aria-hidden": "true", variant: "selection", className: "inkra-pagination__selection", seed: `page-${entry}` }), renderPage ? renderPage(entry, { isCurrent: entry === current }) : _jsx(PencilText, { children: String(entry) })] }) }, entry)
44
+ : _jsxs("li", { className: "inkra-pagination__ellipsis", children: [_jsx("span", { className: "inkra-pagination__sr-only", children: text.ellipsis }), _jsx(PencilText, { "aria-hidden": "true", children: "..." })] }, entry)), showPreviousNext && arrow('next', current + 1, !total || current >= total), showFirstLast && arrow('last', total, !total || current >= total)] }) });
41
45
  });
@@ -48,6 +48,8 @@ export interface PanZoomProps extends Omit<HTMLAttributes<HTMLDivElement>, 'chil
48
48
  minScale?: number;
49
49
  maxScale?: number;
50
50
  wheel?: PanZoomWheelMode;
51
+ /** Scale increment per wheel delta unit. Must be finite and positive. */
52
+ wheelStep?: number;
51
53
  pinch?: boolean;
52
54
  pan?: boolean;
53
55
  keyboard?: boolean;