@heyramzi/ui 2.11.1 → 2.11.2
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/dist/agents/helpers.js +38 -38
- package/dist/agents/page-state.js +49 -50
- package/dist/agents/vendor/color.js +146 -127
- package/dist/agents/vendor/draw.js +617 -508
- package/dist/agents/vendor/engine.js +698 -599
- package/dist/agents/vendor/mesh.js +653 -683
- package/dist/agents/vendor/plastic.js +3913 -3112
- package/dist/agents/vendor/presets.js +163 -164
- package/dist/agents/vendor/shapes.js +21 -20
- package/dist/agents/vendor/ticker.js +41 -42
- package/dist/agents/vendor/types.js +1 -0
- package/dist/agents/vendor/wear.js +1421 -1448
- package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
- package/dist/ai/confirmation/index.js +3 -1
- package/dist/ai/index.js +33 -0
- package/dist/ai/prompt-input/index.js +3 -1
- package/dist/ai/reasoning/index.js +3 -1
- package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
- package/dist/ai/suggestion/index.js +3 -1
- package/dist/ai/typing-dots/index.js +3 -1
- package/dist/animations/text-swap.js +24 -24
- package/dist/brand/index.js +54 -48
- package/dist/canvas/card/resize.js +5 -3
- package/dist/canvas/edges/edge-path.js +255 -242
- package/dist/canvas/editor/context-menu.js +39 -36
- package/dist/canvas/editor/dispatch.svelte.js +154 -151
- package/dist/canvas/editor/editor-context.js +7 -2
- package/dist/canvas/editor/editor-mode.js +1 -0
- package/dist/canvas/editor/frame.js +73 -76
- package/dist/canvas/editor/hand-context.js +5 -2
- package/dist/canvas/editor/history-core.js +163 -191
- package/dist/canvas/editor/keymap.js +95 -169
- package/dist/canvas/editor/layout-actions.js +143 -144
- package/dist/canvas/editor/lock.js +52 -51
- package/dist/canvas/editor/rebase.js +217 -240
- package/dist/canvas/editor/style.js +132 -137
- package/dist/canvas/editor/text-field-context.js +7 -2
- package/dist/canvas/editor/z-order.js +65 -70
- package/dist/canvas/headless/alignment.js +22 -16
- package/dist/canvas/headless/break-apart-find.js +286 -279
- package/dist/canvas/headless/break-apart-pieces.js +62 -58
- package/dist/canvas/headless/break-apart-plan.js +299 -325
- package/dist/canvas/headless/break-apart-style.js +75 -70
- package/dist/canvas/headless/break-apart.js +188 -181
- package/dist/canvas/headless/canvas-cleanup.js +78 -58
- package/dist/canvas/headless/edge-anchors.js +115 -116
- package/dist/canvas/headless/edge-memo.js +24 -22
- package/dist/canvas/headless/edge-serialization.js +17 -27
- package/dist/canvas/headless/figure-geometry.js +232 -264
- package/dist/canvas/headless/fit-viewport.js +84 -67
- package/dist/canvas/headless/grid.js +88 -86
- package/dist/canvas/headless/icon-node.js +44 -36
- package/dist/canvas/headless/index.js +1 -0
- package/dist/canvas/headless/linkify.js +17 -19
- package/dist/canvas/headless/pen-marks.js +101 -95
- package/dist/canvas/headless/sketch.js +231 -209
- package/dist/canvas/headless/snap-guides.js +99 -83
- package/dist/canvas/headless/svg-figure.js +133 -117
- package/dist/canvas/headless/table-layout.js +146 -107
- package/dist/canvas/index.js +2 -0
- package/dist/canvas/lod/lod-mode.svelte.js +20 -18
- package/dist/canvas/lod/render-all.svelte.js +36 -39
- package/dist/canvas/ops/arrange.js +78 -71
- package/dist/canvas/ops/ops.js +260 -255
- package/dist/canvas/pen/colors.js +37 -35
- package/dist/canvas/pen/eraser.js +28 -27
- package/dist/canvas/pen/geometry.js +120 -130
- package/dist/canvas/pen/index.js +1 -0
- package/dist/canvas/pen/ops.js +11 -11
- package/dist/canvas/pen/pen-session.js +150 -159
- package/dist/canvas/pen/plugin.js +107 -104
- package/dist/canvas/pen/shapes.js +73 -68
- package/dist/canvas/pen/stroke-path.js +32 -30
- package/dist/canvas/pen/types.js +21 -21
- package/dist/canvas/pen/ui.js +10 -8
- package/dist/canvas/plugin.js +46 -46
- package/dist/canvas/screen/glyphs.js +12 -11
- package/dist/canvas/screen/labels.js +22 -22
- package/dist/canvas/screen/parts.js +98 -111
- package/dist/canvas/screen/plugin.js +47 -56
- package/dist/canvas/screen/types.js +30 -35
- package/dist/canvas/screen/ui.js +7 -7
- package/dist/canvas/test-utils.js +14 -14
- package/dist/canvas/types.js +1 -0
- package/dist/canvas/ui/forward-wheel.js +24 -25
- package/dist/canvas/ui/icons.js +64 -131
- package/dist/canvas/ui/place-panel.js +58 -49
- package/dist/canvas/ui/shortcut-tip.js +14 -12
- package/dist/canvas/ui/tools.js +300 -385
- package/dist/charts/dashboard/chart.js +4 -0
- package/dist/charts/dashboard/data-table.js +11 -0
- package/dist/charts/dashboard/windows.js +8 -3
- package/dist/charts/edge-fade.js +10 -5
- package/dist/charts/hover-band.js +16 -11
- package/dist/charts/index.js +0 -1
- package/dist/charts/motion.js +11 -7
- package/dist/charts/primitives/chart-utils.js +12 -12
- package/dist/components/copy/clipboard.js +32 -32
- package/dist/components/copy/copied.svelte.js +31 -23
- package/dist/components/data-table/data-table.js +39 -39
- package/dist/components/docs/index.js +4 -0
- package/dist/components/docs/scroll-spy.svelte.js +73 -68
- package/dist/components/list-page/filter-section.js +4 -1
- package/dist/components/list-page/index.js +2 -0
- package/dist/components/list-page/list-table.js +7 -2
- package/dist/components/list-page/queue-rail.js +14 -0
- package/dist/components/list-page/section-host.js +4 -2
- package/dist/components/list-page/step.js +4 -3
- package/dist/components/list-page/types.js +1 -0
- package/dist/components/picker/trigger.js +48 -30
- package/dist/components/picker/types.js +1 -0
- package/dist/components/segmented/collapse.js +38 -27
- package/dist/components/segmented/index.js +8 -0
- package/dist/components/segmented/segmented.js +67 -42
- package/dist/components/star-rating-input/index.js +3 -1
- package/dist/components/table-view/types.js +1 -0
- package/dist/components/tags-input/tags.js +81 -69
- package/dist/components/theme/index.js +4 -0
- package/dist/components/toolbar/index.js +4 -0
- package/dist/components/toolbar/toolbar-trigger.js +14 -1
- package/dist/editor/anchors.js +17 -13
- package/dist/editor/block-id.js +52 -51
- package/dist/editor/block-ops.js +108 -80
- package/dist/editor/doc-blocks.js +155 -166
- package/dist/editor/extensions.js +139 -180
- package/dist/editor/index.js +4 -0
- package/dist/editor/markdown-test-helpers.js +4 -2
- package/dist/editor/markdown.js +420 -408
- package/dist/editor/serializer.js +204 -200
- package/dist/editor/slash-items.js +89 -101
- package/dist/editor/youtube-url.js +25 -20
- package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
- package/dist/effects/animated-beam/types.js +1 -0
- package/dist/effects/border-beam/css.js +92 -78
- package/dist/effects/border-beam/presets.js +1705 -1720
- package/dist/effects/border-beam/register-properties.js +43 -33
- package/dist/effects/border-beam/types.js +1 -0
- package/dist/effects/image-comparison/compare.js +3 -2
- package/dist/effects/image-reveal/types.js +1 -0
- package/dist/effects/magnetic/magnetic.js +58 -52
- package/dist/effects/reveal/observe.js +50 -40
- package/dist/effects/reveal/types.js +1 -0
- package/dist/effects/shader-gradient/color.js +36 -29
- package/dist/effects/shader-gradient/renderer.js +384 -295
- package/dist/effects/shader-gradient/shaders.js +31 -0
- package/dist/effects/shader-gradient/types.js +1 -0
- package/dist/icons/apple-symbol-exports.js +99 -124
- package/dist/icons/sf-symbols.js +357 -461
- package/dist/icons/trades.js +43 -28
- package/dist/inbox/index.js +1 -0
- package/dist/inbox/logic.js +126 -122
- package/dist/inbox/types.js +43 -43
- package/dist/lib/arrive.js +19 -12
- package/dist/lib/icon-types.js +1 -0
- package/dist/lib/initials.js +16 -10
- package/dist/lib/layout.js +5 -0
- package/dist/lib/panel.js +13 -0
- package/dist/lib/result.js +22 -22
- package/dist/lib/sliding-pill.js +59 -44
- package/dist/lib/theme-transition.js +43 -43
- package/dist/lib/tone.js +31 -24
- package/dist/lib/utils.js +3 -1
- package/dist/onboarding/define-tours.js +24 -23
- package/dist/onboarding/onboarding.svelte.js +16 -12
- package/dist/onboarding/pick.js +10 -6
- package/dist/onboarding/stores.js +40 -41
- package/dist/onboarding/wait.js +24 -23
- package/dist/panel/side-panel-types.js +1 -0
- package/dist/record/demo-data.js +459 -486
- package/dist/record/fields.js +129 -131
- package/dist/record/format.js +70 -82
- package/dist/record/gutter.js +1 -0
- package/dist/record/kinds.js +95 -105
- package/dist/record/logic.js +238 -219
- package/dist/record/types.js +49 -48
- package/dist/shadcn/accordion/index.js +3 -1
- package/dist/shadcn/alert/index.js +3 -1
- package/dist/shadcn/alert-dialog/index.js +3 -1
- package/dist/shadcn/attachment/index.js +3 -1
- package/dist/shadcn/avatar/index.js +3 -1
- package/dist/shadcn/breadcrumb/index.js +3 -1
- package/dist/shadcn/bubble/index.js +3 -1
- package/dist/shadcn/button/index.js +3 -1
- package/dist/shadcn/calendar/index.js +3 -1
- package/dist/shadcn/card/index.js +3 -1
- package/dist/shadcn/checkbox/index.js +3 -1
- package/dist/shadcn/code-block/index.js +3 -1
- package/dist/shadcn/collapsible/index.js +3 -1
- package/dist/shadcn/command/index.js +4 -1
- package/dist/shadcn/context-menu/index.js +3 -1
- package/dist/shadcn/dialog/index.js +3 -1
- package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
- package/dist/shadcn/empty/index.js +3 -1
- package/dist/shadcn/field/index.js +3 -1
- package/dist/shadcn/hooks/highlighter.js +77 -75
- package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
- package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
- package/dist/shadcn/input/index.js +3 -1
- package/dist/shadcn/kbd/index.js +3 -1
- package/dist/shadcn/label/index.js +3 -1
- package/dist/shadcn/marker/index.js +3 -1
- package/dist/shadcn/message/index.js +3 -1
- package/dist/shadcn/message-scroller/index.js +3 -1
- package/dist/shadcn/popover/index.js +3 -1
- package/dist/shadcn/progress/index.js +3 -1
- package/dist/shadcn/progress/tone.js +8 -7
- package/dist/shadcn/radio-group/index.js +3 -1
- package/dist/shadcn/scroll-area/index.js +3 -1
- package/dist/shadcn/select/index.js +3 -1
- package/dist/shadcn/separator/index.js +3 -1
- package/dist/shadcn/sheet/index.js +3 -1
- package/dist/shadcn/sidebar/context.svelte.js +42 -28
- package/dist/shadcn/sidebar/index.js +3 -1
- package/dist/shadcn/skeleton/index.js +3 -1
- package/dist/shadcn/slider/index.js +3 -1
- package/dist/shadcn/switch/index.js +3 -1
- package/dist/shadcn/table/index.js +3 -1
- package/dist/shadcn/tabs/index.js +3 -1
- package/dist/shadcn/textarea/index.js +3 -1
- package/dist/shadcn/toggle/index.js +3 -1
- package/dist/shadcn/toggle-group/index.js +3 -1
- package/dist/shadcn/tooltip/context.js +7 -2
- package/dist/shadcn/tooltip/index.js +3 -1
- package/dist/shadcn/trace/index.js +3 -1
- package/dist/slides/bridges.js +65 -59
- package/dist/slides/deck-controller.svelte.js +210 -207
- package/dist/slides/export/capture.js +66 -71
- package/dist/slides/export/ffmpeg.js +76 -72
- package/dist/slides/export/index.js +3 -0
- package/dist/slides/export/launch.js +9 -6
- package/dist/slides/export/pdf.js +60 -54
- package/dist/slides/export/types.js +1 -0
- package/dist/slides/labels.js +24 -24
- package/dist/slides/layout.js +39 -41
- package/dist/slides/model.js +9 -7
- package/dist/slides/navigation.js +78 -73
- package/dist/slides/types.js +2 -2
- package/dist/status/define-statuses.js +18 -14
- package/dist/status/status-types.js +1 -0
- package/dist/test-setup.js +24 -20
- package/dist/thread/logic.js +73 -77
- package/dist/thread/types.js +21 -21
- package/dist/work/board-order.js +94 -90
- package/dist/work/dates.js +16 -9
- package/dist/work/detail/detail-types.js +18 -18
- package/dist/work/docs/edited.js +32 -29
- package/dist/work/docs/index.js +4 -0
- package/dist/work/docs/labels.js +34 -34
- package/dist/work/docs/menu.js +113 -106
- package/dist/work/docs/outline.js +39 -29
- package/dist/work/gantt/logic.js +211 -235
- package/dist/work/inbox/inbox-logic.js +71 -75
- package/dist/work/inbox/inbox-statuses.js +75 -72
- package/dist/work/inbox/inbox-types.js +1 -0
- package/dist/work/kanban-drag.js +27 -22
- package/dist/work/kanban-types.js +1 -0
- package/dist/work/presets/sales.js +36 -32
- package/dist/work/reorder.js +9 -8
- package/dist/work/task-types.js +1 -0
- package/dist/work/time/duration.js +51 -46
- package/dist/work/time/time-types.js +23 -23
- package/dist/work/use-kanban-dnd.svelte.js +126 -125
- package/package.json +3 -1
- package/src/components/list-page/master-detail.svelte +3 -1
- package/src/components/list-page/rail-row.svelte +4 -4
- package/src/editor/markdown.ts +30 -35
|
@@ -1,82 +1,99 @@
|
|
|
1
|
+
/** What a node occupies when the saved data does not say. */
|
|
1
2
|
const FALLBACK_NODE = {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
width: 240,
|
|
4
|
+
height: 80
|
|
4
5
|
};
|
|
5
6
|
function num(value) {
|
|
6
|
-
|
|
7
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
7
8
|
}
|
|
8
9
|
function cssDim(css, prop) {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const found = css.match(new RegExp(`(?:^|[;\\s])${prop}:\\s*(\\d+(?:\\.\\d+)?)px`))?.[1];
|
|
11
|
+
return found ? Number(found) : null;
|
|
11
12
|
}
|
|
13
|
+
/** A stored node often has a width and no height (the DOM measures it), so each side is read alone. */
|
|
12
14
|
function styleDim(style, prop) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
if (typeof style === "string") return cssDim(style, prop);
|
|
16
|
+
if (!style || typeof style !== "object") return null;
|
|
17
|
+
return num(style[prop]);
|
|
16
18
|
}
|
|
17
19
|
function readSize(node) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
return {
|
|
21
|
+
width: num(node.width) ?? styleDim(node.style, "width") ?? FALLBACK_NODE.width,
|
|
22
|
+
height: num(node.height) ?? styleDim(node.style, "height") ?? FALLBACK_NODE.height
|
|
23
|
+
};
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The box every top-level node sits in. Children are parent-relative, so
|
|
27
|
+
* counting them would push the bounds out and shrink the drawing.
|
|
28
|
+
*/
|
|
23
29
|
export function diagramBounds(nodes) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
const boxes = nodes.filter((node) => !node.parentId).map((node) => ({
|
|
31
|
+
...readSize(node),
|
|
32
|
+
x: node.position.x,
|
|
33
|
+
y: node.position.y
|
|
34
|
+
}));
|
|
35
|
+
if (boxes.length === 0) return null;
|
|
36
|
+
const minX = Math.min(...boxes.map((box) => box.x));
|
|
37
|
+
const minY = Math.min(...boxes.map((box) => box.y));
|
|
38
|
+
const maxX = Math.max(...boxes.map((box) => box.x + box.width));
|
|
39
|
+
const maxY = Math.max(...boxes.map((box) => box.y + box.height));
|
|
40
|
+
return {
|
|
41
|
+
x: minX,
|
|
42
|
+
y: minY,
|
|
43
|
+
width: maxX - minX,
|
|
44
|
+
height: maxY - minY
|
|
45
|
+
};
|
|
40
46
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The viewport that centres `nodes` inside a `container` of the given size.
|
|
49
|
+
* `padding` is a fraction of the container, matching fitView's own meaning.
|
|
50
|
+
*/
|
|
51
|
+
export function fitViewport(nodes, container, padding = .04) {
|
|
52
|
+
const bounds = diagramBounds(nodes);
|
|
53
|
+
if (!bounds || bounds.width <= 0 || bounds.height <= 0) return null;
|
|
54
|
+
if (container.width <= 0 || container.height <= 0) return null;
|
|
55
|
+
const usableWidth = container.width * (1 - padding * 2);
|
|
56
|
+
const usableHeight = container.height * (1 - padding * 2);
|
|
57
|
+
const zoom = Math.min(usableWidth / bounds.width, usableHeight / bounds.height);
|
|
58
|
+
return {
|
|
59
|
+
x: (container.width - bounds.width * zoom) / 2 - bounds.x * zoom,
|
|
60
|
+
y: (container.height - bounds.height * zoom) / 2 - bounds.y * zoom,
|
|
61
|
+
zoom
|
|
62
|
+
};
|
|
53
63
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
/** Below this a whole-board fit is too small to read a card's text. */
|
|
65
|
+
const MIN_READABLE_ZOOM = .3;
|
|
66
|
+
/*
|
|
67
|
+
* Where a board with no view opens: a board that fits reads whole, but a tall
|
|
68
|
+
* wall fits at 8%, so it fits its width and starts at the top.
|
|
69
|
+
*/
|
|
70
|
+
export function openingViewport(nodes, container, padding = .04, gutter = 0) {
|
|
71
|
+
const whole = fitViewport(nodes, container, padding);
|
|
72
|
+
if (!whole || whole.zoom >= MIN_READABLE_ZOOM) return whole;
|
|
73
|
+
const bounds = diagramBounds(nodes);
|
|
74
|
+
if (!bounds) return null;
|
|
75
|
+
const inset = container.width * padding;
|
|
76
|
+
const left = inset + gutter;
|
|
77
|
+
const zoom = Math.min((container.width - left - inset) / bounds.width, 1);
|
|
78
|
+
return {
|
|
79
|
+
x: left - bounds.x * zoom,
|
|
80
|
+
y: inset - bounds.y * zoom,
|
|
81
|
+
zoom
|
|
82
|
+
};
|
|
68
83
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Whether a saved view still shows the board: a card is on screen, and a fitting board isn't
|
|
86
|
+
* pushed off the left. `inset` is the tool rail's strip.
|
|
87
|
+
*/
|
|
88
|
+
export function viewShowsContent(nodes, viewport, container, inset = { left: 0 }) {
|
|
89
|
+
const bounds = diagramBounds(nodes);
|
|
90
|
+
if (!bounds || viewport.zoom <= 0) return true;
|
|
91
|
+
const left = (inset.left - viewport.x) / viewport.zoom;
|
|
92
|
+
const right = (container.width - viewport.x) / viewport.zoom;
|
|
93
|
+
const top = -viewport.y / viewport.zoom;
|
|
94
|
+
const bottom = (container.height - viewport.y) / viewport.zoom;
|
|
95
|
+
const onScreen = bounds.x < right && bounds.x + bounds.width > left && bounds.y < bottom && bounds.y + bounds.height > top;
|
|
96
|
+
if (!onScreen) return false;
|
|
97
|
+
const fits = bounds.width * viewport.zoom <= container.width - inset.left;
|
|
98
|
+
return !(fits && bounds.x < left - 4 / viewport.zoom);
|
|
82
99
|
}
|
|
@@ -1,96 +1,98 @@
|
|
|
1
|
+
// The magnets: the grid the editor snaps cards to. A write that isn't a drag snaps through here.
|
|
1
2
|
import { STROKE_NODE } from "../pen/types.js";
|
|
3
|
+
/** Flow units between two dots at the finest level. */
|
|
2
4
|
export const GRID_SIZE = 20;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
];
|
|
7
|
-
|
|
8
|
-
STROKE_NODE
|
|
9
|
-
]);
|
|
5
|
+
/** The value xyflow's `snapGrid` prop takes. */
|
|
6
|
+
export const SNAP_GRID = [GRID_SIZE, GRID_SIZE];
|
|
7
|
+
/** Node types that keep their exact points. A stroke's origin is its bounding box, so moving it shifts the drawing. */
|
|
8
|
+
export const FREEHAND_NODE_TYPES = new Set([STROKE_NODE]);
|
|
9
|
+
/** The nearest dot on one axis, rounded the way xyflow rounds a drag. */
|
|
10
10
|
export function snapToGrid(value) {
|
|
11
|
-
|
|
11
|
+
return GRID_SIZE * Math.round(value / GRID_SIZE);
|
|
12
12
|
}
|
|
13
13
|
function pointOf(value) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
if (typeof value !== "object" || value === null) return null;
|
|
15
|
+
const { x, y } = value;
|
|
16
|
+
return typeof x === "number" && typeof y === "number" && Number.isFinite(x) && Number.isFinite(y) ? {
|
|
17
|
+
x,
|
|
18
|
+
y
|
|
19
|
+
} : null;
|
|
20
20
|
}
|
|
21
21
|
function stringOf(value) {
|
|
22
|
-
|
|
22
|
+
return typeof value === "string" ? value : undefined;
|
|
23
23
|
}
|
|
24
|
+
/** Snaps the nodes a write created or moved and leaves the rest. A child snaps on the page, then goes back to relative. */
|
|
24
25
|
export function snapWrittenNodes(before, after, exempt = FREEHAND_NODE_TYPES) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
26
|
+
const prior = new Map();
|
|
27
|
+
for (const node of before) {
|
|
28
|
+
const id = stringOf(node.id);
|
|
29
|
+
if (id) prior.set(id, {
|
|
30
|
+
at: pointOf(node.position),
|
|
31
|
+
parent: stringOf(node.parentId)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const byId = new Map();
|
|
35
|
+
for (const node of after) {
|
|
36
|
+
const id = stringOf(node.id);
|
|
37
|
+
if (id) byId.set(id, node);
|
|
38
|
+
}
|
|
39
|
+
const written = (node) => {
|
|
40
|
+
const was = prior.get(String(node.id));
|
|
41
|
+
const at = pointOf(node.position);
|
|
42
|
+
const type = stringOf(node.type);
|
|
43
|
+
if (!at || type !== undefined && exempt.has(type)) return false;
|
|
44
|
+
if (!was?.at) return true;
|
|
45
|
+
return was.at.x !== at.x || was.at.y !== at.y || was.parent !== stringOf(node.parentId);
|
|
46
|
+
};
|
|
47
|
+
// Parents settle first, because a child's page position rides on its parent's.
|
|
48
|
+
const settled = new Map();
|
|
49
|
+
const visiting = new Set();
|
|
50
|
+
const settle = (node) => {
|
|
51
|
+
const id = String(node.id);
|
|
52
|
+
const done = settled.get(id);
|
|
53
|
+
if (done) return done;
|
|
54
|
+
const local = pointOf(node.position) ?? {
|
|
55
|
+
x: 0,
|
|
56
|
+
y: 0
|
|
57
|
+
};
|
|
58
|
+
const parentId = stringOf(node.parentId);
|
|
59
|
+
const parent = parentId ? byId.get(parentId) : undefined;
|
|
60
|
+
let origin = {
|
|
61
|
+
x: 0,
|
|
62
|
+
y: 0
|
|
63
|
+
};
|
|
64
|
+
if (parent && !visiting.has(id)) {
|
|
65
|
+
visiting.add(id);
|
|
66
|
+
origin = settle(parent).page;
|
|
67
|
+
visiting.delete(id);
|
|
68
|
+
}
|
|
69
|
+
const page = {
|
|
70
|
+
x: origin.x + local.x,
|
|
71
|
+
y: origin.y + local.y
|
|
72
|
+
};
|
|
73
|
+
const result = written(node) ? {
|
|
74
|
+
local: {
|
|
75
|
+
x: snapToGrid(page.x) - origin.x,
|
|
76
|
+
y: snapToGrid(page.y) - origin.y
|
|
77
|
+
},
|
|
78
|
+
page: {
|
|
79
|
+
x: snapToGrid(page.x),
|
|
80
|
+
y: snapToGrid(page.y)
|
|
81
|
+
}
|
|
82
|
+
} : {
|
|
83
|
+
local,
|
|
84
|
+
page
|
|
85
|
+
};
|
|
86
|
+
settled.set(id, result);
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
return after.map((node) => {
|
|
90
|
+
if (!stringOf(node.id)) return node;
|
|
91
|
+
const at = pointOf(node.position);
|
|
92
|
+
const next = settle(node).local;
|
|
93
|
+
return at && (next.x !== at.x || next.y !== at.y) ? {
|
|
94
|
+
...node,
|
|
95
|
+
position: next
|
|
96
|
+
} : node;
|
|
97
|
+
});
|
|
96
98
|
}
|
|
@@ -1,45 +1,53 @@
|
|
|
1
1
|
export const ICON_SIZE = 56;
|
|
2
2
|
export const ICON_W = 120;
|
|
3
|
+
/** Gap between the mark and its label, either side. */
|
|
3
4
|
export const ICON_LABEL_GAP = 12;
|
|
4
5
|
export const ICON_LABEL_H = 22;
|
|
5
6
|
export const ICON_LINE_H = 20;
|
|
7
|
+
/** A right-hand label is set larger: it names an area, not a tool. */
|
|
6
8
|
export const ICON_SIDE_LABEL_H = 28;
|
|
9
|
+
/** Space between the port line and the first line under it. */
|
|
7
10
|
export const ICON_PORT_GAP = 6;
|
|
11
|
+
/**
|
|
12
|
+
* A label below centres the mark and hands off at its edges. A right label starts it
|
|
13
|
+
* at x 0 and leaves from the box's right edge.
|
|
14
|
+
*/
|
|
8
15
|
export function iconGeometry(icon, width = ICON_W) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
16
|
+
const size = icon.size ?? ICON_SIZE;
|
|
17
|
+
const lines = icon.lines?.length ?? 0;
|
|
18
|
+
const mid = size / 2;
|
|
19
|
+
if (icon.labelSide === "right") {
|
|
20
|
+
// The label sits across the port line: name above it, lines below.
|
|
21
|
+
const top = Math.max(0, (icon.label ? ICON_SIDE_LABEL_H : 0) - mid);
|
|
22
|
+
return {
|
|
23
|
+
size,
|
|
24
|
+
markX: 0,
|
|
25
|
+
markY: top,
|
|
26
|
+
height: Math.max(top + size, top + mid + ICON_PORT_GAP + lines * ICON_LINE_H),
|
|
27
|
+
left: {
|
|
28
|
+
x: 0,
|
|
29
|
+
y: top + mid
|
|
30
|
+
},
|
|
31
|
+
right: {
|
|
32
|
+
x: Math.max(width, size),
|
|
33
|
+
y: top + mid
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const markX = (Math.max(width, size) - size) / 2;
|
|
38
|
+
const text = (icon.label ? ICON_LABEL_H : 0) + lines * ICON_LINE_H;
|
|
39
|
+
return {
|
|
40
|
+
size,
|
|
41
|
+
markX,
|
|
42
|
+
markY: 0,
|
|
43
|
+
height: size + (text ? ICON_LABEL_GAP + text : 0),
|
|
44
|
+
left: {
|
|
45
|
+
x: markX,
|
|
46
|
+
y: mid
|
|
47
|
+
},
|
|
48
|
+
right: {
|
|
49
|
+
x: markX + size,
|
|
50
|
+
y: mid
|
|
51
|
+
}
|
|
52
|
+
};
|
|
45
53
|
}
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
const URL_RE = /https?:\/\/[^\s<>"]+/g;
|
|
2
|
+
/** Sentence punctuation that follows a link without belonging to it. */
|
|
2
3
|
const TRAILING = /[.,;:!?)\]'”]+$/;
|
|
4
|
+
/** Splits a line so each http(s) link is its own part, minus the punctuation after it. */
|
|
3
5
|
export function linkify(text) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (last < text.length) parts.push({
|
|
19
|
-
text: text.slice(last)
|
|
20
|
-
});
|
|
21
|
-
return parts;
|
|
6
|
+
const parts = [];
|
|
7
|
+
let last = 0;
|
|
8
|
+
for (const match of text.matchAll(URL_RE)) {
|
|
9
|
+
const start = match.index ?? 0;
|
|
10
|
+
const href = match[0].replace(TRAILING, "");
|
|
11
|
+
if (start > last) parts.push({ text: text.slice(last, start) });
|
|
12
|
+
parts.push({
|
|
13
|
+
text: href,
|
|
14
|
+
href
|
|
15
|
+
});
|
|
16
|
+
last = start + href.length;
|
|
17
|
+
}
|
|
18
|
+
if (last < text.length) parts.push({ text: text.slice(last) });
|
|
19
|
+
return parts;
|
|
22
20
|
}
|
|
23
21
|
export function hasLink(text) {
|
|
24
|
-
|
|
22
|
+
return /https?:\/\//.test(text);
|
|
25
23
|
}
|