@heyramzi/ui 3.1.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.
Files changed (144) hide show
  1. package/dist/lib/sliding-pill.js +2 -2
  2. package/dist/record/measure-clamp.js +23 -0
  3. package/dist/shadcn/button-group/index.js +6 -0
  4. package/dist/shadcn/checkbox/checkbox-box.js +3 -0
  5. package/dist/shadcn/input-group/index.js +9 -0
  6. package/dist/shadcn/item/index.js +13 -0
  7. package/dist/shadcn/native-select/index.js +6 -0
  8. package/dist/shadcn/pagination/index.js +12 -0
  9. package/package.json +1 -1
  10. package/scripts/sync-shadcn.mjs +0 -5
  11. package/src/ai/generation-progress/GenerationProgress.svelte +14 -12
  12. package/src/canvas/pen/PenOverlay.svelte +1 -30
  13. package/src/canvas/screen/PartView.svelte +13 -8
  14. package/src/canvas/screen/ScreenBody.svelte +18 -11
  15. package/src/canvas/ui/BarTooltip.svelte +46 -0
  16. package/src/canvas/ui/BarTooltip.svelte.d.ts +17 -0
  17. package/src/canvas/ui/CanvasToolbar.svelte +170 -156
  18. package/src/canvas/ui/StylePanel.svelte +179 -102
  19. package/src/canvas/ui/canvas-ui.css +12 -76
  20. package/src/charts/dashboard/data-table.svelte +80 -80
  21. package/src/charts/dashboard/hint.svelte +34 -0
  22. package/src/charts/dashboard/hint.svelte.d.ts +10 -0
  23. package/src/charts/dashboard/rating-chip.svelte +4 -3
  24. package/src/charts/legend.css +0 -8
  25. package/src/charts/legend.svelte +26 -30
  26. package/src/components/copy/copy-button.svelte +10 -2
  27. package/src/components/copy/copy-button.test.ts +7 -0
  28. package/src/components/data-table/table-skeleton.svelte +15 -7
  29. package/src/components/data-table/table-skeleton.svelte.d.ts +4 -0
  30. package/src/components/docs/on-this-page.svelte +2 -2
  31. package/src/components/hold-to-confirm/hold-to-confirm.svelte +2 -1
  32. package/src/components/list-page/filter-item.svelte +20 -7
  33. package/src/components/list-page/filter-popover.svelte +41 -16
  34. package/src/components/list-page/filter-popover.svelte.d.ts +1 -1
  35. package/src/components/list-page/grouped-list.svelte +41 -23
  36. package/src/components/picker/picker.svelte +8 -3
  37. package/src/components/picker/picker.svelte.d.ts +2 -1
  38. package/src/components/segmented/segmented-filter.svelte +19 -10
  39. package/src/components/segmented/segmented-track.svelte +11 -3
  40. package/src/components/table-view/table-view.svelte +22 -6
  41. package/src/components/tags-input/tags-input.svelte +94 -77
  42. package/src/components/toolbar/toolbar.svelte +2 -1
  43. package/src/components/toolbar/toolbar.test.ts +5 -4
  44. package/src/components/work-parts/group-head.svelte +28 -9
  45. package/src/components/work-parts/group-head.svelte.d.ts +5 -2
  46. package/src/editor/editor-toolbar.svelte +223 -193
  47. package/src/editor/editor.svelte +56 -38
  48. package/src/editor/image-toolbar.svelte +99 -37
  49. package/src/editor/image-toolbar.svelte.d.ts +4 -5
  50. package/src/editor/url-dialog.svelte +96 -0
  51. package/src/editor/url-dialog.svelte.d.ts +15 -0
  52. package/src/effects/theme-toggle/ThemeToggle.css +8 -8
  53. package/src/effects/theme-toggle/ThemeToggle.svelte +20 -6
  54. package/src/inbox/thread-inbox.svelte +9 -1
  55. package/src/lib/live-region.svelte +12 -4
  56. package/src/lib/live-region.svelte.d.ts +5 -1
  57. package/src/lib/sliding-pill.ts +3 -2
  58. package/src/record/info-list.svelte +33 -59
  59. package/src/record/measure-clamp.ts +31 -0
  60. package/src/record/record-shell.svelte +38 -39
  61. package/src/record/record-timeline.svelte +10 -5
  62. package/src/record/steps-rail.svelte +50 -36
  63. package/src/record/where-we-are.svelte +5 -19
  64. package/src/shadcn/button-group/button-group-separator.svelte +23 -0
  65. package/src/shadcn/button-group/button-group-separator.svelte.d.ts +13 -0
  66. package/src/shadcn/button-group/button-group-text.svelte +28 -0
  67. package/src/shadcn/button-group/button-group-text.svelte.d.ts +11 -0
  68. package/src/shadcn/button-group/button-group.svelte +46 -0
  69. package/src/shadcn/button-group/button-group.svelte.d.ts +16 -0
  70. package/src/shadcn/button-group/index.ts +15 -0
  71. package/src/shadcn/checkbox/checkbox-box.ts +4 -0
  72. package/src/shadcn/checkbox/checkbox.svelte +2 -4
  73. package/src/shadcn/input-group/index.ts +22 -0
  74. package/src/shadcn/input-group/input-group-addon.svelte +51 -0
  75. package/src/shadcn/input-group/input-group-addon.svelte.d.ts +18 -0
  76. package/src/shadcn/input-group/input-group-button.svelte +49 -0
  77. package/src/shadcn/input-group/input-group-button.svelte.d.ts +18 -0
  78. package/src/shadcn/input-group/input-group-input.svelte +20 -0
  79. package/src/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
  80. package/src/shadcn/input-group/input-group-text.svelte +19 -0
  81. package/src/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
  82. package/src/shadcn/input-group/input-group-textarea.svelte +20 -0
  83. package/src/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
  84. package/src/shadcn/input-group/input-group.svelte +24 -0
  85. package/src/shadcn/input-group/input-group.svelte.d.ts +5 -0
  86. package/src/shadcn/item/index.ts +34 -0
  87. package/src/shadcn/item/item-actions.svelte +20 -0
  88. package/src/shadcn/item/item-actions.svelte.d.ts +5 -0
  89. package/src/shadcn/item/item-content.svelte +23 -0
  90. package/src/shadcn/item/item-content.svelte.d.ts +5 -0
  91. package/src/shadcn/item/item-description.svelte +23 -0
  92. package/src/shadcn/item/item-description.svelte.d.ts +5 -0
  93. package/src/shadcn/item/item-footer.svelte +20 -0
  94. package/src/shadcn/item/item-footer.svelte.d.ts +5 -0
  95. package/src/shadcn/item/item-group.svelte +21 -0
  96. package/src/shadcn/item/item-group.svelte.d.ts +5 -0
  97. package/src/shadcn/item/item-header.svelte +20 -0
  98. package/src/shadcn/item/item-header.svelte.d.ts +5 -0
  99. package/src/shadcn/item/item-media.svelte +42 -0
  100. package/src/shadcn/item/item-media.svelte.d.ts +17 -0
  101. package/src/shadcn/item/item-separator.svelte +19 -0
  102. package/src/shadcn/item/item-separator.svelte.d.ts +13 -0
  103. package/src/shadcn/item/item-title.svelte +20 -0
  104. package/src/shadcn/item/item-title.svelte.d.ts +5 -0
  105. package/src/shadcn/item/item.svelte +61 -0
  106. package/src/shadcn/item/item.svelte.d.ts +28 -0
  107. package/src/shadcn/native-select/index.ts +13 -0
  108. package/src/shadcn/native-select/native-select-opt-group.svelte +20 -0
  109. package/src/shadcn/native-select/native-select-opt-group.svelte.d.ts +5 -0
  110. package/src/shadcn/native-select/native-select-option.svelte +20 -0
  111. package/src/shadcn/native-select/native-select-option.svelte.d.ts +5 -0
  112. package/src/shadcn/native-select/native-select.svelte +45 -0
  113. package/src/shadcn/native-select/native-select.svelte.d.ts +8 -0
  114. package/src/shadcn/pagination/index.ts +31 -0
  115. package/src/shadcn/pagination/pagination-content.svelte +20 -0
  116. package/src/shadcn/pagination/pagination-content.svelte.d.ts +5 -0
  117. package/src/shadcn/pagination/pagination-ellipsis.svelte +22 -0
  118. package/src/shadcn/pagination/pagination-ellipsis.svelte.d.ts +5 -0
  119. package/src/shadcn/pagination/pagination-item.svelte +14 -0
  120. package/src/shadcn/pagination/pagination-item.svelte.d.ts +5 -0
  121. package/src/shadcn/pagination/pagination-link.svelte +41 -0
  122. package/src/shadcn/pagination/pagination-link.svelte.d.ts +9 -0
  123. package/src/shadcn/pagination/pagination-next-button.svelte +34 -0
  124. package/src/shadcn/pagination/pagination-next-button.svelte.d.ts +4 -0
  125. package/src/shadcn/pagination/pagination-next.svelte +26 -0
  126. package/src/shadcn/pagination/pagination-next.svelte.d.ts +4 -0
  127. package/src/shadcn/pagination/pagination-prev-button.svelte +34 -0
  128. package/src/shadcn/pagination/pagination-prev-button.svelte.d.ts +4 -0
  129. package/src/shadcn/pagination/pagination-previous.svelte +30 -0
  130. package/src/shadcn/pagination/pagination-previous.svelte.d.ts +4 -0
  131. package/src/shadcn/pagination/pagination.svelte +27 -0
  132. package/src/shadcn/pagination/pagination.svelte.d.ts +4 -0
  133. package/src/slides/Controls.svelte +39 -36
  134. package/src/slides/Deck.svelte +11 -8
  135. package/src/thread/thread.svelte +15 -8
  136. package/src/work/detail/task-detail.svelte +1 -1
  137. package/src/work/gantt/gantt-task-row.svelte +3 -6
  138. package/src/work/inbox/inbox-card.svelte +7 -5
  139. package/src/work/inbox/inbox-trace.svelte +14 -10
  140. package/src/work/kanban-board.svelte +40 -25
  141. package/src/work/kanban-hidden-rail.svelte +67 -65
  142. package/src/work/task-list.svelte +145 -124
  143. package/src/work/task-list.svelte.test.ts +5 -2
  144. package/src/work/time/time-tracker.svelte +35 -10
@@ -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
- // three ways a strip marks it: a tab, a nav link, a toggle.
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyramzi/ui",
3
- "version": "3.1.0",
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",
@@ -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
- <div
108
- class="gen-track"
109
- data-indeterminate={progress ? undefined : true}
110
- role="progressbar"
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
- <div
118
- class="gen-fill"
119
- style:transform={progress ? `scaleX(${percent / 100})` : undefined}
120
- ></div>
121
- </div>
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
- <button
210
- type="button"
211
- class="cv-tool is-text"
212
- aria-pressed={part.variant !== "secondary"}
213
- onclick={() =>
214
- onCommit({ variant: part.variant === "secondary" ? "primary" : "secondary" }, false)}
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
- {part.variant === "secondary" ? labels.secondary : labels.primary}
217
- </button>
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
- <div class="cv-switch nodrag" role="radiogroup" aria-label={labels.mode}>
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
- <button
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
- </button>
125
+ </RadioGroup.Item>
119
126
  {/each}
120
- </div>
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;