@inkra/react 0.3.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 (226) hide show
  1. package/README.md +15 -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 +126 -31
  11. package/dist/calendar/index.d.ts +4 -1
  12. package/dist/calendar/index.js +31 -12
  13. package/dist/carousel/index.js +13 -6
  14. package/dist/choice-grid/index.d.ts +6 -1
  15. package/dist/choice-grid/index.js +61 -33
  16. package/dist/color-picker/GradientEditor.d.ts +39 -0
  17. package/dist/color-picker/GradientEditor.js +63 -0
  18. package/dist/color-picker/gradient.d.ts +25 -0
  19. package/dist/color-picker/gradient.js +60 -0
  20. package/dist/color-picker/index.d.ts +27 -7
  21. package/dist/color-picker/index.js +134 -32
  22. package/dist/color-swatch/index.d.ts +10 -0
  23. package/dist/color-swatch/index.js +9 -0
  24. package/dist/command/index.js +27 -4
  25. package/dist/content/index.d.ts +5 -0
  26. package/dist/content/index.js +12 -3
  27. package/dist/context-menu/index.js +1 -1
  28. package/dist/data-table/index.d.ts +7 -1
  29. package/dist/data-table/index.js +64 -16
  30. package/dist/data-table/model.d.ts +2 -0
  31. package/dist/data-table/model.js +7 -0
  32. package/dist/date-picker/index.d.ts +2 -0
  33. package/dist/date-picker/index.js +10 -9
  34. package/dist/day-agenda/index.d.ts +6 -1
  35. package/dist/day-agenda/index.js +46 -21
  36. package/dist/day-agenda/layout.js +65 -9
  37. package/dist/display/ProgressRing.js +7 -3
  38. package/dist/display/index.d.ts +4 -1
  39. package/dist/display/index.js +3 -2
  40. package/dist/dockable-panel/index.js +5 -5
  41. package/dist/drawer/index.js +4 -3
  42. package/dist/editable/index.d.ts +24 -2
  43. package/dist/editable/index.js +142 -37
  44. package/dist/empty/illustrations.d.ts +8 -0
  45. package/dist/empty/illustrations.js +78 -0
  46. package/dist/empty/index.d.ts +6 -1
  47. package/dist/empty/index.js +11 -3
  48. package/dist/flip/index.js +28 -8
  49. package/dist/form/Field.js +11 -10
  50. package/dist/form/Textarea.d.ts +3 -1
  51. package/dist/form/Textarea.js +6 -4
  52. package/dist/hotkey/index.d.ts +1 -0
  53. package/dist/hotkey/index.js +6 -4
  54. package/dist/hover-card/index.js +1 -1
  55. package/dist/image-cropper/index.js +4 -2
  56. package/dist/image-viewer/VideoResource.d.ts +21 -0
  57. package/dist/image-viewer/VideoResource.js +194 -0
  58. package/dist/image-viewer/index.d.ts +14 -0
  59. package/dist/image-viewer/index.js +118 -65
  60. package/dist/image-viewer/lucideGeometry.d.ts +3 -0
  61. package/dist/image-viewer/lucideGeometry.js +19 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.js +2 -0
  64. package/dist/input-number/index.js +9 -4
  65. package/dist/internal/cache.d.ts +4 -0
  66. package/dist/internal/cache.js +18 -1
  67. package/dist/internal/iconGeometry.d.ts +2 -0
  68. package/dist/internal/iconGeometry.js +32 -0
  69. package/dist/internal/renderTokens.d.ts +4 -0
  70. package/dist/internal/renderTokens.js +4 -0
  71. package/dist/internal/stroke.d.ts +7 -0
  72. package/dist/internal/stroke.js +63 -7
  73. package/dist/internal/surfaceBudget.d.ts +20 -0
  74. package/dist/internal/surfaceBudget.js +69 -0
  75. package/dist/internal/surfaceRenderer.js +87 -28
  76. package/dist/internal/surfaceVisibility.d.ts +3 -0
  77. package/dist/internal/surfaceVisibility.js +60 -0
  78. package/dist/internal/textRenderer.d.ts +2 -0
  79. package/dist/internal/textRenderer.js +3 -3
  80. package/dist/internal/usePencilSurface.js +17 -1
  81. package/dist/internal/wash.js +4 -3
  82. package/dist/journey-map/terrain.js +10 -5
  83. package/dist/layout/index.js +1 -1
  84. package/dist/log/index.js +29 -22
  85. package/dist/markdown/CodeBlock.d.ts +19 -0
  86. package/dist/markdown/CodeBlock.js +86 -0
  87. package/dist/markdown/Deletion.d.ts +2 -0
  88. package/dist/markdown/Deletion.js +69 -0
  89. package/dist/markdown/icons.d.ts +14 -0
  90. package/dist/markdown/icons.js +16 -0
  91. package/dist/markdown/index.d.ts +17 -0
  92. package/dist/markdown/index.js +175 -0
  93. package/dist/node-graph/index.d.ts +2 -0
  94. package/dist/node-graph/index.js +49 -25
  95. package/dist/number-ticker/precision.js +9 -2
  96. package/dist/overflow-list/index.js +5 -2
  97. package/dist/overlay/Overlay.js +1 -1
  98. package/dist/overlay/Popover.js +3 -3
  99. package/dist/overlay/themeBridge.js +14 -2
  100. package/dist/pagination/index.d.ts +10 -1
  101. package/dist/pagination/index.js +6 -4
  102. package/dist/pan-zoom/index.d.ts +2 -0
  103. package/dist/pan-zoom/index.js +7 -6
  104. package/dist/pencil.d.ts +1 -0
  105. package/dist/pencil.js +4 -4
  106. package/dist/radar/index.d.ts +2 -0
  107. package/dist/radar/index.js +40 -20
  108. package/dist/rating/index.d.ts +4 -0
  109. package/dist/rating/index.js +128 -24
  110. package/dist/resizable/index.js +1 -1
  111. package/dist/scroll-area/index.d.ts +1 -1
  112. package/dist/scroll-area/index.js +5 -2
  113. package/dist/select/MultipleSelect.js +5 -4
  114. package/dist/select/SearchableSelect.js +61 -6
  115. package/dist/select/Select.d.ts +1 -0
  116. package/dist/select/Select.js +3 -1
  117. package/dist/select/StepperSelect.d.ts +29 -0
  118. package/dist/select/StepperSelect.js +119 -0
  119. package/dist/select/index.d.ts +2 -0
  120. package/dist/select/index.js +1 -0
  121. package/dist/select/stepperModel.d.ts +7 -0
  122. package/dist/select/stepperModel.js +29 -0
  123. package/dist/selection/Checkbox.js +4 -3
  124. package/dist/selection/RadioGroup.js +29 -13
  125. package/dist/selection/Switch.js +4 -3
  126. package/dist/selection/useNativeChecked.d.ts +2 -1
  127. package/dist/selection/useNativeChecked.js +26 -8
  128. package/dist/slider/index.js +50 -6
  129. package/dist/sortable/index.js +6 -2
  130. package/dist/sparkline/index.js +17 -2
  131. package/dist/sparkline/model.js +11 -4
  132. package/dist/styles/accordion.css +228 -0
  133. package/dist/styles/alert-dialog.css +139 -0
  134. package/dist/styles/audio-player.css +22 -0
  135. package/dist/styles/base.css +638 -0
  136. package/dist/styles/breadcrumb.css +7 -0
  137. package/dist/styles/calendar.css +62 -0
  138. package/dist/styles/carousel.css +173 -0
  139. package/dist/styles/choice-grid.css +226 -0
  140. package/dist/styles/color-picker.css +281 -0
  141. package/dist/styles/color-swatch.css +47 -0
  142. package/dist/styles/command.css +52 -0
  143. package/dist/styles/content.css +79 -0
  144. package/dist/styles/context-menu.css +35 -0
  145. package/dist/styles/data-table.css +31 -0
  146. package/dist/styles/date-picker.css +107 -0
  147. package/dist/styles/day-agenda.css +30 -0
  148. package/dist/styles/display.css +304 -0
  149. package/dist/styles/dockable-panel.css +110 -0
  150. package/dist/styles/drawer.css +133 -0
  151. package/dist/styles/dropdown-menu.css +68 -0
  152. package/dist/styles/editable.css +145 -0
  153. package/dist/styles/empty.css +108 -0
  154. package/dist/styles/flip.css +101 -0
  155. package/dist/styles/form.css +634 -0
  156. package/dist/styles/heatmap.css +256 -0
  157. package/dist/styles/hotkey.css +189 -0
  158. package/dist/styles/hover-card.css +27 -0
  159. package/dist/styles/image-cropper.css +205 -0
  160. package/dist/styles/image-viewer.css +422 -0
  161. package/dist/styles/input-number.css +21 -0
  162. package/dist/styles/journey-map.css +19 -0
  163. package/dist/styles/keypad.css +92 -0
  164. package/dist/styles/layout.css +72 -0
  165. package/dist/styles/leaflet.css +661 -0
  166. package/dist/styles/log.css +203 -0
  167. package/dist/styles/markdown.css +379 -0
  168. package/dist/styles/node-graph.css +142 -0
  169. package/dist/styles/number-ticker.css +83 -0
  170. package/dist/styles/overflow-list.css +51 -0
  171. package/dist/styles/overlay.css +284 -0
  172. package/dist/styles/pagination.css +21 -0
  173. package/dist/styles/pan-zoom.css +136 -0
  174. package/dist/styles/radar.css +1 -0
  175. package/dist/styles/rating.css +121 -0
  176. package/dist/styles/resizable.css +108 -0
  177. package/dist/styles/scroll-area.css +122 -0
  178. package/dist/styles/select.css +557 -0
  179. package/dist/styles/selection.css +264 -0
  180. package/dist/styles/slider.css +145 -0
  181. package/dist/styles/slot.css +109 -0
  182. package/dist/styles/sortable.css +159 -0
  183. package/dist/styles/sparkline.css +24 -0
  184. package/dist/styles/steps.css +27 -0
  185. package/dist/styles/tabs.css +79 -0
  186. package/dist/styles/tag-input.css +213 -0
  187. package/dist/styles/time-picker.css +66 -0
  188. package/dist/styles/timeline.css +24 -0
  189. package/dist/styles/toast.css +160 -0
  190. package/dist/styles/toggle-group.css +107 -0
  191. package/dist/styles/tooltip.css +78 -0
  192. package/dist/styles/transfer.css +148 -0
  193. package/dist/styles/tree-select.css +131 -0
  194. package/dist/styles/tree.css +59 -0
  195. package/dist/styles/typewriter.css +62 -0
  196. package/dist/styles/upload.css +60 -0
  197. package/dist/styles/virtual-list.css +112 -0
  198. package/dist/styles.css +2094 -421
  199. package/dist/tabs/index.d.ts +9 -0
  200. package/dist/tabs/index.js +66 -16
  201. package/dist/tabs/useOverflow.d.ts +20 -0
  202. package/dist/tabs/useOverflow.js +145 -0
  203. package/dist/tag-input/index.d.ts +7 -2
  204. package/dist/tag-input/index.js +70 -26
  205. package/dist/time-picker/index.js +38 -20
  206. package/dist/toast/index.d.ts +19 -11
  207. package/dist/toast/index.js +48 -8
  208. package/dist/toast/manager.d.ts +42 -0
  209. package/dist/toast/manager.js +85 -0
  210. package/dist/toast/useToastNavigation.d.ts +3 -0
  211. package/dist/toast/useToastNavigation.js +75 -0
  212. package/dist/tokens.css +4 -0
  213. package/dist/tooltip/Tooltip.js +3 -3
  214. package/dist/transfer/index.d.ts +5 -0
  215. package/dist/transfer/index.js +93 -29
  216. package/dist/transfer/model.d.ts +4 -0
  217. package/dist/transfer/model.js +37 -11
  218. package/dist/tree/index.d.ts +5 -1
  219. package/dist/tree/index.js +5 -5
  220. package/dist/tree-select/index.js +67 -30
  221. package/dist/tree-select/model.d.ts +11 -5
  222. package/dist/tree-select/model.js +111 -83
  223. package/dist/typewriter/index.js +10 -10
  224. package/dist/upload/index.js +30 -16
  225. package/dist/virtual-list/index.js +7 -5
  226. package/package.json +413 -3
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Prism from 'prismjs';
3
+ import 'prismjs/components/prism-jsx.js';
4
+ import 'prismjs/components/prism-typescript.js';
5
+ import 'prismjs/components/prism-tsx.js';
6
+ import 'prismjs/components/prism-bash.js';
7
+ import 'prismjs/components/prism-json.js';
8
+ import 'prismjs/components/prism-markdown.js';
9
+ import { useEffect, useMemo, useRef, useState } from 'react';
10
+ import { Button } from '../Button.js';
11
+ import { Card } from '../content/index.js';
12
+ import { PencilIcon } from '../PencilIcon.js';
13
+ import { PencilSurface } from '../PencilSurface.js';
14
+ import { PencilText } from '../PencilText.js';
15
+ import { Tooltip } from '../tooltip/Tooltip.js';
16
+ import { codeIcons } from './icons.js';
17
+ Prism.manual = true;
18
+ const languageAliases = {
19
+ js: 'javascript',
20
+ md: 'markdown',
21
+ sh: 'bash',
22
+ shell: 'bash',
23
+ ts: 'typescript',
24
+ txt: 'plain',
25
+ text: 'plain',
26
+ plaintext: 'plain',
27
+ };
28
+ const languageNames = {
29
+ javascript: 'JavaScript', typescript: 'TypeScript', jsx: 'JSX', tsx: 'TSX',
30
+ bash: 'Bash', json: 'JSON', markdown: 'Markdown', css: 'CSS', html: 'HTML',
31
+ };
32
+ function classes(...values) {
33
+ return values.filter(Boolean).join(' ');
34
+ }
35
+ function flatten(stream, inheritedClasses = '') {
36
+ const tokens = Array.isArray(stream) ? stream : [stream];
37
+ return tokens.flatMap(token => {
38
+ if (typeof token === 'string')
39
+ return [{ text: token, classes: inheritedClasses }];
40
+ const aliases = Array.isArray(token.alias) ? token.alias : token.alias ? [token.alias] : [];
41
+ const tokenClasses = classes(inheritedClasses, `token-${token.type}`, ...aliases.map(alias => `token-${alias}`));
42
+ return flatten(token.content, tokenClasses);
43
+ });
44
+ }
45
+ function languageFromClassName(className) {
46
+ return /(?:^|\s)language-([^\s]+)/.exec(className ?? '')?.[1]?.toLowerCase();
47
+ }
48
+ function tokenize(code, language) {
49
+ const normalized = languageAliases[language ?? ''] ?? language ?? 'plain';
50
+ const grammar = Prism.languages[normalized] ?? Prism.languages.plain;
51
+ return flatten(Prism.tokenize(code, grammar)).flatMap(part => part.text.split(/(\r\n|\r|\n)/).filter(Boolean).map(text => ({ ...part, text })));
52
+ }
53
+ export function MarkdownCodeBlock({ code, codeClassName, className, labels, tabIndex: _tabIndex, ...props }) {
54
+ const language = languageFromClassName(codeClassName);
55
+ const normalizedLanguage = languageAliases[language ?? ''] ?? language ?? 'plain';
56
+ const languageLabel = normalizedLanguage === 'plain' ? labels.plainText : languageNames[normalizedLanguage] ?? language;
57
+ const parts = useMemo(() => tokenize(code, language), [code, language]);
58
+ const [status, setStatus] = useState('idle');
59
+ const request = useRef(0);
60
+ const timer = useRef(undefined);
61
+ useEffect(() => {
62
+ setStatus('idle');
63
+ return () => { request.current += 1; clearTimeout(timer.current); };
64
+ }, [code]);
65
+ const copyLabel = status === 'copied' ? labels.copied : status === 'failed' ? labels.copyFailed : labels.copy;
66
+ async function copyCode() {
67
+ const id = ++request.current;
68
+ clearTimeout(timer.current);
69
+ setStatus('copying');
70
+ try {
71
+ await navigator.clipboard.writeText(code);
72
+ if (request.current !== id)
73
+ return;
74
+ setStatus('copied');
75
+ }
76
+ catch {
77
+ if (request.current !== id)
78
+ return;
79
+ setStatus('failed');
80
+ }
81
+ timer.current = setTimeout(() => setStatus('idle'), 2000);
82
+ }
83
+ return _jsxs(Card.Root, { className: "inkra-markdown__code-frame", radius: 6, seed: `markdown-code-frame-${language ?? 'plain'}`, children: [_jsx(Card.Header, { className: "inkra-markdown__code-header", align: "center", action: _jsx(Tooltip.Provider, { children: _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(Button, { size: "icon", variant: "ghost", "aria-label": copyLabel, "aria-busy": status === 'copying' || undefined, disabled: status === 'copying', onClick: () => void copyCode(), children: _jsx(PencilIcon, { paths: status === 'copied' ? codeIcons.check : codeIcons.copy, size: 22 }) }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Content, { children: copyLabel }) })] }) }), children: _jsxs("span", { className: "inkra-markdown__code-language", children: [_jsx(PencilIcon, { paths: normalizedLanguage === 'bash' ? codeIcons.terminal : codeIcons.code, size: 22 }), _jsx(PencilText, { children: languageLabel })] }) }), _jsx("pre", { ...props, className: classes('inkra-markdown__code-surface', className), "data-language": language ?? 'plain', tabIndex: 0, children: _jsx("code", { className: classes('inkra-markdown__code', codeClassName), children: parts.map((part, index) => /\S/.test(part.text)
84
+ ? _jsx(PencilText, { className: classes('inkra-markdown__code-token', part.classes), seed: `markdown-code-${language ?? 'plain'}-${index}-${part.text}`, children: part.text }, index)
85
+ : part.text) }) }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-markdown__code-focus", radius: 6, seed: "markdown-code-focus", variant: "outline" }), _jsx("span", { className: "inkra-markdown__code-status", role: "status", children: status === 'copied' ? labels.copied : status === 'failed' ? labels.copyFailed : '' })] });
86
+ }
@@ -0,0 +1,2 @@
1
+ import { type ComponentPropsWithoutRef } from 'react';
2
+ export declare function Deletion({ children, className, ...props }: ComponentPropsWithoutRef<'del'>): import("react").JSX.Element;
@@ -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
  }
@@ -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());
@@ -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;
@@ -63,7 +63,7 @@ export const OverlayFooter = forwardRef(function OverlayFooter({ children, class
63
63
  const id = useId();
64
64
  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
65
  });
66
- export const Overlay = Object.freeze({
66
+ export const Overlay = /* @__PURE__ */ Object.freeze({
67
67
  Root: OverlayRoot,
68
68
  Trigger: OverlayTrigger,
69
69
  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, {