@inkra/react 0.2.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -7
- 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.d.ts +1 -1
- package/dist/alert-dialog/index.js +18 -6
- 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.d.ts +24 -2
- package/dist/command/index.js +29 -6
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +12 -3
- package/dist/context-menu/index.d.ts +2 -2
- package/dist/context-menu/index.js +4 -3
- 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.d.ts +1 -1
- package/dist/drawer/index.js +9 -6
- package/dist/dropdown-menu/index.d.ts +1 -1
- package/dist/dropdown-menu/index.js +3 -2
- 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/EditablePencilText.js +34 -0
- package/dist/form/Field.js +11 -10
- package/dist/form/Input.js +1 -1
- 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.d.ts +6 -0
- package/dist/input-number/index.js +131 -15
- package/dist/input-number/model.d.ts +13 -0
- package/dist/input-number/model.js +50 -0
- 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/index.d.ts +11 -9
- package/dist/number-ticker/index.js +14 -11
- package/dist/number-ticker/model.d.ts +38 -5
- package/dist/number-ticker/model.js +139 -10
- package/dist/number-ticker/precision.d.ts +7 -0
- package/dist/number-ticker/precision.js +141 -0
- package/dist/overflow-list/index.js +5 -2
- package/dist/overlay/Overlay.d.ts +1 -1
- package/dist/overlay/Overlay.js +5 -3
- package/dist/overlay/Popover.d.ts +1 -1
- package/dist/overlay/Popover.js +6 -5
- package/dist/overlay/themeBridge.d.ts +12 -1
- package/dist/overlay/themeBridge.js +41 -3
- 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 +4 -0
- package/dist/radar/index.js +41 -21
- 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 +15 -6
- 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 +2241 -443
- 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.d.ts +1 -1
- package/dist/tooltip/Tooltip.js +6 -5
- 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 +10 -7
- package/package.json +416 -5
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { PencilLine } from '../PencilLine.js';
|
|
4
|
+
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
5
|
+
export function Deletion({ children, className, ...props }) {
|
|
6
|
+
const ref = useRef(null);
|
|
7
|
+
const [lines, setLines] = useState([]);
|
|
8
|
+
useBrowserLayoutEffect(() => {
|
|
9
|
+
const node = ref.current;
|
|
10
|
+
if (!node || !document.createElement('canvas').getContext('2d'))
|
|
11
|
+
return;
|
|
12
|
+
let disposed = false;
|
|
13
|
+
let frame = 0;
|
|
14
|
+
const measure = () => {
|
|
15
|
+
frame = 0;
|
|
16
|
+
if (disposed)
|
|
17
|
+
return;
|
|
18
|
+
const box = node.getBoundingClientRect();
|
|
19
|
+
const scaleX = node.offsetWidth ? box.width / node.offsetWidth : 1;
|
|
20
|
+
const scaleY = node.offsetHeight ? box.height / node.offsetHeight : 1;
|
|
21
|
+
const fragments = [];
|
|
22
|
+
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
|
|
23
|
+
const range = document.createRange();
|
|
24
|
+
// Use native line fragments, not one line through the whole wrapped block.
|
|
25
|
+
for (let text = walker.nextNode(); text; text = walker.nextNode()) {
|
|
26
|
+
if (!text.textContent?.trim() || text.parentElement?.closest('[aria-hidden="true"]'))
|
|
27
|
+
continue;
|
|
28
|
+
range.selectNodeContents(text);
|
|
29
|
+
for (const rect of range.getClientRects()) {
|
|
30
|
+
if (!rect.width || !rect.height)
|
|
31
|
+
continue;
|
|
32
|
+
const top = (rect.top - box.top + rect.height * 0.52) / (scaleY || 1) - 3;
|
|
33
|
+
const left = (rect.left - box.left) / (scaleX || 1);
|
|
34
|
+
const width = rect.width / (scaleX || 1);
|
|
35
|
+
const line = fragments.find(item => Math.abs(item.top - top) < 3);
|
|
36
|
+
if (line) {
|
|
37
|
+
const end = Math.max(line.left + line.width, left + width);
|
|
38
|
+
line.left = Math.min(line.left, left);
|
|
39
|
+
line.width = end - line.left;
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
fragments.push({ left, top, width });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const next = fragments.map(line => ({
|
|
46
|
+
left: Math.round(line.left * 2) / 2,
|
|
47
|
+
top: Math.round(line.top * 2) / 2,
|
|
48
|
+
width: Math.round(line.width * 2) / 2,
|
|
49
|
+
}));
|
|
50
|
+
setLines(previous => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
|
|
51
|
+
};
|
|
52
|
+
const schedule = () => {
|
|
53
|
+
if (!disposed && !frame)
|
|
54
|
+
frame = requestAnimationFrame(measure);
|
|
55
|
+
};
|
|
56
|
+
const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
|
|
57
|
+
observer?.observe(node);
|
|
58
|
+
document.fonts?.addEventListener('loadingdone', schedule);
|
|
59
|
+
document.fonts?.ready.then(schedule);
|
|
60
|
+
measure();
|
|
61
|
+
return () => {
|
|
62
|
+
disposed = true;
|
|
63
|
+
observer?.disconnect();
|
|
64
|
+
document.fonts?.removeEventListener('loadingdone', schedule);
|
|
65
|
+
cancelAnimationFrame(frame);
|
|
66
|
+
};
|
|
67
|
+
}, [children]);
|
|
68
|
+
return _jsxs("del", { ...props, className: ['inkra-markdown__deleted', className].filter(Boolean).join(' '), ref: ref, "data-pencil-strike-ready": lines.length > 0 || undefined, children: [children, lines.map((line, index) => _jsx(PencilLine, { className: "inkra-markdown__strike", seed: `markdown-strike-${index}`, weight: 0.7, roughness: 0.4, style: { left: line.left, top: line.top, width: line.width } }, index))] });
|
|
69
|
+
}
|
|
@@ -0,0 +1,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
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
-
import type { NumberTickerLocale } from './model.js';
|
|
2
|
+
import type { NumberTickerCompact, NumberTickerCompactOptions, NumberTickerFormatOptions, NumberTickerLocale, NumberTickerValue } from './model.js';
|
|
3
3
|
export interface NumberTickerRenderDetails {
|
|
4
4
|
animating: boolean;
|
|
5
5
|
formattedValue: string;
|
|
@@ -9,21 +9,23 @@ export interface NumberTickerDeltaDetails {
|
|
|
9
9
|
formattedDelta: string;
|
|
10
10
|
}
|
|
11
11
|
export interface NumberTickerProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'prefix'> {
|
|
12
|
-
value:
|
|
12
|
+
value: NumberTickerValue;
|
|
13
13
|
locale?: NumberTickerLocale;
|
|
14
|
-
formatOptions?:
|
|
14
|
+
formatOptions?: NumberTickerFormatOptions;
|
|
15
|
+
compact?: NumberTickerCompact;
|
|
16
|
+
compactOptions?: NumberTickerCompactOptions;
|
|
15
17
|
prefix?: ReactNode;
|
|
16
18
|
suffix?: ReactNode;
|
|
17
|
-
renderValue?: (value:
|
|
18
|
-
getValueLabel?: (value:
|
|
19
|
+
renderValue?: (value: NumberTickerValue, details: NumberTickerRenderDetails) => ReactNode;
|
|
20
|
+
getValueLabel?: (value: NumberTickerValue, formattedValue: string) => string;
|
|
19
21
|
animate?: boolean;
|
|
20
22
|
duration?: number;
|
|
21
23
|
easing?: (progress: number) => number;
|
|
22
24
|
showDelta?: boolean;
|
|
23
25
|
deltaDuration?: number;
|
|
24
|
-
deltaFormatOptions?:
|
|
25
|
-
renderDelta?: (delta:
|
|
26
|
+
deltaFormatOptions?: NumberTickerFormatOptions;
|
|
27
|
+
renderDelta?: (delta: NumberTickerValue, details: NumberTickerDeltaDetails) => ReactNode;
|
|
26
28
|
}
|
|
27
29
|
export declare const NumberTicker: import("react").ForwardRefExoticComponent<NumberTickerProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
28
|
-
export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, } from './model.js';
|
|
29
|
-
export type { NumberTickerLocale } from './model.js';
|
|
30
|
+
export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, numberTickerUnits, } from './model.js';
|
|
31
|
+
export type { NumberTickerCompact, NumberTickerCompactOptions, NumberTickerFormatOptions, NumberTickerLocale, NumberTickerUnit, NumberTickerValue } from './model.js';
|
|
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { PencilText } from '../PencilText.js';
|
|
4
4
|
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
5
|
-
import { canAnimateTickerValue, createNumberFormatter, easeOutCubic,
|
|
5
|
+
import { canAnimateTickerValue, createNumberFormatter, createTickerFormatter, easeOutCubic, formatDerivedTickerDelta, formatTickerValue, interpolateTickerValue, tickerValueChanged, subtractTickerValues, tickerValueKey, } from './model.js';
|
|
6
|
+
import { ExactDecimal } from './precision.js';
|
|
6
7
|
function usePrefersReducedMotion() {
|
|
7
8
|
const [reduced, setReduced] = useState(false);
|
|
8
9
|
useBrowserLayoutEffect(() => {
|
|
@@ -22,7 +23,7 @@ function lettering(content, seed) {
|
|
|
22
23
|
function accessibleAffix(content) {
|
|
23
24
|
return typeof content === 'string' || typeof content === 'number' ? String(content) : '';
|
|
24
25
|
}
|
|
25
|
-
export const NumberTicker = forwardRef(function NumberTicker({ animate = true, className, deltaDuration = 1600, deltaFormatOptions, duration = 420, easing = easeOutCubic, formatOptions, getValueLabel, locale, prefix, renderDelta, renderValue, showDelta = false, suffix, value, ...props }, forwardedRef) {
|
|
26
|
+
export const NumberTicker = forwardRef(function NumberTicker({ animate = true, className, compact = false, compactOptions, deltaDuration = 1600, deltaFormatOptions, duration = 420, easing = easeOutCubic, formatOptions, getValueLabel, locale, prefix, renderDelta, renderValue, showDelta = false, suffix, value, ...props }, forwardedRef) {
|
|
26
27
|
const rootRef = useRef(null);
|
|
27
28
|
const frameRef = useRef(null);
|
|
28
29
|
const deltaTimerRef = useRef(null);
|
|
@@ -35,15 +36,17 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
35
36
|
const reducedMotion = usePrefersReducedMotion();
|
|
36
37
|
const localeKey = Array.isArray(locale) ? locale.join('\u0000') : locale;
|
|
37
38
|
const formatOptionsKey = JSON.stringify(formatOptions ?? {});
|
|
39
|
+
const compactOptionsKey = tickerValueKey(compactOptions ?? {});
|
|
38
40
|
const deltaFormatOptionsKey = JSON.stringify(deltaFormatOptions ?? formatOptions ?? {});
|
|
39
|
-
const formatter = useMemo(() =>
|
|
40
|
-
const deltaFormatter = useMemo(() =>
|
|
41
|
+
const formatter = useMemo(() => createTickerFormatter(locale, formatOptions, compact, compactOptions), [localeKey, formatOptionsKey, compact, compactOptionsKey]);
|
|
42
|
+
const deltaFormatter = useMemo(() => createTickerFormatter(locale, deltaFormatOptions ?? formatOptions, compact, compactOptions), [localeKey, deltaFormatOptionsKey, compact, compactOptionsKey]);
|
|
43
|
+
const announcementFormatter = useMemo(() => createNumberFormatter(locale, compact ? { ...formatOptions, notation: 'standard' } : formatOptions), [localeKey, formatOptionsKey, compact]);
|
|
41
44
|
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
42
45
|
useBrowserLayoutEffect(() => {
|
|
43
46
|
const previousTarget = targetRef.current;
|
|
44
47
|
targetRef.current = value;
|
|
45
|
-
if (showDelta && tickerValueChanged(previousTarget, value)
|
|
46
|
-
const nextDelta = value
|
|
48
|
+
if (showDelta && tickerValueChanged(previousTarget, value)) {
|
|
49
|
+
const nextDelta = subtractTickerValues(value, previousTarget);
|
|
47
50
|
setDelta(nextDelta);
|
|
48
51
|
if (deltaTimerRef.current !== null)
|
|
49
52
|
clearTimeout(deltaTimerRef.current);
|
|
@@ -109,7 +112,7 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
109
112
|
}, []);
|
|
110
113
|
const formattedValue = formatTickerValue(formatter, displayedValue);
|
|
111
114
|
const finalFormattedValue = formatTickerValue(formatter, value);
|
|
112
|
-
const announcedFormattedValue = formatTickerValue(
|
|
115
|
+
const announcedFormattedValue = formatTickerValue(announcementFormatter, announcedValue);
|
|
113
116
|
const defaultAccessibleLabel = [accessibleAffix(prefix), announcedFormattedValue, accessibleAffix(suffix)]
|
|
114
117
|
.filter(Boolean).join(' ');
|
|
115
118
|
const accessibleLabel = getValueLabel?.(announcedValue, announcedFormattedValue) ?? defaultAccessibleLabel;
|
|
@@ -118,15 +121,15 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
118
121
|
? _jsx("span", { className: "inkra-number-ticker__native-value", children: formattedValue })
|
|
119
122
|
: _jsx(PencilText, { seed: `number-ticker-${finalFormattedValue}`, children: finalFormattedValue }));
|
|
120
123
|
const deltaContent = delta === null ? null : (() => {
|
|
121
|
-
const formattedDelta =
|
|
124
|
+
const formattedDelta = formatDerivedTickerDelta(deltaFormatter, delta);
|
|
122
125
|
const details = {
|
|
123
|
-
direction: delta
|
|
126
|
+
direction: new ExactDecimal(String(delta)).lt(0) ? 'decrease' : 'increase',
|
|
124
127
|
formattedDelta,
|
|
125
128
|
};
|
|
126
129
|
return renderDelta?.(delta, details) ?? _jsx(PencilText, { seed: `number-ticker-delta-${formattedDelta}`, children: formattedDelta });
|
|
127
130
|
})();
|
|
128
131
|
const classes = ['inkra-number-ticker', animating && 'inkra-number-ticker--animating', className]
|
|
129
132
|
.filter(Boolean).join(' ');
|
|
130
|
-
return _jsxs("span", { ...props, className: classes, ref: rootRef, "data-animating": animating || undefined, children: [_jsxs("span", { "aria-hidden": "true", className: "inkra-number-ticker__visual", children: [prefix !== undefined && _jsx("span", { className: "inkra-number-ticker__prefix", children: lettering(prefix, 'number-ticker-prefix') }), _jsx("span", { className: "inkra-number-ticker__value", children: valueContent }), suffix !== undefined && _jsx("span", { className: "inkra-number-ticker__suffix", children: lettering(suffix, 'number-ticker-suffix') }), deltaContent !== null && _jsx("span", { className: "inkra-number-ticker__delta", "data-direction": delta
|
|
133
|
+
return _jsxs("span", { ...props, className: classes, ref: rootRef, "data-animating": animating || undefined, children: [_jsxs("span", { "aria-hidden": "true", className: "inkra-number-ticker__visual", children: [prefix !== undefined && _jsx("span", { className: "inkra-number-ticker__prefix", children: lettering(prefix, 'number-ticker-prefix') }), _jsx("span", { className: "inkra-number-ticker__value", children: valueContent }), suffix !== undefined && _jsx("span", { className: "inkra-number-ticker__suffix", children: lettering(suffix, 'number-ticker-suffix') }), deltaContent !== null && _jsx("span", { className: "inkra-number-ticker__delta", "data-direction": new ExactDecimal(String(delta)).lt(0) ? 'decrease' : 'increase', children: deltaContent })] }), _jsx("span", { className: "inkra-number-ticker__announcement", "aria-atomic": "true", "aria-live": "polite", children: accessibleLabel })] });
|
|
131
134
|
});
|
|
132
|
-
export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, } from './model.js';
|
|
135
|
+
export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, numberTickerUnits, } from './model.js';
|
|
@@ -1,8 +1,41 @@
|
|
|
1
|
+
import { tickerDecimal } from './precision.js';
|
|
2
|
+
import type { NumberTickerValue } from './precision.js';
|
|
3
|
+
export { subtractTickerValues, tickerValueKey } from './precision.js';
|
|
4
|
+
export type { NumberTickerValue } from './precision.js';
|
|
1
5
|
export type NumberTickerLocale = string | readonly string[];
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
|
|
6
|
+
export type NumberTickerCompact = boolean | 'en' | 'zh';
|
|
7
|
+
export interface NumberTickerFormatOptions extends Intl.NumberFormatOptions {
|
|
8
|
+
roundingMode?: 'ceil' | 'floor' | 'expand' | 'trunc' | 'halfCeil' | 'halfFloor' | 'halfExpand' | 'halfTrunc' | 'halfEven';
|
|
9
|
+
}
|
|
10
|
+
export interface NumberTickerUnit {
|
|
11
|
+
factor: NumberTickerValue;
|
|
12
|
+
label: string;
|
|
13
|
+
/** Absolute display magnitude required to enter this unit. */
|
|
14
|
+
threshold?: NumberTickerValue;
|
|
15
|
+
}
|
|
16
|
+
export interface NumberTickerCompactOptions {
|
|
17
|
+
units?: readonly NumberTickerUnit[];
|
|
18
|
+
minUnit?: string;
|
|
19
|
+
maxUnit?: string;
|
|
20
|
+
separator?: string;
|
|
21
|
+
promotionThreshold?: NumberTickerValue;
|
|
22
|
+
promoteAfterRounding?: boolean;
|
|
23
|
+
overflow?: 'unit' | 'scientific';
|
|
24
|
+
}
|
|
25
|
+
export declare const numberTickerUnits: Readonly<Record<'en' | 'zh', readonly NumberTickerUnit[]>>;
|
|
26
|
+
type TickerFormatter = {
|
|
27
|
+
format(value: NumberTickerValue): string;
|
|
28
|
+
formatToParts(value: NumberTickerValue): Intl.NumberFormatPart[];
|
|
29
|
+
formatDecimal(value: ReturnType<typeof tickerDecimal>): Intl.NumberFormatPart[];
|
|
30
|
+
};
|
|
31
|
+
export declare function createNumberFormatter(locale?: NumberTickerLocale, options?: NumberTickerFormatOptions): Intl.NumberFormat;
|
|
32
|
+
export declare function createTickerFormatter(locale?: NumberTickerLocale, options?: NumberTickerFormatOptions, compact?: NumberTickerCompact, compactOptions?: NumberTickerCompactOptions): TickerFormatter;
|
|
33
|
+
export declare function formatTickerValue(formatter: TickerFormatter | Intl.NumberFormat, value: NumberTickerValue): string;
|
|
34
|
+
export declare function formatTickerDelta(formatter: TickerFormatter | Intl.NumberFormat, delta: NumberTickerValue): string;
|
|
35
|
+
export declare function formatDerivedTickerDelta(formatter: TickerFormatter, delta: NumberTickerValue): string;
|
|
5
36
|
export declare function interpolateTickerValue(from: number, to: number, progress: number): number;
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function
|
|
37
|
+
export declare function interpolateTickerValue(from: bigint, to: bigint, progress: number): bigint;
|
|
38
|
+
export declare function interpolateTickerValue(from: NumberTickerValue, to: NumberTickerValue, progress: number): NumberTickerValue;
|
|
39
|
+
export declare function tickerValueChanged(previous: NumberTickerValue, next: NumberTickerValue): boolean;
|
|
40
|
+
export declare function canAnimateTickerValue(from: NumberTickerValue, to: NumberTickerValue, duration: number): boolean;
|
|
8
41
|
export declare function easeOutCubic(progress: number): number;
|