@heyramzi/ui 2.1.0 → 2.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/package.json +7 -8
- package/scripts/sync-shadcn.mjs +3 -4
- package/src/agents/agent-avatar.svelte +604 -575
- package/src/agents/agent-cast.svelte +179 -183
- package/src/agents/demo.svelte +125 -113
- package/src/agents/helpers.ts +23 -17
- package/src/agents/index.ts +4 -4
- package/src/agents/page-state.ts +6 -6
- package/src/agents/types.ts +6 -6
- package/src/ai/ai-button/AiButton.svelte +4 -12
- package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
- package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
- package/src/ai/response/Response.css +2 -3
- package/src/animations/avatar-group-hover.css +2 -16
- package/src/animations/card-tilt.css +3 -6
- package/src/animations/digit-pop-in.css +2 -15
- package/src/animations/dropdown.css +4 -17
- package/src/animations/input-clear-dissolve.css +5 -18
- package/src/animations/input-shake.css +9 -41
- package/src/animations/learn-more-hover.css +17 -9
- package/src/animations/like-button.css +2 -4
- package/src/animations/page-side-by-side.css +2 -10
- package/src/animations/plus-menu-morph.css +2 -5
- package/src/animations/shimmer-text.css +5 -18
- package/src/animations/spinning-counter.css +2 -6
- package/src/animations/success-check.css +2 -15
- package/src/animations/thinking-states.css +2 -7
- package/src/brand/brand.test.ts +21 -5
- package/src/brand/index.ts +4 -10
- package/src/canvas/card/card-frame.svelte +43 -39
- package/src/canvas/card/resize-handles.svelte +89 -80
- package/src/canvas/chrome/chrome-zoom.svelte +101 -100
- package/src/canvas/chrome/lock-badge.svelte +8 -8
- package/src/canvas/chrome/side-handles.svelte +24 -19
- package/src/canvas/chrome/size-chip.svelte +30 -30
- package/src/canvas/chrome/snap-guides.svelte +20 -20
- package/src/canvas/components/icons/ToolIcon.svelte +8 -8
- package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
- package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
- package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
- package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
- package/src/canvas/edges/bend-handle.svelte +46 -46
- package/src/canvas/edges/edge-gradient.svelte +10 -10
- package/src/canvas/edges/gradient-edge.svelte +45 -29
- package/src/canvas/editor/lock.test.ts +6 -1
- package/src/canvas/editor/style.ts +7 -1
- package/src/canvas/headless/svg-figure.test.ts +5 -1
- package/src/canvas/lod/lod-switch.svelte +111 -106
- package/src/canvas/pen/PenField.svelte +87 -81
- package/src/canvas/pen/PenOverlay.svelte +127 -127
- package/src/canvas/pen/ShapeNode.svelte +31 -32
- package/src/canvas/pen/TextNode.svelte +54 -53
- package/src/canvas/pen/host-field.test.svelte +7 -7
- package/src/canvas/pen/shapes.ts +7 -1
- package/src/canvas/screen/GlyphIcon.svelte +22 -17
- package/src/canvas/screen/PartView.svelte +138 -72
- package/src/canvas/screen/ScreenBody.svelte +81 -60
- package/src/canvas/screen/ScreenCard.svelte +20 -20
- package/src/canvas/ui/CanvasToolbar.svelte +173 -85
- package/src/canvas/ui/FrameLayer.svelte +59 -59
- package/src/canvas/ui/PenLayer.svelte +39 -30
- package/src/canvas/ui/StylePanel.svelte +119 -102
- package/src/canvas/ui/canvas-grid.svelte +137 -129
- package/src/charts/animated-value.svelte +1 -1
- package/src/charts/area-chart.css +2 -5
- package/src/charts/area-chart.svelte +1 -1
- package/src/charts/bar-chart.css +2 -3
- package/src/charts/bar-chart.svelte +1 -1
- package/src/charts/charts.css +5 -17
- package/src/charts/crosshair.svelte +1 -1
- package/src/charts/dashboard/dashboard-empty.svelte +25 -25
- package/src/charts/dashboard/dashboard-section.svelte +24 -24
- package/src/charts/dashboard/data-panel.svelte +50 -50
- package/src/charts/dashboard/data-sort-head.svelte +56 -56
- package/src/charts/dashboard/data-table.svelte +138 -138
- package/src/charts/dashboard/panel-grid.svelte +30 -30
- package/src/charts/dashboard/period-filter.svelte +25 -25
- package/src/charts/dashboard/rating-chip.svelte +19 -19
- package/src/charts/dashboard/stat-band.svelte +32 -32
- package/src/charts/dashboard/stat-tile.svelte +106 -99
- package/src/charts/donut-chart.svelte +1 -1
- package/src/charts/edge-fade.svelte +1 -1
- package/src/charts/figure.svelte +1 -1
- package/src/charts/gauge-chart.css +4 -7
- package/src/charts/gauge-chart.svelte +1 -1
- package/src/charts/hover-focus.svelte +1 -1
- package/src/charts/legend.css +1 -3
- package/src/charts/legend.svelte +1 -1
- package/src/charts/line-chart.svelte +1 -1
- package/src/charts/number-chart.css +6 -9
- package/src/charts/number-chart.svelte +1 -1
- package/src/charts/reference-line.svelte +19 -19
- package/src/charts/reveal.svelte +1 -1
- package/src/charts/ring-chart.css +2 -4
- package/src/charts/ring-chart.svelte +1 -1
- package/src/charts/sparkline.svelte +5 -2
- package/src/charts/stat-card.svelte +1 -1
- package/src/charts/time-series-chart.css +2 -5
- package/src/charts/trend-pill.css +2 -4
- package/src/charts/trend-pill.svelte +1 -1
- package/src/components/copy/copy-button.svelte +93 -98
- package/src/components/copy/copy-button.test.ts +10 -2
- package/src/components/data-table/infinite-scroll.svelte +42 -42
- package/src/components/data-table/pagination.svelte +34 -22
- package/src/components/data-table/row-arrow.svelte +19 -16
- package/src/components/data-table/row-context-menu.svelte +52 -52
- package/src/components/data-table/sortable-head.svelte +25 -25
- package/src/components/data-table/table-skeleton.svelte +41 -36
- package/src/components/docs/doc-page.svelte +18 -9
- package/src/components/docs/on-this-page.svelte +16 -3
- package/src/components/docs/scroll-spy.svelte.ts +2 -3
- package/src/components/field-row/field-row.svelte +10 -8
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
- package/src/components/icon/icon.svelte +14 -14
- package/src/components/language-flag/language-flag.svelte +3 -1
- package/src/components/list-page/detail-empty.svelte +34 -34
- package/src/components/list-page/detail-header.svelte +55 -47
- package/src/components/list-page/detail-panel.svelte +51 -59
- package/src/components/list-page/detail-row.svelte +19 -19
- package/src/components/list-page/detail-stack.svelte +21 -21
- package/src/components/list-page/empty-state.svelte +30 -30
- package/src/components/list-page/filter-item.svelte +20 -20
- package/src/components/list-page/filter-popover.svelte +60 -55
- package/src/components/list-page/grouped-list.svelte +4 -2
- package/src/components/list-page/index.ts +5 -1
- package/src/components/list-page/list-page-shell.svelte +2 -5
- package/src/components/list-page/master-detail.svelte +87 -88
- package/src/components/list-page/notice-bar.svelte +38 -38
- package/src/components/list-page/prev-next.svelte +63 -63
- package/src/components/list-page/rail-body.svelte +18 -18
- package/src/components/list-page/rail-empty.svelte +23 -30
- package/src/components/list-page/rail-head.svelte +24 -24
- package/src/components/list-page/rail-row.svelte +77 -73
- package/src/components/list-page/section-host.ts +2 -3
- package/src/components/locale-switch/locale-switch.svelte +41 -41
- package/src/components/mark/mark.svelte +34 -38
- package/src/components/open-in/index.ts +1 -5
- package/src/components/open-in/open-in.svelte +40 -40
- package/src/components/picker/mark-picker.svelte +49 -49
- package/src/components/picker/picker.svelte +5 -3
- package/src/components/section-header/section-header.test.ts +7 -1
- package/src/components/section-header/section-label.svelte +20 -20
- package/src/components/segmented/animated-tabs.svelte +31 -2
- package/src/components/segmented/collapse.test.ts +2 -1
- package/src/components/segmented/index.ts +2 -0
- package/src/components/segmented/segment.svelte +52 -49
- package/src/components/segmented/segmented-filter.svelte +79 -80
- package/src/components/segmented/segmented-nav.svelte +34 -40
- package/src/components/segmented/segmented-overflow.svelte +53 -50
- package/src/components/segmented/segmented-track.svelte +62 -46
- package/src/components/segmented/segmented.ts +2 -3
- package/src/components/star-rating-input/star-rating-input.svelte +103 -103
- package/src/components/svg/svg.svelte +4 -4
- package/src/components/table-view/table-view.svelte +17 -2
- package/src/components/table-view/table-view.test.ts +7 -1
- package/src/components/table-view/types.ts +11 -11
- package/src/components/tags-input/index.ts +13 -0
- package/src/components/tags-input/tags-input.svelte +305 -0
- package/src/components/tags-input/tags.test.ts +64 -0
- package/src/components/tags-input/tags.ts +142 -0
- package/src/components/theme/index.ts +2 -4
- package/src/components/theme/theme-provider.svelte +1 -3
- package/src/components/toolbar/toolbar-search.svelte +25 -25
- package/src/components/toolbar/toolbar.svelte +11 -7
- package/src/components/toolbar/toolbar.test.ts +10 -1
- package/src/design/index.css +21 -78
- package/src/design/polish.css +26 -35
- package/src/editor/editor-toolbar.svelte +104 -99
- package/src/editor/editor.svelte +296 -266
- package/src/editor/image-toolbar.svelte +20 -22
- package/src/editor/index.ts +1 -4
- package/src/editor/inline-editor.svelte +280 -238
- package/src/editor/markdown.ts +15 -15
- package/src/editor/renderer.svelte +71 -71
- package/src/editor/slash-menu.svelte +24 -27
- package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
- package/src/effects/image-comparison/ImageComparison.svelte +11 -1
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
- package/src/effects/magic-card/MagicCard.css +14 -7
- package/src/effects/marquee/Marquee.svelte +3 -5
- package/src/effects/reveal/observe.ts +0 -3
- package/src/effects/spotlight/Spotlight.svelte +1 -1
- package/src/effects/theme-toggle/ThemeToggle.css +25 -22
- package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
- package/src/gallery.test.ts +14 -4
- package/src/inbox/README.md +1 -3
- package/src/inbox/inbox.test.ts +181 -138
- package/src/inbox/index.ts +20 -20
- package/src/inbox/logic.ts +114 -111
- package/src/inbox/thread-inbox.svelte +227 -151
- package/src/inbox/types.ts +130 -108
- package/src/lib/theme-transition.ts +2 -4
- package/src/lib/tone.ts +2 -4
- package/src/lib/utils.ts +2 -2
- package/src/modules.test.ts +79 -69
- package/src/onboarding/define-tours.test.ts +55 -40
- package/src/onboarding/define-tours.ts +23 -23
- package/src/onboarding/demo.svelte +24 -5
- package/src/onboarding/index.ts +1 -8
- package/src/onboarding/onboarding-provider.svelte +19 -7
- package/src/onboarding/onboarding.svelte.ts +16 -16
- package/src/onboarding/pick.test.ts +61 -52
- package/src/onboarding/pick.ts +16 -13
- package/src/onboarding/stores.test.ts +80 -80
- package/src/onboarding/stores.ts +41 -39
- package/src/onboarding/tour-card.svelte +30 -5
- package/src/onboarding/tour-runner.svelte +12 -3
- package/src/onboarding/types.ts +20 -12
- package/src/onboarding/wait.ts +22 -22
- package/src/panel/side-panel.svelte +94 -93
- package/src/record/demo-data.ts +291 -225
- package/src/record/field-section.svelte +38 -34
- package/src/record/fields.ts +148 -96
- package/src/record/format.ts +63 -56
- package/src/record/index.ts +49 -35
- package/src/record/info-list.svelte +145 -114
- package/src/record/kinds.ts +31 -31
- package/src/record/logic.ts +216 -214
- package/src/record/next-up.svelte +45 -34
- package/src/record/project-list.svelte +56 -50
- package/src/record/record-demo.svelte +130 -110
- package/src/record/record-header.svelte +37 -28
- package/src/record/record-shell.svelte +54 -54
- package/src/record/record-stats.svelte +25 -25
- package/src/record/record-timeline.svelte +134 -114
- package/src/record/record.test.ts +430 -309
- package/src/record/steps-rail.svelte +225 -216
- package/src/record/types.ts +234 -215
- package/src/record/where-we-are.svelte +62 -58
- package/src/shadcn/button/button.svelte +1 -1
- package/src/shadcn/card/card-title.svelte +1 -5
- package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
- package/src/shadcn/sheet/sheet-content.svelte +2 -4
- package/src/shadcn/table/table-cell.svelte +2 -3
- package/src/shadcn/utilities.css +3 -8
- package/src/slides/Controls.svelte +150 -124
- package/src/slides/Deck.svelte +243 -238
- package/src/slides/Fragment.svelte +64 -57
- package/src/slides/Overview.svelte +106 -101
- package/src/slides/Presenter.svelte +300 -266
- package/src/slides/Slide.svelte +185 -170
- package/src/slides/SlideList.svelte +16 -12
- package/src/slides/ambient.d.ts +1 -1
- package/src/slides/bridges.ts +42 -23
- package/src/slides/deck-controller.svelte.ts +47 -35
- package/src/slides/demo-slides.svelte +330 -306
- package/src/slides/demo.svelte +164 -160
- package/src/slides/export/capture.ts +20 -16
- package/src/slides/export/ffmpeg.ts +43 -38
- package/src/slides/export/index.ts +5 -5
- package/src/slides/export/launch.ts +3 -1
- package/src/slides/export/pdf.ts +30 -27
- package/src/slides/export/types.ts +10 -8
- package/src/slides/index.ts +11 -11
- package/src/slides/labels.ts +22 -19
- package/src/slides/layout.ts +9 -2
- package/src/slides/model.ts +4 -4
- package/src/slides/navigation.ts +43 -31
- package/src/slides/theme.css +5 -7
- package/src/slides/types.ts +9 -10
- package/src/status/define-statuses.ts +17 -24
- package/src/status/index.ts +5 -5
- package/src/status/status-badge.svelte +3 -1
- package/src/status/status-dot.svelte +1 -3
- package/src/status/status-icon.svelte +1 -1
- package/src/status/status-types.ts +21 -21
- package/src/tasks/board-order.ts +2 -4
- package/src/tasks/demo.svelte +189 -31
- package/src/tasks/detail/detail-demo.svelte +73 -13
- package/src/tasks/detail/detail-types.ts +45 -45
- package/src/tasks/detail/task-detail.svelte +40 -10
- package/src/tasks/detail/task-property.svelte +8 -2
- package/src/tasks/detail/task-thread.svelte +53 -12
- package/src/tasks/gantt/gantt-bar.svelte +17 -6
- package/src/tasks/gantt/gantt-grid.svelte +40 -9
- package/src/tasks/gantt/gantt-group-row.svelte +8 -2
- package/src/tasks/gantt/gantt-header.svelte +9 -3
- package/src/tasks/gantt/gantt-task-row.svelte +26 -6
- package/src/tasks/gantt/gantt.css +2 -1
- package/src/tasks/gantt/gantt.test.ts +3 -1
- package/src/tasks/gantt/logic.test.ts +110 -20
- package/src/tasks/gantt/logic.ts +42 -6
- package/src/tasks/inbox/inbox-card.svelte +13 -5
- package/src/tasks/inbox/inbox-draft.svelte +1 -1
- package/src/tasks/inbox/inbox-logic.ts +6 -40
- package/src/tasks/inbox/inbox-statuses.ts +23 -23
- package/src/tasks/inbox/inbox-trace.svelte +9 -3
- package/src/tasks/inbox/inbox-types.ts +54 -58
- package/src/tasks/inbox/index.ts +22 -21
- package/src/tasks/index.ts +49 -52
- package/src/tasks/kanban-board.svelte +16 -12
- package/src/tasks/kanban-types.ts +2 -3
- package/src/tasks/presets/sales.ts +6 -6
- package/src/tasks/task-gantt.svelte +28 -8
- package/src/tasks/task-list.svelte +41 -17
- package/src/tasks/time/duration.ts +40 -40
- package/src/tasks/time/time-chip.svelte +9 -2
- package/src/tasks/time/time-dot.svelte +1 -1
- package/src/tasks/time/time-ring.svelte +11 -3
- package/src/tasks/time/time-tracker.svelte +57 -15
- package/src/tasks/time/time-types.ts +57 -57
- package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
- package/src/thread/index.ts +7 -7
- package/src/thread/logic.ts +36 -33
- package/src/thread/thread.svelte +82 -16
- package/src/thread/thread.test.ts +58 -47
- package/src/thread/types.ts +68 -68
- package/src/tokens/craft.css +17 -53
- package/src/tokens/index.css +3 -13
- package/src/tokens/palette.css +13 -11
- package/src/tokens/primitives.css +10 -23
- package/src/tasks/inbox/inbox.svelte +0 -203
|
@@ -1,235 +1,236 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
// =========================
|
|
3
|
-
// IMPORTS
|
|
4
|
-
// =========================
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "phosphor-svelte";
|
|
15
|
-
import { cn } from "../lib/utils.js";
|
|
16
|
-
import { Button } from "../shadcn/button/index.js";
|
|
17
|
-
import { Input } from "../shadcn/input/index.js";
|
|
18
|
-
import { Skeleton } from "../shadcn/skeleton/index.js";
|
|
19
|
-
import * as Popover from "../shadcn/popover/index.js";
|
|
20
|
-
import * as ContextMenu from "../shadcn/context-menu/index.js";
|
|
21
|
-
import { todayIso } from "../tasks/dates.js";
|
|
22
|
-
import { formatRange } from "./format.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} from "./logic.js";
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from "./types.js";
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import {
|
|
6
|
+
ArrowCounterClockwise,
|
|
7
|
+
ArrowDown,
|
|
8
|
+
ArrowUp,
|
|
9
|
+
Check,
|
|
10
|
+
DotsThreeVertical,
|
|
11
|
+
Eye,
|
|
12
|
+
EyeSlash,
|
|
13
|
+
PencilSimple,
|
|
14
|
+
} from "phosphor-svelte";
|
|
15
|
+
import { cn } from "../lib/utils.js";
|
|
16
|
+
import { Button } from "../shadcn/button/index.js";
|
|
17
|
+
import { Input } from "../shadcn/input/index.js";
|
|
18
|
+
import { Skeleton } from "../shadcn/skeleton/index.js";
|
|
19
|
+
import * as Popover from "../shadcn/popover/index.js";
|
|
20
|
+
import * as ContextMenu from "../shadcn/context-menu/index.js";
|
|
21
|
+
import { todayIso } from "../tasks/dates.js";
|
|
22
|
+
import { formatRange } from "./format.js";
|
|
23
|
+
import {
|
|
24
|
+
groupSteps,
|
|
25
|
+
nudgeStep,
|
|
26
|
+
reorderIds,
|
|
27
|
+
stepMarkers,
|
|
28
|
+
stepProgress,
|
|
29
|
+
visibleSteps,
|
|
30
|
+
} from "./logic.js";
|
|
31
|
+
import {
|
|
32
|
+
resolveLabels,
|
|
33
|
+
type RecordLabels,
|
|
34
|
+
type StepDef,
|
|
35
|
+
type StepGroupDef,
|
|
36
|
+
type StepMarker,
|
|
37
|
+
} from "./types.js";
|
|
38
38
|
|
|
39
|
-
// =========================
|
|
40
|
-
// PROPS
|
|
41
|
-
// =========================
|
|
42
|
-
interface Props {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
let {
|
|
62
|
-
steps,
|
|
63
|
-
groups = [],
|
|
64
|
-
activeId = null,
|
|
65
|
-
onSelect,
|
|
66
|
-
onToggle,
|
|
67
|
-
onReorder,
|
|
68
|
-
onRename,
|
|
69
|
-
loading = false,
|
|
70
|
-
labels,
|
|
71
|
-
locale,
|
|
72
|
-
today = todayIso(),
|
|
73
|
-
class: className,
|
|
74
|
-
}: Props = $props();
|
|
75
|
-
|
|
76
|
-
// =========================
|
|
77
|
-
// STATE
|
|
78
|
-
// =========================
|
|
79
|
-
let menuOpen = $state(false);
|
|
80
|
-
// The order shown while the host round-trips. Cleared when the steps catch up,
|
|
81
|
-
// or after five seconds so a failed save can't pin a wrong order.
|
|
82
|
-
let pendingOrder = $state<string[] | null>(null);
|
|
83
|
-
let pendingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
84
|
-
let draggedId = $state<string | null>(null);
|
|
85
|
-
let draggedGroup = $state<string | undefined>(undefined);
|
|
86
|
-
let overId = $state<string | null>(null);
|
|
87
|
-
let insertBefore = $state(true);
|
|
88
|
-
let renamingId = $state<string | null>(null);
|
|
89
|
-
let renameValue = $state("");
|
|
90
|
-
let renameInput = $state<HTMLInputElement | null>(null);
|
|
91
|
-
// WHY: the context menu closing hands focus back and blurs a fresh input, which
|
|
92
|
-
// would commit a rename nobody typed. Blur counts only once the input has had a frame.
|
|
93
|
-
let renameArmed = $state(false);
|
|
94
|
-
|
|
95
|
-
// =========================
|
|
96
|
-
// DERIVED
|
|
97
|
-
// =========================
|
|
98
|
-
let t = $derived(resolveLabels(labels));
|
|
99
|
-
let ordered = $derived.by(() => {
|
|
100
|
-
if (!pendingOrder) return steps;
|
|
101
|
-
const byId = new Map(steps.map((s) => [s.id, s]));
|
|
102
|
-
return pendingOrder.flatMap((id) => byId.get(id) ?? []);
|
|
103
|
-
});
|
|
104
|
-
let visible = $derived(visibleSteps(ordered));
|
|
105
|
-
let markers = $derived(stepMarkers(ordered, today));
|
|
106
|
-
let buckets = $derived(groupSteps(visible, groups));
|
|
107
|
-
let menuBuckets = $derived(groupSteps(ordered, groups));
|
|
108
|
-
let progress = $derived(stepProgress(ordered));
|
|
109
|
-
let hiddenCount = $derived(ordered.length - visible.length);
|
|
110
|
-
let canReorder = $derived(Boolean(onReorder));
|
|
111
|
-
|
|
112
|
-
// =========================
|
|
113
|
-
// EFFECTS
|
|
114
|
-
// =========================
|
|
115
|
-
$effect(() => {
|
|
116
|
-
if (!pendingOrder) return;
|
|
117
|
-
if (steps.map((s) => s.id).join(",") === pendingOrder.join(",")) clearPending();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
$effect(() => {
|
|
121
|
-
if (renamingId && renameInput) {
|
|
122
|
-
renameInput.focus();
|
|
123
|
-
renameInput.select();
|
|
124
|
-
const frame = requestAnimationFrame(() => (renameArmed = true));
|
|
125
|
-
return () => {
|
|
126
|
-
cancelAnimationFrame(frame);
|
|
127
|
-
renameArmed = false;
|
|
128
|
-
};
|
|
39
|
+
// =========================
|
|
40
|
+
// PROPS
|
|
41
|
+
// =========================
|
|
42
|
+
interface Props {
|
|
43
|
+
steps: StepDef[];
|
|
44
|
+
/** Group headers in the order to draw them, such as Sales then Delivery. */
|
|
45
|
+
groups?: StepGroupDef[];
|
|
46
|
+
activeId?: string | null;
|
|
47
|
+
onSelect?: (id: string) => void;
|
|
48
|
+
/** Show or hide a step. Without it the visibility menu isn't drawn. */
|
|
49
|
+
onToggle?: (id: string, visible: boolean) => void;
|
|
50
|
+
/** The whole new id order, after a drag or a keyboard move. */
|
|
51
|
+
onReorder?: (ids: string[]) => void;
|
|
52
|
+
/** An empty label resets the name. */
|
|
53
|
+
onRename?: (id: string, label: string) => void;
|
|
54
|
+
loading?: boolean;
|
|
55
|
+
labels?: Partial<RecordLabels>;
|
|
56
|
+
locale?: string;
|
|
57
|
+
/** ISO date. Decides "late". Defaults to the viewer's today. */
|
|
58
|
+
today?: string;
|
|
59
|
+
class?: string;
|
|
129
60
|
}
|
|
130
|
-
|
|
131
|
-
|
|
61
|
+
let {
|
|
62
|
+
steps,
|
|
63
|
+
groups = [],
|
|
64
|
+
activeId = null,
|
|
65
|
+
onSelect,
|
|
66
|
+
onToggle,
|
|
67
|
+
onReorder,
|
|
68
|
+
onRename,
|
|
69
|
+
loading = false,
|
|
70
|
+
labels,
|
|
71
|
+
locale,
|
|
72
|
+
today = todayIso(),
|
|
73
|
+
class: className,
|
|
74
|
+
}: Props = $props();
|
|
132
75
|
|
|
133
|
-
// =========================
|
|
134
|
-
//
|
|
135
|
-
// =========================
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
76
|
+
// =========================
|
|
77
|
+
// STATE
|
|
78
|
+
// =========================
|
|
79
|
+
let menuOpen = $state(false);
|
|
80
|
+
// The order shown while the host round-trips. Cleared when the steps catch up,
|
|
81
|
+
// or after five seconds so a failed save can't pin a wrong order.
|
|
82
|
+
let pendingOrder = $state<string[] | null>(null);
|
|
83
|
+
let pendingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
84
|
+
let draggedId = $state<string | null>(null);
|
|
85
|
+
let draggedGroup = $state<string | undefined>(undefined);
|
|
86
|
+
let overId = $state<string | null>(null);
|
|
87
|
+
let insertBefore = $state(true);
|
|
88
|
+
let renamingId = $state<string | null>(null);
|
|
89
|
+
let renameValue = $state("");
|
|
90
|
+
let renameInput = $state<HTMLInputElement | null>(null);
|
|
91
|
+
// WHY: the context menu closing hands focus back and blurs a fresh input, which
|
|
92
|
+
// would commit a rename nobody typed. Blur counts only once the input has had a frame.
|
|
93
|
+
let renameArmed = $state(false);
|
|
141
94
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
95
|
+
// =========================
|
|
96
|
+
// DERIVED
|
|
97
|
+
// =========================
|
|
98
|
+
let t = $derived(resolveLabels(labels));
|
|
99
|
+
let ordered = $derived.by(() => {
|
|
100
|
+
if (!pendingOrder) return steps;
|
|
101
|
+
const byId = new Map(steps.map((s) => [s.id, s]));
|
|
102
|
+
return pendingOrder.flatMap((id) => byId.get(id) ?? []);
|
|
103
|
+
});
|
|
104
|
+
let visible = $derived(visibleSteps(ordered));
|
|
105
|
+
let markers = $derived(stepMarkers(ordered, today));
|
|
106
|
+
let buckets = $derived(groupSteps(visible, groups));
|
|
107
|
+
let menuBuckets = $derived(groupSteps(ordered, groups));
|
|
108
|
+
let progress = $derived(stepProgress(ordered));
|
|
109
|
+
let hiddenCount = $derived(ordered.length - visible.length);
|
|
110
|
+
let canReorder = $derived(Boolean(onReorder));
|
|
149
111
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
112
|
+
// =========================
|
|
113
|
+
// EFFECTS
|
|
114
|
+
// =========================
|
|
115
|
+
$effect(() => {
|
|
116
|
+
if (!pendingOrder) return;
|
|
117
|
+
if (steps.map((s) => s.id).join(",") === pendingOrder.join(",")) clearPending();
|
|
118
|
+
});
|
|
154
119
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
120
|
+
$effect(() => {
|
|
121
|
+
if (renamingId && renameInput) {
|
|
122
|
+
renameInput.focus();
|
|
123
|
+
renameInput.select();
|
|
124
|
+
const frame = requestAnimationFrame(() => (renameArmed = true));
|
|
125
|
+
return () => {
|
|
126
|
+
cancelAnimationFrame(frame);
|
|
127
|
+
renameArmed = false;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
renameArmed = false;
|
|
131
|
+
});
|
|
160
132
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
133
|
+
// =========================
|
|
134
|
+
// FUNCTIONS
|
|
135
|
+
// =========================
|
|
136
|
+
function clearPending() {
|
|
137
|
+
pendingOrder = null;
|
|
138
|
+
if (pendingTimer) clearTimeout(pendingTimer);
|
|
139
|
+
pendingTimer = null;
|
|
167
140
|
}
|
|
168
|
-
}
|
|
169
141
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const box = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
177
|
-
const before = event.clientY < box.top + box.height / 2;
|
|
178
|
-
if (overId !== target.id || insertBefore !== before) {
|
|
179
|
-
overId = target.id;
|
|
180
|
-
insertBefore = before;
|
|
142
|
+
function applyOrder(ids: string[]) {
|
|
143
|
+
if (ids.join(",") === ordered.map((s) => s.id).join(",")) return;
|
|
144
|
+
pendingOrder = ids;
|
|
145
|
+
if (pendingTimer) clearTimeout(pendingTimer);
|
|
146
|
+
pendingTimer = setTimeout(clearPending, 5000);
|
|
147
|
+
onReorder?.(ids);
|
|
181
148
|
}
|
|
182
|
-
}
|
|
183
149
|
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
resetDrag();
|
|
189
|
-
if (!moved || moved === target.id || !sameGroup) return;
|
|
190
|
-
event.preventDefault();
|
|
191
|
-
applyOrder(
|
|
192
|
-
reorderIds(
|
|
193
|
-
ordered.map((s) => s.id),
|
|
194
|
-
moved,
|
|
195
|
-
target.id,
|
|
196
|
-
before,
|
|
197
|
-
),
|
|
198
|
-
);
|
|
199
|
-
}
|
|
150
|
+
function dateLabel(step: StepDef): string | null {
|
|
151
|
+
const from = step.start ?? step.end;
|
|
152
|
+
return from ? formatRange(from, step.start ? step.end : null, locale) : null;
|
|
153
|
+
}
|
|
200
154
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
applyOrder(nudgeStep(ordered, step.id, event.key === "ArrowUp" ? -1 : 1));
|
|
207
|
-
}
|
|
155
|
+
function resetDrag() {
|
|
156
|
+
draggedId = null;
|
|
157
|
+
draggedGroup = undefined;
|
|
158
|
+
overId = null;
|
|
159
|
+
}
|
|
208
160
|
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
161
|
+
function dragStart(event: DragEvent, step: StepDef) {
|
|
162
|
+
draggedId = step.id;
|
|
163
|
+
draggedGroup = step.group;
|
|
164
|
+
if (event.dataTransfer) {
|
|
165
|
+
event.dataTransfer.effectAllowed = "move";
|
|
166
|
+
event.dataTransfer.setData("text/plain", step.id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
213
169
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
170
|
+
// A step moves inside its own group only: a group is a phase, and dragging
|
|
171
|
+
// between two would silently change what a step means.
|
|
172
|
+
function dragOver(event: DragEvent, target: StepDef) {
|
|
173
|
+
if (!draggedId || draggedId === target.id || target.group !== draggedGroup) return;
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
176
|
+
const box = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
177
|
+
const before = event.clientY < box.top + box.height / 2;
|
|
178
|
+
if (overId !== target.id || insertBefore !== before) {
|
|
179
|
+
overId = target.id;
|
|
180
|
+
insertBefore = before;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
223
183
|
|
|
224
|
-
function
|
|
225
|
-
|
|
184
|
+
function drop(event: DragEvent, target: StepDef) {
|
|
185
|
+
const moved = draggedId;
|
|
186
|
+
const before = insertBefore;
|
|
187
|
+
const sameGroup = target.group === draggedGroup;
|
|
188
|
+
resetDrag();
|
|
189
|
+
if (!moved || moved === target.id || !sameGroup) return;
|
|
226
190
|
event.preventDefault();
|
|
227
|
-
|
|
228
|
-
|
|
191
|
+
applyOrder(
|
|
192
|
+
reorderIds(
|
|
193
|
+
ordered.map((s) => s.id),
|
|
194
|
+
moved,
|
|
195
|
+
target.id,
|
|
196
|
+
before,
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Alt plus an arrow, not a bare arrow: the rail is walked with arrow keys and
|
|
202
|
+
// taking them would lock keyboard users out.
|
|
203
|
+
function keydown(event: KeyboardEvent, step: StepDef) {
|
|
204
|
+
if (!canReorder || !event.altKey || (event.key !== "ArrowUp" && event.key !== "ArrowDown"))
|
|
205
|
+
return;
|
|
229
206
|
event.preventDefault();
|
|
207
|
+
applyOrder(nudgeStep(ordered, step.id, event.key === "ArrowUp" ? -1 : 1));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function startRename(step: StepDef) {
|
|
211
|
+
renamingId = step.id;
|
|
212
|
+
renameValue = step.label;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function commitRename() {
|
|
216
|
+
if (!renamingId) return;
|
|
217
|
+
const step = ordered.find((s) => s.id === renamingId);
|
|
218
|
+
const next = renameValue.trim();
|
|
230
219
|
renamingId = null;
|
|
220
|
+
// WHY: an unchanged name sends nothing, so a stray blur can't mark a step renamed.
|
|
221
|
+
if (!step || next === step.label) return;
|
|
222
|
+
onRename?.(step.id, next);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function renameKey(event: KeyboardEvent) {
|
|
226
|
+
if (event.key === "Enter") {
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
commitRename();
|
|
229
|
+
} else if (event.key === "Escape") {
|
|
230
|
+
event.preventDefault();
|
|
231
|
+
renamingId = null;
|
|
232
|
+
}
|
|
231
233
|
}
|
|
232
|
-
}
|
|
233
234
|
</script>
|
|
234
235
|
|
|
235
236
|
<!-- =========================== -->
|
|
@@ -289,7 +290,9 @@ function renameKey(event: KeyboardEvent) {
|
|
|
289
290
|
<Popover.Content align="end" class="w-64 p-1">
|
|
290
291
|
{#each menuBuckets as bucket (bucket.key)}
|
|
291
292
|
{#if bucket.label}
|
|
292
|
-
<p
|
|
293
|
+
<p
|
|
294
|
+
class="px-2 pt-2 pb-1 text-xs font-medium tracking-wide text-muted-foreground uppercase"
|
|
295
|
+
>
|
|
293
296
|
{bucket.label}
|
|
294
297
|
</p>
|
|
295
298
|
{/if}
|
|
@@ -316,7 +319,9 @@ function renameKey(event: KeyboardEvent) {
|
|
|
316
319
|
</Button>
|
|
317
320
|
{/each}
|
|
318
321
|
{/each}
|
|
319
|
-
<p
|
|
322
|
+
<p
|
|
323
|
+
class="mt-1 border-t border-border px-2 pt-2 pb-1 text-xs tabular-nums text-muted-foreground"
|
|
324
|
+
>
|
|
320
325
|
{t.shownCount(visible.length, ordered.length)}
|
|
321
326
|
</p>
|
|
322
327
|
</Popover.Content>
|
|
@@ -337,7 +342,9 @@ function renameKey(event: KeyboardEvent) {
|
|
|
337
342
|
{#each buckets as bucket (bucket.key)}
|
|
338
343
|
<div class="flex flex-col">
|
|
339
344
|
{#if bucket.label}
|
|
340
|
-
<h3
|
|
345
|
+
<h3
|
|
346
|
+
class="mb-1 px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase select-none"
|
|
347
|
+
>
|
|
341
348
|
{bucket.label}
|
|
342
349
|
</h3>
|
|
343
350
|
{/if}
|
|
@@ -388,7 +395,9 @@ function renameKey(event: KeyboardEvent) {
|
|
|
388
395
|
)}
|
|
389
396
|
></span>
|
|
390
397
|
{/if}
|
|
391
|
-
<span
|
|
398
|
+
<span
|
|
399
|
+
class="relative z-10 mt-0.5 flex size-4 shrink-0 items-center justify-center"
|
|
400
|
+
>
|
|
392
401
|
{@render marker(kind)}
|
|
393
402
|
</span>
|
|
394
403
|
<span class="min-w-0 flex-1">
|