@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.
- package/README.md +15 -3
- package/dist/Button.js +3 -2
- package/dist/PencilIcon.d.ts +10 -0
- package/dist/PencilIcon.js +83 -39
- package/dist/PencilLine.js +9 -1
- package/dist/PencilText.js +15 -9
- package/dist/accordion/Accordion.js +65 -5
- package/dist/alert-dialog/index.js +14 -4
- package/dist/audio-player/index.d.ts +2 -0
- package/dist/audio-player/index.js +126 -31
- package/dist/calendar/index.d.ts +4 -1
- package/dist/calendar/index.js +31 -12
- package/dist/carousel/index.js +13 -6
- package/dist/choice-grid/index.d.ts +6 -1
- package/dist/choice-grid/index.js +61 -33
- package/dist/color-picker/GradientEditor.d.ts +39 -0
- package/dist/color-picker/GradientEditor.js +63 -0
- package/dist/color-picker/gradient.d.ts +25 -0
- package/dist/color-picker/gradient.js +60 -0
- package/dist/color-picker/index.d.ts +27 -7
- package/dist/color-picker/index.js +134 -32
- package/dist/color-swatch/index.d.ts +10 -0
- package/dist/color-swatch/index.js +9 -0
- package/dist/command/index.js +27 -4
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +12 -3
- package/dist/context-menu/index.js +1 -1
- package/dist/data-table/index.d.ts +7 -1
- package/dist/data-table/index.js +64 -16
- package/dist/data-table/model.d.ts +2 -0
- package/dist/data-table/model.js +7 -0
- package/dist/date-picker/index.d.ts +2 -0
- package/dist/date-picker/index.js +10 -9
- package/dist/day-agenda/index.d.ts +6 -1
- package/dist/day-agenda/index.js +46 -21
- package/dist/day-agenda/layout.js +65 -9
- package/dist/display/ProgressRing.js +7 -3
- package/dist/display/index.d.ts +4 -1
- package/dist/display/index.js +3 -2
- package/dist/dockable-panel/index.js +5 -5
- package/dist/drawer/index.js +4 -3
- package/dist/editable/index.d.ts +24 -2
- package/dist/editable/index.js +142 -37
- package/dist/empty/illustrations.d.ts +8 -0
- package/dist/empty/illustrations.js +78 -0
- package/dist/empty/index.d.ts +6 -1
- package/dist/empty/index.js +11 -3
- package/dist/flip/index.js +28 -8
- package/dist/form/Field.js +11 -10
- package/dist/form/Textarea.d.ts +3 -1
- package/dist/form/Textarea.js +6 -4
- package/dist/hotkey/index.d.ts +1 -0
- package/dist/hotkey/index.js +6 -4
- package/dist/hover-card/index.js +1 -1
- package/dist/image-cropper/index.js +4 -2
- package/dist/image-viewer/VideoResource.d.ts +21 -0
- package/dist/image-viewer/VideoResource.js +194 -0
- package/dist/image-viewer/index.d.ts +14 -0
- package/dist/image-viewer/index.js +118 -65
- package/dist/image-viewer/lucideGeometry.d.ts +3 -0
- package/dist/image-viewer/lucideGeometry.js +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/input-number/index.js +9 -4
- package/dist/internal/cache.d.ts +4 -0
- package/dist/internal/cache.js +18 -1
- package/dist/internal/iconGeometry.d.ts +2 -0
- package/dist/internal/iconGeometry.js +32 -0
- package/dist/internal/renderTokens.d.ts +4 -0
- package/dist/internal/renderTokens.js +4 -0
- package/dist/internal/stroke.d.ts +7 -0
- package/dist/internal/stroke.js +63 -7
- package/dist/internal/surfaceBudget.d.ts +20 -0
- package/dist/internal/surfaceBudget.js +69 -0
- package/dist/internal/surfaceRenderer.js +87 -28
- package/dist/internal/surfaceVisibility.d.ts +3 -0
- package/dist/internal/surfaceVisibility.js +60 -0
- package/dist/internal/textRenderer.d.ts +2 -0
- package/dist/internal/textRenderer.js +3 -3
- package/dist/internal/usePencilSurface.js +17 -1
- package/dist/internal/wash.js +4 -3
- package/dist/journey-map/terrain.js +10 -5
- package/dist/layout/index.js +1 -1
- package/dist/log/index.js +29 -22
- package/dist/markdown/CodeBlock.d.ts +19 -0
- package/dist/markdown/CodeBlock.js +86 -0
- package/dist/markdown/Deletion.d.ts +2 -0
- package/dist/markdown/Deletion.js +69 -0
- package/dist/markdown/icons.d.ts +14 -0
- package/dist/markdown/icons.js +16 -0
- package/dist/markdown/index.d.ts +17 -0
- package/dist/markdown/index.js +175 -0
- package/dist/node-graph/index.d.ts +2 -0
- package/dist/node-graph/index.js +49 -25
- package/dist/number-ticker/precision.js +9 -2
- package/dist/overflow-list/index.js +5 -2
- package/dist/overlay/Overlay.js +1 -1
- package/dist/overlay/Popover.js +3 -3
- package/dist/overlay/themeBridge.js +14 -2
- package/dist/pagination/index.d.ts +10 -1
- package/dist/pagination/index.js +6 -4
- package/dist/pan-zoom/index.d.ts +2 -0
- package/dist/pan-zoom/index.js +7 -6
- package/dist/pencil.d.ts +1 -0
- package/dist/pencil.js +4 -4
- package/dist/radar/index.d.ts +2 -0
- package/dist/radar/index.js +40 -20
- package/dist/rating/index.d.ts +4 -0
- package/dist/rating/index.js +128 -24
- package/dist/resizable/index.js +1 -1
- package/dist/scroll-area/index.d.ts +1 -1
- package/dist/scroll-area/index.js +5 -2
- package/dist/select/MultipleSelect.js +5 -4
- package/dist/select/SearchableSelect.js +61 -6
- package/dist/select/Select.d.ts +1 -0
- package/dist/select/Select.js +3 -1
- package/dist/select/StepperSelect.d.ts +29 -0
- package/dist/select/StepperSelect.js +119 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +1 -0
- package/dist/select/stepperModel.d.ts +7 -0
- package/dist/select/stepperModel.js +29 -0
- package/dist/selection/Checkbox.js +4 -3
- package/dist/selection/RadioGroup.js +29 -13
- package/dist/selection/Switch.js +4 -3
- package/dist/selection/useNativeChecked.d.ts +2 -1
- package/dist/selection/useNativeChecked.js +26 -8
- package/dist/slider/index.js +50 -6
- package/dist/sortable/index.js +6 -2
- package/dist/sparkline/index.js +17 -2
- package/dist/sparkline/model.js +11 -4
- package/dist/styles/accordion.css +228 -0
- package/dist/styles/alert-dialog.css +139 -0
- package/dist/styles/audio-player.css +22 -0
- package/dist/styles/base.css +638 -0
- package/dist/styles/breadcrumb.css +7 -0
- package/dist/styles/calendar.css +62 -0
- package/dist/styles/carousel.css +173 -0
- package/dist/styles/choice-grid.css +226 -0
- package/dist/styles/color-picker.css +281 -0
- package/dist/styles/color-swatch.css +47 -0
- package/dist/styles/command.css +52 -0
- package/dist/styles/content.css +79 -0
- package/dist/styles/context-menu.css +35 -0
- package/dist/styles/data-table.css +31 -0
- package/dist/styles/date-picker.css +107 -0
- package/dist/styles/day-agenda.css +30 -0
- package/dist/styles/display.css +304 -0
- package/dist/styles/dockable-panel.css +110 -0
- package/dist/styles/drawer.css +133 -0
- package/dist/styles/dropdown-menu.css +68 -0
- package/dist/styles/editable.css +145 -0
- package/dist/styles/empty.css +108 -0
- package/dist/styles/flip.css +101 -0
- package/dist/styles/form.css +634 -0
- package/dist/styles/heatmap.css +256 -0
- package/dist/styles/hotkey.css +189 -0
- package/dist/styles/hover-card.css +27 -0
- package/dist/styles/image-cropper.css +205 -0
- package/dist/styles/image-viewer.css +422 -0
- package/dist/styles/input-number.css +21 -0
- package/dist/styles/journey-map.css +19 -0
- package/dist/styles/keypad.css +92 -0
- package/dist/styles/layout.css +72 -0
- package/dist/styles/leaflet.css +661 -0
- package/dist/styles/log.css +203 -0
- package/dist/styles/markdown.css +379 -0
- package/dist/styles/node-graph.css +142 -0
- package/dist/styles/number-ticker.css +83 -0
- package/dist/styles/overflow-list.css +51 -0
- package/dist/styles/overlay.css +284 -0
- package/dist/styles/pagination.css +21 -0
- package/dist/styles/pan-zoom.css +136 -0
- package/dist/styles/radar.css +1 -0
- package/dist/styles/rating.css +121 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/scroll-area.css +122 -0
- package/dist/styles/select.css +557 -0
- package/dist/styles/selection.css +264 -0
- package/dist/styles/slider.css +145 -0
- package/dist/styles/slot.css +109 -0
- package/dist/styles/sortable.css +159 -0
- package/dist/styles/sparkline.css +24 -0
- package/dist/styles/steps.css +27 -0
- package/dist/styles/tabs.css +79 -0
- package/dist/styles/tag-input.css +213 -0
- package/dist/styles/time-picker.css +66 -0
- package/dist/styles/timeline.css +24 -0
- package/dist/styles/toast.css +160 -0
- package/dist/styles/toggle-group.css +107 -0
- package/dist/styles/tooltip.css +78 -0
- package/dist/styles/transfer.css +148 -0
- package/dist/styles/tree-select.css +131 -0
- package/dist/styles/tree.css +59 -0
- package/dist/styles/typewriter.css +62 -0
- package/dist/styles/upload.css +60 -0
- package/dist/styles/virtual-list.css +112 -0
- package/dist/styles.css +2094 -421
- package/dist/tabs/index.d.ts +9 -0
- package/dist/tabs/index.js +66 -16
- package/dist/tabs/useOverflow.d.ts +20 -0
- package/dist/tabs/useOverflow.js +145 -0
- package/dist/tag-input/index.d.ts +7 -2
- package/dist/tag-input/index.js +70 -26
- package/dist/time-picker/index.js +38 -20
- package/dist/toast/index.d.ts +19 -11
- package/dist/toast/index.js +48 -8
- package/dist/toast/manager.d.ts +42 -0
- package/dist/toast/manager.js +85 -0
- package/dist/toast/useToastNavigation.d.ts +3 -0
- package/dist/toast/useToastNavigation.js +75 -0
- package/dist/tokens.css +4 -0
- package/dist/tooltip/Tooltip.js +3 -3
- package/dist/transfer/index.d.ts +5 -0
- package/dist/transfer/index.js +93 -29
- package/dist/transfer/model.d.ts +4 -0
- package/dist/transfer/model.js +37 -11
- package/dist/tree/index.d.ts +5 -1
- package/dist/tree/index.js +5 -5
- package/dist/tree-select/index.js +67 -30
- package/dist/tree-select/model.d.ts +11 -5
- package/dist/tree-select/model.js +111 -83
- package/dist/typewriter/index.js +10 -10
- package/dist/upload/index.js +30 -16
- package/dist/virtual-list/index.js +7 -5
- 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,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,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;
|
package/dist/node-graph/index.js
CHANGED
|
@@ -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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
}
|
|
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
|
-
|
|
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 =
|
|
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;
|
package/dist/overlay/Overlay.js
CHANGED
|
@@ -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,
|
package/dist/overlay/Popover.js
CHANGED
|
@@ -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
|
-
|
|
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 = () =>
|
|
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, {
|