@heyramzi/ui 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts/dual-axis.js +38 -0
- package/dist/lib/sliding-pill.js +2 -2
- package/dist/record/measure-clamp.js +23 -0
- package/dist/shadcn/button-group/index.js +6 -0
- package/dist/shadcn/checkbox/checkbox-box.js +3 -0
- package/dist/shadcn/input-group/index.js +9 -0
- package/dist/shadcn/item/index.js +13 -0
- package/dist/shadcn/native-select/index.js +6 -0
- package/dist/shadcn/pagination/index.js +12 -0
- package/dist/slides/deck-controller.svelte.js +62 -15
- package/dist/slides/layout.js +3 -2
- package/package.json +9 -2
- package/scripts/svelte-dts.mjs +3 -0
- package/scripts/sync-shadcn.mjs +0 -5
- package/src/ai/generation-progress/GenerationProgress.svelte +14 -12
- package/src/canvas/pen/PenOverlay.svelte +1 -30
- package/src/canvas/screen/PartView.svelte +13 -8
- package/src/canvas/screen/ScreenBody.svelte +18 -11
- package/src/canvas/ui/BarTooltip.svelte +46 -0
- package/src/canvas/ui/BarTooltip.svelte.d.ts +17 -0
- package/src/canvas/ui/CanvasToolbar.svelte +170 -156
- package/src/canvas/ui/StylePanel.svelte +179 -102
- package/src/canvas/ui/canvas-ui.css +12 -76
- package/src/charts/area-chart.css +6 -0
- package/src/charts/area-chart.svelte +115 -17
- package/src/charts/area-chart.svelte.d.ts +11 -2
- package/src/charts/chart-tokens.css +4 -3
- package/src/charts/dashboard/data-table.svelte +80 -80
- package/src/charts/dashboard/hint.svelte +34 -0
- package/src/charts/dashboard/hint.svelte.d.ts +10 -0
- package/src/charts/dashboard/rating-chip.svelte +4 -3
- package/src/charts/dual-axis.test.ts +40 -0
- package/src/charts/dual-axis.ts +43 -0
- package/src/charts/legend.css +0 -8
- package/src/charts/legend.svelte +26 -30
- package/src/components/copy/copy-button.svelte +10 -2
- package/src/components/copy/copy-button.test.ts +7 -0
- package/src/components/data-table/table-skeleton.svelte +15 -7
- package/src/components/data-table/table-skeleton.svelte.d.ts +4 -0
- package/src/components/docs/on-this-page.svelte +2 -2
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +2 -1
- package/src/components/list-page/filter-item.svelte +20 -7
- package/src/components/list-page/filter-popover.svelte +41 -16
- package/src/components/list-page/filter-popover.svelte.d.ts +1 -1
- package/src/components/list-page/grouped-list.svelte +41 -23
- package/src/components/picker/picker.svelte +8 -3
- package/src/components/picker/picker.svelte.d.ts +2 -1
- package/src/components/segmented/segmented-filter.svelte +19 -10
- package/src/components/segmented/segmented-track.svelte +11 -3
- package/src/components/table-view/table-view.svelte +22 -6
- package/src/components/tags-input/tags-input.svelte +94 -77
- package/src/components/toolbar/toolbar.svelte +2 -1
- package/src/components/toolbar/toolbar.test.ts +5 -4
- package/src/components/work-parts/group-head.svelte +28 -9
- package/src/components/work-parts/group-head.svelte.d.ts +5 -2
- package/src/editor/editor-toolbar.svelte +231 -193
- package/src/editor/editor.svelte +57 -39
- package/src/editor/image-toolbar.svelte +99 -37
- package/src/editor/image-toolbar.svelte.d.ts +4 -5
- package/src/editor/url-dialog.svelte +96 -0
- package/src/editor/url-dialog.svelte.d.ts +15 -0
- package/src/effects/theme-toggle/ThemeToggle.css +8 -8
- package/src/effects/theme-toggle/ThemeToggle.svelte +20 -6
- package/src/inbox/thread-inbox.svelte +9 -1
- package/src/lib/live-region.svelte +12 -4
- package/src/lib/live-region.svelte.d.ts +5 -1
- package/src/lib/sliding-pill.ts +3 -2
- package/src/record/info-list.svelte +33 -59
- package/src/record/measure-clamp.ts +31 -0
- package/src/record/record-shell.svelte +38 -39
- package/src/record/record-timeline.svelte +10 -5
- package/src/record/steps-rail.svelte +50 -36
- package/src/record/where-we-are.svelte +5 -19
- package/src/shadcn/button-group/button-group-separator.svelte +23 -0
- package/src/shadcn/button-group/button-group-separator.svelte.d.ts +13 -0
- package/src/shadcn/button-group/button-group-text.svelte +28 -0
- package/src/shadcn/button-group/button-group-text.svelte.d.ts +11 -0
- package/src/shadcn/button-group/button-group.svelte +46 -0
- package/src/shadcn/button-group/button-group.svelte.d.ts +16 -0
- package/src/shadcn/button-group/index.ts +15 -0
- package/src/shadcn/checkbox/checkbox-box.ts +4 -0
- package/src/shadcn/checkbox/checkbox.svelte +2 -4
- package/src/shadcn/input-group/index.ts +22 -0
- package/src/shadcn/input-group/input-group-addon.svelte +51 -0
- package/src/shadcn/input-group/input-group-addon.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-button.svelte +49 -0
- package/src/shadcn/input-group/input-group-button.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-input.svelte +20 -0
- package/src/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
- package/src/shadcn/input-group/input-group-text.svelte +19 -0
- package/src/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
- package/src/shadcn/input-group/input-group-textarea.svelte +20 -0
- package/src/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
- package/src/shadcn/input-group/input-group.svelte +24 -0
- package/src/shadcn/input-group/input-group.svelte.d.ts +5 -0
- package/src/shadcn/item/index.ts +34 -0
- package/src/shadcn/item/item-actions.svelte +20 -0
- package/src/shadcn/item/item-actions.svelte.d.ts +5 -0
- package/src/shadcn/item/item-content.svelte +23 -0
- package/src/shadcn/item/item-content.svelte.d.ts +5 -0
- package/src/shadcn/item/item-description.svelte +23 -0
- package/src/shadcn/item/item-description.svelte.d.ts +5 -0
- package/src/shadcn/item/item-footer.svelte +20 -0
- package/src/shadcn/item/item-footer.svelte.d.ts +5 -0
- package/src/shadcn/item/item-group.svelte +21 -0
- package/src/shadcn/item/item-group.svelte.d.ts +5 -0
- package/src/shadcn/item/item-header.svelte +20 -0
- package/src/shadcn/item/item-header.svelte.d.ts +5 -0
- package/src/shadcn/item/item-media.svelte +42 -0
- package/src/shadcn/item/item-media.svelte.d.ts +17 -0
- package/src/shadcn/item/item-separator.svelte +19 -0
- package/src/shadcn/item/item-separator.svelte.d.ts +13 -0
- package/src/shadcn/item/item-title.svelte +20 -0
- package/src/shadcn/item/item-title.svelte.d.ts +5 -0
- package/src/shadcn/item/item.svelte +61 -0
- package/src/shadcn/item/item.svelte.d.ts +28 -0
- package/src/shadcn/native-select/index.ts +13 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte +20 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select-option.svelte +20 -0
- package/src/shadcn/native-select/native-select-option.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select.svelte +45 -0
- package/src/shadcn/native-select/native-select.svelte.d.ts +8 -0
- package/src/shadcn/pagination/index.ts +31 -0
- package/src/shadcn/pagination/pagination-content.svelte +20 -0
- package/src/shadcn/pagination/pagination-content.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte +22 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-item.svelte +14 -0
- package/src/shadcn/pagination/pagination-item.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-link.svelte +41 -0
- package/src/shadcn/pagination/pagination-link.svelte.d.ts +9 -0
- package/src/shadcn/pagination/pagination-next-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-next-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-next.svelte +26 -0
- package/src/shadcn/pagination/pagination-next.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-previous.svelte +30 -0
- package/src/shadcn/pagination/pagination-previous.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination.svelte +27 -0
- package/src/shadcn/pagination/pagination.svelte.d.ts +4 -0
- package/src/slides/Controls.svelte +39 -36
- package/src/slides/Deck.svelte +23 -10
- package/src/slides/Overview.svelte +10 -4
- package/src/slides/README.md +1 -1
- package/src/slides/Slide.svelte +17 -11
- package/src/slides/deck-controller.svelte.ts +62 -16
- package/src/slides/layout.ts +3 -2
- package/src/slides/theme.css +34 -5
- package/src/thread/thread.svelte +15 -8
- package/src/work/detail/task-detail.svelte +1 -1
- package/src/work/gantt/gantt-task-row.svelte +3 -6
- package/src/work/inbox/inbox-card.svelte +7 -5
- package/src/work/inbox/inbox-trace.svelte +14 -10
- package/src/work/kanban-board.svelte +40 -25
- package/src/work/kanban-hidden-rail.svelte +67 -65
- package/src/work/task-list.svelte +145 -124
- package/src/work/task-list.svelte.test.ts +5 -2
- package/src/work/time/time-tracker.svelte +35 -10
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const NICE = [
|
|
2
|
+
1,
|
|
3
|
+
2,
|
|
4
|
+
2.5,
|
|
5
|
+
5,
|
|
6
|
+
10
|
|
7
|
+
];
|
|
8
|
+
/** The smallest 1, 2, 2.5 or 5 times a power of ten that is at least `raw`. */
|
|
9
|
+
export function niceStep(raw) {
|
|
10
|
+
if (!(raw > 0) || !Number.isFinite(raw)) return 1;
|
|
11
|
+
const power = 10 ** Math.floor(Math.log10(raw));
|
|
12
|
+
// WHY the epsilon: 0.3 / 0.1 is 2.9999999999999996, which would skip 3 x 0.1 for the next step.
|
|
13
|
+
const multiple = NICE.find((m) => m * power >= raw * (1 - 1e-9)) ?? 10;
|
|
14
|
+
return multiple * power;
|
|
15
|
+
}
|
|
16
|
+
function ticksOf(step, count) {
|
|
17
|
+
// Rounded so 0.1 x 3 reads 0.3 on the axis, not 0.30000000000000004.
|
|
18
|
+
return Array.from({ length: count + 1 }, (_, i) => Number((i * step).toPrecision(12)));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Left and right domains from 0, with the same tick count so both axes sit on one grid.
|
|
22
|
+
* `target` is the number of intervals wanted; the left data may round it to one more or less.
|
|
23
|
+
*/
|
|
24
|
+
export function dualAxis(leftMax, rightMax, target = 4) {
|
|
25
|
+
const leftStep = niceStep(Math.max(0, leftMax) / target);
|
|
26
|
+
const count = Math.max(1, Math.ceil(Math.max(0, leftMax) / leftStep - 1e-9));
|
|
27
|
+
const rightStep = niceStep(Math.max(0, rightMax) / count);
|
|
28
|
+
return {
|
|
29
|
+
left: {
|
|
30
|
+
max: leftStep * count,
|
|
31
|
+
ticks: ticksOf(leftStep, count)
|
|
32
|
+
},
|
|
33
|
+
right: {
|
|
34
|
+
max: rightStep * count,
|
|
35
|
+
ticks: ticksOf(rightStep, count)
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/lib/sliding-pill.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// WHY attach to the pill, not the list: the pill finds its list via
|
|
3
3
|
// `parentElement`, so any strip that renders one span gets the motion.
|
|
4
4
|
// WHY measure: the active item's width is its label. The selector covers the
|
|
5
|
-
//
|
|
5
|
+
// ways a strip marks it: a tab, a nav link, a pressed button, a bits-ui toggle group item.
|
|
6
6
|
// Per transitions.dev `tabs-sliding`: first paint and resizes write the
|
|
7
7
|
// position with the transition off, so the pill snaps in instead of flying.
|
|
8
|
-
const ACTIVE = "[data-state=\"active\"],[aria-current=\"page\"],[aria-pressed=\"true\"]";
|
|
8
|
+
const ACTIVE = "[data-state=\"active\"],[aria-current=\"page\"],[aria-pressed=\"true\"],[data-state=\"on\"]";
|
|
9
9
|
export const slidingPill = (pill) => {
|
|
10
10
|
const list = pill.parentElement;
|
|
11
11
|
if (!list) return;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { untrack } from "svelte";
|
|
2
|
+
/** Reports whether a line-clamped box cuts its text, so "More" shows only then. */
|
|
3
|
+
// WHY measure: a note that fits two lines at 1280px overflows at 390px.
|
|
4
|
+
// WHY untracked: an open box keeps its last answer, so a fold never remounts the toggle under focus.
|
|
5
|
+
export function measureClamp(expanded, onCut) {
|
|
6
|
+
return (el) => {
|
|
7
|
+
const measure = () => untrack(() => {
|
|
8
|
+
if (!expanded()) onCut(el.scrollHeight > el.clientHeight + 1);
|
|
9
|
+
});
|
|
10
|
+
measure();
|
|
11
|
+
const observer = new ResizeObserver(measure);
|
|
12
|
+
observer.observe(el);
|
|
13
|
+
// A web font landing rewraps the text in a box that keeps its height, and the observer stays quiet.
|
|
14
|
+
let live = true;
|
|
15
|
+
void document.fonts?.ready.then(() => live && measure());
|
|
16
|
+
document.fonts?.addEventListener("loadingdone", measure);
|
|
17
|
+
return () => {
|
|
18
|
+
live = false;
|
|
19
|
+
observer.disconnect();
|
|
20
|
+
document.fonts?.removeEventListener("loadingdone", measure);
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Separator from "../../../src/shadcn/button-group/button-group-separator.svelte";
|
|
2
|
+
import Text from "../../../src/shadcn/button-group/button-group-text.svelte";
|
|
3
|
+
import Root, { buttonGroupVariants } from "../../../src/shadcn/button-group/button-group.svelte";
|
|
4
|
+
export { Root, Text, Separator, buttonGroupVariants,
|
|
5
|
+
//
|
|
6
|
+
Root as ButtonGroup, Text as ButtonGroupText, Separator as ButtonGroupSeparator };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// The checkbox's square, shared with rows that draw it as a picture inside a menu item,
|
|
2
|
+
// where a real checkbox would put a button inside a button.
|
|
3
|
+
export const checkboxBoxClass = "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 not-data-checked:enabled:hover:border-foreground/30 focus-visible:border-ring focus-visible:shadow-(--ring-focus) aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-sm border shadow-btn transition-[background-color,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] group-has-disabled/field:opacity-50 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 any-pointer-coarse:after:-inset-[15px] disabled:cursor-not-allowed disabled:opacity-50";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Addon from "../../../src/shadcn/input-group/input-group-addon.svelte";
|
|
2
|
+
import Button from "../../../src/shadcn/input-group/input-group-button.svelte";
|
|
3
|
+
import Input from "../../../src/shadcn/input-group/input-group-input.svelte";
|
|
4
|
+
import Text from "../../../src/shadcn/input-group/input-group-text.svelte";
|
|
5
|
+
import Textarea from "../../../src/shadcn/input-group/input-group-textarea.svelte";
|
|
6
|
+
import Root from "../../../src/shadcn/input-group/input-group.svelte";
|
|
7
|
+
export { Root, Addon, Button, Input, Text, Textarea,
|
|
8
|
+
//
|
|
9
|
+
Root as InputGroup, Addon as InputGroupAddon, Button as InputGroupButton, Input as InputGroupInput, Text as InputGroupText, Textarea as InputGroupTextarea };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Actions from "../../../src/shadcn/item/item-actions.svelte";
|
|
2
|
+
import Content from "../../../src/shadcn/item/item-content.svelte";
|
|
3
|
+
import Description from "../../../src/shadcn/item/item-description.svelte";
|
|
4
|
+
import Footer from "../../../src/shadcn/item/item-footer.svelte";
|
|
5
|
+
import Group from "../../../src/shadcn/item/item-group.svelte";
|
|
6
|
+
import Header from "../../../src/shadcn/item/item-header.svelte";
|
|
7
|
+
import Media from "../../../src/shadcn/item/item-media.svelte";
|
|
8
|
+
import Separator from "../../../src/shadcn/item/item-separator.svelte";
|
|
9
|
+
import Title from "../../../src/shadcn/item/item-title.svelte";
|
|
10
|
+
import Root from "../../../src/shadcn/item/item.svelte";
|
|
11
|
+
export { Root, Group, Separator, Header, Footer, Content, Title, Description, Actions, Media,
|
|
12
|
+
//
|
|
13
|
+
Root as Item, Group as ItemGroup, Separator as ItemSeparator, Header as ItemHeader, Footer as ItemFooter, Content as ItemContent, Title as ItemTitle, Description as ItemDescription, Actions as ItemActions, Media as ItemMedia };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Root from "../../../src/shadcn/native-select/native-select.svelte";
|
|
2
|
+
import Option from "../../../src/shadcn/native-select/native-select-option.svelte";
|
|
3
|
+
import OptGroup from "../../../src/shadcn/native-select/native-select-opt-group.svelte";
|
|
4
|
+
export { Root, Option, OptGroup,
|
|
5
|
+
//
|
|
6
|
+
Root as NativeSelect, Option as NativeSelectOption, OptGroup as NativeSelectOptGroup };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Content from "../../../src/shadcn/pagination/pagination-content.svelte";
|
|
2
|
+
import Ellipsis from "../../../src/shadcn/pagination/pagination-ellipsis.svelte";
|
|
3
|
+
import Item from "../../../src/shadcn/pagination/pagination-item.svelte";
|
|
4
|
+
import Link from "../../../src/shadcn/pagination/pagination-link.svelte";
|
|
5
|
+
import NextButton from "../../../src/shadcn/pagination/pagination-next-button.svelte";
|
|
6
|
+
import Next from "../../../src/shadcn/pagination/pagination-next.svelte";
|
|
7
|
+
import PrevButton from "../../../src/shadcn/pagination/pagination-prev-button.svelte";
|
|
8
|
+
import Previous from "../../../src/shadcn/pagination/pagination-previous.svelte";
|
|
9
|
+
import Root from "../../../src/shadcn/pagination/pagination.svelte";
|
|
10
|
+
export { Root, Content, Item, Link, PrevButton, NextButton, Ellipsis, Previous, Next,
|
|
11
|
+
//
|
|
12
|
+
Root as Pagination, Content as PaginationContent, Item as PaginationItem, Link as PaginationLink, PrevButton as PaginationPrevButton, NextButton as PaginationNextButton, Ellipsis as PaginationEllipsis, Previous as PaginationPrevious, Next as PaginationNext };
|
|
@@ -5,9 +5,19 @@ import { resolveLabels } from "./labels.js";
|
|
|
5
5
|
import { DEFAULT_CANVAS } from "./types.js";
|
|
6
6
|
/** Height the controls bar takes at the bottom of the overview, in px. */
|
|
7
7
|
const CONTROLS_ROOM = 56;
|
|
8
|
-
/** How long the overview morph flag stays up, a little past the longest stagger. */
|
|
9
|
-
const MORPH_MS = 900;
|
|
10
8
|
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
9
|
+
/**
|
|
10
|
+
* Where `cell` lands when the camera moves `target` onto `fit`. Every box shares one scale and
|
|
11
|
+
* shift, and a transform tween is linear in both, so the grid stays rigid while it zooms.
|
|
12
|
+
*/
|
|
13
|
+
function frame(cell, target, fit) {
|
|
14
|
+
const k = fit.scale / target.scale;
|
|
15
|
+
return {
|
|
16
|
+
x: fit.x + (cell.x - target.x) * k,
|
|
17
|
+
y: fit.y + (cell.y - target.y) * k,
|
|
18
|
+
scale: cell.scale * k
|
|
19
|
+
};
|
|
20
|
+
}
|
|
11
21
|
function isEditable(el) {
|
|
12
22
|
return el.isContentEditable || [
|
|
13
23
|
"INPUT",
|
|
@@ -22,7 +32,7 @@ export class DeckController {
|
|
|
22
32
|
/** The highlighted thumbnail in the overview, as an index among the visible slides. */
|
|
23
33
|
cursor = $state(0);
|
|
24
34
|
direction = $state(1);
|
|
25
|
-
|
|
35
|
+
zoom = $state(null);
|
|
26
36
|
size = $state({
|
|
27
37
|
w: 0,
|
|
28
38
|
h: 0
|
|
@@ -30,6 +40,7 @@ export class DeckController {
|
|
|
30
40
|
options;
|
|
31
41
|
#listeners = new Set();
|
|
32
42
|
#timer;
|
|
43
|
+
#frame = 0;
|
|
33
44
|
constructor(options) {
|
|
34
45
|
this.options = options;
|
|
35
46
|
}
|
|
@@ -61,8 +72,13 @@ export class DeckController {
|
|
|
61
72
|
}, this.canvas, this.visible.length));
|
|
62
73
|
/** Where slide `index` sits right now: centred to fit, or in its overview cell. */
|
|
63
74
|
boxOf(index) {
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
const zoom = this.zoom;
|
|
76
|
+
const cell = this.grid.boxes[this.visible.indexOf(index)];
|
|
77
|
+
if (zoom?.framed) {
|
|
78
|
+
const target = this.grid.boxes[this.visible.indexOf(zoom.index)];
|
|
79
|
+
return cell && target ? frame(cell, target, this.fit) : this.fit;
|
|
80
|
+
}
|
|
81
|
+
return this.overview || zoom ? cell : this.fit;
|
|
66
82
|
}
|
|
67
83
|
register(entry) {
|
|
68
84
|
untrack(() => {
|
|
@@ -78,7 +94,7 @@ export class DeckController {
|
|
|
78
94
|
return () => this.#listeners.delete(listener);
|
|
79
95
|
}
|
|
80
96
|
mount() {
|
|
81
|
-
return () =>
|
|
97
|
+
return () => this.#stopZoom();
|
|
82
98
|
}
|
|
83
99
|
/** Which deck hears a key is the page's call: Deck passes only keys from focus inside it. */
|
|
84
100
|
get listening() {
|
|
@@ -88,14 +104,15 @@ export class DeckController {
|
|
|
88
104
|
this.options.index = position.index;
|
|
89
105
|
this.options.step = position.step;
|
|
90
106
|
}
|
|
91
|
-
|
|
107
|
+
/** `animate: false` skips the slide transition, for a move the overview zoom already shows. */
|
|
108
|
+
commit(target, source = "local", animate = true) {
|
|
92
109
|
if (!target) return;
|
|
93
110
|
const from = this.position;
|
|
94
111
|
const to = settle(this.slides, target);
|
|
95
112
|
if (to.index === from.index && to.step === from.step) return;
|
|
96
113
|
this.direction = to.index >= from.index ? 1 : -1;
|
|
97
114
|
const apply = () => this.#write(to);
|
|
98
|
-
if (to.index !== from.index && this.options.transition) this.options.transition(apply);
|
|
115
|
+
if (animate && to.index !== from.index && this.options.transition) this.options.transition(apply);
|
|
99
116
|
else apply();
|
|
100
117
|
for (const listener of this.#listeners) listener(to, source);
|
|
101
118
|
}
|
|
@@ -111,23 +128,53 @@ export class DeckController {
|
|
|
111
128
|
step
|
|
112
129
|
}, source);
|
|
113
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Opening zooms the camera out from the current slide to the whole grid. Closing zooms into
|
|
133
|
+
* the current slide, so `select` moves there first.
|
|
134
|
+
*/
|
|
114
135
|
toggleOverview(force) {
|
|
115
136
|
const open = force ?? !this.overview;
|
|
116
137
|
if (open === this.overview) return;
|
|
117
|
-
|
|
138
|
+
const index = this.position.index;
|
|
139
|
+
if (open) this.cursor = Math.max(0, this.visible.indexOf(index));
|
|
118
140
|
this.view = open ? "overview" : "slide";
|
|
119
|
-
this.#
|
|
141
|
+
this.#stopZoom();
|
|
142
|
+
const ms = this.options.zoomMs?.() ?? 0;
|
|
143
|
+
if (ms <= 0 || !this.visible.includes(index)) return;
|
|
144
|
+
/*
|
|
145
|
+
* Each way paints its start state first and moves on a later frame. Moved in the same
|
|
146
|
+
* flush, a style read in between (the overview handing focus back) commits the boxes
|
|
147
|
+
* before the transition rule is on, and the zoom jumps to its end.
|
|
148
|
+
*/
|
|
149
|
+
this.zoom = {
|
|
150
|
+
index,
|
|
151
|
+
framed: open,
|
|
152
|
+
running: !open
|
|
153
|
+
};
|
|
154
|
+
this.#frame = requestAnimationFrame(() => {
|
|
155
|
+
this.#frame = requestAnimationFrame(() => {
|
|
156
|
+
this.zoom = {
|
|
157
|
+
index,
|
|
158
|
+
framed: !open,
|
|
159
|
+
running: true
|
|
160
|
+
};
|
|
161
|
+
this.#timer = setTimeout(() => this.#stopZoom(), ms);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
120
164
|
}
|
|
121
165
|
/** Leaves the overview on slide `index`. Picking the current slide keeps its step. */
|
|
122
166
|
select(index) {
|
|
123
|
-
this.toggleOverview(false);
|
|
124
167
|
const keep = index === this.position.index;
|
|
125
|
-
this.
|
|
168
|
+
this.commit({
|
|
169
|
+
index,
|
|
170
|
+
step: keep ? this.position.step : 0
|
|
171
|
+
}, "local", false);
|
|
172
|
+
this.toggleOverview(false);
|
|
126
173
|
}
|
|
127
|
-
#
|
|
128
|
-
this.morphing = true;
|
|
174
|
+
#stopZoom() {
|
|
129
175
|
clearTimeout(this.#timer);
|
|
130
|
-
this.#
|
|
176
|
+
cancelAnimationFrame(this.#frame);
|
|
177
|
+
this.zoom = null;
|
|
131
178
|
}
|
|
132
179
|
#moveCursor(to) {
|
|
133
180
|
this.cursor = clamp(to, 0, this.visible.length - 1);
|
package/dist/slides/layout.js
CHANGED
|
@@ -10,7 +10,8 @@ export function fitBox(view, canvas) {
|
|
|
10
10
|
function scaleFor(cols, count, view, canvas, pad, gap) {
|
|
11
11
|
const rows = Math.ceil(count / cols);
|
|
12
12
|
const byWidth = (view.w - 2 * pad - (cols - 1) * gap) / cols / canvas.w;
|
|
13
|
-
|
|
13
|
+
/* Every row, the last too, keeps a gap under it for its numbers. */
|
|
14
|
+
const byHeight = (view.h - 2 * pad - rows * gap) / rows / canvas.h;
|
|
14
15
|
return Math.min(byWidth, byHeight);
|
|
15
16
|
}
|
|
16
17
|
/** Lays `count` canvases in the roomiest grid that shows them all, centred. */
|
|
@@ -32,7 +33,7 @@ export function gridBoxes(view, canvas, count) {
|
|
|
32
33
|
const cellH = canvas.h * scale;
|
|
33
34
|
const rows = Math.ceil(count / cols);
|
|
34
35
|
const left = (view.w - (cols * cellW + (cols - 1) * gap)) / 2;
|
|
35
|
-
const top = (view.h -
|
|
36
|
+
const top = (view.h - rows * (cellH + gap)) / 2;
|
|
36
37
|
const boxes = Array.from({ length: count }, (_, i) => ({
|
|
37
38
|
x: left + i % cols * (cellW + gap),
|
|
38
39
|
y: top + Math.floor(i / cols) * (cellH + gap),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyramzi/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Svelte 5 UI for apps: primitives, effects and animations, plus work (tasks and docs), charts, slides, an editor, onboarding and a canvas as subpaths.",
|
|
5
5
|
"homepage": "https://ui.heyramzi.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -271,6 +271,10 @@
|
|
|
271
271
|
"default": "./src/editor/block-ops.ts"
|
|
272
272
|
},
|
|
273
273
|
"./editor/inline-editor": {
|
|
274
|
+
"types": [
|
|
275
|
+
"./src/editor/inline-editor.svelte.d.ts",
|
|
276
|
+
"./src/editor/inline-editor.svelte"
|
|
277
|
+
],
|
|
274
278
|
"svelte": "./src/editor/inline-editor.svelte",
|
|
275
279
|
"default": "./src/editor/inline-editor.svelte"
|
|
276
280
|
},
|
|
@@ -291,7 +295,10 @@
|
|
|
291
295
|
"default": "./src/canvas/index.ts"
|
|
292
296
|
},
|
|
293
297
|
"./canvas/*.svelte": {
|
|
294
|
-
"types":
|
|
298
|
+
"types": [
|
|
299
|
+
"./src/canvas/*.svelte.d.ts",
|
|
300
|
+
"./src/canvas/*.svelte"
|
|
301
|
+
],
|
|
295
302
|
"svelte": "./src/canvas/*.svelte",
|
|
296
303
|
"default": "./src/canvas/*.svelte"
|
|
297
304
|
},
|
package/scripts/svelte-dts.mjs
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// `.svelte` under node_modules, so without them every component types as `any`.
|
|
4
4
|
|
|
5
5
|
// prepack writes them, postpack deletes them (`--clean`): left in src, they'd shadow the components.
|
|
6
|
+
// An export whose target is a `.svelte` file lists its `types` as [`x.svelte.d.ts`, `x.svelte`]:
|
|
7
|
+
// from a `.svelte` target TypeScript only looks for `x.d.svelte.ts`, and svelte-check stops
|
|
8
|
+
// faking that file once a real `x.svelte.d.ts` sits beside it, so the import found nothing (3.0.0).
|
|
6
9
|
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
10
|
import { createRequire } from "node:module";
|
|
8
11
|
import { tmpdir } from "node:os";
|
package/scripts/sync-shadcn.mjs
CHANGED
|
@@ -76,19 +76,14 @@ const DEMO_ALIASES = [
|
|
|
76
76
|
/* Registry primitives this package does not ship: no product imports them. Skipped, so a sync does not bring them back. */
|
|
77
77
|
const NOT_SHIPPED = new Set([
|
|
78
78
|
"aspect-ratio",
|
|
79
|
-
"button-group",
|
|
80
79
|
"carousel",
|
|
81
80
|
"chart",
|
|
82
81
|
"data-table",
|
|
83
82
|
"drawer",
|
|
84
83
|
"form",
|
|
85
|
-
"input-group",
|
|
86
|
-
"item",
|
|
87
84
|
"kbd",
|
|
88
85
|
"menubar",
|
|
89
|
-
"native-select",
|
|
90
86
|
"navigation-menu",
|
|
91
|
-
"pagination",
|
|
92
87
|
"range-calendar",
|
|
93
88
|
"resizable",
|
|
94
89
|
// Its `pascalToKebab` has no importer, in the registry or here.
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// =========================
|
|
20
20
|
// IMPORTS
|
|
21
21
|
// =========================
|
|
22
|
+
import { Progress as ProgressPrimitive } from "bits-ui";
|
|
22
23
|
import { cn } from "../../lib/utils.js";
|
|
23
24
|
|
|
24
25
|
// =========================
|
|
@@ -104,21 +105,22 @@
|
|
|
104
105
|
</span>
|
|
105
106
|
</div>
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
<!-- Bits' root, not the shadcn bar: that one has no indeterminate look and fills with primary. -->
|
|
109
|
+
<ProgressPrimitive.Root
|
|
110
|
+
value={progress ? progress.completed : null}
|
|
111
|
+
max={progress?.total}
|
|
111
112
|
aria-label={heading}
|
|
112
|
-
aria-valuemin={progress ? 0 : undefined}
|
|
113
|
-
aria-valuemax={progress?.total}
|
|
114
|
-
aria-valuenow={progress?.completed}
|
|
115
113
|
aria-valuetext={progress ? `${progress.completed} of ${progress.total}` : undefined}
|
|
116
114
|
>
|
|
117
|
-
|
|
118
|
-
class="gen-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
{#snippet child({ props })}
|
|
116
|
+
<div {...props} class="gen-track">
|
|
117
|
+
<div
|
|
118
|
+
class="gen-fill"
|
|
119
|
+
style:transform={progress ? `scaleX(${percent / 100})` : undefined}
|
|
120
|
+
></div>
|
|
121
|
+
</div>
|
|
122
|
+
{/snippet}
|
|
123
|
+
</ProgressPrimitive.Root>
|
|
122
124
|
|
|
123
125
|
{#if progress?.currentStep}
|
|
124
126
|
{#key progress.currentStep}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { PenOp } from "./ops.js";
|
|
9
9
|
import { createPenSession, type PenPreview } from "./pen-session.js";
|
|
10
10
|
import type { PenColor, PenTool } from "./types.js";
|
|
11
|
+
import { forwardWheel } from "../ui/forward-wheel.js";
|
|
11
12
|
|
|
12
13
|
// =========================
|
|
13
14
|
// TYPES
|
|
@@ -106,36 +107,6 @@
|
|
|
106
107
|
if (event.key === "Escape") session.cancel();
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
// The overlay sits above xyflow's zoom pane, so a wheel or a trackpad pinch would
|
|
110
|
-
// never reach it. Hand those to the pane so you can zoom and pan mid-drawing.
|
|
111
|
-
function forwardWheel(node: HTMLElement) {
|
|
112
|
-
const handler = (event: WheelEvent) => {
|
|
113
|
-
const zoomPane = node.closest(".svelte-flow")?.querySelector(".svelte-flow__zoom");
|
|
114
|
-
if (!zoomPane) return;
|
|
115
|
-
event.preventDefault();
|
|
116
|
-
zoomPane.dispatchEvent(
|
|
117
|
-
new WheelEvent("wheel", {
|
|
118
|
-
deltaX: event.deltaX,
|
|
119
|
-
deltaY: event.deltaY,
|
|
120
|
-
deltaZ: event.deltaZ,
|
|
121
|
-
deltaMode: event.deltaMode,
|
|
122
|
-
clientX: event.clientX,
|
|
123
|
-
clientY: event.clientY,
|
|
124
|
-
screenX: event.screenX,
|
|
125
|
-
screenY: event.screenY,
|
|
126
|
-
ctrlKey: event.ctrlKey,
|
|
127
|
-
shiftKey: event.shiftKey,
|
|
128
|
-
altKey: event.altKey,
|
|
129
|
-
metaKey: event.metaKey,
|
|
130
|
-
bubbles: true,
|
|
131
|
-
cancelable: true,
|
|
132
|
-
}),
|
|
133
|
-
);
|
|
134
|
-
};
|
|
135
|
-
node.addEventListener("wheel", handler, { passive: false });
|
|
136
|
-
return () => node.removeEventListener("wheel", handler);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
110
|
function cursorFor(current: PenTool): string {
|
|
140
111
|
if (current === "text") return "text";
|
|
141
112
|
return "crosshair";
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// IMPORTS
|
|
4
4
|
// =========================
|
|
5
5
|
import { tick } from "svelte";
|
|
6
|
+
import { Toggle } from "../../shadcn/toggle/index.js";
|
|
6
7
|
import GlyphIcon from "./GlyphIcon.svelte";
|
|
7
8
|
import { addItem, removeItem, setItem } from "./parts.js";
|
|
8
9
|
import type { ScreenLabels } from "./labels.js";
|
|
@@ -206,15 +207,19 @@
|
|
|
206
207
|
{#if !readonly}
|
|
207
208
|
<div class="cv-tools nodrag">
|
|
208
209
|
{#if part.kind === "button"}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
<!-- The button is drawn here, so the card's own classes replace the toggle's utilities. -->
|
|
211
|
+
<Toggle
|
|
212
|
+
bind:pressed={
|
|
213
|
+
() => part.variant !== "secondary",
|
|
214
|
+
(primary) => onCommit({ variant: primary ? "primary" : "secondary" }, false)
|
|
215
|
+
}
|
|
215
216
|
>
|
|
216
|
-
{
|
|
217
|
-
|
|
217
|
+
{#snippet child({ props })}
|
|
218
|
+
<button {...props} class="cv-tool is-text">
|
|
219
|
+
{part.variant === "secondary" ? labels.secondary : labels.primary}
|
|
220
|
+
</button>
|
|
221
|
+
{/snippet}
|
|
222
|
+
</Toggle>
|
|
218
223
|
{/if}
|
|
219
224
|
<button
|
|
220
225
|
type="button"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// =========================
|
|
3
3
|
// IMPORTS
|
|
4
4
|
// =========================
|
|
5
|
+
import { RadioGroup } from "bits-ui";
|
|
5
6
|
import { getDiagramEditorContext } from "../editor/editor-context.js";
|
|
6
7
|
import { isLocked } from "../editor/lock.js";
|
|
7
8
|
import { DEFAULT_SCREEN_LABELS, type ScreenLabels } from "./labels.js";
|
|
@@ -71,6 +72,13 @@
|
|
|
71
72
|
if (mode !== screen.mode) write({ mode });
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// Svelte Flow nudges a selected card on an arrow, so the mode switch keeps the keys it moves by.
|
|
76
|
+
function keepArrows(event: KeyboardEvent) {
|
|
77
|
+
if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End") {
|
|
78
|
+
event.stopPropagation();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
function add(kind: PartKind) {
|
|
75
83
|
write({ parts: addPart(screen.parts, kind) });
|
|
76
84
|
tray = false;
|
|
@@ -104,20 +112,19 @@
|
|
|
104
112
|
}}
|
|
105
113
|
/>
|
|
106
114
|
{#if !readonly}
|
|
107
|
-
<
|
|
115
|
+
<RadioGroup.Root
|
|
116
|
+
class="cv-switch nodrag"
|
|
117
|
+
orientation="horizontal"
|
|
118
|
+
aria-label={labels.mode}
|
|
119
|
+
bind:value={() => screen.mode, (mode) => setMode(mode as ScreenMode)}
|
|
120
|
+
onkeydown={keepArrows}
|
|
121
|
+
>
|
|
108
122
|
{#each SCREEN_MODES as mode (mode)}
|
|
109
|
-
<
|
|
110
|
-
type="button"
|
|
111
|
-
role="radio"
|
|
112
|
-
class="cv-seg"
|
|
113
|
-
aria-checked={screen.mode === mode}
|
|
114
|
-
data-mode-option={mode}
|
|
115
|
-
onclick={() => setMode(mode)}
|
|
116
|
-
>
|
|
123
|
+
<RadioGroup.Item value={mode} class="cv-seg" data-mode-option={mode}>
|
|
117
124
|
{labels.modes[mode]}
|
|
118
|
-
</
|
|
125
|
+
</RadioGroup.Item>
|
|
119
126
|
{/each}
|
|
120
|
-
</
|
|
127
|
+
</RadioGroup.Root>
|
|
121
128
|
{/if}
|
|
122
129
|
</div>
|
|
123
130
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import { Kbd } from "../../shadcn/kbd/index.js";
|
|
7
|
+
import * as Tooltip from "../../shadcn/tooltip/index.js";
|
|
8
|
+
|
|
9
|
+
// =========================
|
|
10
|
+
// TYPES
|
|
11
|
+
// =========================
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Shown in the tooltip, and the button's name unless `name` is set. */
|
|
14
|
+
label: string;
|
|
15
|
+
/** The name a screen reader reads, when it needs more than the tooltip, like "Fill: Red". */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** The single key that picks this tool, shown beside the label and set as aria-keyshortcuts. */
|
|
18
|
+
shortcut?: string;
|
|
19
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
20
|
+
/** The bar control. Spread `props` on it, so the tooltip opens from it and names it. */
|
|
21
|
+
child: Snippet<[{ props: Record<string, unknown> }]>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// =========================
|
|
25
|
+
// PROPS
|
|
26
|
+
// =========================
|
|
27
|
+
// Renamed, because the trigger's own `child` snippet below would shadow it.
|
|
28
|
+
let { label, name = label, shortcut, side = "top", child: control }: Props = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<!-- =========================== -->
|
|
32
|
+
<!-- MARKUP -->
|
|
33
|
+
<!-- =========================== -->
|
|
34
|
+
<Tooltip.Root>
|
|
35
|
+
<Tooltip.Trigger>
|
|
36
|
+
{#snippet child({ props })}
|
|
37
|
+
{@render control({
|
|
38
|
+
props: { ...props, "aria-label": name, "aria-keyshortcuts": shortcut || undefined },
|
|
39
|
+
})}
|
|
40
|
+
{/snippet}
|
|
41
|
+
</Tooltip.Trigger>
|
|
42
|
+
<Tooltip.Content {side} sideOffset={10} class="flex items-center gap-2">
|
|
43
|
+
{label}
|
|
44
|
+
{#if shortcut}<Kbd>{shortcut}</Kbd>{/if}
|
|
45
|
+
</Tooltip.Content>
|
|
46
|
+
</Tooltip.Root>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Shown in the tooltip, and the button's name unless `name` is set. */
|
|
4
|
+
label: string;
|
|
5
|
+
/** The name a screen reader reads, when it needs more than the tooltip, like "Fill: Red". */
|
|
6
|
+
name?: string;
|
|
7
|
+
/** The single key that picks this tool, shown beside the label and set as aria-keyshortcuts. */
|
|
8
|
+
shortcut?: string;
|
|
9
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
10
|
+
/** The bar control. Spread `props` on it, so the tooltip opens from it and names it. */
|
|
11
|
+
child: Snippet<[{
|
|
12
|
+
props: Record<string, unknown>;
|
|
13
|
+
}]>;
|
|
14
|
+
}
|
|
15
|
+
declare const $$Component: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type $$Component = ReturnType<typeof $$Component>;
|
|
17
|
+
export default $$Component;
|