@inkra/react 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +316 -2
- package/THIRD_PARTY_NOTICES.md +39 -0
- package/Yozai-OFL.txt +94 -0
- package/dist/Button.d.ts +36 -0
- package/dist/Button.js +89 -0
- package/dist/PencilIcon.d.ts +22 -0
- package/dist/PencilIcon.js +210 -0
- package/dist/PencilLine.d.ts +10 -0
- package/dist/PencilLine.js +91 -0
- package/dist/PencilSurface.d.ts +19 -0
- package/dist/PencilSurface.js +19 -0
- package/dist/PencilText.d.ts +16 -0
- package/dist/PencilText.js +338 -0
- package/dist/accordion/Accordion.d.ts +27 -0
- package/dist/accordion/Accordion.js +40 -0
- package/dist/accordion/index.d.ts +2 -0
- package/dist/accordion/index.js +1 -0
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +109 -0
- package/dist/assets/ui-hand-yozai.woff2 +0 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/breadcrumb/index.d.ts +16 -0
- package/dist/breadcrumb/index.js +25 -0
- package/dist/calendar/index.d.ts +21 -0
- package/dist/calendar/index.js +41 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +23 -0
- package/dist/command/index.js +39 -0
- package/dist/content/index.d.ts +106 -0
- package/dist/content/index.js +80 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +240 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/day-agenda/index.d.ts +43 -0
- package/dist/day-agenda/index.js +47 -0
- package/dist/day-agenda/layout.d.ts +20 -0
- package/dist/day-agenda/layout.js +60 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +59 -0
- package/dist/display/index.js +75 -0
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +86 -0
- package/dist/dropdown-menu/index.d.ts +34 -0
- package/dist/dropdown-menu/index.js +40 -0
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.d.ts +13 -0
- package/dist/form/EditablePencilText.js +323 -0
- package/dist/form/Field.d.ts +13 -0
- package/dist/form/Field.js +26 -0
- package/dist/form/Input.d.ts +27 -0
- package/dist/form/Input.js +97 -0
- package/dist/form/InputAction.d.ts +7 -0
- package/dist/form/InputAction.js +24 -0
- package/dist/form/Textarea.d.ts +17 -0
- package/dist/form/Textarea.js +102 -0
- package/dist/form/context.d.ts +12 -0
- package/dist/form/context.js +10 -0
- package/dist/form/controlProps.d.ts +23 -0
- package/dist/form/controlProps.js +20 -0
- package/dist/form/index.d.ts +7 -0
- package/dist/form/index.js +3 -0
- package/dist/form/types.d.ts +6 -0
- package/dist/form/types.js +1 -0
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/images/layers-2x.png +0 -0
- package/dist/images/layers.png +0 -0
- package/dist/images/marker-icon.png +0 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +69 -0
- package/dist/input-number/index.d.ts +16 -0
- package/dist/input-number/index.js +77 -0
- package/dist/internal/InputActionContext.d.ts +1 -0
- package/dist/internal/InputActionContext.js +3 -0
- package/dist/internal/cache.d.ts +14 -0
- package/dist/internal/cache.js +54 -0
- package/dist/internal/line.d.ts +4 -0
- package/dist/internal/line.js +21 -0
- package/dist/internal/paper.d.ts +1 -0
- package/dist/internal/paper.js +65 -0
- package/dist/internal/random.d.ts +3 -0
- package/dist/internal/random.js +30 -0
- package/dist/internal/recovery.d.ts +1 -0
- package/dist/internal/recovery.js +52 -0
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/stroke.d.ts +24 -0
- package/dist/internal/stroke.js +144 -0
- package/dist/internal/surfaceRenderer.d.ts +41 -0
- package/dist/internal/surfaceRenderer.js +178 -0
- package/dist/internal/textQueue.d.ts +1 -0
- package/dist/internal/textQueue.js +37 -0
- package/dist/internal/textRenderer.d.ts +28 -0
- package/dist/internal/textRenderer.js +239 -0
- package/dist/internal/travelIcons.d.ts +24 -0
- package/dist/internal/travelIcons.js +14 -0
- package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
- package/dist/internal/useBrowserLayoutEffect.js +3 -0
- package/dist/internal/usePencilSurface.d.ts +3 -0
- package/dist/internal/usePencilSurface.js +116 -0
- package/dist/internal/useSelectionDrawing.d.ts +2 -0
- package/dist/internal/useSelectionDrawing.js +90 -0
- package/dist/internal/wash.d.ts +16 -0
- package/dist/internal/wash.js +149 -0
- package/dist/journey-map/MapScale.d.ts +4 -0
- package/dist/journey-map/MapScale.js +54 -0
- package/dist/journey-map/index.d.ts +39 -0
- package/dist/journey-map/index.js +197 -0
- package/dist/journey-map/terrain.d.ts +8 -0
- package/dist/journey-map/terrain.js +127 -0
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +29 -0
- package/dist/number-ticker/index.js +132 -0
- package/dist/number-ticker/model.d.ts +8 -0
- package/dist/number-ticker/model.js +34 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/overlay/Overlay.d.ts +65 -0
- package/dist/overlay/Overlay.js +77 -0
- package/dist/overlay/Popover.d.ts +43 -0
- package/dist/overlay/Popover.js +45 -0
- package/dist/overlay/index.d.ts +4 -0
- package/dist/overlay/index.js +2 -0
- package/dist/overlay/themeBridge.d.ts +8 -0
- package/dist/overlay/themeBridge.js +98 -0
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/pencil.d.ts +10 -0
- package/dist/pencil.js +39 -0
- package/dist/radar/index.d.ts +54 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/select/MultipleSelect.d.ts +44 -0
- package/dist/select/MultipleSelect.js +124 -0
- package/dist/select/SearchableSelect.d.ts +45 -0
- package/dist/select/SearchableSelect.js +50 -0
- package/dist/select/Select.d.ts +109 -0
- package/dist/select/Select.js +205 -0
- package/dist/select/index.d.ts +4 -0
- package/dist/select/index.js +2 -0
- package/dist/selection/Checkbox.d.ts +9 -0
- package/dist/selection/Checkbox.js +37 -0
- package/dist/selection/RadioGroup.d.ts +23 -0
- package/dist/selection/RadioGroup.js +82 -0
- package/dist/selection/Switch.d.ts +8 -0
- package/dist/selection/Switch.js +36 -0
- package/dist/selection/index.d.ts +6 -0
- package/dist/selection/index.js +3 -0
- package/dist/selection/useNativeChecked.d.ts +13 -0
- package/dist/selection/useNativeChecked.js +27 -0
- package/dist/slider/index.d.ts +11 -0
- package/dist/slider/index.js +45 -0
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +278 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +8226 -0
- package/dist/tabs/index.d.ts +22 -0
- package/dist/tabs/index.js +42 -0
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +356 -0
- package/dist/tooltip/Tooltip.d.ts +24 -0
- package/dist/tooltip/Tooltip.js +32 -0
- package/dist/tooltip/index.d.ts +2 -0
- package/dist/tooltip/index.js +1 -0
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +128 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +119 -4
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Connection, Edge, EdgeChange, FitViewOptions, Node, NodeChange, OnConnect, Viewport } from '@xyflow/react';
|
|
2
|
+
import type { HTMLAttributes, ReactElement, ReactNode, RefAttributes } from 'react';
|
|
3
|
+
export type NodeGraphPortPosition = 'top' | 'right' | 'bottom' | 'left';
|
|
4
|
+
export interface NodeGraphPortDefinition {
|
|
5
|
+
id: string;
|
|
6
|
+
type: 'source' | 'target';
|
|
7
|
+
position?: NodeGraphPortPosition;
|
|
8
|
+
label: string;
|
|
9
|
+
connectable?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface NodeGraphNodeData extends Record<string, unknown> {
|
|
12
|
+
label?: ReactNode;
|
|
13
|
+
description?: ReactNode;
|
|
14
|
+
content?: ReactNode;
|
|
15
|
+
ports?: NodeGraphPortDefinition[];
|
|
16
|
+
}
|
|
17
|
+
export interface NodeGraphEdgeData extends Record<string, unknown> {
|
|
18
|
+
tone?: 'neutral' | 'accent';
|
|
19
|
+
}
|
|
20
|
+
export type NodeGraphNode<Data extends Record<string, unknown> = NodeGraphNodeData> = Node<Data>;
|
|
21
|
+
export type NodeGraphEdge<Data extends Record<string, unknown> = NodeGraphEdgeData> = Edge<Data>;
|
|
22
|
+
export type NodeGraphNodeChange<NodeType extends NodeGraphNode = NodeGraphNode> = NodeChange<NodeType>;
|
|
23
|
+
export type NodeGraphEdgeChange<EdgeType extends NodeGraphEdge = NodeGraphEdge> = EdgeChange<EdgeType>;
|
|
24
|
+
export type NodeGraphConnection = Connection;
|
|
25
|
+
export type NodeGraphViewport = Viewport;
|
|
26
|
+
export interface NodeGraphRenderNodeProps<Data extends Record<string, unknown> = NodeGraphNodeData> {
|
|
27
|
+
id: string;
|
|
28
|
+
data: Data;
|
|
29
|
+
selected: boolean;
|
|
30
|
+
dragging: boolean;
|
|
31
|
+
readOnly: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface NodeGraphRenderEdgeProps<EdgeType extends NodeGraphEdge = NodeGraphEdge> {
|
|
34
|
+
id: string;
|
|
35
|
+
edge: EdgeType;
|
|
36
|
+
selected: boolean;
|
|
37
|
+
path: string;
|
|
38
|
+
labelX: number;
|
|
39
|
+
labelY: number;
|
|
40
|
+
}
|
|
41
|
+
export interface NodeGraphRef {
|
|
42
|
+
fitView: (options?: FitViewOptions) => Promise<boolean>;
|
|
43
|
+
zoomIn: () => Promise<boolean>;
|
|
44
|
+
zoomOut: () => Promise<boolean>;
|
|
45
|
+
getViewport: () => NodeGraphViewport;
|
|
46
|
+
}
|
|
47
|
+
export interface NodeGraphControlLabels {
|
|
48
|
+
zoomIn: string;
|
|
49
|
+
zoomOut: string;
|
|
50
|
+
fitView: string;
|
|
51
|
+
}
|
|
52
|
+
export interface NodeGraphRootProps<NodeType extends NodeGraphNode = NodeGraphNode, EdgeType extends NodeGraphEdge = NodeGraphEdge> extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onError'> {
|
|
53
|
+
nodes: NodeType[];
|
|
54
|
+
edges: EdgeType[];
|
|
55
|
+
onNodesChange?: (changes: NodeGraphNodeChange<NodeType>[]) => void;
|
|
56
|
+
onEdgesChange?: (changes: NodeGraphEdgeChange<EdgeType>[]) => void;
|
|
57
|
+
onConnect?: OnConnect;
|
|
58
|
+
isValidConnection?: (connection: NodeGraphConnection | EdgeType) => boolean;
|
|
59
|
+
renderNode?: (props: NodeGraphRenderNodeProps<NodeType['data']>) => ReactNode;
|
|
60
|
+
renderEdge?: (props: NodeGraphRenderEdgeProps<EdgeType>) => ReactNode;
|
|
61
|
+
renderEdgeLabel?: (edge: EdgeType) => ReactNode;
|
|
62
|
+
readOnly?: boolean;
|
|
63
|
+
emptyState?: ReactNode;
|
|
64
|
+
controls?: boolean;
|
|
65
|
+
controlLabels?: Partial<NodeGraphControlLabels>;
|
|
66
|
+
fitView?: boolean;
|
|
67
|
+
fitViewOptions?: FitViewOptions<NodeType>;
|
|
68
|
+
defaultViewport?: NodeGraphViewport;
|
|
69
|
+
viewport?: NodeGraphViewport;
|
|
70
|
+
onViewportChange?: (viewport: NodeGraphViewport) => void;
|
|
71
|
+
minZoom?: number;
|
|
72
|
+
maxZoom?: number;
|
|
73
|
+
panOnDrag?: boolean;
|
|
74
|
+
zoomOnScroll?: boolean;
|
|
75
|
+
zoomOnPinch?: boolean;
|
|
76
|
+
connectOnClick?: boolean;
|
|
77
|
+
onlyRenderVisibleElements?: boolean;
|
|
78
|
+
}
|
|
79
|
+
export interface NodeGraphPortProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'children'> {
|
|
80
|
+
id: string;
|
|
81
|
+
type: 'source' | 'target';
|
|
82
|
+
position?: NodeGraphPortPosition;
|
|
83
|
+
label: string;
|
|
84
|
+
connectable?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface NodeGraphNodeProps extends HTMLAttributes<HTMLDivElement> {
|
|
87
|
+
selected?: boolean;
|
|
88
|
+
dragging?: boolean;
|
|
89
|
+
readOnly?: boolean;
|
|
90
|
+
label?: ReactNode;
|
|
91
|
+
description?: ReactNode;
|
|
92
|
+
ports?: NodeGraphPortDefinition[];
|
|
93
|
+
children?: ReactNode;
|
|
94
|
+
seed?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface NodeGraphEdgeProps {
|
|
97
|
+
id: string;
|
|
98
|
+
path: string;
|
|
99
|
+
labelX?: number;
|
|
100
|
+
labelY?: number;
|
|
101
|
+
selected?: boolean;
|
|
102
|
+
interactionWidth?: number;
|
|
103
|
+
label?: ReactNode;
|
|
104
|
+
className?: string;
|
|
105
|
+
}
|
|
106
|
+
export interface NodeGraphControlsProps extends HTMLAttributes<HTMLDivElement> {
|
|
107
|
+
fitViewOptions?: FitViewOptions;
|
|
108
|
+
zoomInLabel?: string;
|
|
109
|
+
zoomOutLabel?: string;
|
|
110
|
+
fitViewLabel?: string;
|
|
111
|
+
}
|
|
112
|
+
declare function NodeGraphControls({ className, fitViewOptions, zoomInLabel, zoomOutLabel, fitViewLabel, ...props }: NodeGraphControlsProps): import("react").JSX.Element;
|
|
113
|
+
export declare function applyNodeGraphNodeChanges<NodeType extends NodeGraphNode>(changes: NodeGraphNodeChange<NodeType>[], nodes: NodeType[]): NodeType[];
|
|
114
|
+
export declare function applyNodeGraphEdgeChanges<EdgeType extends NodeGraphEdge>(changes: NodeGraphEdgeChange<EdgeType>[], edges: EdgeType[]): EdgeType[];
|
|
115
|
+
export declare function addNodeGraphEdge<EdgeType extends NodeGraphEdge>(connection: NodeGraphConnection | EdgeType, edges: EdgeType[]): EdgeType[];
|
|
116
|
+
export declare const NodeGraph: {
|
|
117
|
+
Root: <NodeType extends NodeGraphNode = NodeGraphNode<NodeGraphNodeData>, EdgeType extends NodeGraphEdge = NodeGraphEdge<NodeGraphEdgeData>>(props: NodeGraphRootProps<NodeType, EdgeType> & RefAttributes<NodeGraphRef>) => ReactElement | null;
|
|
118
|
+
Node: import("react").ForwardRefExoticComponent<NodeGraphNodeProps & RefAttributes<HTMLDivElement>>;
|
|
119
|
+
Port: import("react").ForwardRefExoticComponent<NodeGraphPortProps & RefAttributes<HTMLDivElement>>;
|
|
120
|
+
Edge: import("react").NamedExoticComponent<NodeGraphEdgeProps>;
|
|
121
|
+
Controls: typeof NodeGraphControls;
|
|
122
|
+
};
|
|
123
|
+
export {};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { EdgeLabelRenderer, Handle, Panel, Position, ReactFlow, ReactFlowProvider, addEdge as addFlowEdge, applyEdgeChanges as applyFlowEdgeChanges, applyNodeChanges as applyFlowNodeChanges, getBezierPath, useReactFlow, useViewport, } from '@xyflow/react';
|
|
3
|
+
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
4
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
5
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
6
|
+
import { PencilText } from '../PencilText.js';
|
|
7
|
+
import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable, effectiveDpr, } from '../internal/cache.js';
|
|
8
|
+
import { observeCanvasRecovery } from '../internal/recovery.js';
|
|
9
|
+
import { seedToNumber } from '../internal/random.js';
|
|
10
|
+
import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from '../internal/renderTokens.js';
|
|
11
|
+
import { pencilStroke } from '../internal/stroke.js';
|
|
12
|
+
const positionMap = {
|
|
13
|
+
top: Position.Top,
|
|
14
|
+
right: Position.Right,
|
|
15
|
+
bottom: Position.Bottom,
|
|
16
|
+
left: Position.Left,
|
|
17
|
+
};
|
|
18
|
+
const zoomInPaths = [
|
|
19
|
+
{ d: 'M 12 5 L 12 19' },
|
|
20
|
+
{ d: 'M 5 12 L 19 12' },
|
|
21
|
+
];
|
|
22
|
+
const zoomOutPaths = [{ d: 'M 5 12 L 19 12' }];
|
|
23
|
+
const fitViewPaths = [
|
|
24
|
+
{ d: 'M 9 5 L 5 5 L 5 9' },
|
|
25
|
+
{ d: 'M 15 5 L 19 5 L 19 9' },
|
|
26
|
+
{ d: 'M 5 15 L 5 19 L 9 19' },
|
|
27
|
+
{ d: 'M 19 15 L 19 19 L 15 19' },
|
|
28
|
+
];
|
|
29
|
+
const edgeCanvasCache = new BoundedCache(96, 24 * 1024 * 1024);
|
|
30
|
+
const edgeCanvasPadding = 12;
|
|
31
|
+
const edgeCanvasMaxDimension = 1024;
|
|
32
|
+
const edgeCanvasMaxPixels = 512 * 512;
|
|
33
|
+
const edgeCanvasMinScale = 0.5;
|
|
34
|
+
const edgeMaxSampleSegments = 1024;
|
|
35
|
+
function readableLabel(value, fallback) {
|
|
36
|
+
return typeof value === 'string' || typeof value === 'number' ? String(value) : fallback;
|
|
37
|
+
}
|
|
38
|
+
const NodeGraphPort = forwardRef(function NodeGraphPort({ id, type, position = type === 'source' ? 'right' : 'left', label, connectable = true, className, style, ...props }, ref) {
|
|
39
|
+
const { zoom } = useViewport();
|
|
40
|
+
const portStyle = {
|
|
41
|
+
...style,
|
|
42
|
+
'--inkra-node-graph-port-scale': 1 / Math.max(zoom, 0.01),
|
|
43
|
+
};
|
|
44
|
+
return _jsx(Handle, { ...props, ref: ref, id: id, type: type, position: positionMap[position], isConnectable: connectable, "aria-label": label, className: ['inkra-node-graph__port', className].filter(Boolean).join(' '), "data-port-position": position, style: portStyle, children: _jsxs("span", { "aria-hidden": "true", className: "inkra-node-graph__port-visual", children: [_jsx(PencilSurface, { as: "span", className: "inkra-node-graph__port-feedback", radius: 8, seed: `node-graph-port-${type}-${id}-feedback`, variant: "quiet" }), _jsx(PencilSurface, { as: "span", className: "inkra-node-graph__port-mark", radius: 8, seed: `node-graph-port-${type}-${id}`, variant: "outline" })] }) });
|
|
45
|
+
});
|
|
46
|
+
const NodeGraphNode = forwardRef(function NodeGraphNode({ selected = false, dragging = false, readOnly = false, label, description, ports = [], children, className, seed = 'node-graph-node', ...props }, ref) {
|
|
47
|
+
return _jsxs("div", { ...props, ref: ref, className: ['inkra-node-graph__node-shell', className].filter(Boolean).join(' '), "data-selected": selected || undefined, "data-dragging": dragging || undefined, "data-read-only": readOnly || undefined, children: [_jsxs(PencilSurface, { className: "inkra-node-graph__node-paper", variant: "paper", radius: 8, seed: seed, children: [selected && _jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-node-graph__node-selection", variant: "selection", seed: `${seed}-selection` }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-node-graph__node-focus", radius: 8, seed: `${seed}-focus`, variant: "outline" }), _jsxs("div", { className: "inkra-node-graph__node-content", children: [label != null && _jsx("div", { className: "inkra-node-graph__node-label", children: typeof label === 'string' ? _jsx(PencilText, { children: label }) : label }), description != null && _jsx("div", { className: "inkra-node-graph__node-description", children: description }), children != null && _jsx("div", { className: "inkra-node-graph__node-body", children: children })] })] }), ports.map(port => _jsx(NodeGraphPort, { ...port, connectable: !readOnly && port.connectable !== false }, `${port.type}-${port.id}`))] });
|
|
48
|
+
});
|
|
49
|
+
const DefaultNodeRenderer = memo(function DefaultNodeRenderer({ id, data, selected, dragging }) {
|
|
50
|
+
const originalData = data.__inkraData ?? data;
|
|
51
|
+
const custom = data.__inkraRenderNode?.({
|
|
52
|
+
id,
|
|
53
|
+
data: originalData,
|
|
54
|
+
selected,
|
|
55
|
+
dragging,
|
|
56
|
+
readOnly: Boolean(data.__inkraReadOnly),
|
|
57
|
+
});
|
|
58
|
+
if (custom != null)
|
|
59
|
+
return custom;
|
|
60
|
+
return _jsx(NodeGraphNode, { label: originalData.label, description: originalData.description, ports: originalData.ports, selected: selected, dragging: dragging, readOnly: Boolean(data.__inkraReadOnly), seed: `node-graph-${id}`, children: originalData.content });
|
|
61
|
+
});
|
|
62
|
+
function boundsEqual(previous, next) {
|
|
63
|
+
return previous != null
|
|
64
|
+
&& Math.abs(previous.x - next.x) < 0.01
|
|
65
|
+
&& Math.abs(previous.y - next.y) < 0.01
|
|
66
|
+
&& Math.abs(previous.width - next.width) < 0.01
|
|
67
|
+
&& Math.abs(previous.height - next.height) < 0.01;
|
|
68
|
+
}
|
|
69
|
+
function edgeCanvasBudget(bounds, requestedScale) {
|
|
70
|
+
const cssPixels = bounds.width * bounds.height;
|
|
71
|
+
if (!Number.isFinite(cssPixels) || cssPixels <= 0)
|
|
72
|
+
return null;
|
|
73
|
+
const scale = Math.min(requestedScale, edgeCanvasMaxDimension / bounds.width, edgeCanvasMaxDimension / bounds.height, Math.sqrt(edgeCanvasMaxPixels / cssPixels));
|
|
74
|
+
if (!Number.isFinite(scale) || scale < edgeCanvasMinScale)
|
|
75
|
+
return null;
|
|
76
|
+
const width = Math.max(1, Math.floor(bounds.width * scale));
|
|
77
|
+
const height = Math.max(1, Math.floor(bounds.height * scale));
|
|
78
|
+
if (width > edgeCanvasMaxDimension || height > edgeCanvasMaxDimension || width * height > edgeCanvasMaxPixels)
|
|
79
|
+
return null;
|
|
80
|
+
return { width, height, scaleX: width / bounds.width, scaleY: height / bounds.height };
|
|
81
|
+
}
|
|
82
|
+
function sampleSvgPath(path, bounds) {
|
|
83
|
+
const total = path.getTotalLength();
|
|
84
|
+
if (!Number.isFinite(total) || total <= 0)
|
|
85
|
+
return [];
|
|
86
|
+
const count = Math.min(edgeMaxSampleSegments, Math.max(2, Math.ceil(total / 1.4)));
|
|
87
|
+
return Array.from({ length: count + 1 }, (_, index) => {
|
|
88
|
+
const point = path.getPointAtLength(total * index / count);
|
|
89
|
+
return [point.x - bounds.x, point.y - bounds.y];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function PencilEdgeCanvas({ id, path, className, selected = false }) {
|
|
93
|
+
const pathRef = useRef(null);
|
|
94
|
+
const canvasRef = useRef(null);
|
|
95
|
+
const [bounds, setBounds] = useState(null);
|
|
96
|
+
const [ready, setReady] = useState(false);
|
|
97
|
+
const budget = useMemo(() => bounds ? edgeCanvasBudget(bounds, effectiveDpr(2)) : null, [bounds]);
|
|
98
|
+
const measure = useCallback(() => {
|
|
99
|
+
const element = pathRef.current;
|
|
100
|
+
if (!element)
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
const box = element.getBBox();
|
|
104
|
+
const next = {
|
|
105
|
+
x: box.x - edgeCanvasPadding,
|
|
106
|
+
y: box.y - edgeCanvasPadding,
|
|
107
|
+
width: Math.max(1, box.width + edgeCanvasPadding * 2),
|
|
108
|
+
height: Math.max(1, box.height + edgeCanvasPadding * 2),
|
|
109
|
+
};
|
|
110
|
+
setBounds(previous => boundsEqual(previous, next) ? previous : next);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
setBounds(null);
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
116
|
+
useLayoutEffect(() => {
|
|
117
|
+
measure();
|
|
118
|
+
}, [measure, path]);
|
|
119
|
+
useLayoutEffect(() => {
|
|
120
|
+
setReady(false);
|
|
121
|
+
}, [budget, path]);
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
const canvas = canvasRef.current;
|
|
124
|
+
const pathElement = pathRef.current;
|
|
125
|
+
if (!canvas || !pathElement || !bounds || !budget)
|
|
126
|
+
return undefined;
|
|
127
|
+
let disposed = false;
|
|
128
|
+
let frame = 0;
|
|
129
|
+
let lastPaint = '';
|
|
130
|
+
const paint = () => {
|
|
131
|
+
frame = 0;
|
|
132
|
+
if (disposed)
|
|
133
|
+
return;
|
|
134
|
+
try {
|
|
135
|
+
const points = sampleSvgPath(pathElement, bounds);
|
|
136
|
+
if (points.length < 2)
|
|
137
|
+
throw new Error('NodeGraph edge has no drawable path');
|
|
138
|
+
const style = getComputedStyle(canvas);
|
|
139
|
+
const width = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
|
|
140
|
+
const roughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
|
|
141
|
+
const numericSeed = seedToNumber(`node-graph-edge-${id}`);
|
|
142
|
+
const key = JSON.stringify([
|
|
143
|
+
PENCIL_RENDERER_VERSION, path, bounds, style.color, width, roughness, numericSeed, budget, selected,
|
|
144
|
+
]);
|
|
145
|
+
if (key === lastPaint && canvasIsUsable(canvas))
|
|
146
|
+
return;
|
|
147
|
+
let image = edgeCanvasCache.get(key);
|
|
148
|
+
if (image && !canvasIsUsable(image)) {
|
|
149
|
+
edgeCanvasCache.delete(key);
|
|
150
|
+
image = undefined;
|
|
151
|
+
}
|
|
152
|
+
if (!image) {
|
|
153
|
+
image = document.createElement('canvas');
|
|
154
|
+
image.width = budget.width;
|
|
155
|
+
image.height = budget.height;
|
|
156
|
+
const context = image.getContext('2d');
|
|
157
|
+
if (!context)
|
|
158
|
+
throw new Error('NodeGraph edge canvas unavailable');
|
|
159
|
+
context.scale(budget.scaleX, budget.scaleY);
|
|
160
|
+
pencilStroke(context, points, style.color, width * (selected ? 1.15 : 0.92), numericSeed, roughness, 3, false, {
|
|
161
|
+
variation: 0.82,
|
|
162
|
+
breaks: selected ? 0.16 : 0.25,
|
|
163
|
+
grain: 0.7,
|
|
164
|
+
gain: selected ? 2.5 : 2.1,
|
|
165
|
+
step: 0.65,
|
|
166
|
+
taperLength: 5,
|
|
167
|
+
edgeGrain: true,
|
|
168
|
+
});
|
|
169
|
+
edgeCanvasCache.set(key, image, canvasByteCost(image));
|
|
170
|
+
}
|
|
171
|
+
const context = canvas.getContext('2d');
|
|
172
|
+
if (!context || context.isContextLost?.())
|
|
173
|
+
throw new Error('NodeGraph edge canvas unavailable');
|
|
174
|
+
canvas.width = image.width;
|
|
175
|
+
canvas.height = image.height;
|
|
176
|
+
context.drawImage(image, 0, 0);
|
|
177
|
+
lastPaint = key;
|
|
178
|
+
setReady(true);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
lastPaint = '';
|
|
182
|
+
canvas.width = 0;
|
|
183
|
+
canvas.height = 0;
|
|
184
|
+
setReady(false);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const schedule = () => {
|
|
188
|
+
if (!frame)
|
|
189
|
+
frame = requestAnimationFrame(paint);
|
|
190
|
+
};
|
|
191
|
+
const inheritedStyle = new MutationObserver(schedule);
|
|
192
|
+
for (let element = canvas; element; element = element.parentElement) {
|
|
193
|
+
inheritedStyle.observe(element, { attributes: true, attributeFilter: ['style', 'class', 'data-theme', 'data-inkra-theme'] });
|
|
194
|
+
}
|
|
195
|
+
const scheme = matchMedia('(prefers-color-scheme: dark)');
|
|
196
|
+
scheme.addEventListener('change', schedule);
|
|
197
|
+
const stopRecovery = observeCanvasRecovery(() => { lastPaint = ''; schedule(); });
|
|
198
|
+
paint();
|
|
199
|
+
return () => {
|
|
200
|
+
disposed = true;
|
|
201
|
+
inheritedStyle.disconnect();
|
|
202
|
+
scheme.removeEventListener('change', schedule);
|
|
203
|
+
stopRecovery();
|
|
204
|
+
cancelAnimationFrame(frame);
|
|
205
|
+
};
|
|
206
|
+
}, [bounds, budget, id, path, selected]);
|
|
207
|
+
return _jsxs("g", { className: ['inkra-node-graph__edge-pencil', className].filter(Boolean).join(' '), "data-pencil-ready": ready && budget ? true : undefined, "data-selected": selected || undefined, children: [_jsx("path", { ref: pathRef, d: path, className: "inkra-node-graph__edge-measure", fill: "none" }), _jsx("path", { d: path, className: "inkra-node-graph__edge-fallback", fill: "none" }), bounds && budget && _jsx("foreignObject", { "aria-hidden": "true", className: "inkra-node-graph__edge-foreign-object", height: bounds.height, pointerEvents: "none", width: bounds.width, x: bounds.x, y: bounds.y, children: _jsx("canvas", { ref: canvasRef, className: "inkra-node-graph__edge-canvas", style: { height: bounds.height, width: bounds.width } }) })] });
|
|
208
|
+
}
|
|
209
|
+
const NodeGraphEdge = memo(function NodeGraphEdge({ id, path, labelX, labelY, selected, interactionWidth = 24, label, className, }) {
|
|
210
|
+
return _jsxs(_Fragment, { children: [interactionWidth > 0 && _jsx("path", { d: path, className: "inkra-node-graph__edge-hit", fill: "none", stroke: "transparent", strokeWidth: interactionWidth }), _jsx(PencilEdgeCanvas, { id: id, path: path, className: className, selected: selected }), label != null && Number.isFinite(labelX) && Number.isFinite(labelY) && _jsx(EdgeLabelRenderer, { children: _jsx(PencilSurface, { className: "inkra-node-graph__edge-label nodrag nopan", variant: "paper", radius: 6, seed: `edge-label-${id}`, style: { transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }, children: label }) })] });
|
|
211
|
+
});
|
|
212
|
+
function DefaultEdgeRenderer({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, selected = false, interactionWidth, data, }) {
|
|
213
|
+
const [path, labelX, labelY] = getBezierPath({
|
|
214
|
+
sourceX,
|
|
215
|
+
sourceY,
|
|
216
|
+
targetX,
|
|
217
|
+
targetY,
|
|
218
|
+
sourcePosition,
|
|
219
|
+
targetPosition,
|
|
220
|
+
});
|
|
221
|
+
const edge = data?.__inkraEdge;
|
|
222
|
+
const custom = edge && data?.__inkraRenderEdge?.({ id, edge, selected, path, labelX, labelY });
|
|
223
|
+
if (custom != null)
|
|
224
|
+
return _jsxs(_Fragment, { children: [_jsx("path", { d: path, className: "inkra-node-graph__edge-hit", fill: "none", stroke: "transparent", strokeWidth: interactionWidth ?? 24 }), custom] });
|
|
225
|
+
const label = edge == null ? undefined : data?.__inkraRenderEdgeLabel?.(edge) ?? edge.label;
|
|
226
|
+
return _jsx(NodeGraphEdge, { id: id, interactionWidth: interactionWidth, label: label, labelX: labelX, labelY: labelY, path: path, selected: selected });
|
|
227
|
+
}
|
|
228
|
+
const nodeTypes = { inkra: DefaultNodeRenderer };
|
|
229
|
+
const edgeTypes = { inkra: DefaultEdgeRenderer };
|
|
230
|
+
function NodeGraphConnectionLine({ fromX, fromY, toX, toY, fromPosition, toPosition }) {
|
|
231
|
+
const [path] = getBezierPath({
|
|
232
|
+
sourceX: fromX,
|
|
233
|
+
sourceY: fromY,
|
|
234
|
+
sourcePosition: fromPosition,
|
|
235
|
+
targetX: toX,
|
|
236
|
+
targetY: toY,
|
|
237
|
+
targetPosition: toPosition,
|
|
238
|
+
});
|
|
239
|
+
return _jsx(PencilEdgeCanvas, { id: "connection-preview", path: path, className: "inkra-node-graph__connection-line" });
|
|
240
|
+
}
|
|
241
|
+
function NodeGraphControls({ className, fitViewOptions, zoomInLabel = 'Zoom in', zoomOutLabel = 'Zoom out', fitViewLabel = 'Fit graph to view', ...props }) {
|
|
242
|
+
const flow = useReactFlow();
|
|
243
|
+
const actions = [
|
|
244
|
+
{ label: zoomInLabel, paths: zoomInPaths, run: () => flow.zoomIn() },
|
|
245
|
+
{ label: zoomOutLabel, paths: zoomOutPaths, run: () => flow.zoomOut() },
|
|
246
|
+
{ label: fitViewLabel, paths: fitViewPaths, run: () => flow.fitView(fitViewOptions) },
|
|
247
|
+
];
|
|
248
|
+
return _jsx("div", { ...props, className: ['inkra-node-graph__controls', 'nopan', className].filter(Boolean).join(' '), children: actions.map(action => _jsxs(PencilSurface, { as: "button", "aria-label": action.label, className: "inkra-node-graph__control", "data-tooltip": action.label, onClick: () => void action.run(), variant: "paper", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-node-graph__control-focus", radius: 6, seed: `node-graph-${action.label}-focus`, variant: "outline" }), _jsx(PencilIcon, { decorative: true, paths: action.paths, size: 22, sourceSize: 24, seed: `node-graph-${action.label}` })] }, action.label)) });
|
|
249
|
+
}
|
|
250
|
+
function SsrFallback({ nodes, emptyState, label, }) {
|
|
251
|
+
return _jsx("div", { className: "inkra-node-graph__ssr", role: "group", "aria-label": label, children: nodes.length === 0 ? _jsx("div", { className: "inkra-node-graph__empty", children: emptyState }) : _jsx("ul", { children: nodes.map(node => _jsx("li", { children: readableLabel(node.data.label, node.id) }, node.id)) }) });
|
|
252
|
+
}
|
|
253
|
+
function NodeGraphRootInner({ nodes, edges, onNodesChange, onEdgesChange, onConnect, isValidConnection, renderNode, renderEdge, renderEdgeLabel, readOnly = false, emptyState = 'No nodes yet', controls = true, controlLabels, fitView = false, fitViewOptions, defaultViewport = { x: 0, y: 0, zoom: 1 }, viewport, onViewportChange, minZoom = 0.25, maxZoom = 2, panOnDrag = true, zoomOnScroll = true, zoomOnPinch = true, connectOnClick = true, onlyRenderVisibleElements = false, className, style, 'aria-label': ariaLabel = 'Node graph', ...props }, forwardedRef) {
|
|
254
|
+
const [mounted, setMounted] = useState(false);
|
|
255
|
+
const instanceRef = useRef(null);
|
|
256
|
+
useEffect(() => setMounted(true), []);
|
|
257
|
+
useImperativeHandle(forwardedRef, () => ({
|
|
258
|
+
fitView: options => instanceRef.current?.fitView(options) ?? Promise.resolve(false),
|
|
259
|
+
zoomIn: () => instanceRef.current?.zoomIn() ?? Promise.resolve(false),
|
|
260
|
+
zoomOut: () => instanceRef.current?.zoomOut() ?? Promise.resolve(false),
|
|
261
|
+
getViewport: () => instanceRef.current?.getViewport() ?? defaultViewport,
|
|
262
|
+
}), [defaultViewport]);
|
|
263
|
+
const internalNodes = useMemo(() => nodes.map(node => ({
|
|
264
|
+
...node,
|
|
265
|
+
type: 'inkra',
|
|
266
|
+
ariaLabel: node.ariaLabel ?? readableLabel(node.data.label, node.id),
|
|
267
|
+
data: {
|
|
268
|
+
__inkraRenderNode: renderNode,
|
|
269
|
+
__inkraReadOnly: readOnly,
|
|
270
|
+
__inkraData: node.data,
|
|
271
|
+
},
|
|
272
|
+
})), [nodes, readOnly, renderNode]);
|
|
273
|
+
const internalEdges = useMemo(() => edges.map(edge => ({
|
|
274
|
+
...edge,
|
|
275
|
+
type: 'inkra',
|
|
276
|
+
ariaLabel: edge.ariaLabel ?? `Edge from ${edge.source} to ${edge.target}`,
|
|
277
|
+
label: undefined,
|
|
278
|
+
data: {
|
|
279
|
+
...edge.data,
|
|
280
|
+
__inkraEdge: edge,
|
|
281
|
+
__inkraRenderEdge: renderEdge,
|
|
282
|
+
__inkraRenderEdgeLabel: renderEdgeLabel,
|
|
283
|
+
},
|
|
284
|
+
})), [edges, renderEdge, renderEdgeLabel]);
|
|
285
|
+
const rootStyle = { minHeight: 320, ...style };
|
|
286
|
+
return _jsx("div", { ...props, "aria-label": ariaLabel, className: ['inkra-node-graph', className].filter(Boolean).join(' '), "data-empty": nodes.length === 0 || undefined, "data-read-only": readOnly || undefined, style: rootStyle, children: !mounted ? _jsx(SsrFallback, { nodes: nodes, emptyState: emptyState, label: ariaLabel }) : _jsxs(ReactFlowProvider, { children: [_jsx(ReactFlow, { "aria-label": ariaLabel, nodes: internalNodes, edges: internalEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onInit: instance => { instanceRef.current = instance; }, onNodesChange: changes => onNodesChange?.(changes), onEdgesChange: changes => onEdgesChange?.(changes), onConnect: readOnly ? undefined : onConnect, isValidConnection: isValidConnection, nodesDraggable: !readOnly, nodesConnectable: !readOnly, elementsSelectable: !readOnly, edgesReconnectable: !readOnly, nodesFocusable: true, edgesFocusable: true, deleteKeyCode: readOnly ? null : ['Backspace', 'Delete'], fitView: fitView, fitViewOptions: fitViewOptions, defaultViewport: defaultViewport, viewport: viewport, onViewportChange: onViewportChange, minZoom: minZoom, maxZoom: maxZoom, panOnDrag: panOnDrag, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, connectOnClick: connectOnClick && !readOnly, connectionLineComponent: NodeGraphConnectionLine, onlyRenderVisibleElements: onlyRenderVisibleElements, noDragClassName: "nodrag", noPanClassName: "nopan", children: controls && _jsx(Panel, { position: "bottom-left", children: _jsx(NodeGraphControls, { fitViewLabel: controlLabels?.fitView, fitViewOptions: fitViewOptions, zoomInLabel: controlLabels?.zoomIn, zoomOutLabel: controlLabels?.zoomOut }) }) }), nodes.length === 0 && _jsx("div", { className: "inkra-node-graph__empty", role: "status", children: emptyState })] }) });
|
|
287
|
+
}
|
|
288
|
+
const NodeGraphRoot = forwardRef(NodeGraphRootInner);
|
|
289
|
+
export function applyNodeGraphNodeChanges(changes, nodes) {
|
|
290
|
+
return applyFlowNodeChanges(changes, nodes);
|
|
291
|
+
}
|
|
292
|
+
export function applyNodeGraphEdgeChanges(changes, edges) {
|
|
293
|
+
return applyFlowEdgeChanges(changes, edges);
|
|
294
|
+
}
|
|
295
|
+
export function addNodeGraphEdge(connection, edges) {
|
|
296
|
+
return addFlowEdge(connection, edges);
|
|
297
|
+
}
|
|
298
|
+
export const NodeGraph = {
|
|
299
|
+
Root: NodeGraphRoot,
|
|
300
|
+
Node: NodeGraphNode,
|
|
301
|
+
Port: NodeGraphPort,
|
|
302
|
+
Edge: NodeGraphEdge,
|
|
303
|
+
Controls: NodeGraphControls,
|
|
304
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
import type { NumberTickerLocale } from './model.js';
|
|
3
|
+
export interface NumberTickerRenderDetails {
|
|
4
|
+
animating: boolean;
|
|
5
|
+
formattedValue: string;
|
|
6
|
+
}
|
|
7
|
+
export interface NumberTickerDeltaDetails {
|
|
8
|
+
direction: 'increase' | 'decrease';
|
|
9
|
+
formattedDelta: string;
|
|
10
|
+
}
|
|
11
|
+
export interface NumberTickerProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'prefix'> {
|
|
12
|
+
value: number;
|
|
13
|
+
locale?: NumberTickerLocale;
|
|
14
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
15
|
+
prefix?: ReactNode;
|
|
16
|
+
suffix?: ReactNode;
|
|
17
|
+
renderValue?: (value: number, details: NumberTickerRenderDetails) => ReactNode;
|
|
18
|
+
getValueLabel?: (value: number, formattedValue: string) => string;
|
|
19
|
+
animate?: boolean;
|
|
20
|
+
duration?: number;
|
|
21
|
+
easing?: (progress: number) => number;
|
|
22
|
+
showDelta?: boolean;
|
|
23
|
+
deltaDuration?: number;
|
|
24
|
+
deltaFormatOptions?: Intl.NumberFormatOptions;
|
|
25
|
+
renderDelta?: (delta: number, details: NumberTickerDeltaDetails) => ReactNode;
|
|
26
|
+
}
|
|
27
|
+
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';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { PencilText } from '../PencilText.js';
|
|
4
|
+
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
5
|
+
import { canAnimateTickerValue, createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, tickerValueChanged, } from './model.js';
|
|
6
|
+
function usePrefersReducedMotion() {
|
|
7
|
+
const [reduced, setReduced] = useState(false);
|
|
8
|
+
useBrowserLayoutEffect(() => {
|
|
9
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
|
|
10
|
+
return undefined;
|
|
11
|
+
const query = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
12
|
+
const update = () => setReduced(query.matches);
|
|
13
|
+
update();
|
|
14
|
+
query.addEventListener('change', update);
|
|
15
|
+
return () => query.removeEventListener('change', update);
|
|
16
|
+
}, []);
|
|
17
|
+
return reduced;
|
|
18
|
+
}
|
|
19
|
+
function lettering(content, seed) {
|
|
20
|
+
return typeof content === 'string' ? _jsx(PencilText, { seed: seed, children: content }) : content;
|
|
21
|
+
}
|
|
22
|
+
function accessibleAffix(content) {
|
|
23
|
+
return typeof content === 'string' || typeof content === 'number' ? String(content) : '';
|
|
24
|
+
}
|
|
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
|
+
const rootRef = useRef(null);
|
|
27
|
+
const frameRef = useRef(null);
|
|
28
|
+
const deltaTimerRef = useRef(null);
|
|
29
|
+
const displayedRef = useRef(value);
|
|
30
|
+
const targetRef = useRef(value);
|
|
31
|
+
const [displayedValue, setDisplayedValue] = useState(value);
|
|
32
|
+
const [announcedValue, setAnnouncedValue] = useState(value);
|
|
33
|
+
const [animating, setAnimating] = useState(false);
|
|
34
|
+
const [delta, setDelta] = useState(null);
|
|
35
|
+
const reducedMotion = usePrefersReducedMotion();
|
|
36
|
+
const localeKey = Array.isArray(locale) ? locale.join('\u0000') : locale;
|
|
37
|
+
const formatOptionsKey = JSON.stringify(formatOptions ?? {});
|
|
38
|
+
const deltaFormatOptionsKey = JSON.stringify(deltaFormatOptions ?? formatOptions ?? {});
|
|
39
|
+
const formatter = useMemo(() => createNumberFormatter(locale, formatOptions), [localeKey, formatOptionsKey]);
|
|
40
|
+
const deltaFormatter = useMemo(() => createNumberFormatter(locale, deltaFormatOptions ?? formatOptions), [localeKey, deltaFormatOptionsKey]);
|
|
41
|
+
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
42
|
+
useBrowserLayoutEffect(() => {
|
|
43
|
+
const previousTarget = targetRef.current;
|
|
44
|
+
targetRef.current = value;
|
|
45
|
+
if (showDelta && tickerValueChanged(previousTarget, value) && Number.isFinite(previousTarget) && Number.isFinite(value)) {
|
|
46
|
+
const nextDelta = value - previousTarget;
|
|
47
|
+
setDelta(nextDelta);
|
|
48
|
+
if (deltaTimerRef.current !== null)
|
|
49
|
+
clearTimeout(deltaTimerRef.current);
|
|
50
|
+
if (deltaDuration > 0 && Number.isFinite(deltaDuration)) {
|
|
51
|
+
deltaTimerRef.current = setTimeout(() => {
|
|
52
|
+
deltaTimerRef.current = null;
|
|
53
|
+
setDelta(null);
|
|
54
|
+
}, deltaDuration);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (!showDelta) {
|
|
58
|
+
setDelta(null);
|
|
59
|
+
}
|
|
60
|
+
if (frameRef.current !== null) {
|
|
61
|
+
cancelAnimationFrame(frameRef.current);
|
|
62
|
+
frameRef.current = null;
|
|
63
|
+
}
|
|
64
|
+
const reduceImmediately = reducedMotion
|
|
65
|
+
|| (typeof window !== 'undefined'
|
|
66
|
+
&& typeof window.matchMedia === 'function'
|
|
67
|
+
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
68
|
+
const from = displayedRef.current;
|
|
69
|
+
if (!animate || reduceImmediately || !canAnimateTickerValue(from, value, duration)) {
|
|
70
|
+
displayedRef.current = value;
|
|
71
|
+
setDisplayedValue(value);
|
|
72
|
+
setAnnouncedValue(value);
|
|
73
|
+
setAnimating(false);
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
let startTime = null;
|
|
77
|
+
setAnimating(true);
|
|
78
|
+
const tick = (time) => {
|
|
79
|
+
if (startTime === null)
|
|
80
|
+
startTime = time;
|
|
81
|
+
const progress = Math.min(1, Math.max(0, (time - startTime) / duration));
|
|
82
|
+
const next = interpolateTickerValue(from, value, easing(progress));
|
|
83
|
+
displayedRef.current = next;
|
|
84
|
+
setDisplayedValue(next);
|
|
85
|
+
if (progress < 1) {
|
|
86
|
+
frameRef.current = requestAnimationFrame(tick);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
frameRef.current = null;
|
|
90
|
+
displayedRef.current = value;
|
|
91
|
+
setDisplayedValue(value);
|
|
92
|
+
setAnnouncedValue(value);
|
|
93
|
+
setAnimating(false);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
frameRef.current = requestAnimationFrame(tick);
|
|
97
|
+
return () => {
|
|
98
|
+
if (frameRef.current !== null) {
|
|
99
|
+
cancelAnimationFrame(frameRef.current);
|
|
100
|
+
frameRef.current = null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}, [animate, deltaDuration, duration, easing, reducedMotion, showDelta, value]);
|
|
104
|
+
useEffect(() => () => {
|
|
105
|
+
if (frameRef.current !== null)
|
|
106
|
+
cancelAnimationFrame(frameRef.current);
|
|
107
|
+
if (deltaTimerRef.current !== null)
|
|
108
|
+
clearTimeout(deltaTimerRef.current);
|
|
109
|
+
}, []);
|
|
110
|
+
const formattedValue = formatTickerValue(formatter, displayedValue);
|
|
111
|
+
const finalFormattedValue = formatTickerValue(formatter, value);
|
|
112
|
+
const announcedFormattedValue = formatTickerValue(formatter, announcedValue);
|
|
113
|
+
const defaultAccessibleLabel = [accessibleAffix(prefix), announcedFormattedValue, accessibleAffix(suffix)]
|
|
114
|
+
.filter(Boolean).join(' ');
|
|
115
|
+
const accessibleLabel = getValueLabel?.(announcedValue, announcedFormattedValue) ?? defaultAccessibleLabel;
|
|
116
|
+
const valueContent = renderValue?.(displayedValue, { animating, formattedValue })
|
|
117
|
+
?? (animating
|
|
118
|
+
? _jsx("span", { className: "inkra-number-ticker__native-value", children: formattedValue })
|
|
119
|
+
: _jsx(PencilText, { seed: `number-ticker-${finalFormattedValue}`, children: finalFormattedValue }));
|
|
120
|
+
const deltaContent = delta === null ? null : (() => {
|
|
121
|
+
const formattedDelta = formatTickerDelta(deltaFormatter, delta);
|
|
122
|
+
const details = {
|
|
123
|
+
direction: delta < 0 ? 'decrease' : 'increase',
|
|
124
|
+
formattedDelta,
|
|
125
|
+
};
|
|
126
|
+
return renderDelta?.(delta, details) ?? _jsx(PencilText, { seed: `number-ticker-delta-${formattedDelta}`, children: formattedDelta });
|
|
127
|
+
})();
|
|
128
|
+
const classes = ['inkra-number-ticker', animating && 'inkra-number-ticker--animating', className]
|
|
129
|
+
.filter(Boolean).join(' ');
|
|
130
|
+
return _jsxs("span", { ...props, className: classes, ref: rootRef, "data-animating": animating || undefined, children: [_jsxs("span", { "aria-hidden": "true", className: "inkra-number-ticker__visual", children: [prefix !== undefined && _jsx("span", { className: "inkra-number-ticker__prefix", children: lettering(prefix, 'number-ticker-prefix') }), _jsx("span", { className: "inkra-number-ticker__value", children: valueContent }), suffix !== undefined && _jsx("span", { className: "inkra-number-ticker__suffix", children: lettering(suffix, 'number-ticker-suffix') }), deltaContent !== null && _jsx("span", { className: "inkra-number-ticker__delta", "data-direction": delta < 0 ? 'decrease' : 'increase', children: deltaContent })] }), _jsx("span", { className: "inkra-number-ticker__announcement", "aria-atomic": "true", "aria-live": "polite", children: accessibleLabel })] });
|
|
131
|
+
});
|
|
132
|
+
export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, } from './model.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type NumberTickerLocale = string | readonly string[];
|
|
2
|
+
export declare function createNumberFormatter(locale?: NumberTickerLocale, options?: Intl.NumberFormatOptions): Intl.NumberFormat;
|
|
3
|
+
export declare function formatTickerValue(formatter: Intl.NumberFormat, value: number): string;
|
|
4
|
+
export declare function formatTickerDelta(formatter: Intl.NumberFormat, delta: number): string;
|
|
5
|
+
export declare function interpolateTickerValue(from: number, to: number, progress: number): number;
|
|
6
|
+
export declare function tickerValueChanged(previous: number, next: number): boolean;
|
|
7
|
+
export declare function canAnimateTickerValue(from: number, to: number, duration: number): boolean;
|
|
8
|
+
export declare function easeOutCubic(progress: number): number;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function createNumberFormatter(locale = 'en-US', options) {
|
|
2
|
+
return new Intl.NumberFormat(locale, options);
|
|
3
|
+
}
|
|
4
|
+
export function formatTickerValue(formatter, value) {
|
|
5
|
+
return formatter.format(value);
|
|
6
|
+
}
|
|
7
|
+
export function formatTickerDelta(formatter, delta) {
|
|
8
|
+
const sign = delta > 0 ? '+' : delta < 0 ? '\u2212' : '';
|
|
9
|
+
const magnitude = formatter.formatToParts(Math.abs(delta))
|
|
10
|
+
.filter(part => part.type !== 'plusSign' && part.type !== 'minusSign')
|
|
11
|
+
.map(part => part.value)
|
|
12
|
+
.join('');
|
|
13
|
+
return `${sign}${magnitude}`;
|
|
14
|
+
}
|
|
15
|
+
export function interpolateTickerValue(from, to, progress) {
|
|
16
|
+
if (!Number.isFinite(from) || !Number.isFinite(to))
|
|
17
|
+
return to;
|
|
18
|
+
const boundedProgress = Math.min(1, Math.max(0, progress));
|
|
19
|
+
return from + (to - from) * boundedProgress;
|
|
20
|
+
}
|
|
21
|
+
export function tickerValueChanged(previous, next) {
|
|
22
|
+
return !Object.is(previous, next);
|
|
23
|
+
}
|
|
24
|
+
export function canAnimateTickerValue(from, to, duration) {
|
|
25
|
+
return Number.isFinite(from)
|
|
26
|
+
&& Number.isFinite(to)
|
|
27
|
+
&& Number.isFinite(duration)
|
|
28
|
+
&& duration > 0
|
|
29
|
+
&& tickerValueChanged(from, to);
|
|
30
|
+
}
|
|
31
|
+
export function easeOutCubic(progress) {
|
|
32
|
+
const boundedProgress = Math.min(1, Math.max(0, progress));
|
|
33
|
+
return 1 - (1 - boundedProgress) ** 3;
|
|
34
|
+
}
|