@heyramzi/ui 2.11.0 → 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/components/tags-input/tags-input.svelte +2 -0
- package/src/editor/markdown.ts +30 -35
|
@@ -1,161 +1,200 @@
|
|
|
1
|
+
// Table geometry: column widths, and the height that follows.
|
|
2
|
+
// One owner, because the component, node-sizing and board-doc must agree.
|
|
3
|
+
// Flat numbers stay in metrics.ts.
|
|
4
|
+
/** 13px Satoshi. Deliberately wide: guessing narrow leaves a gap. */
|
|
1
5
|
const CELL_CHAR_W = 6.7;
|
|
6
|
+
/** px-3 on both sides of a cell. */
|
|
2
7
|
export const TABLE_CELL_PAD_X = 12;
|
|
8
|
+
/** One line of a cell: 13px over leading-[18px]. */
|
|
3
9
|
const ROW_LINE_H = 18;
|
|
10
|
+
/** The hairline between two rows. */
|
|
4
11
|
const ROW_BORDER = 1;
|
|
12
|
+
/** The "no rows configured" placeholder. */
|
|
5
13
|
const TABLE_EMPTY_H = 56;
|
|
14
|
+
/** A section band: 11px caps over a 16px line + py-1.5 + border. */
|
|
6
15
|
const SECTION_ROW_H = 29;
|
|
16
|
+
/** A note under a row: 11px over a 16px line. */
|
|
7
17
|
const NOTE_LINE_H = 16;
|
|
18
|
+
/** Indent of the first cell per depth step. */
|
|
8
19
|
export const TABLE_DEPTH_INDENT = 16;
|
|
20
|
+
// The table card's own rhythm.
|
|
21
|
+
/** Title row: 24px icon chip + py-3 + border. */
|
|
9
22
|
export const TABLE_TITLE_H = 49;
|
|
23
|
+
/** Column heads: 16px line + py-2 + border. */
|
|
10
24
|
export const TABLE_COLHEAD_H = 33;
|
|
25
|
+
/** py-2.5 top and bottom. */
|
|
11
26
|
const TABLE_ROW_PAD_Y = 20;
|
|
27
|
+
/** The narrowest a table card gets, however little it says. */
|
|
12
28
|
export const TABLE_MIN_W = 360;
|
|
29
|
+
/** The widest it gets, so a 13px line stays under about 75 characters. */
|
|
13
30
|
export const TABLE_MAX_W = 760;
|
|
31
|
+
/** A column left blank to be filled in still gets room to write. */
|
|
14
32
|
const FILL_COL_CH = 32;
|
|
33
|
+
/** Excalifont runs about 12% wider than Satoshi over the same text. */
|
|
15
34
|
const HAND_FONT_FACTOR = 1.12;
|
|
35
|
+
/** The widest a drag may make a card. */
|
|
16
36
|
export const TABLE_RESIZE_MAX_W = 1600;
|
|
37
|
+
/** The floor. Under 9 characters a head like "Price" wraps. */
|
|
17
38
|
const MIN_COL_CH = 9;
|
|
39
|
+
/** The ceiling, so one long column cannot eat the card. */
|
|
18
40
|
const MAX_COL_CH = 48;
|
|
19
41
|
export function tableColumnCount(headers, rows) {
|
|
20
|
-
|
|
42
|
+
return Math.max(headers.length, rows.reduce((max, row) => Math.max(max, row.cells?.length ?? 0), 0), 1);
|
|
21
43
|
}
|
|
44
|
+
/** Fractions summing to 1. `explicit` is relative, so [1,1,3] and [10,10,30]
|
|
45
|
+
* are one split. Otherwise the content decides. */
|
|
22
46
|
export function tableColumnWeights(headers, rows, explicit) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
47
|
+
const count = tableColumnCount(headers, rows);
|
|
48
|
+
if (explicit && explicit.length === count && explicit.every((w) => w > 0)) {
|
|
49
|
+
return normalize(explicit);
|
|
50
|
+
}
|
|
51
|
+
return normalize(naturalColumnChars(headers, rows, count));
|
|
28
52
|
}
|
|
53
|
+
/** Characters each column wants: its longest line, between the floor and the ceiling. */
|
|
29
54
|
function naturalColumnChars(headers, rows, count) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
return Array.from({ length: count }, (_, i) => {
|
|
56
|
+
const head = headers[i]?.length ?? 0;
|
|
57
|
+
const cells = rows.filter((row) => !row.section).map((row) => row.cells?.[i] ?? "");
|
|
58
|
+
const body = cells.reduce((max, cell) => Math.max(max, cell.length), 0);
|
|
59
|
+
// "...", "…" or nothing: the column is a blank to write in, not content.
|
|
60
|
+
const blank = cells.length > 0 && cells.every(isBlankCell);
|
|
61
|
+
const wants = blank ? Math.max(head, FILL_COL_CH) : Math.max(head, body);
|
|
62
|
+
return Math.min(MAX_COL_CH, Math.max(MIN_COL_CH, wants));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** A placeholder rather than a value: empty, dots, an ellipsis, a dash or a question mark. */
|
|
41
66
|
export function isBlankCell(value) {
|
|
42
|
-
|
|
67
|
+
return /^[\s.…?\-–—_]*$/.test(value);
|
|
43
68
|
}
|
|
69
|
+
/** The narrowest a column gets: enough for a head like "Price" not to wrap. */
|
|
44
70
|
export const TABLE_COL_MIN_W = MIN_COL_CH * CELL_CHAR_W + TABLE_CELL_PAD_X * 2;
|
|
71
|
+
/** `minmax(floor, …fr)`, not a bare `fr`: a long cell must not widen its column,
|
|
72
|
+
* and a thin weight must not squeeze a head below the floor. */
|
|
45
73
|
export function tableGridTemplate(weights, floor = 0) {
|
|
46
|
-
|
|
47
|
-
|
|
74
|
+
const min = floor > 0 ? `${floor}px` : "0";
|
|
75
|
+
return weights.map((w) => `minmax(${min}, ${w.toFixed(4)}fr)`).join(" ");
|
|
48
76
|
}
|
|
77
|
+
/** Pixel widths for weights, with columns under the floor pinned there, as the grid does. */
|
|
49
78
|
export function tableColumnPx(weights, width) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
const out = weights.map(() => 0);
|
|
80
|
+
const pinned = new Set();
|
|
81
|
+
let changed = true;
|
|
82
|
+
while (changed) {
|
|
83
|
+
changed = false;
|
|
84
|
+
const free = weights.reduce((sum, w, i) => sum + (pinned.has(i) ? 0 : w), 0);
|
|
85
|
+
const room = width - pinned.size * TABLE_COL_MIN_W;
|
|
86
|
+
weights.forEach((w, i) => {
|
|
87
|
+
if (pinned.has(i)) return;
|
|
88
|
+
out[i] = free > 0 ? room * w / free : 0;
|
|
89
|
+
if (out[i] < TABLE_COL_MIN_W && pinned.size < weights.length - 1) {
|
|
90
|
+
pinned.add(i);
|
|
91
|
+
changed = true;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return out.map((px, i) => pinned.has(i) ? TABLE_COL_MIN_W : px);
|
|
67
96
|
}
|
|
68
97
|
function charWidth(font) {
|
|
69
|
-
|
|
98
|
+
return font === "hand" ? CELL_CHAR_W * HAND_FONT_FACTOR : CELL_CHAR_W;
|
|
70
99
|
}
|
|
100
|
+
/** The width that fits the content, clamped to TABLE_MIN_W..TABLE_MAX_W and
|
|
101
|
+
* never past `ceiling`. A card the user resized keeps that width instead. */
|
|
71
102
|
export function tableWidth(headers, rows, ceiling = TABLE_MAX_W, font = "plain") {
|
|
72
|
-
|
|
73
|
-
|
|
103
|
+
const fitted = Math.ceil(naturalWidth(headers, rows, font));
|
|
104
|
+
return Math.min(ceiling, TABLE_MAX_W, Math.max(TABLE_MIN_W, fitted));
|
|
74
105
|
}
|
|
106
|
+
/** The narrowest a drag may make a card: every column at its floor. */
|
|
75
107
|
export function tableMinWidth(headers, rows) {
|
|
76
|
-
|
|
108
|
+
return tableColumnCount(headers, rows) * TABLE_COL_MIN_W + 2;
|
|
77
109
|
}
|
|
110
|
+
/** The width at which every column holds its longest line, up to the per-column cap. */
|
|
78
111
|
function naturalWidth(headers, rows, font) {
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
const count = tableColumnCount(headers, rows);
|
|
113
|
+
return naturalColumnChars(headers, rows, count).reduce((sum, ch) => sum + ch * charWidth(font) + TABLE_CELL_PAD_X * 2, 0);
|
|
81
114
|
}
|
|
115
|
+
/** Column weights for a card `width` wide. A card with room for the content
|
|
116
|
+
* splits by content; a squeezed one falls back to the author's weights. */
|
|
82
117
|
export function tableWeightsAt(headers, rows, width, { explicit, font = "plain" } = {}) {
|
|
83
|
-
|
|
84
|
-
|
|
118
|
+
if (width >= naturalWidth(headers, rows, font)) return tableColumnWeights(headers, rows);
|
|
119
|
+
return tableColumnWeights(headers, rows, explicit);
|
|
85
120
|
}
|
|
121
|
+
/** Figure columns get `tabular-nums`. Not right-aligned: a table node is read
|
|
122
|
+
* across, and a right-aligned middle column opens a gutter mid-sentence. */
|
|
86
123
|
export function tableNumericColumns(rows, count) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return values.length > 0 && values.every(isFigure);
|
|
92
|
-
});
|
|
124
|
+
return Array.from({ length: count }, (_, i) => {
|
|
125
|
+
const values = rows.filter((row) => !row.section).map((row) => row.cells?.[i]?.trim() ?? "").filter(Boolean);
|
|
126
|
+
return values.length > 0 && values.every(isFigure);
|
|
127
|
+
});
|
|
93
128
|
}
|
|
129
|
+
/** A price, a count, a percentage, a multiplier. Not "$20 a month". */
|
|
94
130
|
function isFigure(value) {
|
|
95
|
-
|
|
131
|
+
return /^[-+]?[$€£]?\s?\d[\d\s.,]*\s?[%x×]?$/.test(value);
|
|
96
132
|
}
|
|
133
|
+
/** How many lines a cell takes at the width its column gets. */
|
|
97
134
|
function wrappedLines(text, columnWidth, charW = CELL_CHAR_W) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
135
|
+
const inner = Math.max(charW, columnWidth - TABLE_CELL_PAD_X * 2);
|
|
136
|
+
const perLine = Math.max(1, Math.floor(inner / charW));
|
|
137
|
+
return Math.max(1, Math.ceil(text.length / perLine));
|
|
101
138
|
}
|
|
139
|
+
/** Pixel widths of the columns when the card is `width` wide. */
|
|
102
140
|
export function tableColumnsAt(headers, rows, width, { sizing, weights }) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
141
|
+
const count = tableColumnCount(headers, rows);
|
|
142
|
+
const inner = Math.max(count * TABLE_COL_MIN_W, width - 2);
|
|
143
|
+
const pinned = sizing.columnWidths;
|
|
144
|
+
if (pinned && pinned.length === count && pinned.every((w) => w > 0)) {
|
|
145
|
+
return tableColumnPx(pinned, inner);
|
|
146
|
+
}
|
|
147
|
+
const split = sizing.userWidth != null ? tableColumnWeights(headers, rows, weights) : tableWeightsAt(headers, rows, width, {
|
|
148
|
+
explicit: weights,
|
|
149
|
+
font: sizing.font
|
|
150
|
+
});
|
|
151
|
+
return tableColumnPx(split, inner);
|
|
152
|
+
}
|
|
153
|
+
/** The card's width and the pixel width of each column, from what the user fixed. */
|
|
115
154
|
export function tableColumns(headers, rows, sizing = {}, weights) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
155
|
+
const count = tableColumnCount(headers, rows);
|
|
156
|
+
const pinned = sizing.columnWidths;
|
|
157
|
+
const hasPinned = pinned && pinned.length === count && pinned.every((w) => w > 0);
|
|
158
|
+
const width = sizing.userWidth ?? (hasPinned ? Math.round(pinned.reduce((sum, w) => sum + w, 0)) + 2 : tableWidth(headers, rows, TABLE_MAX_W, sizing.font));
|
|
159
|
+
return {
|
|
160
|
+
width,
|
|
161
|
+
columns: tableColumnsAt(headers, rows, width, {
|
|
162
|
+
sizing,
|
|
163
|
+
weights
|
|
164
|
+
})
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** Each row's height: its content, or the height the user pinned if that is more. */
|
|
128
168
|
export function tableRowHeights(rows, columns, sizing = {}) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
169
|
+
const charW = charWidth(sizing.font ?? "plain");
|
|
170
|
+
const full = columns.reduce((sum, px) => sum + px, 0);
|
|
171
|
+
return rows.map((row, i) => {
|
|
172
|
+
if (row.section) return Math.max(SECTION_ROW_H, sizing.rowHeights?.[i] ?? 0);
|
|
173
|
+
const indent = (row.depth ?? 0) * TABLE_DEPTH_INDENT;
|
|
174
|
+
const lines = columns.reduce((max, px, c) => Math.max(max, wrappedLines(row.cells?.[c] ?? "", c === 0 ? px - indent : px, charW)), 1);
|
|
175
|
+
const note = row.note ? wrappedLines(row.note, full - indent) * NOTE_LINE_H : 0;
|
|
176
|
+
const content = TABLE_ROW_PAD_Y + lines * ROW_LINE_H + ROW_BORDER + note;
|
|
177
|
+
return Math.max(content, sizing.rowHeights?.[i] ?? 0);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** The card's height at a given width, wrapping included. A height the user set is a floor. */
|
|
140
181
|
export function tableHeight(headers, rows, width, { explicit, sizing = {} } = {}) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
182
|
+
const head = headers.length > 0 ? TABLE_COLHEAD_H : 0;
|
|
183
|
+
if (rows.length === 0) return TABLE_TITLE_H + head + TABLE_EMPTY_H;
|
|
184
|
+
const columns = tableColumnsAt(headers, rows, width, {
|
|
185
|
+
sizing,
|
|
186
|
+
weights: explicit
|
|
187
|
+
});
|
|
188
|
+
const body = tableRowHeights(rows, columns, sizing).reduce((sum, h) => sum + h, 0);
|
|
189
|
+
return Math.max(sizing.userHeight ?? 0, TABLE_TITLE_H + head + body);
|
|
190
|
+
}
|
|
191
|
+
/** The width a column needs to hold its longest line, for a double-click on its border. */
|
|
150
192
|
export function tableColumnFit(headers, rows, col, font = "plain") {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
].reduce((max, text)=>Math.max(max, text.length), 0);
|
|
155
|
-
const px = Math.ceil(longest * charWidth(font)) + TABLE_CELL_PAD_X * 2;
|
|
156
|
-
return Math.max(TABLE_COL_MIN_W, Math.min(px, TABLE_RESIZE_MAX_W));
|
|
193
|
+
const longest = [headers[col] ?? "", ...rows.filter((row) => !row.section).map((row) => row.cells?.[col] ?? "")].reduce((max, text) => Math.max(max, text.length), 0);
|
|
194
|
+
const px = Math.ceil(longest * charWidth(font)) + TABLE_CELL_PAD_X * 2;
|
|
195
|
+
return Math.max(TABLE_COL_MIN_W, Math.min(px, TABLE_RESIZE_MAX_W));
|
|
157
196
|
}
|
|
158
197
|
function normalize(weights) {
|
|
159
|
-
|
|
160
|
-
|
|
198
|
+
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
199
|
+
return total > 0 ? weights.map((w) => w / total) : weights.map(() => 1 / weights.length);
|
|
161
200
|
}
|
package/dist/canvas/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @heyramzi/ui/canvas public API. Server-safe: nothing here touches the DOM.
|
|
2
|
+
// Svelte components are imported by path, like "@heyramzi/ui/canvas/ui/canvas-grid.svelte".
|
|
1
3
|
export * from "./types.js";
|
|
2
4
|
export * from "./plugin.js";
|
|
3
5
|
export { tryCatch, tryCatchAsync } from "../lib/result.js";
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { untrack } from "svelte";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
// One flag per canvas: a card reading it re-renders when it flips, not on every zoom frame.
|
|
3
|
+
/** Below this zoom a card is a box. */
|
|
4
|
+
export const LOW_BELOW = .4;
|
|
5
|
+
/** Back to the full card above this. The gap stops a zoom held at the line from swapping every card each frame. */
|
|
6
|
+
export const FULL_ABOVE = .46;
|
|
4
7
|
const modes = new WeakMap();
|
|
8
|
+
/** The shared flag for one canvas's store. The first card to ask starts the one effect that keeps it. */
|
|
5
9
|
export function lodMode(store) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
21
|
-
return mode;
|
|
10
|
+
const known = modes.get(store);
|
|
11
|
+
if (known) return known;
|
|
12
|
+
const mode = $state({ low: store.viewport.zoom < LOW_BELOW });
|
|
13
|
+
modes.set(store, mode);
|
|
14
|
+
$effect.root(() => {
|
|
15
|
+
$effect(() => {
|
|
16
|
+
const zoom = store.viewport.zoom;
|
|
17
|
+
untrack(() => {
|
|
18
|
+
if (mode.low && zoom > FULL_ABOVE) mode.low = false;
|
|
19
|
+
else if (!mode.low && zoom < LOW_BELOW) mode.low = true;
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
return mode;
|
|
22
24
|
}
|
|
@@ -1,45 +1,42 @@
|
|
|
1
1
|
import { tick } from "svelte";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export const renderAll = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const frame = ()=>new Promise((resolve)=>requestAnimationFrame(()=>resolve()));
|
|
2
|
+
// An export reads the page's markup, so it holds this open: every card is drawn in full until it lets go.
|
|
3
|
+
const hold = $state({ count: 0 });
|
|
4
|
+
/** True while something needs every card drawn in full. */
|
|
5
|
+
export const renderAll = { get on() {
|
|
6
|
+
return hold.count > 0;
|
|
7
|
+
} };
|
|
8
|
+
const frame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
9
|
+
// Arrows draw a beat after their cards, once the cards are measured, so they are counted too.
|
|
11
10
|
const DRAWN = ".svelte-flow__node, .svelte-flow__edge";
|
|
12
11
|
const SETTLE_FRAMES = 8;
|
|
13
|
-
const GIVE_UP_MS =
|
|
12
|
+
const GIVE_UP_MS = 4e3;
|
|
13
|
+
/** The page's card fade-in is switched off under this attribute, so a card that remounts is captured solid. */
|
|
14
14
|
export const EVERY_CARD_ATTR = "data-cv-every-card";
|
|
15
|
+
/** Runs `use` with every card drawn in full. Waits until the number of cards and arrows on the page holds still. */
|
|
15
16
|
export async function withEveryCard(root, use) {
|
|
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
|
-
} finally{
|
|
42
|
-
hold.count -= 1;
|
|
43
|
-
if (hold.count === 0) marked?.removeAttribute(EVERY_CARD_ATTR);
|
|
44
|
-
}
|
|
17
|
+
hold.count += 1;
|
|
18
|
+
const marked = root instanceof Element ? root : null;
|
|
19
|
+
marked?.setAttribute(EVERY_CARD_ATTR, "");
|
|
20
|
+
try {
|
|
21
|
+
await tick();
|
|
22
|
+
const start = performance.now();
|
|
23
|
+
let last = -1;
|
|
24
|
+
let still = 0;
|
|
25
|
+
while (still < SETTLE_FRAMES && performance.now() - start < GIVE_UP_MS) {
|
|
26
|
+
await frame();
|
|
27
|
+
const now = root.querySelectorAll(DRAWN).length;
|
|
28
|
+
still = now === last ? still + 1 : 0;
|
|
29
|
+
last = now;
|
|
30
|
+
}
|
|
31
|
+
// A fade that began before the switch-off still has to land.
|
|
32
|
+
if ("getAnimations" in root) {
|
|
33
|
+
const running = root.getAnimations({ subtree: true });
|
|
34
|
+
await Promise.race([Promise.allSettled(running.map((animation) => animation.finished)), new Promise((resolve) => setTimeout(resolve, 1e3))]);
|
|
35
|
+
}
|
|
36
|
+
await frame();
|
|
37
|
+
return await use();
|
|
38
|
+
} finally {
|
|
39
|
+
hold.count -= 1;
|
|
40
|
+
if (hold.count === 0) marked?.removeAttribute(EVERY_CARD_ATTR);
|
|
41
|
+
}
|
|
45
42
|
}
|
|
@@ -1,83 +1,90 @@
|
|
|
1
1
|
export const ALIGNMENTS = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"left",
|
|
3
|
+
"right",
|
|
4
|
+
"top",
|
|
5
|
+
"bottom",
|
|
6
|
+
"center-horizontal",
|
|
7
|
+
"center-vertical"
|
|
8
8
|
];
|
|
9
|
+
// =========================
|
|
10
|
+
// ALIGN
|
|
11
|
+
// =========================
|
|
12
|
+
/** "center-horizontal" puts the centres on one vertical axis, as tldraw named it. */
|
|
9
13
|
export function alignPositions(items, alignment) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
const moved = new Map();
|
|
15
|
+
if (items.length < 2) return moved;
|
|
16
|
+
const left = Math.min(...items.map((item) => item.position.x));
|
|
17
|
+
const top = Math.min(...items.map((item) => item.position.y));
|
|
18
|
+
const right = Math.max(...items.map((item) => item.position.x + item.width));
|
|
19
|
+
const bottom = Math.max(...items.map((item) => item.position.y + item.height));
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
const { x, y } = item.position;
|
|
22
|
+
const next = {
|
|
23
|
+
x,
|
|
24
|
+
y
|
|
25
|
+
};
|
|
26
|
+
if (alignment === "left") next.x = left;
|
|
27
|
+
else if (alignment === "right") next.x = right - item.width;
|
|
28
|
+
else if (alignment === "top") next.y = top;
|
|
29
|
+
else if (alignment === "bottom") next.y = bottom - item.height;
|
|
30
|
+
else if (alignment === "center-horizontal") next.x = (left + right) / 2 - item.width / 2;
|
|
31
|
+
else next.y = (top + bottom) / 2 - item.height / 2;
|
|
32
|
+
if (next.x !== x || next.y !== y) moved.set(item.id, next);
|
|
33
|
+
}
|
|
34
|
+
return moved;
|
|
31
35
|
}
|
|
36
|
+
// =========================
|
|
37
|
+
// DISTRIBUTE AND STACK
|
|
38
|
+
// =========================
|
|
32
39
|
function along(axis) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const horizontal = axis === "horizontal";
|
|
41
|
+
return {
|
|
42
|
+
start: (item) => horizontal ? item.position.x : item.position.y,
|
|
43
|
+
size: (item) => horizontal ? item.width : item.height,
|
|
44
|
+
place: (item, at) => horizontal ? {
|
|
45
|
+
x: at,
|
|
46
|
+
y: item.position.y
|
|
47
|
+
} : {
|
|
48
|
+
x: item.position.x,
|
|
49
|
+
y: at
|
|
50
|
+
}
|
|
51
|
+
};
|
|
45
52
|
}
|
|
46
53
|
function inOrder(items, axis) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
...items
|
|
50
|
-
].sort((a, b)=>start(a) - start(b));
|
|
54
|
+
const { start } = along(axis);
|
|
55
|
+
return [...items].sort((a, b) => start(a) - start(b));
|
|
51
56
|
}
|
|
57
|
+
/** Space the boxes so every gap matches. The first and last stay where they are. */
|
|
52
58
|
export function distributePositions(items, axis) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
const moved = new Map();
|
|
60
|
+
if (items.length < 3) return moved;
|
|
61
|
+
const { start, size, place } = along(axis);
|
|
62
|
+
const sorted = inOrder(items, axis);
|
|
63
|
+
const first = sorted[0];
|
|
64
|
+
const last = sorted[sorted.length - 1];
|
|
65
|
+
if (!first || !last) return moved;
|
|
66
|
+
const span = start(last) + size(last) - start(first);
|
|
67
|
+
const used = sorted.reduce((sum, item) => sum + size(item), 0);
|
|
68
|
+
const gap = (span - used) / (sorted.length - 1);
|
|
69
|
+
let cursor = start(first);
|
|
70
|
+
for (const item of sorted) {
|
|
71
|
+
if (cursor !== start(item)) moved.set(item.id, place(item, cursor));
|
|
72
|
+
cursor += size(item) + gap;
|
|
73
|
+
}
|
|
74
|
+
return moved;
|
|
69
75
|
}
|
|
76
|
+
/** Put the boxes in a row or a column, `gap` apart, in the order they sit now. The first stays put. */
|
|
70
77
|
export function stackPositions(items, axis, gap) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
const moved = new Map();
|
|
79
|
+
if (items.length < 2) return moved;
|
|
80
|
+
const { start, size, place } = along(axis);
|
|
81
|
+
const sorted = inOrder(items, axis);
|
|
82
|
+
const first = sorted[0];
|
|
83
|
+
if (!first) return moved;
|
|
84
|
+
let cursor = start(first);
|
|
85
|
+
for (const item of sorted) {
|
|
86
|
+
if (cursor !== start(item)) moved.set(item.id, place(item, cursor));
|
|
87
|
+
cursor += size(item) + gap;
|
|
88
|
+
}
|
|
89
|
+
return moved;
|
|
83
90
|
}
|