flowfig 0.10.0 → 0.12.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/README.md +152 -23
- package/dist/atlas.js +6 -2
- package/dist/auto.js +104 -9
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +9 -4
- package/dist/check.d.ts +4 -3
- package/dist/check.js +143 -8
- package/dist/cli.js +180 -70
- package/dist/diff.d.ts +2 -0
- package/dist/diff.js +79 -24
- package/dist/element-looks.js +4 -0
- package/dist/element-world.js +6 -0
- package/dist/element.d.ts +27 -0
- package/dist/element.js +67 -0
- package/dist/explain.d.ts +45 -0
- package/dist/explain.js +161 -0
- package/dist/frames.d.ts +34 -0
- package/dist/frames.js +117 -0
- package/dist/index.js +328 -152
- package/dist/load.js +9 -4
- package/dist/looks.d.ts +15 -0
- package/dist/looks.js +32 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +14 -6
- package/dist/model.d.ts +105 -3
- package/dist/model.js +221 -21
- package/dist/scene.d.ts +2 -0
- package/dist/source.d.ts +4 -2
- package/dist/source.js +15 -9
- package/dist/svg.d.ts +12 -0
- package/dist/svg.js +245 -81
- package/dist/verify.d.ts +6 -0
- package/dist/verify.js +104 -56
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/world-art.d.ts +59 -0
- package/dist/world-art.js +854 -0
- package/dist/world.d.ts +233 -0
- package/dist/world.js +1577 -0
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
2
3
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Fragment, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Fragment, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
4
5
|
import { arcRoom, avoidOf, markSide, route } from './geometry.js';
|
|
5
6
|
import { foldedLabel, groupBox, layoutRail, railState, RAIL } from './rail.js';
|
|
6
7
|
import { textWidth } from './text.js';
|
|
7
8
|
import { checkScene, checkSpec, checkTheme } from './check.js';
|
|
8
9
|
import { autoLayout, rowBreaks } from './auto.js';
|
|
9
|
-
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, EDGE_OFF, EDGE_ON, LIGHT, TONES, ON_ACCENT, toneFill, toneTint, decisions, edgeId, groupGap, isGroup, isRows, lanePlan as planLanes, laneEnd, laneBlock, STUB_ROOM, labelPillW, str, altText, LANE_BLOCK_GAP, LANE_PAD, LANE_ROW_GAP, isLanesLayout, nodes, timelineBeats, timelineLayout, TL_AXIS_H, TL_AXIS_W, TL_BAR_H, TL_ROW_GAP, toBeat, diamondLines, diamondRoom, tightCopies, nodeWidth, labelLines, fitCap, LABEL_LINE, beatMs, STEP_HOLD_MS, playheadItem, loopStartItem, dateLabelRaised, edgeTip, fitScale, specKey, nextWide, } from './model.js';
|
|
10
|
+
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, EDGE_OFF, EDGE_ON, LIGHT, TONES, ON_ACCENT, toneFill, toneTint, decisions, edgeId, groupGap, isGroup, isRows, lanePlan as planLanes, laneEnd, laneBlock, STUB_ROOM, labelPillW, str, altText, LANE_BLOCK_GAP, LANE_PAD, LANE_ROW_GAP, isLanesLayout, nodes, timelineBeats, timelineLayout, TL_AXIS_H, TL_AXIS_W, TL_BAR_H, TL_ROW_GAP, toBeat, diamondLines, diamondRoom, tightCopies, nodeWidth, labelLines, fitCap, LABEL_LINE, beatMs, STEP_HOLD_MS, playheadItem, loopStartItem, dateLabelRaised, edgeTip, fitScale, shownFont, textFloor, specKey, nextWide, scenes, insideStep, flowOrder, readMs, portLabel, OPEN_MS, } from './model.js';
|
|
10
11
|
export { LIGHT, DARK } from './model.js';
|
|
11
12
|
const DEFAULTS = { ...LIGHT, font: 'inherit' };
|
|
12
13
|
const v = (k) => `var(--fig-${k}, ${DEFAULTS[k]})`;
|
|
@@ -56,21 +57,75 @@ const HIDDEN = {
|
|
|
56
57
|
* Use `toSvg` from `flowfig/svg` for a static animated SVG.
|
|
57
58
|
*/
|
|
58
59
|
export function Flow(props) {
|
|
59
|
-
const
|
|
60
|
+
const all = useMemo(() => scenes(props), [props.layout, props.edges, props.steps, props.lanes, props.timeline, props.rail, props.theme, props.speed]);
|
|
61
|
+
const [path, setPath] = useState([]);
|
|
62
|
+
const [full, setFull] = useState(false);
|
|
63
|
+
const [start, setStart] = useState();
|
|
64
|
+
const memo = useRef({});
|
|
65
|
+
const at = path.join('>');
|
|
66
|
+
const s = all.find((x) => x.path.join('>') === at) ?? all[0];
|
|
67
|
+
const { layout, edges, steps, lanes, timeline } = s.props;
|
|
60
68
|
const key = useMemo(() => specKey(layout, edges, steps), [layout, edges, steps]);
|
|
61
69
|
const [narrow, setNarrow] = useState({ wide: 0, key: '' });
|
|
62
70
|
const fold = !lanes && !timeline && props.rail !== 'only';
|
|
63
71
|
const wide = fold && narrow.key === key ? narrow.wide : 0;
|
|
64
72
|
const setWide = useCallback((w) => setNarrow({ wide: w, key }), [key]);
|
|
65
|
-
const placed = useMemo(() => autoLayout({ layout: wide ? { ...layout, auto: true, direction: 'column' } : layout, edges, steps, lanes, timeline }), [wide, layout, edges, steps, lanes, timeline]);
|
|
66
|
-
|
|
73
|
+
const placed = useMemo(() => autoLayout({ layout: wide > 0 ? { ...layout, auto: true, direction: 'column' } : layout, edges, steps, lanes, timeline }), [wide, layout, edges, steps, lanes, timeline]);
|
|
74
|
+
const level = all.length > 1
|
|
75
|
+
? {
|
|
76
|
+
scene: s,
|
|
77
|
+
owners: new Map(all.filter((x) => x.path.slice(0, -1).join('>') === at && x.path.length > s.path.length).map((x) => [x.path.at(-1), x.parts])),
|
|
78
|
+
path: s.path.map((id, i) => ({ id, name: all.find((x) => x.path.join('>') === s.path.slice(0, i + 1).join('>')).name })),
|
|
79
|
+
onOpen: (id, from) => {
|
|
80
|
+
memo.current[at] = from;
|
|
81
|
+
const child = all.find((x) => x.path.join('>') === [...s.path, id].join('>'));
|
|
82
|
+
setStart({
|
|
83
|
+
step: child.props.steps?.length ? insideStep(child, from.port) : null,
|
|
84
|
+
beat: 0,
|
|
85
|
+
playing: from.playing,
|
|
86
|
+
rect: from.rect,
|
|
87
|
+
});
|
|
88
|
+
setPath([...s.path, id]);
|
|
89
|
+
},
|
|
90
|
+
onUp: (depth) => {
|
|
91
|
+
const to = s.path.slice(0, depth);
|
|
92
|
+
const { step, beat, playing } = memo.current[to.join('>')];
|
|
93
|
+
const beats = step == null ? 1 : (all.find((x) => x.path.join('>') === to.join('>')).props.steps?.[step].flow.length ?? 1);
|
|
94
|
+
setStart({ step, beat: Math.min(beat + 1, beats - 1), playing, box: s.path[depth] });
|
|
95
|
+
setPath(to);
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
: undefined;
|
|
99
|
+
const figure = useRef(null);
|
|
100
|
+
const descId = useId();
|
|
101
|
+
const alt = useMemo(() => altText({ layout: placed.layout, edges: placed.edges, steps }), [placed, steps]);
|
|
102
|
+
return (_jsxs("figure", { ref: figure, className: "flowfig", "data-inside": level ? '' : undefined, "aria-label": alt.title, "aria-describedby": descId, style: {
|
|
103
|
+
...Object.fromEntries(Object.entries(props.theme ?? {}).map(([k, val]) => [`--fig-${k}`, val])),
|
|
104
|
+
position: full ? 'fixed' : 'relative',
|
|
105
|
+
inset: full ? 0 : undefined,
|
|
106
|
+
zIndex: full ? 1000 : undefined,
|
|
107
|
+
overflow: full ? 'auto' : undefined,
|
|
108
|
+
margin: full ? 0 : '24px 0',
|
|
109
|
+
padding: full ? '48px 24px 24px' : '20px 16px 16px',
|
|
110
|
+
border: full ? 'none' : `1px solid ${v('border')}`,
|
|
111
|
+
borderRadius: full ? 0 : 16,
|
|
112
|
+
background: v('bg'),
|
|
113
|
+
fontFamily: v('font'),
|
|
114
|
+
color: v('fg'),
|
|
115
|
+
}, children: [_jsx("div", { id: descId, style: HIDDEN, children: alt.desc }), _createElement(FlowBody, { ...props, ...s.props, key: at, layout: placed.layout, edges: placed.edges, written: s.props, wide: wide, setWide: fold ? setWide : undefined, full: full, setFull: setFull, level: level, start: start, figure: figure })] }));
|
|
67
116
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
117
|
+
const reduced = () => window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
118
|
+
// The ASYNC tag is 9 px.
|
|
119
|
+
const RAIL_TEXT = 9;
|
|
120
|
+
// offsetWidth is rounded, so a rect ratio reads scale 1 as 0.999.
|
|
121
|
+
const shownScale = (el) => {
|
|
122
|
+
let k = 1;
|
|
123
|
+
for (let n = el; n; n = n.parentElement)
|
|
124
|
+
k *= new DOMMatrix(getComputedStyle(n).transform).a;
|
|
125
|
+
return k;
|
|
126
|
+
};
|
|
127
|
+
function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, autoplay = true, check = false, rail: withRail = false, lanes, timeline, today, written, wide, setWide, full, setFull, level, start, figure, }) {
|
|
71
128
|
const tl = timeline && isLanesLayout(layout);
|
|
72
|
-
const alt = useMemo(() => altText({ layout, edges, steps: stepsIn }), [layout, edges, stepsIn]);
|
|
73
|
-
const descId = useId();
|
|
74
129
|
const tabsId = useId();
|
|
75
130
|
const synthetic = tl && !stepsIn.length;
|
|
76
131
|
const steps = useMemo(() => (synthetic ? timelineBeats({ layout, edges, timeline, today }) : stepsIn), [synthetic, layout, edges, timeline, today, stepsIn]);
|
|
@@ -78,31 +133,77 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
78
133
|
const area = useRef(null);
|
|
79
134
|
const root = useRef(null);
|
|
80
135
|
const outer = useRef(null);
|
|
81
|
-
const [fit, setFit] = useState({ scale: 1, height: 0 });
|
|
82
|
-
const
|
|
136
|
+
const [fit, setFit] = useState({ scale: 1, height: 0, scroll: false });
|
|
137
|
+
const backBtn = useRef(null);
|
|
138
|
+
const ghost = useRef(null);
|
|
139
|
+
const place = (n) => {
|
|
140
|
+
const f = figure.current, a = f.getBoundingClientRect(), r = n.getBoundingClientRect();
|
|
141
|
+
return { x: r.left - a.left - f.clientLeft + f.scrollLeft, y: r.top - a.top - f.clientTop + f.scrollTop, w: r.width, h: r.height };
|
|
142
|
+
};
|
|
143
|
+
const showGhost = (r) => {
|
|
144
|
+
Object.assign(ghost.current.style, { display: 'block', left: `${r.x}px`, top: `${r.y}px`, width: `${r.w}px`, height: `${r.h}px` });
|
|
145
|
+
return ghost.current;
|
|
146
|
+
};
|
|
147
|
+
const toward = (from, to) => `translate(${from.x - to.x}px, ${from.y - to.y}px) scale(${from.w / to.w}, ${from.h / to.h})`;
|
|
148
|
+
const moving = useRef(false);
|
|
149
|
+
const goUp = (depth) => {
|
|
150
|
+
const map = root.current, r = start?.rect;
|
|
151
|
+
if (moving.current)
|
|
152
|
+
return;
|
|
153
|
+
if (reduced() || !map || !r)
|
|
154
|
+
return level.onUp(depth);
|
|
155
|
+
moving.current = true;
|
|
156
|
+
map.animate([{ opacity: 1 }, { opacity: 0 }], { duration: OPEN_MS.hide, fill: 'forwards' });
|
|
157
|
+
const to = place(map);
|
|
158
|
+
showGhost(to).animate([{ transform: 'none' }, { transform: toward(r, to) }], {
|
|
159
|
+
duration: OPEN_MS.shrink,
|
|
160
|
+
delay: OPEN_MS.hide,
|
|
161
|
+
easing: 'cubic-bezier(0.4, 0, 1, 1)',
|
|
162
|
+
fill: 'forwards',
|
|
163
|
+
}).onfinish = () => level.onUp(depth);
|
|
164
|
+
};
|
|
165
|
+
const up = level?.path.length ? () => goUp(level.path.length - 1) : undefined;
|
|
83
166
|
useEffect(() => {
|
|
84
|
-
if (!full)
|
|
167
|
+
if (!full && !up)
|
|
85
168
|
return;
|
|
86
|
-
const onKey = (e) =>
|
|
169
|
+
const onKey = (e) => {
|
|
170
|
+
const at = document.activeElement;
|
|
171
|
+
const inside = !!at && !!figure.current?.contains(at);
|
|
172
|
+
if (e.key === 'Escape' && up && (inside || full))
|
|
173
|
+
up();
|
|
174
|
+
else if (e.key === 'Escape')
|
|
175
|
+
setFull(false);
|
|
176
|
+
else if (e.key === 'Backspace' && up && inside && !at.closest('input,textarea,[contenteditable]')) {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
up();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
87
181
|
addEventListener('keydown', onKey);
|
|
88
182
|
return () => removeEventListener('keydown', onKey);
|
|
89
|
-
}, [full]);
|
|
183
|
+
}, [full, up]);
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (start?.box)
|
|
186
|
+
figure.current?.querySelector(`[data-fig="${CSS.escape(start.box)}"]`)?.focus();
|
|
187
|
+
else if (level?.path.length)
|
|
188
|
+
backBtn.current?.focus();
|
|
189
|
+
}, []);
|
|
90
190
|
const dots = useRef([]);
|
|
91
191
|
const chips = useRef([]);
|
|
92
192
|
const bar = useRef(null);
|
|
93
|
-
const [beat, setBeat] = useState(0);
|
|
193
|
+
const [beat, setBeat] = useState(start?.beat ?? 0);
|
|
94
194
|
const [holding, setHolding] = useState(false);
|
|
95
195
|
const [returning, setReturning] = useState(false);
|
|
96
196
|
const firstLabel = useRef('');
|
|
97
197
|
const paths = useRef({});
|
|
98
198
|
const [routed, setRouted] = useState([]);
|
|
199
|
+
const routedOn = useRef(new WeakMap());
|
|
99
200
|
const markSides = new Set(nodes(layout).flatMap((n) => (n.mark ? [markSide(n.id, n.mark, routed)] : [])));
|
|
100
201
|
const [extraTall, setExtraTall] = useState(new Set());
|
|
101
202
|
const lanePlan = useMemo(() => lanes && !tl && isLanesLayout(layout)
|
|
102
203
|
? planLanes({ layout, edges, steps, lanes }, { floor: withRail ? (layoutRail({ layout, edges, steps, rail: true }, 560)?.width ?? 0) : 0 })
|
|
103
204
|
: null, [lanes, tl, layout, edges, steps, withRail]);
|
|
104
|
-
const [active, setActive] = useState(steps.length ? 0 : null);
|
|
105
|
-
const [playing, setPlaying] = useState(autoplay);
|
|
205
|
+
const [active, setActive] = useState(start ? start.step : steps.length ? 0 : null);
|
|
206
|
+
const [playing, setPlaying] = useState(start ? start.playing : autoplay);
|
|
106
207
|
const playingRef = useRef(playing);
|
|
107
208
|
playingRef.current = playing;
|
|
108
209
|
const [rate, setRate] = useState(1);
|
|
@@ -139,17 +240,45 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
139
240
|
beats.slice(0, beat + 1).forEach((b, i) => Object.keys(b.show ?? {}).forEach((k) => (at[k] = i)));
|
|
140
241
|
return at;
|
|
141
242
|
}, [beats, beat]);
|
|
142
|
-
const figure = useRef(null);
|
|
143
243
|
const [mapW, setMapW] = useState(0);
|
|
144
244
|
const railOnly = withRail === 'only';
|
|
145
245
|
const rail = useMemo(() => (withRail ? layoutRail({ layout, edges, steps, rail: true }, railOnly ? 560 : mapW) : null), [withRail, railOnly, layout, edges, steps, mapW]);
|
|
146
246
|
const noMap = railOnly && rail != null;
|
|
147
247
|
const railPane = useRef(null);
|
|
248
|
+
const [railBox, setRailBox] = useState(0);
|
|
249
|
+
const zoom = wide ? 11 / RAIL_TEXT : noMap && railBox ? textFloor(railBox) / RAIL_TEXT : 0;
|
|
148
250
|
const follow = useRef(true);
|
|
149
251
|
const user = useRef(false);
|
|
150
252
|
const left = useRef(0);
|
|
151
253
|
const busy = useRef(0);
|
|
152
|
-
const jump = useRef(null);
|
|
254
|
+
const jump = useRef(start?.beat ?? null);
|
|
255
|
+
const wait = useRef(start?.rect && start.playing
|
|
256
|
+
? OPEN_MS.grow +
|
|
257
|
+
OPEN_MS.stagger * (nodes(layout).length - 1) +
|
|
258
|
+
readMs([level.scene.line, ...level.scene.ports.map(portLabel)].join(' '))
|
|
259
|
+
: 0);
|
|
260
|
+
useLayoutEffect(() => {
|
|
261
|
+
const map = root.current;
|
|
262
|
+
if (!map || reduced())
|
|
263
|
+
return;
|
|
264
|
+
if (start?.box)
|
|
265
|
+
return void map.animate([{ opacity: 0 }, { opacity: 1 }], { duration: OPEN_MS.fade });
|
|
266
|
+
if (!start?.rect)
|
|
267
|
+
return;
|
|
268
|
+
const to = place(map);
|
|
269
|
+
showGhost(to).animate([{ transform: toward(start.rect, to) }, { transform: 'none' }], {
|
|
270
|
+
duration: OPEN_MS.grow,
|
|
271
|
+
easing: 'cubic-bezier(0.2, 0, 0, 1)',
|
|
272
|
+
});
|
|
273
|
+
for (const e of map.querySelectorAll(':scope > svg'))
|
|
274
|
+
e.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 1, delay: OPEN_MS.grow, fill: 'backwards' });
|
|
275
|
+
const shows = flowOrder(level.scene).map((id, k) => map.querySelector(`[data-fig="${CSS.escape(id)}"]`)?.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
276
|
+
duration: OPEN_MS.fade,
|
|
277
|
+
delay: OPEN_MS.grow + OPEN_MS.stagger * k,
|
|
278
|
+
fill: 'backwards',
|
|
279
|
+
}));
|
|
280
|
+
shows.at(-1)?.finished.then(() => ghost.current && (ghost.current.style.display = 'none'));
|
|
281
|
+
}, []);
|
|
153
282
|
const [hoverEdge, setHoverEdge] = useState(null);
|
|
154
283
|
const [still, setStill] = useState(false);
|
|
155
284
|
useEffect(() => setStill(window.matchMedia('(prefers-reduced-motion: reduce)').matches), []);
|
|
@@ -176,13 +305,12 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
176
305
|
if (setWide && goal !== wide)
|
|
177
306
|
return setWide(goal);
|
|
178
307
|
const scale = fitScale(wide, box.clientWidth, el.offsetWidth);
|
|
179
|
-
setFit({ scale, height: el.offsetHeight * scale });
|
|
308
|
+
setFit({ scale, height: el.offsetHeight * scale, scroll: scale > box.clientWidth / el.offsetWidth });
|
|
180
309
|
setMapW(el.offsetWidth);
|
|
181
310
|
if (area.current)
|
|
182
311
|
setAxisW(area.current.offsetWidth);
|
|
183
312
|
const base = el.getBoundingClientRect();
|
|
184
|
-
|
|
185
|
-
const k = base.width / el.offsetWidth;
|
|
313
|
+
const k = shownScale(el);
|
|
186
314
|
const rects = {};
|
|
187
315
|
el.querySelectorAll('[data-fig]').forEach((n) => {
|
|
188
316
|
const r = n.getBoundingClientRect();
|
|
@@ -246,6 +374,7 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
246
374
|
}),
|
|
247
375
|
...(!tl && e.label != null && { labelW: labelPillW(str(e.label)) }),
|
|
248
376
|
})), rects, tips, avoid, pillArea, lanePlan ? { bands: Object.values(laneBands), boxes: nodes(layout).flatMap((n) => (rects[n.id] ? [rects[n.id]] : [])) } : undefined);
|
|
377
|
+
routedOn.current.set(next, rects);
|
|
249
378
|
setRouted(next);
|
|
250
379
|
if (lanePlan) {
|
|
251
380
|
const more = [...tightCopies(layout.children, lanePlan.ends, edges, next)].filter((k) => !lanePlan.tall.has(k) && !extraTall.has(k));
|
|
@@ -263,7 +392,7 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
263
392
|
useEffect(() => {
|
|
264
393
|
const pane = railPane.current;
|
|
265
394
|
const n = rail?.rows.flatMap((r) => (r.kind === 'message' && r.step === active && r.beat === beat ? [r.n] : []))[0];
|
|
266
|
-
const now =
|
|
395
|
+
const now = zoom && follow.current && n != null ? pane?.querySelector(`[data-rail-row="${n}"] path`) : null;
|
|
267
396
|
if (!pane || !now)
|
|
268
397
|
return;
|
|
269
398
|
const [p, r] = [pane.getBoundingClientRect(), now.getBoundingClientRect()];
|
|
@@ -271,25 +400,28 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
271
400
|
// Some browsers fire no scrollend event.
|
|
272
401
|
busy.current = performance.now() + 1000;
|
|
273
402
|
pane.scrollTo({ left: pane.scrollLeft + (r.left + r.right) / 2 - p.left - pane.clientWidth / 2, behavior: still ? 'auto' : 'smooth' });
|
|
274
|
-
}, [
|
|
403
|
+
}, [zoom, active, beat, still, rail]);
|
|
275
404
|
useEffect(() => {
|
|
276
405
|
const pane = railPane.current;
|
|
277
406
|
if (!pane)
|
|
278
407
|
return;
|
|
279
408
|
const end = () => (busy.current = 0);
|
|
280
409
|
pane.addEventListener('scrollend', end);
|
|
281
|
-
|
|
282
|
-
|
|
410
|
+
const ro = new ResizeObserver(() => setRailBox(pane.clientWidth));
|
|
411
|
+
if (noMap)
|
|
412
|
+
ro.observe(pane);
|
|
413
|
+
return () => (pane.removeEventListener('scrollend', end), ro.disconnect());
|
|
414
|
+
}, [rail, noMap]);
|
|
283
415
|
const reported = useRef(new Set());
|
|
284
416
|
useEffect(() => {
|
|
285
417
|
const el = root.current, box = outer.current;
|
|
286
418
|
const fig = figure.current;
|
|
287
|
-
if (!check || !fig || (
|
|
419
|
+
if (!check || !fig || (noMap ? !railBox : !el || !box || !el.offsetWidth))
|
|
288
420
|
return;
|
|
289
421
|
if (setWide && el && box && nextWide(wide, box.clientWidth, el.offsetWidth) !== wide)
|
|
290
422
|
return;
|
|
291
423
|
const base = (el ?? fig).getBoundingClientRect();
|
|
292
|
-
const k = el ?
|
|
424
|
+
const k = el ? shownScale(el) : 1;
|
|
293
425
|
const rel = (n) => {
|
|
294
426
|
const r = n.getBoundingClientRect();
|
|
295
427
|
return { x: (r.left - base.left) / k, y: (r.top - base.top) / k, w: r.width / k, h: r.height / k };
|
|
@@ -306,7 +438,7 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
306
438
|
.filter((t) => !t.children.length && t.textContent?.trim() && t.clientWidth > 0 && !t.closest('[aria-hidden]'))
|
|
307
439
|
.map((t) => {
|
|
308
440
|
const fontSize = parseFloat(getComputedStyle(t).fontSize);
|
|
309
|
-
if (!n.dataset.diamond)
|
|
441
|
+
if (!n.dataset.diamond || t.closest('[data-fig-outside]'))
|
|
310
442
|
return { text: t.textContent, fontSize, room: t.clientWidth, need: t.scrollWidth };
|
|
311
443
|
const [r, range] = [rel(t), document.createRange()];
|
|
312
444
|
range.selectNodeContents(t);
|
|
@@ -320,6 +452,12 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
320
452
|
}),
|
|
321
453
|
};
|
|
322
454
|
});
|
|
455
|
+
const moved = (b) => {
|
|
456
|
+
const r = routedOn.current.get(routed)?.[b.id];
|
|
457
|
+
return !r || Math.abs(r.x - b.rect.x) > 1 || Math.abs(r.y - b.rect.y) > 1;
|
|
458
|
+
};
|
|
459
|
+
if (el && boxes.some(moved))
|
|
460
|
+
return;
|
|
323
461
|
const fonts = [...fig.querySelectorAll('*')]
|
|
324
462
|
.filter((t) => !t.children.length &&
|
|
325
463
|
t.textContent?.trim() &&
|
|
@@ -328,8 +466,9 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
328
466
|
!t.closest('[data-fig-tag],[aria-hidden]'))
|
|
329
467
|
.map((t) => {
|
|
330
468
|
const svg = railPane.current?.firstElementChild;
|
|
331
|
-
const
|
|
332
|
-
|
|
469
|
+
const place = el?.contains(t) ? 'map' : svg?.contains(t) && rail ? 'rail' : 'plain';
|
|
470
|
+
const railScale = svg && rail ? svg.getBoundingClientRect().width / rail.width : 1;
|
|
471
|
+
return shownFont(parseFloat(getComputedStyle(t).fontSize), place, { map: k, rail: railScale });
|
|
333
472
|
});
|
|
334
473
|
const labels = Object.fromEntries([...fig.querySelectorAll('[data-fig-label]')].map((n) => [n.dataset.figLabel, rel(n)]));
|
|
335
474
|
const lanesIn = el ? [...el.querySelectorAll('[data-fig-band]')] : [];
|
|
@@ -378,7 +517,11 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
378
517
|
],
|
|
379
518
|
minFont: Math.min(...fonts),
|
|
380
519
|
};
|
|
381
|
-
for (const f of [
|
|
520
|
+
for (const f of [
|
|
521
|
+
...checkSpec(written),
|
|
522
|
+
...checkScene(scene, { width: (box ?? fig).clientWidth, minText: textFloor((box ?? fig).clientWidth), auto: written.layout.auto }),
|
|
523
|
+
...checkTheme(theme),
|
|
524
|
+
]) {
|
|
382
525
|
const key = f.rule + ':' + f.ids.join() + ':' + f.message;
|
|
383
526
|
if (reported.current.has(key))
|
|
384
527
|
continue;
|
|
@@ -386,13 +529,14 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
386
529
|
// oxlint-disable-next-line no-console
|
|
387
530
|
console.warn(`flowfig check: ${f.severity} ${f.rule}: ${f.message}`);
|
|
388
531
|
}
|
|
389
|
-
}, [check, routed, written, layout, edges, steps, ids, theme, rail, noMap, lanePlan, wide, setWide]);
|
|
532
|
+
}, [check, routed, written, layout, edges, steps, ids, theme, rail, noMap, railBox, lanePlan, wide, setWide]);
|
|
390
533
|
useEffect(() => {
|
|
391
534
|
const gs = dots.current, cs = chips.current, edgeOf = Object.fromEntries(edges.map((e, i) => [ids[i], e]));
|
|
392
535
|
if (clock.current.beats !== beats) {
|
|
393
536
|
const j = jump.current ?? 0;
|
|
394
537
|
jump.current = null;
|
|
395
|
-
clock.current = { beats, elapsed: beats.slice(0, j).reduce((t, b) => t + beatMs(b, speed), 0) };
|
|
538
|
+
clock.current = { beats, elapsed: beats.slice(0, j).reduce((t, b) => t + beatMs(b, speed), 0) - wait.current * BASE_RATE };
|
|
539
|
+
wait.current = 0;
|
|
396
540
|
setBeat(j);
|
|
397
541
|
}
|
|
398
542
|
if (!beats.length || (!noMap && !routed.length && !(tl && !edges.length)))
|
|
@@ -414,7 +558,7 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
414
558
|
return setActive((a) => ((a ?? 0) + 1) % steps.length);
|
|
415
559
|
clock.current.elapsed = 0;
|
|
416
560
|
}
|
|
417
|
-
const t = clock.current.elapsed;
|
|
561
|
+
const t = Math.max(0, clock.current.elapsed);
|
|
418
562
|
if (bar.current)
|
|
419
563
|
bar.current.style.transform = `scaleX(${Math.min(1, t / total)})`;
|
|
420
564
|
const next = ends.findIndex((e) => t < e);
|
|
@@ -518,7 +662,6 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
518
662
|
}
|
|
519
663
|
};
|
|
520
664
|
const focus = hover != null || hoverEdge != null || step != null;
|
|
521
|
-
const vars = Object.fromEntries(Object.entries(theme ?? {}).map(([k, val]) => [`--fig-${k}`, val]));
|
|
522
665
|
const timelineFig = useMemo(() => (tl ? timelineLayout({ layout, edges, timeline, today }, axisW) : null), [tl, layout, edges, timeline, today, axisW]);
|
|
523
666
|
const renderBar = (n, it) => {
|
|
524
667
|
const lit = litNodes.has(n.id);
|
|
@@ -712,9 +855,36 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
712
855
|
const card = carded.has(item.id);
|
|
713
856
|
const dl = diamond && !card ? diamondLines(item, nodeWidth(item, false) + 70) : null;
|
|
714
857
|
const w = diamond || card || item.width != null || str(item.label) ? nodeWidth(item, card, cap) + (diamond ? 70 : 0) : undefined;
|
|
715
|
-
const lines = w != null && !diamond && str(item.label) ? labelLines(item, w) : null;
|
|
716
858
|
const bt = item.tone && TONES[item.tone];
|
|
717
|
-
|
|
859
|
+
const parts = level?.owners.get(item.id);
|
|
860
|
+
const port = level?.scene.ports.some((p) => p.id === item.id);
|
|
861
|
+
const lines = w != null && str(item.label) ? labelLines(item, w) : null;
|
|
862
|
+
const open = () => {
|
|
863
|
+
const hop = cur?.hops.find((h) => (h.back ? edges[ids.indexOf(h.edge)]?.from : edges[ids.indexOf(h.edge)]?.to) === item.id);
|
|
864
|
+
const e = hop && edges[ids.indexOf(hop.edge)];
|
|
865
|
+
const at = { step: active, beat, playing, port: e ? (hop.back ? e.to : e.from) : undefined };
|
|
866
|
+
const map = root.current, n = map?.querySelector(`[data-fig="${CSS.escape(item.id)}"]`);
|
|
867
|
+
if (moving.current)
|
|
868
|
+
return;
|
|
869
|
+
if (reduced() || !map || !n)
|
|
870
|
+
return level.onOpen(item.id, at);
|
|
871
|
+
moving.current = true;
|
|
872
|
+
const rect = place(n);
|
|
873
|
+
showGhost(rect);
|
|
874
|
+
map.animate([{ opacity: 1 }, { opacity: 0 }], { duration: OPEN_MS.fade, fill: 'forwards' }).onfinish = () => level.onOpen(item.id, { ...at, rect });
|
|
875
|
+
};
|
|
876
|
+
return (_jsxs("div", { "data-fig": item.id, title: item.source, "data-diamond": diamond || undefined, onMouseEnter: () => setHover(item.id), onMouseLeave: () => setHover(null), ...(parts != null && {
|
|
877
|
+
role: 'button',
|
|
878
|
+
tabIndex: 0,
|
|
879
|
+
'aria-label': `Open ${str(item.label) || item.id}, ${parts} parts`,
|
|
880
|
+
onClick: open,
|
|
881
|
+
onKeyDown: (e) => {
|
|
882
|
+
if (e.key !== 'Enter' && e.key !== ' ')
|
|
883
|
+
return;
|
|
884
|
+
e.preventDefault();
|
|
885
|
+
open();
|
|
886
|
+
},
|
|
887
|
+
}), style: {
|
|
718
888
|
position: 'relative',
|
|
719
889
|
isolation: 'isolate',
|
|
720
890
|
minWidth: 100,
|
|
@@ -727,19 +897,23 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
727
897
|
justifyContent: 'center',
|
|
728
898
|
padding: diamond
|
|
729
899
|
? '22px 34px'
|
|
730
|
-
: `calc(${store ?
|
|
900
|
+
: `calc(${store ? 23 : 9}px - var(--ff-b, 0px)) calc(${card ? 10 : 16}px - var(--ff-b, 0px)) calc(9px - var(--ff-b, 0px))`,
|
|
731
901
|
textAlign: 'center',
|
|
732
902
|
background: diamond ? undefined : bt ? toneTint(bt, v('bg')) : v('bg'),
|
|
733
|
-
color: v('fg'),
|
|
734
|
-
border: diamond ? undefined : `1px solid ${bt ?? (lit ? v('accent') : v('border'))}`,
|
|
903
|
+
color: port ? v('muted') : v('fg'),
|
|
904
|
+
border: diamond ? undefined : `1px ${port ? 'dashed' : 'solid'} ${bt ?? (lit ? v('accent') : v('border'))}`,
|
|
735
905
|
...(bt && { '--ff-box': bt }),
|
|
736
|
-
boxShadow: diamond
|
|
906
|
+
boxShadow: diamond
|
|
907
|
+
? undefined
|
|
908
|
+
: parts != null
|
|
909
|
+
? `3px 3px 0 -1px ${v('bg')}, 3px 3px 0 0 ${v('border')}`
|
|
910
|
+
: '0 1px 2px rgba(0,0,0,.06)',
|
|
737
911
|
opacity: focus && !lit ? 0.7 : 1,
|
|
738
912
|
borderRadius: store ? '50% / 12px' : 10,
|
|
739
913
|
fontSize: 14,
|
|
740
914
|
fontWeight: 500,
|
|
741
915
|
transition: 'border-color .4s, background .4s, box-shadow .4s, border-width .4s, padding .4s, opacity .25s',
|
|
742
|
-
cursor: 'default',
|
|
916
|
+
cursor: parts != null ? 'pointer' : 'default',
|
|
743
917
|
}, children: [diamond && (_jsx("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", style: {
|
|
744
918
|
position: 'absolute',
|
|
745
919
|
inset: 0,
|
|
@@ -770,7 +944,7 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
770
944
|
border: `1.5px solid ${bt ?? v('accent')}`,
|
|
771
945
|
background: `radial-gradient(circle, ${bt ?? v('accent')} 0 4px, transparent 4.5px)`,
|
|
772
946
|
}),
|
|
773
|
-
} })), lines ? (lines.map((l, i) => (_jsx("div", { style: { whiteSpace: 'nowrap', lineHeight: `${LABEL_LINE}px` }, children: l }, i)))) : (_jsx("div", { style: { lineHeight: `${LABEL_LINE}px` }, children: item.label })), dl
|
|
947
|
+
} })), lines && port ? (_jsx("div", { style: { lineHeight: `${LABEL_LINE}px`, width: 'min-content', margin: '0 auto' }, children: lines.map((l, i) => (_jsxs(Fragment, { children: [i > 0 && ' ', _jsx("span", { style: { whiteSpace: 'nowrap' }, children: l })] }, i))) })) : lines ? (lines.map((l, i) => (_jsx("div", { style: { whiteSpace: 'nowrap', lineHeight: `${LABEL_LINE}px` }, children: l }, i)))) : (_jsx("div", { style: { lineHeight: `${LABEL_LINE}px` }, children: item.label })), dl
|
|
774
948
|
? dl.subs.map((l, i) => (_jsx("div", { style: { fontSize: 12, fontWeight: 400, color: v('muted'), lineHeight: '15px', whiteSpace: 'nowrap' }, children: l.text }, i)))
|
|
775
949
|
: item.sub != null && _jsx("div", { style: { fontSize: 12, fontWeight: 400, color: v('muted'), marginTop: 2 }, children: item.sub }), card && (_jsxs("div", { style: {
|
|
776
950
|
marginTop: 8,
|
|
@@ -803,20 +977,7 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
803
977
|
background: v('bg'),
|
|
804
978
|
color: v('muted'),
|
|
805
979
|
};
|
|
806
|
-
return (_jsxs(
|
|
807
|
-
...vars,
|
|
808
|
-
position: full ? 'fixed' : 'relative',
|
|
809
|
-
inset: full ? 0 : undefined,
|
|
810
|
-
zIndex: full ? 1000 : undefined,
|
|
811
|
-
overflow: full ? 'auto' : undefined,
|
|
812
|
-
margin: full ? 0 : '24px 0',
|
|
813
|
-
padding: full ? '48px 24px 24px' : '20px 16px 16px',
|
|
814
|
-
border: full ? 'none' : `1px solid ${v('border')}`,
|
|
815
|
-
borderRadius: full ? 0 : 16,
|
|
816
|
-
background: v('bg'),
|
|
817
|
-
fontFamily: v('font'),
|
|
818
|
-
color: v('fg'),
|
|
819
|
-
}, children: [_jsx("div", { id: descId, style: HIDDEN, children: alt.desc }), _jsx("button", { type: "button", "aria-label": full ? 'Close full screen' : 'Full screen', title: full ? 'Close (Esc)' : 'Full screen', onClick: () => setFull((f) => !f), style: {
|
|
980
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", "aria-label": full ? 'Close full screen' : 'Full screen', title: full ? 'Close (Esc)' : 'Full screen', onClick: () => setFull(!full), style: {
|
|
820
981
|
...iconBtn,
|
|
821
982
|
position: 'absolute',
|
|
822
983
|
top: 10,
|
|
@@ -827,113 +988,128 @@ function FlowBody({ layout, edges, steps: stepsIn = NONE, theme, speed = 900, au
|
|
|
827
988
|
}, children: _jsx(Icon, { d: full ? 'M4 4l8 8M12 4l-8 8' : 'M9 3h4v4M7 13H3V9M13 3L9 7M3 13l4-4' }) }), _jsx("style", { children: '@keyframes flowfig-label{from{opacity:0}to{opacity:0}}@keyframes flowfig-in{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}' +
|
|
828
989
|
// The 2 px active border takes 1 px of padding, so the box size stays.
|
|
829
990
|
`.${ACTIVE}:not([data-diamond]){--ff-b:1px;border-width:2px!important;background:color-mix(in srgb, ${hue} 10%, ${v('surface')})!important;box-shadow:${glow}!important;border-color:${hue}!important}` +
|
|
830
|
-
`.${ACTIVE} polygon{fill:color-mix(in srgb, ${hue} 10%, ${v('surface')});stroke:${hue};stroke-width:2px}`
|
|
991
|
+
`.${ACTIVE} polygon{fill:color-mix(in srgb, ${hue} 10%, ${v('surface')});stroke:${hue};stroke-width:2px}` +
|
|
992
|
+
(level
|
|
993
|
+
? '@media (prefers-reduced-motion: reduce){.flowfig[data-inside] *{transition:none!important;animation:none!important}}'
|
|
994
|
+
: '') }), level && (_jsx("div", { ref: ghost, style: {
|
|
995
|
+
display: 'none',
|
|
996
|
+
position: 'absolute',
|
|
997
|
+
zIndex: 4,
|
|
998
|
+
boxSizing: 'border-box',
|
|
999
|
+
border: `1.5px solid ${v('accent')}`,
|
|
1000
|
+
borderRadius: 10,
|
|
1001
|
+
transformOrigin: '0 0',
|
|
1002
|
+
pointerEvents: 'none',
|
|
1003
|
+
} })), !noMap && (_jsx(_Fragment, { children: _jsxs("div", { style: {
|
|
831
1004
|
margin: full ? '-48px -24px 0' : '-20px -16px 0',
|
|
832
1005
|
padding: full ? '48px 24px 24px' : '24px 16px',
|
|
833
1006
|
borderRadius: full ? 0 : '16px 16px 0 0',
|
|
834
1007
|
borderBottom: `1px solid ${v('border')}`,
|
|
835
1008
|
backgroundImage: `radial-gradient(color-mix(in srgb, ${v('fg')} 16%, transparent) 1px, transparent 1.2px)`,
|
|
836
1009
|
backgroundSize: '14px 14px',
|
|
837
|
-
}, children:
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1010
|
+
}, children: [up && (_jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12, fontSize: 13, color: v('muted') }, children: [_jsx("button", { ref: backBtn, type: "button", onClick: up, style: { ...iconBtn, width: 'auto', padding: '0 12px', font: 'inherit' }, children: "Back" }), level.path.map((p, i) => i < level.path.length - 1 ? (_jsxs(Fragment, { children: [_jsx("button", { type: "button", onClick: () => goUp(i + 1), style: { border: 0, padding: 0, background: 'none', font: 'inherit', color: v('accent'), cursor: 'pointer' }, children: p.name }), ' > '] }, p.id)) : (_jsx("span", { style: { color: v('fg') }, children: p.name }, p.id))), _jsx("div", { "aria-live": "polite", style: HIDDEN, children: `${level.scene.line}: ${level.scene.parts} parts.` +
|
|
1011
|
+
level.scene.ports
|
|
1012
|
+
.map((p) => p.way === 'in' ? ` In from ${p.name}.` : p.way === 'out' ? ` Out to ${p.name}.` : ` In and out: ${p.name}.`)
|
|
1013
|
+
.join('') })] })), _jsx("div", { ref: outer, role: "tabpanel", id: `${tabsId}-panel`, "aria-labelledby": `${tabsId}-tab-${active ?? 0}`, style: {
|
|
1014
|
+
overflow: wide || fit.scroll ? 'auto' : 'hidden',
|
|
1015
|
+
height: fit.scale < 1 ? fit.height : undefined,
|
|
1016
|
+
}, children: _jsxs("div", { ref: root, style: {
|
|
1017
|
+
position: 'relative',
|
|
1018
|
+
width: 'max-content',
|
|
1019
|
+
margin: '0 auto',
|
|
1020
|
+
transform: fit.scale < 1 ? `scale(${fit.scale})` : undefined,
|
|
1021
|
+
transformOrigin: 'top left',
|
|
1022
|
+
paddingTop: arcRoom(routed, 'above') || (markSides.has('t') ? 20 : 4),
|
|
1023
|
+
paddingBottom: arcRoom(routed, 'below') || (markSides.has('b') ? 20 : 4),
|
|
1024
|
+
paddingLeft: arcRoom(routed, 'left') || 4,
|
|
1025
|
+
paddingRight: arcRoom(routed, 'right') || 4,
|
|
1026
|
+
}, children: [renderItem(layout, 0), _jsxs("svg", { style: {
|
|
1027
|
+
position: 'absolute',
|
|
1028
|
+
inset: 0,
|
|
1029
|
+
width: '100%',
|
|
1030
|
+
height: '100%',
|
|
1031
|
+
overflow: 'visible',
|
|
1032
|
+
pointerEvents: 'none',
|
|
1033
|
+
zIndex: tl ? 0 : undefined,
|
|
1034
|
+
}, children: [_jsx("defs", { children: ['off', 'on'].map((k) => (_jsx("marker", { id: `fig-arrow-${k}`, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "7", markerHeight: "7", orient: "auto-start-reverse", children: _jsx("path", { d: "M 0 1 L 9 5 L 0 9 z", fill: "context-stroke" }) }, k))) }), routed.map((r) => {
|
|
1035
|
+
const on = litEdges.has(r.id);
|
|
1036
|
+
const tone = hopTone(r.id);
|
|
1037
|
+
const hidden = !on && edges[ids.indexOf(r.id)].quiet;
|
|
1038
|
+
const tip = edgeTip(r.id, edges[ids.indexOf(r.id)].source, allBeats);
|
|
1039
|
+
const look = (d, key, guides = false) => (_jsx("path", { ref: guides
|
|
1040
|
+
? (p) => {
|
|
1041
|
+
paths.current[r.id] = p;
|
|
1042
|
+
}
|
|
1043
|
+
: undefined, d: d, fill: "none", stroke: on ? (tone ?? v('accent')) : v('muted'), strokeWidth: on ? EDGE_ON : EDGE_OFF, strokeOpacity: hidden ? 0 : focus && !on ? 0.35 : 1, markerEnd: hidden ? undefined : `url(#fig-arrow-${on ? 'on' : 'off'})`, style: { transition: 'stroke .25s, stroke-opacity .25s', pointerEvents: tip ? 'stroke' : undefined }, children: tip && _jsx("title", { children: tip }) }, key));
|
|
1044
|
+
const guide = (_jsx("path", { ref: (p) => {
|
|
865
1045
|
paths.current[r.id] = p;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
}
|
|
875
|
-
dots.current[j] = g;
|
|
876
|
-
}, style: { opacity: 0, color: v('accent') }, children: [_jsx("circle", { r: 10, fill: "currentColor", opacity: 0.2 }), _jsx("circle", { r: 4.5, fill: "currentColor" })] }, j)))] }), Array.from({ length: Math.max(1, ...beats.map((b) => b.hops.length)) }, (_, j) => (_jsx("div", { ref: (c) => {
|
|
877
|
-
chips.current[j] = c;
|
|
878
|
-
}, style: {
|
|
879
|
-
position: 'absolute',
|
|
880
|
-
left: 0,
|
|
881
|
-
top: 0,
|
|
882
|
-
zIndex: 2,
|
|
883
|
-
opacity: 0,
|
|
884
|
-
maxWidth: 220,
|
|
885
|
-
width: 'max-content',
|
|
886
|
-
padding: '4px 9px',
|
|
887
|
-
borderRadius: 8,
|
|
888
|
-
fontSize: 11.5,
|
|
889
|
-
lineHeight: '15px',
|
|
890
|
-
pointerEvents: 'none',
|
|
891
|
-
background: v('accent'),
|
|
892
|
-
color: ON_ACCENT,
|
|
893
|
-
boxShadow: '0 4px 14px rgba(0,0,0,.18)',
|
|
894
|
-
transition: 'opacity .2s',
|
|
895
|
-
}, children: cur?.hops[j]?.data }, j))), routed.flatMap((r) => {
|
|
896
|
-
const e = edges[ids.indexOf(r.id)];
|
|
897
|
-
const texts = r.stub && lanePlan?.stubs.get(r.id);
|
|
898
|
-
if (!texts && (e.label == null || tl))
|
|
899
|
-
return [];
|
|
900
|
-
const on = litEdges.has(r.id);
|
|
901
|
-
const tone = hopTone(r.id);
|
|
902
|
-
const pills = texts
|
|
903
|
-
? r.stub.pills.map((p, j) => ({
|
|
904
|
-
key: `${r.id}:${j}`,
|
|
905
|
-
x: p.x + p.w / 2,
|
|
906
|
-
y: p.y + p.h / 2,
|
|
907
|
-
text: (j === 0 && r.stub.short ? texts[2] : texts[j]),
|
|
908
|
-
}))
|
|
909
|
-
: [{ key: r.id, x: r.mid.x, y: r.mid.y, text: e.label }];
|
|
910
|
-
return pills.map((p) => (_jsx("div", { "data-fig-label": p.key, style: {
|
|
1046
|
+
}, d: r.d, fill: "none", stroke: "none" }, r.id));
|
|
1047
|
+
if (r.stub)
|
|
1048
|
+
return [...r.stub.parts.map((d, j) => look(d, `${r.id}:${j}`)), guide];
|
|
1049
|
+
return look(r.d, r.id, true);
|
|
1050
|
+
}), Array.from({ length: Math.max(1, ...beats.map((b) => b.hops.length)) }, (_, j) => (_jsxs("g", { ref: (g) => {
|
|
1051
|
+
dots.current[j] = g;
|
|
1052
|
+
}, style: { opacity: 0, color: v('accent') }, children: [_jsx("circle", { r: 10, fill: "currentColor", opacity: 0.2 }), _jsx("circle", { r: 4.5, fill: "currentColor" })] }, j)))] }), Array.from({ length: Math.max(1, ...beats.map((b) => b.hops.length)) }, (_, j) => (_jsx("div", { ref: (c) => {
|
|
1053
|
+
chips.current[j] = c;
|
|
1054
|
+
}, style: {
|
|
911
1055
|
position: 'absolute',
|
|
912
|
-
left:
|
|
913
|
-
top:
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1056
|
+
left: 0,
|
|
1057
|
+
top: 0,
|
|
1058
|
+
zIndex: 2,
|
|
1059
|
+
opacity: 0,
|
|
1060
|
+
maxWidth: 220,
|
|
1061
|
+
width: 'max-content',
|
|
1062
|
+
padding: '4px 9px',
|
|
1063
|
+
borderRadius: 8,
|
|
1064
|
+
fontSize: 11.5,
|
|
1065
|
+
lineHeight: '15px',
|
|
920
1066
|
pointerEvents: 'none',
|
|
921
|
-
background:
|
|
922
|
-
color:
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1067
|
+
background: v('accent'),
|
|
1068
|
+
color: ON_ACCENT,
|
|
1069
|
+
boxShadow: '0 4px 14px rgba(0,0,0,.18)',
|
|
1070
|
+
transition: 'opacity .2s',
|
|
1071
|
+
}, children: cur?.hops[j]?.data }, j))), routed.flatMap((r) => {
|
|
1072
|
+
const e = edges[ids.indexOf(r.id)];
|
|
1073
|
+
const texts = r.stub && lanePlan?.stubs.get(r.id);
|
|
1074
|
+
if (!texts && (e.label == null || tl))
|
|
1075
|
+
return [];
|
|
1076
|
+
const on = litEdges.has(r.id);
|
|
1077
|
+
const tone = hopTone(r.id);
|
|
1078
|
+
const pills = texts
|
|
1079
|
+
? r.stub.pills.map((p, j) => ({
|
|
1080
|
+
key: `${r.id}:${j}`,
|
|
1081
|
+
x: p.x + p.w / 2,
|
|
1082
|
+
y: p.y + p.h / 2,
|
|
1083
|
+
text: (j === 0 && r.stub.short ? texts[2] : texts[j]),
|
|
1084
|
+
}))
|
|
1085
|
+
: [{ key: r.id, x: r.mid.x, y: r.mid.y, text: e.label }];
|
|
1086
|
+
return pills.map((p) => (_jsx("div", { "data-fig-label": p.key, style: {
|
|
1087
|
+
position: 'absolute',
|
|
1088
|
+
left: p.x,
|
|
1089
|
+
top: p.y,
|
|
1090
|
+
transform: 'translate(-50%, -50%)',
|
|
1091
|
+
fontSize: 11,
|
|
1092
|
+
lineHeight: '16px',
|
|
1093
|
+
padding: '0 7px',
|
|
1094
|
+
borderRadius: 999,
|
|
1095
|
+
whiteSpace: 'nowrap',
|
|
1096
|
+
pointerEvents: 'none',
|
|
1097
|
+
background: on ? (tone ? toneFill(tone) : v('accent')) : v('bg'),
|
|
1098
|
+
color: on ? ON_ACCENT : v('muted'),
|
|
1099
|
+
border: `1px solid ${on ? (tone ?? v('accent')) : v('border')}`,
|
|
1100
|
+
opacity: !on && e.quiet ? 0 : focus && !on ? 0.6 : 1,
|
|
1101
|
+
fontFamily: MONO,
|
|
1102
|
+
transition: 'background .25s, color .25s, opacity .25s',
|
|
1103
|
+
}, children: p.text }, p.key)));
|
|
1104
|
+
})] }) })] }) })), rail && (_jsx("div", { ref: railPane, onWheel: () => (user.current = true), onPointerDown: () => (user.current = true), onTouchStart: () => (user.current = true), onScroll: (e) => {
|
|
929
1105
|
const x = e.currentTarget.scrollLeft;
|
|
930
1106
|
if (user.current && x !== left.current && performance.now() > busy.current)
|
|
931
1107
|
follow.current = false;
|
|
932
1108
|
left.current = x;
|
|
933
|
-
}, style: { overflowX:
|
|
1109
|
+
}, style: { overflowX: zoom ? 'auto' : undefined }, children: _jsxs("svg", { viewBox: `0 0 ${rail.width} ${rail.height}`, style: {
|
|
934
1110
|
display: 'block',
|
|
935
|
-
width:
|
|
936
|
-
maxWidth:
|
|
1111
|
+
width: zoom ? rail.width * zoom : '100%',
|
|
1112
|
+
maxWidth: zoom ? undefined : rail.width,
|
|
937
1113
|
height: 'auto',
|
|
938
1114
|
margin: `${noMap ? 0 : RAIL.gap}px auto 0`,
|
|
939
1115
|
overflow: 'visible',
|