@fusion.dev/helix-headless 0.103.3 → 0.104.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/custom-elements.json +9253 -880
- package/dist/announcement/root/announcement-root.d.ts +9 -2
- package/dist/announcement/root/announcement-root.js +9 -6
- package/dist/badge/root/badge-root.js +2 -3
- package/dist/canvas/edge/canvas-edge.d.ts +112 -0
- package/dist/canvas/edge/canvas-edge.js +66 -0
- package/dist/canvas/index.d.ts +4 -0
- package/dist/canvas/index.js +3 -0
- package/dist/canvas/node/canvas-node.d.ts +211 -0
- package/dist/canvas/node/canvas-node.js +148 -0
- package/dist/canvas/root/canvas-document.d.ts +215 -0
- package/dist/canvas/root/canvas-document.js +284 -0
- package/dist/canvas/root/canvas-root.d.ts +912 -0
- package/dist/canvas/root/canvas-root.js +1814 -0
- package/dist/carousel/root/carousel-root.d.ts +53 -16
- package/dist/carousel/root/carousel-root.js +46 -11
- package/dist/chat/root/chat-root.d.ts +4 -0
- package/dist/chat/root/chat-root.js +11 -7
- package/dist/checkbox/root/checkbox-root.js +1 -0
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +10 -0
- package/dist/checkbox-group/root/checkbox-group-root.js +5 -0
- package/dist/color-input/root/color-input-root.js +21 -5
- package/dist/color-picker/root/color-picker-root.d.ts +55 -0
- package/dist/color-picker/root/color-picker-root.js +61 -12
- package/dist/combobox/root/combobox-root.d.ts +21 -2
- package/dist/combobox/root/combobox-root.js +23 -9
- package/dist/command/root/command-root.d.ts +1 -0
- package/dist/command/root/command-root.js +1 -1
- package/dist/data-table/root/data-table-root.d.ts +43 -42
- package/dist/data-table/root/data-table-root.js +35 -34
- package/dist/date-input/root/date-input-root.d.ts +27 -0
- package/dist/date-input/root/date-input-root.js +38 -9
- package/dist/date-picker/root/date-picker-root.d.ts +28 -16
- package/dist/date-picker/root/date-picker-root.js +17 -15
- package/dist/drilldown/root/drilldown-root.d.ts +1 -0
- package/dist/drilldown/root/drilldown-root.js +5 -2
- package/dist/element-events.generated.js +15 -0
- package/dist/empty/root/empty-root.d.ts +12 -7
- package/dist/empty/root/empty-root.js +13 -10
- package/dist/file-input/root/file-input-root.d.ts +5 -1
- package/dist/file-input/root/file-input-root.js +6 -4
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +1 -1
- package/dist/filter-input/root/filter-input-root.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/input/root/input-root.js +1 -1
- package/dist/lightbox/root/lightbox-root.d.ts +9 -1
- package/dist/lightbox/root/lightbox-root.js +17 -7
- package/dist/listbox/root/listbox-root.d.ts +16 -14
- package/dist/listbox/root/listbox-root.js +24 -15
- package/dist/map/root/map-root.d.ts +6 -3
- package/dist/map/root/map-root.js +8 -2
- package/dist/menu/root/menu-root.d.ts +1 -0
- package/dist/menu/root/menu-root.js +1 -1
- package/dist/notifications/group/notification-group.js +5 -4
- package/dist/notifications/notification/notification.d.ts +10 -10
- package/dist/notifications/notification/notification.js +3 -3
- package/dist/notifications/root/notifications-root.d.ts +1 -0
- package/dist/notifications/root/notifications-root.js +1 -0
- package/dist/number-input/root/number-input-root.d.ts +17 -0
- package/dist/number-input/root/number-input-root.js +22 -6
- package/dist/object-inspector/root/object-inspector-root.d.ts +10 -0
- package/dist/object-inspector/root/object-inspector-root.js +10 -2
- package/dist/outline/root/outline-root.d.ts +15 -0
- package/dist/outline/root/outline-root.js +12 -7
- package/dist/pagination/root/pagination-root.d.ts +18 -18
- package/dist/pagination/root/pagination-root.js +5 -5
- package/dist/pagination/size/pagination-size.d.ts +10 -9
- package/dist/pagination/size/pagination-size.js +4 -4
- package/dist/pagination/summary/pagination-summary.js +6 -5
- package/dist/pin-input/root/pin-input-root.d.ts +5 -0
- package/dist/pin-input/root/pin-input-root.js +10 -6
- package/dist/rating/root/rating-root.js +3 -2
- package/dist/select/root/select-root.d.ts +1 -0
- package/dist/select/root/select-root.js +8 -3
- package/dist/slider/root/slider-root.d.ts +12 -0
- package/dist/slider/root/slider-root.js +13 -1
- package/dist/splitter/root/splitter-root.d.ts +44 -35
- package/dist/splitter/root/splitter-root.js +27 -22
- package/dist/switch/root/switch-root.js +1 -0
- package/dist/table/column/table-column.d.ts +17 -0
- package/dist/table/column/table-column.js +16 -4
- package/dist/table/root/table-root.d.ts +1 -0
- package/dist/table/root/table-root.js +1 -1
- package/dist/textarea/root/textarea-root.js +1 -1
- package/dist/time-input/root/time-input-root.d.ts +29 -0
- package/dist/time-input/root/time-input-root.js +31 -10
- package/dist/time-picker/root/time-picker-root.d.ts +14 -0
- package/dist/time-picker/root/time-picker-root.js +16 -14
- package/dist/toast/item/toast-item.d.ts +12 -11
- package/dist/toast/item/toast-item.js +11 -5
- package/dist/toolbar/root/toolbar-root.js +2 -16
- package/dist/tooltip/root/tooltip-root.d.ts +1 -0
- package/dist/tooltip/root/tooltip-root.js +6 -0
- package/dist/tour/root/tour-root.d.ts +10 -10
- package/dist/tour/root/tour-root.js +5 -5
- package/dist/tree/root/tree-root.d.ts +1 -0
- package/dist/tree/root/tree-root.js +1 -1
- package/dist/video/root/video-root.d.ts +84 -0
- package/dist/video/root/video-root.js +116 -41
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +4 -3
- package/package.json +5 -5
- package/types/jsx.d.ts +5419 -716
- package/types/svelte.d.ts +3 -0
- package/types/vue.d.ts +1607 -213
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON Canvas document, and the reading of it this family does.
|
|
3
|
+
*
|
|
4
|
+
* [JSON Canvas](https://jsoncanvas.org) is an open format: a node carries `x`,
|
|
5
|
+
* `y`, `width`, `height` and a type, and an edge names the two nodes and,
|
|
6
|
+
* optionally, the side it leaves and arrives on. **Position is data here**,
|
|
7
|
+
* which is the whole reason this family draws and `Map` does not: somebody
|
|
8
|
+
* placed every card and the placement carries the meaning, so there is no
|
|
9
|
+
* layout to compute and drawing it is arithmetic.
|
|
10
|
+
*/
|
|
11
|
+
/** Which edge of a card a connector leaves from or arrives at. */
|
|
12
|
+
export type CanvasSide = 'top' | 'right' | 'bottom' | 'left';
|
|
13
|
+
/** One card, group or link on the board. */
|
|
14
|
+
export interface CanvasNode {
|
|
15
|
+
/** What identifies it, and what an edge names. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Whatever the card says, where it says anything. */
|
|
18
|
+
text?: string | undefined;
|
|
19
|
+
/** What it is: a card of text, a file, a link, or a box around others. */
|
|
20
|
+
type: 'text' | 'file' | 'link' | 'group';
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
/** A preset from `1` to `6`, or a hex color the document chose itself. */
|
|
26
|
+
color?: string | undefined;
|
|
27
|
+
/** The file it stands for, where it is one. */
|
|
28
|
+
file?: string | undefined;
|
|
29
|
+
/** The address it points at, where it is one. */
|
|
30
|
+
url?: string | undefined;
|
|
31
|
+
/** What the group is called. */
|
|
32
|
+
label?: string | undefined;
|
|
33
|
+
/** Where in the file it points, a heading or a block, starting with `#`. */
|
|
34
|
+
subpath?: string | undefined;
|
|
35
|
+
/** The picture behind a group, where it has one. */
|
|
36
|
+
background?: string | undefined;
|
|
37
|
+
/** How that picture fills the group: `cover`, `ratio` or `repeat`. */
|
|
38
|
+
backgroundStyle?: 'cover' | 'ratio' | 'repeat' | undefined;
|
|
39
|
+
}
|
|
40
|
+
/** What a connector carries at an end: an arrowhead, or nothing. */
|
|
41
|
+
export type CanvasEnd = 'none' | 'arrow';
|
|
42
|
+
/** One connector between two nodes. */
|
|
43
|
+
export interface CanvasEdge {
|
|
44
|
+
id: string;
|
|
45
|
+
fromNode: string;
|
|
46
|
+
toNode: string;
|
|
47
|
+
fromSide?: CanvasSide | undefined;
|
|
48
|
+
toSide?: CanvasSide | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* What sits at the end it leaves from. Absent is nothing, which is the
|
|
51
|
+
* format's own default and what makes an ordinary edge a one-way arrow.
|
|
52
|
+
*/
|
|
53
|
+
fromEnd?: CanvasEnd | undefined;
|
|
54
|
+
/** What sits at the end it arrives at. Absent is an arrow, as the format says. */
|
|
55
|
+
toEnd?: CanvasEnd | undefined;
|
|
56
|
+
color?: string | undefined;
|
|
57
|
+
label?: string | undefined;
|
|
58
|
+
/** How it travels, where it says; the board's own default where it does not. */
|
|
59
|
+
path?: CanvasPath | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** A whole board. */
|
|
62
|
+
export interface CanvasDocument {
|
|
63
|
+
nodes?: CanvasNode[];
|
|
64
|
+
edges?: CanvasEdge[];
|
|
65
|
+
}
|
|
66
|
+
/** A rectangle in the document's own coordinates. */
|
|
67
|
+
interface Box {
|
|
68
|
+
x: number;
|
|
69
|
+
y: number;
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
}
|
|
73
|
+
/** Whether the inner box sits inside the outer one. */
|
|
74
|
+
export declare const inside: (inner: Box, outer: Box) => boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Which group a node belongs to, which the format does not say and the geometry
|
|
77
|
+
* does.
|
|
78
|
+
*
|
|
79
|
+
* **The smallest group that contains it**, because groups nest: a card inside a
|
|
80
|
+
* group inside a group belongs to the inner one, and taking the first match
|
|
81
|
+
* would hand it to whichever was written first. A group never belongs to
|
|
82
|
+
* itself.
|
|
83
|
+
*/
|
|
84
|
+
export declare const groupOf: (node: CanvasNode, nodes: CanvasNode[]) => CanvasNode | undefined;
|
|
85
|
+
/** The whole board's extent, which is what the plane is sized to. */
|
|
86
|
+
export declare const extent: (nodes: CanvasNode[]) => Box;
|
|
87
|
+
/** The point an edge leaves from or arrives at, on a named side. */
|
|
88
|
+
export declare const port: (node: CanvasNode, side: CanvasSide) => {
|
|
89
|
+
x: number;
|
|
90
|
+
y: number;
|
|
91
|
+
};
|
|
92
|
+
/** How a connector travels between the two cards it joins. */
|
|
93
|
+
export type CanvasPath = 'curve' | 'straight' | 'step';
|
|
94
|
+
/** A point in the document's own units. */
|
|
95
|
+
type Point = {
|
|
96
|
+
x: number;
|
|
97
|
+
y: number;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The path one connector is drawn along, in board coordinates.
|
|
101
|
+
*
|
|
102
|
+
* **A curve by default**: the control points sit out along the normal of each
|
|
103
|
+
* side, so a connector leaves a card square to the edge it named and arrives
|
|
104
|
+
* the same way, which is what makes the two ends read as attached rather than
|
|
105
|
+
* aimed. `straight` is the shortest line between the two sides, and `step`
|
|
106
|
+
* turns in right angles, which is what a flowchart reads as.
|
|
107
|
+
*/
|
|
108
|
+
export declare const connector: (edge: CanvasEdge, from: CanvasNode, to: CanvasNode, path?: CanvasPath, heads?: {
|
|
109
|
+
start: boolean;
|
|
110
|
+
end: boolean;
|
|
111
|
+
}) => {
|
|
112
|
+
d: string;
|
|
113
|
+
mid: {
|
|
114
|
+
x: number;
|
|
115
|
+
y: number;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
/** How long an arrowhead is, in the document's own units. */
|
|
119
|
+
export declare const HEAD = 8;
|
|
120
|
+
/**
|
|
121
|
+
* Where a connection meets each card, and on which side: the sides it names,
|
|
122
|
+
* and the nearest where it names none.
|
|
123
|
+
*/
|
|
124
|
+
export declare const ends: (edge: CanvasEdge, from: CanvasNode, to: CanvasNode) => {
|
|
125
|
+
start: Point;
|
|
126
|
+
end: Point;
|
|
127
|
+
fromSide: CanvasSide;
|
|
128
|
+
toSide: CanvasSide;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* The side of a card nearest a point, which is where a connection a reader is
|
|
132
|
+
* holding arrives.
|
|
133
|
+
*
|
|
134
|
+
* **The reader's point, not the card's middle.** A connection dropped on a card
|
|
135
|
+
* arrived at whichever side faced the card it came from, so a reader aiming for
|
|
136
|
+
* the top of a card watched it land on the left.
|
|
137
|
+
*/
|
|
138
|
+
export declare const sideNear: (node: CanvasNode, point: Point) => CanvasSide;
|
|
139
|
+
/**
|
|
140
|
+
* The order a screen reader meets the cards in, which space does not supply.
|
|
141
|
+
*
|
|
142
|
+
* **Spatial is a heuristic and says so.** Cards are banded into rows by their
|
|
143
|
+
* top edge, then read left to right within a row, groups before what they hold,
|
|
144
|
+
* which is how a person reads a board that was laid out on a grid. A loose
|
|
145
|
+
* board defeats it, which is why `edges` exists.
|
|
146
|
+
*
|
|
147
|
+
* **Edges is the arrows.** It starts at the cards nothing points to and follows
|
|
148
|
+
* the connectors, which on a board that *is* a flow is the order its author
|
|
149
|
+
* meant. Anything unreachable follows in spatial order, or a card nobody linked
|
|
150
|
+
* would vanish from the list that exists to find it.
|
|
151
|
+
*/
|
|
152
|
+
export declare const reading: (nodes: CanvasNode[], edges: CanvasEdge[], order: 'spatial' | 'edges') => CanvasNode[];
|
|
153
|
+
/** A card as the elements take it: attributes, and the text to put inside. */
|
|
154
|
+
export interface CanvasNodeProps {
|
|
155
|
+
name: string;
|
|
156
|
+
x: number;
|
|
157
|
+
y: number;
|
|
158
|
+
width: number;
|
|
159
|
+
height: number;
|
|
160
|
+
/** What it means, from the format's six presets. */
|
|
161
|
+
tone?: CanvasTone | undefined;
|
|
162
|
+
/** A color the document chose that is none of the six, to set as `--canvas-node-color`. */
|
|
163
|
+
color?: string | undefined;
|
|
164
|
+
label?: string | undefined;
|
|
165
|
+
group: boolean;
|
|
166
|
+
/** What the card says, for a card of text. */
|
|
167
|
+
text?: string | undefined;
|
|
168
|
+
/** The file it stands for, for a card that is one. */
|
|
169
|
+
file?: string | undefined;
|
|
170
|
+
/** The address it points at, for a card that is a link. */
|
|
171
|
+
url?: string | undefined;
|
|
172
|
+
/** Where in the file it points, for a card that is one. */
|
|
173
|
+
subpath?: string | undefined;
|
|
174
|
+
/** The picture behind a group, and how it fills it. */
|
|
175
|
+
background?: string | undefined;
|
|
176
|
+
backgroundStyle?: 'cover' | 'ratio' | 'repeat' | undefined;
|
|
177
|
+
}
|
|
178
|
+
/** A connection as the elements take it. */
|
|
179
|
+
export interface CanvasEdgeProps {
|
|
180
|
+
name: string;
|
|
181
|
+
from: string;
|
|
182
|
+
to: string;
|
|
183
|
+
fromSide?: CanvasSide | undefined;
|
|
184
|
+
toSide?: CanvasSide | undefined;
|
|
185
|
+
fromEnd?: CanvasEnd | undefined;
|
|
186
|
+
toEnd?: CanvasEnd | undefined;
|
|
187
|
+
/** What it means, from the format's six presets. */
|
|
188
|
+
tone?: CanvasTone | undefined;
|
|
189
|
+
/** A color the document chose that is none of the six, to set as `--canvas-edge-color`. */
|
|
190
|
+
color?: string | undefined;
|
|
191
|
+
label?: string | undefined;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* A JSON Canvas document as the props of the elements that draw it.
|
|
195
|
+
*
|
|
196
|
+
* **Props rather than elements**, because what a card holds is the consumer's
|
|
197
|
+
* to render: a text card is text, a file card is whatever they show for that
|
|
198
|
+
* file, a link card is the link as they want it drawn. So this does the half
|
|
199
|
+
* that is mapping, ids to names and types to `group`, and hands back the
|
|
200
|
+
* content for the consumer to put inside, in whichever renderer they use.
|
|
201
|
+
*/
|
|
202
|
+
export declare const fromJsonCanvas: (document: CanvasDocument) => {
|
|
203
|
+
nodes: CanvasNodeProps[];
|
|
204
|
+
edges: CanvasEdgeProps[];
|
|
205
|
+
};
|
|
206
|
+
/** What every Helix component calls a meaning. */
|
|
207
|
+
export type CanvasTone = 'accent' | 'danger' | 'info' | 'neutral' | 'primary' | 'success' | 'warning';
|
|
208
|
+
/** The tone a JSON Canvas color means, where it is one of the six presets. */
|
|
209
|
+
export declare const toneOf: (color: string | undefined) => CanvasTone | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* The JSON Canvas color a card or connection writes back out: its tone as a
|
|
212
|
+
* preset, or the custom color it was given, or nothing.
|
|
213
|
+
*/
|
|
214
|
+
export declare const colorOf: (tone: CanvasTone | undefined, custom: string) => string | undefined;
|
|
215
|
+
export {};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
export const inside = (inner, outer) => inner.x >= outer.x &&
|
|
2
|
+
inner.y >= outer.y &&
|
|
3
|
+
inner.x + inner.width <= outer.x + outer.width &&
|
|
4
|
+
inner.y + inner.height <= outer.y + outer.height;
|
|
5
|
+
export const groupOf = (node, nodes) => {
|
|
6
|
+
let held;
|
|
7
|
+
for (const other of nodes) {
|
|
8
|
+
if (other.type !== 'group' || other.id === node.id || !inside(node, other)) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (!held || other.width * other.height < held.width * held.height) {
|
|
12
|
+
held = other;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return held;
|
|
16
|
+
};
|
|
17
|
+
export const extent = (nodes) => {
|
|
18
|
+
if (nodes.length === 0) {
|
|
19
|
+
return { height: 0, width: 0, x: 0, y: 0 };
|
|
20
|
+
}
|
|
21
|
+
const x = Math.min(...nodes.map((one) => one.x));
|
|
22
|
+
const y = Math.min(...nodes.map((one) => one.y));
|
|
23
|
+
const right = Math.max(...nodes.map((one) => one.x + one.width));
|
|
24
|
+
const bottom = Math.max(...nodes.map((one) => one.y + one.height));
|
|
25
|
+
return { height: bottom - y, width: right - x, x, y };
|
|
26
|
+
};
|
|
27
|
+
export const port = (node, side) => {
|
|
28
|
+
const midX = node.x + node.width / 2;
|
|
29
|
+
const midY = node.y + node.height / 2;
|
|
30
|
+
if (side === 'top') {
|
|
31
|
+
return { x: midX, y: node.y };
|
|
32
|
+
}
|
|
33
|
+
if (side === 'bottom') {
|
|
34
|
+
return { x: midX, y: node.y + node.height };
|
|
35
|
+
}
|
|
36
|
+
if (side === 'left') {
|
|
37
|
+
return { x: node.x, y: midY };
|
|
38
|
+
}
|
|
39
|
+
return { x: node.x + node.width, y: midY };
|
|
40
|
+
};
|
|
41
|
+
const nearestSides = (from, to) => {
|
|
42
|
+
const acrossX = to.x + to.width / 2 - (from.x + from.width / 2);
|
|
43
|
+
const acrossY = to.y + to.height / 2 - (from.y + from.height / 2);
|
|
44
|
+
if (Math.abs(acrossX) >= Math.abs(acrossY)) {
|
|
45
|
+
return acrossX >= 0 ? ['right', 'left'] : ['left', 'right'];
|
|
46
|
+
}
|
|
47
|
+
return acrossY >= 0 ? ['bottom', 'top'] : ['top', 'bottom'];
|
|
48
|
+
};
|
|
49
|
+
const normal = (side) => ({
|
|
50
|
+
x: side === 'left' ? -1 : side === 'right' ? 1 : 0,
|
|
51
|
+
y: side === 'top' ? -1 : side === 'bottom' ? 1 : 0,
|
|
52
|
+
});
|
|
53
|
+
const STUB = 20;
|
|
54
|
+
const RADIUS = 8;
|
|
55
|
+
const segments = (points) => points.slice(1).map((point, index) => [points[index] ?? point, point]);
|
|
56
|
+
const along = (points, share) => {
|
|
57
|
+
const runs = segments(points).map(([from, to]) => ({
|
|
58
|
+
from,
|
|
59
|
+
length: Math.hypot(to.x - from.x, to.y - from.y),
|
|
60
|
+
to,
|
|
61
|
+
}));
|
|
62
|
+
let left = runs.reduce((sum, one) => sum + one.length, 0) * share;
|
|
63
|
+
for (const { from, length, to } of runs) {
|
|
64
|
+
if (left <= length && length > 0) {
|
|
65
|
+
return {
|
|
66
|
+
x: from.x + ((to.x - from.x) * left) / length,
|
|
67
|
+
y: from.y + ((to.y - from.y) * left) / length,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
left -= length;
|
|
71
|
+
}
|
|
72
|
+
return points[points.length - 1] ?? { x: 0, y: 0 };
|
|
73
|
+
};
|
|
74
|
+
const rounded = (points) => {
|
|
75
|
+
const first = points[0] ?? { x: 0, y: 0 };
|
|
76
|
+
const last = points[points.length - 1] ?? first;
|
|
77
|
+
const corners = points
|
|
78
|
+
.slice(1, -1)
|
|
79
|
+
.map((corner, index) => {
|
|
80
|
+
const before = points[index] ?? corner;
|
|
81
|
+
const after = points[index + 2] ?? corner;
|
|
82
|
+
const inLength = Math.hypot(corner.x - before.x, corner.y - before.y);
|
|
83
|
+
const outLength = Math.hypot(after.x - corner.x, after.y - corner.y);
|
|
84
|
+
const radius = Math.min(RADIUS, inLength / 2, outLength / 2);
|
|
85
|
+
const enter = {
|
|
86
|
+
x: corner.x - ((corner.x - before.x) / (inLength || 1)) * radius,
|
|
87
|
+
y: corner.y - ((corner.y - before.y) / (inLength || 1)) * radius,
|
|
88
|
+
};
|
|
89
|
+
const leave = {
|
|
90
|
+
x: corner.x + ((after.x - corner.x) / (outLength || 1)) * radius,
|
|
91
|
+
y: corner.y + ((after.y - corner.y) / (outLength || 1)) * radius,
|
|
92
|
+
};
|
|
93
|
+
return ` L ${enter.x} ${enter.y} Q ${corner.x} ${corner.y} ${leave.x} ${leave.y}`;
|
|
94
|
+
})
|
|
95
|
+
.join('');
|
|
96
|
+
return `M ${first.x} ${first.y}${corners} L ${last.x} ${last.y}`;
|
|
97
|
+
};
|
|
98
|
+
const steps = (start, fromSide, end, toSide) => {
|
|
99
|
+
const out = normal(fromSide);
|
|
100
|
+
const into = normal(toSide);
|
|
101
|
+
const a = { x: start.x + out.x * STUB, y: start.y + out.y * STUB };
|
|
102
|
+
const b = { x: end.x + into.x * STUB, y: end.y + into.y * STUB };
|
|
103
|
+
const across = (side) => side === 'left' || side === 'right';
|
|
104
|
+
let middle;
|
|
105
|
+
if (across(fromSide) && across(toSide)) {
|
|
106
|
+
const x = (a.x + b.x) / 2;
|
|
107
|
+
middle = [
|
|
108
|
+
{ x, y: a.y },
|
|
109
|
+
{ x, y: b.y },
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
else if (!across(fromSide) && !across(toSide)) {
|
|
113
|
+
const y = (a.y + b.y) / 2;
|
|
114
|
+
middle = [
|
|
115
|
+
{ x: a.x, y },
|
|
116
|
+
{ x: b.x, y },
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
else if (across(fromSide)) {
|
|
120
|
+
middle = [{ x: b.x, y: a.y }];
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
middle = [{ x: a.x, y: b.y }];
|
|
124
|
+
}
|
|
125
|
+
const all = [start, a, ...middle, b, end];
|
|
126
|
+
return all.filter((point, at) => {
|
|
127
|
+
if (at === 0 || at === all.length - 1) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
const before = all[at - 1] ?? point;
|
|
131
|
+
const after = all[at + 1] ?? point;
|
|
132
|
+
const same = point.x === before.x && point.y === before.y;
|
|
133
|
+
const into = { x: Math.sign(point.x - before.x), y: Math.sign(point.y - before.y) };
|
|
134
|
+
const onward = { x: Math.sign(after.x - point.x), y: Math.sign(after.y - point.y) };
|
|
135
|
+
const straight = into.x === onward.x && into.y === onward.y;
|
|
136
|
+
return !same && !straight;
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
export const connector = (edge, from, to, path = 'curve', heads = { end: false, start: false }) => {
|
|
140
|
+
const { end: port2, fromSide, start: port1, toSide } = ends(edge, from, to);
|
|
141
|
+
const inward = (point, side, other) => {
|
|
142
|
+
if (path === 'straight') {
|
|
143
|
+
const length = Math.hypot(other.x - point.x, other.y - point.y) || 1;
|
|
144
|
+
return {
|
|
145
|
+
x: point.x + ((other.x - point.x) / length) * HEAD,
|
|
146
|
+
y: point.y + ((other.y - point.y) / length) * HEAD,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { x: point.x + normal(side).x * HEAD, y: point.y + normal(side).y * HEAD };
|
|
150
|
+
};
|
|
151
|
+
const start = heads.start ? inward(port1, fromSide, port2) : port1;
|
|
152
|
+
const end = heads.end ? inward(port2, toSide, port1) : port2;
|
|
153
|
+
if (path === 'straight') {
|
|
154
|
+
return {
|
|
155
|
+
d: `M ${start.x} ${start.y} L ${end.x} ${end.y}`,
|
|
156
|
+
mid: { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 },
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (path === 'step') {
|
|
160
|
+
const points = steps(start, fromSide, end, toSide);
|
|
161
|
+
return { d: rounded(points), mid: along(points, 0.5) };
|
|
162
|
+
}
|
|
163
|
+
const reach = Math.max(40, Math.hypot(end.x - start.x, end.y - start.y) / 3);
|
|
164
|
+
const c1 = { x: start.x + normal(fromSide).x * reach, y: start.y + normal(fromSide).y * reach };
|
|
165
|
+
const c2 = { x: end.x + normal(toSide).x * reach, y: end.y + normal(toSide).y * reach };
|
|
166
|
+
return {
|
|
167
|
+
d: `M ${start.x} ${start.y} C ${c1.x} ${c1.y} ${c2.x} ${c2.y} ${end.x} ${end.y}`,
|
|
168
|
+
mid: { x: (start.x + end.x + c1.x + c2.x) / 4, y: (start.y + end.y + c1.y + c2.y) / 4 },
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
export const HEAD = 8;
|
|
172
|
+
export const ends = (edge, from, to) => {
|
|
173
|
+
const [fallbackFrom, fallbackTo] = nearestSides(from, to);
|
|
174
|
+
const fromSide = edge.fromSide ?? fallbackFrom;
|
|
175
|
+
const toSide = edge.toSide ?? fallbackTo;
|
|
176
|
+
return { end: port(to, toSide), fromSide, start: port(from, fromSide), toSide };
|
|
177
|
+
};
|
|
178
|
+
export const sideNear = (node, point) => ['top', 'right', 'bottom', 'left']
|
|
179
|
+
.map((side) => {
|
|
180
|
+
const at = port(node, side);
|
|
181
|
+
return { distance: Math.hypot(at.x - point.x, at.y - point.y), side };
|
|
182
|
+
})
|
|
183
|
+
.sort((a, b) => a.distance - b.distance)[0]?.side ?? 'top';
|
|
184
|
+
export const reading = (nodes, edges, order) => {
|
|
185
|
+
const band = 80;
|
|
186
|
+
const byPlace = (a, b) => {
|
|
187
|
+
const rowA = Math.round(a.y / band);
|
|
188
|
+
const rowB = Math.round(b.y / band);
|
|
189
|
+
return rowA === rowB ? a.x - b.x : rowA - rowB;
|
|
190
|
+
};
|
|
191
|
+
const groups = nodes.filter((node) => node.type === 'group');
|
|
192
|
+
const holder = new Map(nodes.map((node) => [node.id, groupOf(node, groups)?.id]));
|
|
193
|
+
const members = new Map();
|
|
194
|
+
for (const node of nodes) {
|
|
195
|
+
const id = holder.get(node.id);
|
|
196
|
+
members.set(id, [...(members.get(id) ?? []), node]);
|
|
197
|
+
}
|
|
198
|
+
const held = (id) => (members.get(id) ?? []).sort(byPlace);
|
|
199
|
+
const spatial = [];
|
|
200
|
+
const placed = new Set();
|
|
201
|
+
const visit = (node) => {
|
|
202
|
+
if (placed.has(node.id)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
placed.add(node.id);
|
|
206
|
+
spatial.push(node);
|
|
207
|
+
if (node.type === 'group') {
|
|
208
|
+
for (const inner of held(node.id)) {
|
|
209
|
+
visit(inner);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
for (const node of held(undefined)) {
|
|
214
|
+
visit(node);
|
|
215
|
+
}
|
|
216
|
+
for (const node of [...nodes].sort(byPlace)) {
|
|
217
|
+
visit(node);
|
|
218
|
+
}
|
|
219
|
+
if (order === 'spatial') {
|
|
220
|
+
return spatial;
|
|
221
|
+
}
|
|
222
|
+
const pointedAt = new Set(edges.map((one) => one.toNode));
|
|
223
|
+
const out = new Map();
|
|
224
|
+
for (const edge of edges) {
|
|
225
|
+
out.set(edge.fromNode, [...(out.get(edge.fromNode) ?? []), edge.toNode]);
|
|
226
|
+
}
|
|
227
|
+
const byId = new Map(nodes.map((one) => [one.id, one]));
|
|
228
|
+
const seen = new Set();
|
|
229
|
+
const walked = [];
|
|
230
|
+
const walk = (id) => {
|
|
231
|
+
if (seen.has(id)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
seen.add(id);
|
|
235
|
+
const node = byId.get(id);
|
|
236
|
+
if (node) {
|
|
237
|
+
walked.push(node);
|
|
238
|
+
}
|
|
239
|
+
for (const next of out.get(id) ?? []) {
|
|
240
|
+
walk(next);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
for (const node of spatial.filter((one) => !pointedAt.has(one.id))) {
|
|
244
|
+
walk(node.id);
|
|
245
|
+
}
|
|
246
|
+
return [...walked, ...spatial.filter((one) => !seen.has(one.id))];
|
|
247
|
+
};
|
|
248
|
+
export const fromJsonCanvas = (document) => ({
|
|
249
|
+
edges: (document.edges ?? []).map((edge) => ({
|
|
250
|
+
color: toneOf(edge.color) === undefined ? edge.color : undefined,
|
|
251
|
+
from: edge.fromNode,
|
|
252
|
+
fromEnd: edge.fromEnd,
|
|
253
|
+
fromSide: edge.fromSide,
|
|
254
|
+
label: edge.label,
|
|
255
|
+
name: edge.id,
|
|
256
|
+
to: edge.toNode,
|
|
257
|
+
toEnd: edge.toEnd,
|
|
258
|
+
tone: toneOf(edge.color),
|
|
259
|
+
toSide: edge.toSide,
|
|
260
|
+
})),
|
|
261
|
+
nodes: (document.nodes ?? []).map((node) => ({
|
|
262
|
+
background: node.background,
|
|
263
|
+
backgroundStyle: node.backgroundStyle,
|
|
264
|
+
color: toneOf(node.color) === undefined ? node.color : undefined,
|
|
265
|
+
file: node.file,
|
|
266
|
+
group: node.type === 'group',
|
|
267
|
+
height: node.height,
|
|
268
|
+
label: node.label,
|
|
269
|
+
name: node.id,
|
|
270
|
+
subpath: node.subpath,
|
|
271
|
+
text: node.text,
|
|
272
|
+
tone: toneOf(node.color),
|
|
273
|
+
url: node.url,
|
|
274
|
+
width: node.width,
|
|
275
|
+
x: node.x,
|
|
276
|
+
y: node.y,
|
|
277
|
+
})),
|
|
278
|
+
});
|
|
279
|
+
const PRESETS = ['danger', 'warning', 'accent', 'success', 'info', 'primary'];
|
|
280
|
+
export const toneOf = (color) => color !== undefined && /^[1-6]$/.test(color) ? PRESETS[Number(color) - 1] : undefined;
|
|
281
|
+
export const colorOf = (tone, custom) => {
|
|
282
|
+
const at = tone === undefined ? -1 : PRESETS.indexOf(tone);
|
|
283
|
+
return at >= 0 ? String(at + 1) : custom.trim() || undefined;
|
|
284
|
+
};
|