@heyramzi/ui 2.11.0 → 2.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/dist/agents/helpers.js +38 -38
  2. package/dist/agents/page-state.js +49 -50
  3. package/dist/agents/vendor/color.js +146 -127
  4. package/dist/agents/vendor/draw.js +617 -508
  5. package/dist/agents/vendor/engine.js +698 -599
  6. package/dist/agents/vendor/mesh.js +653 -683
  7. package/dist/agents/vendor/plastic.js +3913 -3112
  8. package/dist/agents/vendor/presets.js +163 -164
  9. package/dist/agents/vendor/shapes.js +21 -20
  10. package/dist/agents/vendor/ticker.js +41 -42
  11. package/dist/agents/vendor/types.js +1 -0
  12. package/dist/agents/vendor/wear.js +1421 -1448
  13. package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
  14. package/dist/ai/confirmation/index.js +3 -1
  15. package/dist/ai/index.js +33 -0
  16. package/dist/ai/prompt-input/index.js +3 -1
  17. package/dist/ai/reasoning/index.js +3 -1
  18. package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
  19. package/dist/ai/suggestion/index.js +3 -1
  20. package/dist/ai/typing-dots/index.js +3 -1
  21. package/dist/animations/text-swap.js +24 -24
  22. package/dist/brand/index.js +54 -48
  23. package/dist/canvas/card/resize.js +5 -3
  24. package/dist/canvas/edges/edge-path.js +255 -242
  25. package/dist/canvas/editor/context-menu.js +39 -36
  26. package/dist/canvas/editor/dispatch.svelte.js +154 -151
  27. package/dist/canvas/editor/editor-context.js +7 -2
  28. package/dist/canvas/editor/editor-mode.js +1 -0
  29. package/dist/canvas/editor/frame.js +73 -76
  30. package/dist/canvas/editor/hand-context.js +5 -2
  31. package/dist/canvas/editor/history-core.js +163 -191
  32. package/dist/canvas/editor/keymap.js +95 -169
  33. package/dist/canvas/editor/layout-actions.js +143 -144
  34. package/dist/canvas/editor/lock.js +52 -51
  35. package/dist/canvas/editor/rebase.js +217 -240
  36. package/dist/canvas/editor/style.js +132 -137
  37. package/dist/canvas/editor/text-field-context.js +7 -2
  38. package/dist/canvas/editor/z-order.js +65 -70
  39. package/dist/canvas/headless/alignment.js +22 -16
  40. package/dist/canvas/headless/break-apart-find.js +286 -279
  41. package/dist/canvas/headless/break-apart-pieces.js +62 -58
  42. package/dist/canvas/headless/break-apart-plan.js +299 -325
  43. package/dist/canvas/headless/break-apart-style.js +75 -70
  44. package/dist/canvas/headless/break-apart.js +188 -181
  45. package/dist/canvas/headless/canvas-cleanup.js +78 -58
  46. package/dist/canvas/headless/edge-anchors.js +115 -116
  47. package/dist/canvas/headless/edge-memo.js +24 -22
  48. package/dist/canvas/headless/edge-serialization.js +17 -27
  49. package/dist/canvas/headless/figure-geometry.js +232 -264
  50. package/dist/canvas/headless/fit-viewport.js +84 -67
  51. package/dist/canvas/headless/grid.js +88 -86
  52. package/dist/canvas/headless/icon-node.js +44 -36
  53. package/dist/canvas/headless/index.js +1 -0
  54. package/dist/canvas/headless/linkify.js +17 -19
  55. package/dist/canvas/headless/pen-marks.js +101 -95
  56. package/dist/canvas/headless/sketch.js +231 -209
  57. package/dist/canvas/headless/snap-guides.js +99 -83
  58. package/dist/canvas/headless/svg-figure.js +133 -117
  59. package/dist/canvas/headless/table-layout.js +146 -107
  60. package/dist/canvas/index.js +2 -0
  61. package/dist/canvas/lod/lod-mode.svelte.js +20 -18
  62. package/dist/canvas/lod/render-all.svelte.js +36 -39
  63. package/dist/canvas/ops/arrange.js +78 -71
  64. package/dist/canvas/ops/ops.js +260 -255
  65. package/dist/canvas/pen/colors.js +37 -35
  66. package/dist/canvas/pen/eraser.js +28 -27
  67. package/dist/canvas/pen/geometry.js +120 -130
  68. package/dist/canvas/pen/index.js +1 -0
  69. package/dist/canvas/pen/ops.js +11 -11
  70. package/dist/canvas/pen/pen-session.js +150 -159
  71. package/dist/canvas/pen/plugin.js +107 -104
  72. package/dist/canvas/pen/shapes.js +73 -68
  73. package/dist/canvas/pen/stroke-path.js +32 -30
  74. package/dist/canvas/pen/types.js +21 -21
  75. package/dist/canvas/pen/ui.js +10 -8
  76. package/dist/canvas/plugin.js +46 -46
  77. package/dist/canvas/screen/glyphs.js +12 -11
  78. package/dist/canvas/screen/labels.js +22 -22
  79. package/dist/canvas/screen/parts.js +98 -111
  80. package/dist/canvas/screen/plugin.js +47 -56
  81. package/dist/canvas/screen/types.js +30 -35
  82. package/dist/canvas/screen/ui.js +7 -7
  83. package/dist/canvas/test-utils.js +14 -14
  84. package/dist/canvas/types.js +1 -0
  85. package/dist/canvas/ui/forward-wheel.js +24 -25
  86. package/dist/canvas/ui/icons.js +64 -131
  87. package/dist/canvas/ui/place-panel.js +58 -49
  88. package/dist/canvas/ui/shortcut-tip.js +14 -12
  89. package/dist/canvas/ui/tools.js +300 -385
  90. package/dist/charts/dashboard/chart.js +4 -0
  91. package/dist/charts/dashboard/data-table.js +11 -0
  92. package/dist/charts/dashboard/windows.js +8 -3
  93. package/dist/charts/edge-fade.js +10 -5
  94. package/dist/charts/hover-band.js +16 -11
  95. package/dist/charts/index.js +0 -1
  96. package/dist/charts/motion.js +11 -7
  97. package/dist/charts/primitives/chart-utils.js +12 -12
  98. package/dist/components/copy/clipboard.js +32 -32
  99. package/dist/components/copy/copied.svelte.js +31 -23
  100. package/dist/components/data-table/data-table.js +39 -39
  101. package/dist/components/docs/index.js +4 -0
  102. package/dist/components/docs/scroll-spy.svelte.js +73 -68
  103. package/dist/components/list-page/filter-section.js +4 -1
  104. package/dist/components/list-page/index.js +2 -0
  105. package/dist/components/list-page/list-table.js +7 -2
  106. package/dist/components/list-page/queue-rail.js +14 -0
  107. package/dist/components/list-page/section-host.js +4 -2
  108. package/dist/components/list-page/step.js +4 -3
  109. package/dist/components/list-page/types.js +1 -0
  110. package/dist/components/picker/trigger.js +48 -30
  111. package/dist/components/picker/types.js +1 -0
  112. package/dist/components/segmented/collapse.js +38 -27
  113. package/dist/components/segmented/index.js +8 -0
  114. package/dist/components/segmented/segmented.js +67 -42
  115. package/dist/components/star-rating-input/index.js +3 -1
  116. package/dist/components/table-view/types.js +1 -0
  117. package/dist/components/tags-input/tags.js +81 -69
  118. package/dist/components/theme/index.js +4 -0
  119. package/dist/components/toolbar/index.js +4 -0
  120. package/dist/components/toolbar/toolbar-trigger.js +14 -1
  121. package/dist/editor/anchors.js +17 -13
  122. package/dist/editor/block-id.js +52 -51
  123. package/dist/editor/block-ops.js +108 -80
  124. package/dist/editor/doc-blocks.js +155 -166
  125. package/dist/editor/extensions.js +139 -180
  126. package/dist/editor/index.js +4 -0
  127. package/dist/editor/markdown-test-helpers.js +4 -2
  128. package/dist/editor/markdown.js +420 -408
  129. package/dist/editor/serializer.js +204 -200
  130. package/dist/editor/slash-items.js +89 -101
  131. package/dist/editor/youtube-url.js +25 -20
  132. package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
  133. package/dist/effects/animated-beam/types.js +1 -0
  134. package/dist/effects/border-beam/css.js +92 -78
  135. package/dist/effects/border-beam/presets.js +1705 -1720
  136. package/dist/effects/border-beam/register-properties.js +43 -33
  137. package/dist/effects/border-beam/types.js +1 -0
  138. package/dist/effects/image-comparison/compare.js +3 -2
  139. package/dist/effects/image-reveal/types.js +1 -0
  140. package/dist/effects/magnetic/magnetic.js +58 -52
  141. package/dist/effects/reveal/observe.js +50 -40
  142. package/dist/effects/reveal/types.js +1 -0
  143. package/dist/effects/shader-gradient/color.js +36 -29
  144. package/dist/effects/shader-gradient/renderer.js +384 -295
  145. package/dist/effects/shader-gradient/shaders.js +31 -0
  146. package/dist/effects/shader-gradient/types.js +1 -0
  147. package/dist/icons/apple-symbol-exports.js +99 -124
  148. package/dist/icons/sf-symbols.js +357 -461
  149. package/dist/icons/trades.js +43 -28
  150. package/dist/inbox/index.js +1 -0
  151. package/dist/inbox/logic.js +126 -122
  152. package/dist/inbox/types.js +43 -43
  153. package/dist/lib/arrive.js +19 -12
  154. package/dist/lib/icon-types.js +1 -0
  155. package/dist/lib/initials.js +16 -10
  156. package/dist/lib/layout.js +5 -0
  157. package/dist/lib/panel.js +13 -0
  158. package/dist/lib/result.js +22 -22
  159. package/dist/lib/sliding-pill.js +59 -44
  160. package/dist/lib/theme-transition.js +43 -43
  161. package/dist/lib/tone.js +31 -24
  162. package/dist/lib/utils.js +3 -1
  163. package/dist/onboarding/define-tours.js +24 -23
  164. package/dist/onboarding/onboarding.svelte.js +16 -12
  165. package/dist/onboarding/pick.js +10 -6
  166. package/dist/onboarding/stores.js +40 -41
  167. package/dist/onboarding/wait.js +24 -23
  168. package/dist/panel/side-panel-types.js +1 -0
  169. package/dist/record/demo-data.js +459 -486
  170. package/dist/record/fields.js +129 -131
  171. package/dist/record/format.js +70 -82
  172. package/dist/record/gutter.js +1 -0
  173. package/dist/record/kinds.js +95 -105
  174. package/dist/record/logic.js +238 -219
  175. package/dist/record/types.js +49 -48
  176. package/dist/shadcn/accordion/index.js +3 -1
  177. package/dist/shadcn/alert/index.js +3 -1
  178. package/dist/shadcn/alert-dialog/index.js +3 -1
  179. package/dist/shadcn/attachment/index.js +3 -1
  180. package/dist/shadcn/avatar/index.js +3 -1
  181. package/dist/shadcn/breadcrumb/index.js +3 -1
  182. package/dist/shadcn/bubble/index.js +3 -1
  183. package/dist/shadcn/button/index.js +3 -1
  184. package/dist/shadcn/calendar/index.js +3 -1
  185. package/dist/shadcn/card/index.js +3 -1
  186. package/dist/shadcn/checkbox/index.js +3 -1
  187. package/dist/shadcn/code-block/index.js +3 -1
  188. package/dist/shadcn/collapsible/index.js +3 -1
  189. package/dist/shadcn/command/index.js +4 -1
  190. package/dist/shadcn/context-menu/index.js +3 -1
  191. package/dist/shadcn/dialog/index.js +3 -1
  192. package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
  193. package/dist/shadcn/empty/index.js +3 -1
  194. package/dist/shadcn/field/index.js +3 -1
  195. package/dist/shadcn/hooks/highlighter.js +77 -75
  196. package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
  197. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
  198. package/dist/shadcn/input/index.js +3 -1
  199. package/dist/shadcn/kbd/index.js +3 -1
  200. package/dist/shadcn/label/index.js +3 -1
  201. package/dist/shadcn/marker/index.js +3 -1
  202. package/dist/shadcn/message/index.js +3 -1
  203. package/dist/shadcn/message-scroller/index.js +3 -1
  204. package/dist/shadcn/popover/index.js +3 -1
  205. package/dist/shadcn/progress/index.js +3 -1
  206. package/dist/shadcn/progress/tone.js +8 -7
  207. package/dist/shadcn/radio-group/index.js +3 -1
  208. package/dist/shadcn/scroll-area/index.js +3 -1
  209. package/dist/shadcn/select/index.js +3 -1
  210. package/dist/shadcn/separator/index.js +3 -1
  211. package/dist/shadcn/sheet/index.js +3 -1
  212. package/dist/shadcn/sidebar/context.svelte.js +42 -28
  213. package/dist/shadcn/sidebar/index.js +3 -1
  214. package/dist/shadcn/skeleton/index.js +3 -1
  215. package/dist/shadcn/slider/index.js +3 -1
  216. package/dist/shadcn/switch/index.js +3 -1
  217. package/dist/shadcn/table/index.js +3 -1
  218. package/dist/shadcn/tabs/index.js +3 -1
  219. package/dist/shadcn/textarea/index.js +3 -1
  220. package/dist/shadcn/toggle/index.js +3 -1
  221. package/dist/shadcn/toggle-group/index.js +3 -1
  222. package/dist/shadcn/tooltip/context.js +7 -2
  223. package/dist/shadcn/tooltip/index.js +3 -1
  224. package/dist/shadcn/trace/index.js +3 -1
  225. package/dist/slides/bridges.js +65 -59
  226. package/dist/slides/deck-controller.svelte.js +210 -207
  227. package/dist/slides/export/capture.js +66 -71
  228. package/dist/slides/export/ffmpeg.js +76 -72
  229. package/dist/slides/export/index.js +3 -0
  230. package/dist/slides/export/launch.js +9 -6
  231. package/dist/slides/export/pdf.js +60 -54
  232. package/dist/slides/export/types.js +1 -0
  233. package/dist/slides/labels.js +24 -24
  234. package/dist/slides/layout.js +39 -41
  235. package/dist/slides/model.js +9 -7
  236. package/dist/slides/navigation.js +78 -73
  237. package/dist/slides/types.js +2 -2
  238. package/dist/status/define-statuses.js +18 -14
  239. package/dist/status/status-types.js +1 -0
  240. package/dist/test-setup.js +24 -20
  241. package/dist/thread/logic.js +73 -77
  242. package/dist/thread/types.js +21 -21
  243. package/dist/work/board-order.js +94 -90
  244. package/dist/work/dates.js +16 -9
  245. package/dist/work/detail/detail-types.js +18 -18
  246. package/dist/work/docs/edited.js +32 -29
  247. package/dist/work/docs/index.js +4 -0
  248. package/dist/work/docs/labels.js +34 -34
  249. package/dist/work/docs/menu.js +113 -106
  250. package/dist/work/docs/outline.js +39 -29
  251. package/dist/work/gantt/logic.js +211 -235
  252. package/dist/work/inbox/inbox-logic.js +71 -75
  253. package/dist/work/inbox/inbox-statuses.js +75 -72
  254. package/dist/work/inbox/inbox-types.js +1 -0
  255. package/dist/work/kanban-drag.js +27 -22
  256. package/dist/work/kanban-types.js +1 -0
  257. package/dist/work/presets/sales.js +36 -32
  258. package/dist/work/reorder.js +9 -8
  259. package/dist/work/task-types.js +1 -0
  260. package/dist/work/time/duration.js +51 -46
  261. package/dist/work/time/time-types.js +23 -23
  262. package/dist/work/use-kanban-dnd.svelte.js +126 -125
  263. package/package.json +3 -1
  264. package/src/components/list-page/master-detail.svelte +3 -1
  265. package/src/components/list-page/rail-row.svelte +4 -4
  266. package/src/components/tags-input/tags-input.svelte +2 -0
  267. package/src/editor/markdown.ts +30 -35
@@ -1,33 +1,51 @@
1
1
  import { tv } from "tailwind-variants";
2
+ /**
3
+ * The one look for a control that opens a picker, and for the plain values
4
+ * that sit in the same column beside it.
5
+ */
6
+ // WHY a recipe: a property row can be a menu trigger, a link or a plain
7
+ // string. All three draw the same box, and only one is a button.
8
+ // It replaced eight geometries in one sidebar: three heights, three radii, two
9
+ // paddings, and a border on three rows out of eight.
10
+ // Borderless at rest on purpose. A value that's set is a fact to read, so the
11
+ // box shows only under the pointer and on focus.
2
12
  export const pickerTriggerClass = tv({
3
- base: [
4
- "t-press inline-flex min-w-0 items-center gap-1.5 rounded-md text-[13px] font-medium whitespace-nowrap select-none",
5
- "hover:bg-muted data-[state=open]:bg-muted",
6
- "disabled:pointer-events-none disabled:opacity-50",
7
- "[&_svg]:pointer-events-none [&_svg]:shrink-0"
8
- ],
9
- variants: {
10
- size: {
11
- sm: "h-6 ps-1.5 pe-2 any-pointer-coarse:h-11 any-pointer-coarse:pe-2.5",
12
- default: "h-7 ps-1.5 pe-2.5 any-pointer-coarse:h-11"
13
- },
14
- block: {
15
- true: "flex w-full",
16
- false: "w-fit"
17
- },
18
- empty: {
19
- true: "text-muted-foreground/70 hover:text-muted-foreground",
20
- false: ""
21
- },
22
- inert: {
23
- true: "hover:bg-transparent cursor-default",
24
- false: ""
25
- }
26
- },
27
- defaultVariants: {
28
- size: "default",
29
- block: false,
30
- empty: false,
31
- inert: false
32
- }
13
+ base: [
14
+ "t-press inline-flex min-w-0 items-center gap-1.5 rounded-md text-[13px] font-medium whitespace-nowrap select-none",
15
+ "hover:bg-muted data-[state=open]:bg-muted",
16
+ "disabled:pointer-events-none disabled:opacity-50",
17
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0"
18
+ ],
19
+ variants: {
20
+ // Heights match `segmentedTrackClass`, so a strip and a picker line up.
21
+ // Padding is 6px leading, 10px trailing: an even 8px shunted icon rows right.
22
+ size: {
23
+ sm: "h-6 ps-1.5 pe-2 any-pointer-coarse:h-11 any-pointer-coarse:pe-2.5",
24
+ default: "h-7 ps-1.5 pe-2.5 any-pointer-coarse:h-11"
25
+ },
26
+ /**
27
+ * Fill the row. Off by default: Linear's property rows sit at natural width
28
+ * (80px for "Assign"), so a set of rows reads as a set of chips.
29
+ */
30
+ block: {
31
+ true: "flex w-full",
32
+ false: "w-fit"
33
+ },
34
+ /** No value yet: the label is a prompt, so it recedes until hovered. */
35
+ empty: {
36
+ true: "text-muted-foreground/70 hover:text-muted-foreground",
37
+ false: ""
38
+ },
39
+ /** A row that carries no action still needs the column's box. */
40
+ inert: {
41
+ true: "hover:bg-transparent cursor-default",
42
+ false: ""
43
+ }
44
+ },
45
+ defaultVariants: {
46
+ size: "default",
47
+ block: false,
48
+ empty: false,
49
+ inert: false
50
+ }
33
51
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -1,32 +1,43 @@
1
+ /** How a segmented strip that outgrew its row decides what to keep. */
2
+ // WHY not a scrollbar: a sideways scroll hid four of five tabs
3
+ // behind a gesture nobody makes with a mouse. The tail becomes a menu instead.
4
+ // WHY a count, not a measurement: the strip sits in a toolbar sized by its own
5
+ // content, so measuring reads its own width back and never collapses.
6
+ /**
7
+ * The most objects a strip draws before its tail becomes a menu, the menu
8
+ * counted as one of them. Two to six values is where a segmented control reads best.
9
+ */
1
10
  export const SEGMENT_MAX = 6;
11
+ /**
12
+ * Split items into the drawn ones and the menu's. The current item always stays visible.
13
+ * @param max objects the row may draw, the menu counted as one
14
+ */
2
15
  export function splitSegments(items, max, activeIndex) {
3
- if (max < 3 || items.length <= max) return {
4
- visible: [
5
- ...items
6
- ],
7
- hidden: []
8
- };
9
- const slots = max - 1;
10
- if (activeIndex < slots && activeIndex >= 0) {
11
- return {
12
- visible: items.slice(0, slots),
13
- hidden: items.slice(slots)
14
- };
15
- }
16
- if (activeIndex < 0 || activeIndex >= items.length) {
17
- return {
18
- visible: items.slice(0, slots),
19
- hidden: items.slice(slots)
20
- };
21
- }
22
- return {
23
- visible: [
24
- ...items.slice(0, slots - 1),
25
- ...items.slice(activeIndex, activeIndex + 1)
26
- ],
27
- hidden: items.filter((_, i)=>i >= slots - 1 && i !== activeIndex)
28
- };
16
+ // Under three the row is the menu and a single segment beside it, which says
17
+ // less than the menu alone. Below that, and whenever everything fits, no split.
18
+ if (max < 3 || items.length <= max) return {
19
+ visible: [...items],
20
+ hidden: []
21
+ };
22
+ const slots = max - 1;
23
+ if (activeIndex < slots && activeIndex >= 0) {
24
+ return {
25
+ visible: items.slice(0, slots),
26
+ hidden: items.slice(slots)
27
+ };
28
+ }
29
+ if (activeIndex < 0 || activeIndex >= items.length) {
30
+ return {
31
+ visible: items.slice(0, slots),
32
+ hidden: items.slice(slots)
33
+ };
34
+ }
35
+ return {
36
+ visible: [...items.slice(0, slots - 1), ...items.slice(activeIndex, activeIndex + 1)],
37
+ hidden: items.filter((_, i) => i >= slots - 1 && i !== activeIndex)
38
+ };
29
39
  }
40
+ /** `"none"` draws every segment: the row scrolls sideways instead of folding a tail into a menu. */
30
41
  export function resolveSegmentMax(max) {
31
- return max === "none" ? Number.POSITIVE_INFINITY : max;
42
+ return max === "none" ? Number.POSITIVE_INFINITY : max;
32
43
  }
@@ -1,3 +1,11 @@
1
+ /**
2
+ * The segmented control. Three components draw it, one per ARIA shape, and they
3
+ * share the track (`segmented.ts`) and segment body (`segment.svelte`).
4
+ */
5
+ // WHY no low-level `Tabs.Root`/`List`/`Trigger` here: that's `shadcn/tabs`. This
6
+ // is the data-driven layer on top, and `AnimatedTabs` is the way in.
7
+ // Past `SEGMENT_MAX` objects the tail collapses into a menu: `collapse.ts`
8
+ // decides, `segmented-overflow.svelte` draws it. No call site opts in.
1
9
  import AnimatedTabs from "../../../src/components/segmented/animated-tabs.svelte";
2
10
  import SegmentedFilter from "../../../src/components/segmented/segmented-filter.svelte";
3
11
  import SegmentedNav from "../../../src/components/segmented/segmented-nav.svelte";
@@ -1,50 +1,75 @@
1
1
  import { tv } from "tailwind-variants";
2
2
  import { pageGutterClass } from "../../lib/layout.js";
3
+ /**
4
+ * The one look for a segmented control: the track and trigger here, what a
5
+ * segment says in `segment.svelte`.
6
+ */
7
+ // WHY a recipe, not one component: tabs are a `tablist`, filters a `group` of
8
+ // `aria-pressed` toggles, page links a `nav`. Only the ARIA shape may differ.
9
+ // The look used to drift three ways, one with an at-rest shadow. All three read from here now.
3
10
  export const segmentedTrackClass = tv({
4
- base: "h-7 any-pointer-coarse:h-11 shrink-0 items-stretch",
5
- variants: {
6
- variant: {
7
- pill: "overflow-hidden rounded-lg border border-border p-0.5 any-pointer-coarse:border-0 any-pointer-coarse:px-[3px] any-pointer-coarse:py-0",
8
- underline: "gap-5 border-b border-border"
9
- },
10
- fill: {
11
- true: "flex w-full",
12
- false: "inline-flex w-fit"
13
- }
14
- },
15
- defaultVariants: {
16
- variant: "pill",
17
- fill: false
18
- }
11
+ // WHY the height lives on the track: it carries the inset, so an `h-7` trigger
12
+ // drew 30px beside a 28px button. One toolbar row came out at five heights.
13
+ // WHY no size variant: `sm` drew 24px, and every tab control must share one height.
14
+ base: "h-7 any-pointer-coarse:h-11 shrink-0 items-stretch",
15
+ variants: {
16
+ // `pill`: a bordered well with a travelling highlight. `underline`: bare
17
+ // labels over one hairline, the active one marked by a rule that travels.
18
+ variant: {
19
+ pill: "overflow-hidden rounded-lg border border-border p-0.5 any-pointer-coarse:border-0 any-pointer-coarse:px-[3px] any-pointer-coarse:py-0",
20
+ underline: "gap-5 border-b border-border"
21
+ },
22
+ // Fill the row rather than sit at its natural width. For a narrow rail.
23
+ fill: {
24
+ true: "flex w-full",
25
+ false: "inline-flex w-fit"
26
+ }
27
+ },
28
+ defaultVariants: {
29
+ variant: "pill",
30
+ fill: false
31
+ }
19
32
  });
20
33
  export const segmentedTriggerClass = tv({
21
- base: [
22
- "ui-segment group/segment t-press relative z-[1] inline-flex items-center justify-center gap-1.5 whitespace-nowrap font-medium select-none",
23
- "text-muted-foreground hover:text-foreground",
24
- "disabled:pointer-events-none disabled:opacity-50",
25
- "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
26
- "focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-inset focus-visible:outline-none",
27
- "h-full text-xs"
28
- ],
29
- variants: {
30
- variant: {
31
- pill: "rounded-[calc(var(--radius-lg)-3px)] px-2.5 any-pointer-coarse:px-3.5",
32
- underline: "rounded-sm px-0"
33
- },
34
- fill: {
35
- true: "min-w-fit flex-auto",
36
- false: ""
37
- },
38
- active: {
39
- true: "text-foreground",
40
- false: ""
41
- }
42
- },
43
- defaultVariants: {
44
- variant: "pill",
45
- fill: false,
46
- active: false
47
- }
34
+ base: [
35
+ "ui-segment group/segment t-press relative z-[1] inline-flex items-center justify-center gap-1.5 whitespace-nowrap font-medium select-none",
36
+ "text-muted-foreground hover:text-foreground",
37
+ "disabled:pointer-events-none disabled:opacity-50",
38
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
39
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-inset focus-visible:outline-none",
40
+ "h-full text-xs"
41
+ ],
42
+ variants: {
43
+ // The pill sits concentric in the track's 2px inset; the underline has no well, so
44
+ // no padding, and its rule is as wide as the label.
45
+ variant: {
46
+ pill: "rounded-[calc(var(--radius-lg)-3px)] px-2.5 any-pointer-coarse:px-3.5",
47
+ underline: "rounded-sm px-0"
48
+ },
49
+ // WHY `min-w-fit flex-auto`, not equal fractions: fractions cut "Approved" to
50
+ // "Appro…" in a 280px rail. Each segment claims its label and shares the slack.
51
+ fill: {
52
+ true: "min-w-fit flex-auto",
53
+ false: ""
54
+ },
55
+ active: {
56
+ true: "text-foreground",
57
+ false: ""
58
+ }
59
+ },
60
+ defaultVariants: {
61
+ variant: "pill",
62
+ fill: false,
63
+ active: false
64
+ }
48
65
  });
66
+ /**
67
+ * The row a segmented strip sits in, and the one recipe for a navigation row
68
+ * under the app header. Written per caller it produced two different left
69
+ * edges.
70
+ */
71
+ // WHY no bottom hairline: with a section strip and a page toolbar stacked it
72
+ // drew two rules for one boundary. The gap is the boundary.
49
73
  export const segmentedRowClass = `flex shrink-0 flex-wrap items-center gap-x-4 gap-y-2 py-2 ${pageGutterClass}`;
74
+ /** The active style, for a strip whose state lives in a `data-active` attribute. */
50
75
  export const segmentedActiveClass = "data-active:text-foreground";
@@ -1,2 +1,4 @@
1
1
  import Root from "../../../src/components/star-rating-input/star-rating-input.svelte";
2
- export { Root, Root as StarRatingInput };
2
+ export { Root,
3
+ //
4
+ Root as StarRatingInput };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,93 +1,105 @@
1
+ // The tag list's rules, with no DOM. Options are the values already used, so a spelling gets picked,
2
+ // never retyped, and a new tag is a deliberate act.
3
+ /** Splits typed or pasted text on commas and newlines, trimmed, empties dropped. */
1
4
  export function splitTags(text) {
2
- return text.split(/[,\n]/).map((part)=>part.trim()).filter(Boolean);
5
+ return text.split(/[,\n]/).map((part) => part.trim()).filter(Boolean);
3
6
  }
7
+ /** Splits typed or pasted text for a free field: commas, semicolons, newlines and spaces all separate. */
4
8
  export function splitTyped(text) {
5
- return text.split(/[\s,;]+/).filter(Boolean);
9
+ return text.split(/[\s,;]+/).filter(Boolean);
6
10
  }
11
+ /** What two spellings are compared by: no case, no spaces, no marks ("Share Point" is "SharePoint"). */
7
12
  export function fold(text) {
8
- return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(/[\s._-]+/g, "");
13
+ return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(/[\s._-]+/g, "");
9
14
  }
10
- const same = (a, b)=>fold(a) === fold(b);
15
+ const same = (a, b) => fold(a) === fold(b);
16
+ /** Edits (insert, delete, replace, swap of neighbours) between two strings, stopping past `limit`. */
11
17
  export function editDistance(a, b, limit = 2) {
12
- if (Math.abs(a.length - b.length) > limit) return limit + 1;
13
- let prev2 = [];
14
- let prev = Array.from({
15
- length: b.length + 1
16
- }, (_, j)=>j);
17
- for(let i = 1; i <= a.length; i++){
18
- const row = [
19
- i
20
- ];
21
- for(let j = 1; j <= b.length; j++){
22
- let d = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
23
- if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) d = Math.min(d, prev2[j - 2] + 1);
24
- row.push(d);
25
- }
26
- prev2 = prev;
27
- prev = row;
28
- }
29
- return prev[b.length];
18
+ if (Math.abs(a.length - b.length) > limit) return limit + 1;
19
+ let prev2 = [];
20
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
21
+ for (let i = 1; i <= a.length; i++) {
22
+ const row = [i];
23
+ for (let j = 1; j <= b.length; j++) {
24
+ let d = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
25
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) d = Math.min(d, prev2[j - 2] + 1);
26
+ row.push(d);
27
+ }
28
+ prev2 = prev;
29
+ prev = row;
30
+ }
31
+ return prev[b.length];
30
32
  }
33
+ /** How close `query` is to `option`: 0 the same, 1 a prefix, 2 contained, 3 a typo away, else null. */
31
34
  function rank(option, query) {
32
- const o = fold(option);
33
- const q = fold(query);
34
- if (o === q) return 0;
35
- if (o.startsWith(q)) return 1;
36
- if (o.includes(q)) return 2;
37
- const limit = q.length >= 7 ? 2 : q.length >= 4 ? 1 : 0;
38
- return limit > 0 && editDistance(o, q, limit) <= limit ? 3 : null;
35
+ const o = fold(option);
36
+ const q = fold(query);
37
+ if (o === q) return 0;
38
+ if (o.startsWith(q)) return 1;
39
+ if (o.includes(q)) return 2;
40
+ // One edit for a short word, two from 7 letters, and not at all for 3 or fewer.
41
+ const limit = q.length >= 7 ? 2 : q.length >= 4 ? 1 : 0;
42
+ return limit > 0 && editDistance(o, q, limit) <= limit ? 3 : null;
39
43
  }
44
+ /**
45
+ * The options to offer for what's typed, closest first, from exact match down to a typo away.
46
+ * Options already on the field are left out.
47
+ */
40
48
  export function matchOptions(options, tags, query, limit = 8) {
41
- const open = options.filter((option)=>!tags.some((held)=>same(held, option)));
42
- if (!query.trim()) return open.slice(0, limit);
43
- return open.flatMap((option)=>{
44
- const r = rank(option, query);
45
- return r === null ? [] : [
46
- {
47
- option,
48
- r
49
- }
50
- ];
51
- }).sort((a, b)=>a.r - b.r).map(({ option })=>option).slice(0, limit);
49
+ const open = options.filter((option) => !tags.some((held) => same(held, option)));
50
+ if (!query.trim()) return open.slice(0, limit);
51
+ return open.flatMap((option) => {
52
+ const r = rank(option, query);
53
+ return r === null ? [] : [{
54
+ option,
55
+ r
56
+ }];
57
+ }).sort((a, b) => a.r - b.r).map(({ option }) => option).slice(0, limit);
52
58
  }
59
+ /** The existing option that is `input` apart from case, spacing and marks, or undefined. */
53
60
  export function findOption(options, input) {
54
- return options.find((option)=>same(option, input));
61
+ return options.find((option) => same(option, input));
55
62
  }
63
+ /**
64
+ * Whether typing `query` may offer "Create": never for a word an option or tag already is. A
65
+ * typo's close option shows above it, never instead.
66
+ */
56
67
  export function canCreate(options, tags, query, allowCreate = true) {
57
- return allowCreate && query.trim() !== "" && ![
58
- ...options,
59
- ...tags
60
- ].some((held)=>same(held, query));
68
+ return allowCreate && query.trim() !== "" && ![...options, ...tags].some((held) => same(held, query));
61
69
  }
70
+ /**
71
+ * The list after adding the option `name`, in the option's spelling. A repeat returns the same
72
+ * array, so a caller can skip the save.
73
+ */
62
74
  export function addTag(tags, name, options = [], max) {
63
- const tag = findOption(options, name) ?? name.trim();
64
- if (!tag || tags.some((held)=>same(held, tag))) return tags;
65
- if (max !== undefined && tags.length >= max) return tags;
66
- return [
67
- ...tags,
68
- tag
69
- ];
75
+ const tag = findOption(options, name) ?? name.trim();
76
+ if (!tag || tags.some((held) => same(held, tag))) return tags;
77
+ if (max !== undefined && tags.length >= max) return tags;
78
+ return [...tags, tag];
70
79
  }
80
+ /** The list without `tag`, whatever its casing. */
71
81
  export function removeTag(tags, tag) {
72
- return tags.filter((held)=>!same(held, tag));
82
+ return tags.filter((held) => !same(held, tag));
73
83
  }
84
+ /**
85
+ * Collapses spellings of one word to the first, and takes an option's spelling when one matches.
86
+ * For a list that was stored before the options existed.
87
+ */
74
88
  export function cleanTags(tags, options = []) {
75
- return tags.reduce((out, tag)=>[
76
- ...addTag(out, tag, options)
77
- ], []);
89
+ return tags.reduce((out, tag) => [...addTag(out, tag, options)], []);
78
90
  }
91
+ /**
92
+ * The distinct values across many lists, one spelling each: the most used wins, then the first
93
+ * seen. This is what an "existing values" field offers as its options.
94
+ */
79
95
  export function collectOptions(lists) {
80
- const seen = new Map();
81
- for (const list of lists)for (const raw of list){
82
- const tag = raw.trim();
83
- if (!tag) continue;
84
- const spellings = seen.get(fold(tag)) ?? new Map();
85
- spellings.set(tag, (spellings.get(tag) ?? 0) + 1);
86
- seen.set(fold(tag), spellings);
87
- }
88
- return [
89
- ...seen.values()
90
- ].map((spellings)=>[
91
- ...spellings
92
- ].sort((a, b)=>b[1] - a[1])[0][0]).sort((a, b)=>a.localeCompare(b));
96
+ const seen = new Map();
97
+ for (const list of lists) for (const raw of list) {
98
+ const tag = raw.trim();
99
+ if (!tag) continue;
100
+ const spellings = seen.get(fold(tag)) ?? new Map();
101
+ spellings.set(tag, (spellings.get(tag) ?? 0) + 1);
102
+ seen.set(fold(tag), spellings);
103
+ }
104
+ return [...seen.values()].map((spellings) => [...spellings].sort((a, b) => b[1] - a[1])[0][0]).sort((a, b) => a.localeCompare(b));
93
105
  }
@@ -1,3 +1,7 @@
1
+ /**
2
+ * The one light and dark system, wrapping mode-watcher. Mount `ThemeProvider` once in the root
3
+ * layout, or the no-flash script never runs.
4
+ */
1
5
  export { default as ThemeProvider } from "../../../src/components/theme/theme-provider.svelte";
2
6
  export { revealTheme } from "../../lib/theme-transition.js";
3
7
  export { mode, resetMode, setMode, systemPrefersMode, toggleMode, userPrefersMode } from "mode-watcher";
@@ -1,3 +1,7 @@
1
+ /**
2
+ * One toolbar row for a list page. Wide, it shows search, tabs and a view switch inline. Narrow,
3
+ * each of those folds into a menu and the row never wraps.
4
+ */
1
5
  export { default as Toolbar } from "../../../src/components/toolbar/toolbar.svelte";
2
6
  export { default as ToolbarSearch } from "../../../src/components/toolbar/toolbar-search.svelte";
3
7
  export { toolbarTriggerClass } from "./toolbar-trigger.js";
@@ -1,4 +1,17 @@
1
1
  import { cn } from "../../lib/utils.js";
2
+ // The trigger class for every toolbar control. Coarse pointers get a taller
3
+ // box at the same type size, since the chip is too small for a thumb.
4
+ // One fixed height, matching the `sm` button and the segmented track. A second
5
+ // size put a 24px filter beside 28px controls on the calendar (14 Oct 2026).
2
6
  export function toolbarTriggerClass({ active = false, className } = {}) {
3
- return cn("t-press inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border font-medium shadow-btn transition-[background-color,border-color,color] duration-quick ease-smooth-out hover:border-foreground/15 motion-reduce:transition-none", "h-7 px-2.5 text-xs any-pointer-coarse:h-11 any-pointer-coarse:px-3", active ? "bg-foreground/6 text-foreground" : "bg-card text-muted-foreground hover:bg-foreground/3 hover:text-foreground", className);
7
+ return cn(
8
+ // The outline button's edge: a real border plus the resting shadow. A `ring-1` hairline
9
+ // shares box-shadow with `shadow-btn` and drew a broken edge beside Display (26 Oct 2026).
10
+ "t-press inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border font-medium shadow-btn transition-[background-color,border-color,color] duration-quick ease-smooth-out hover:border-foreground/15 motion-reduce:transition-none",
11
+ "h-7 px-2.5 text-xs any-pointer-coarse:h-11 any-pointer-coarse:px-3",
12
+ // Active is a selected state, so it may tint; the tint reads off the foreground and
13
+ // shows on the canvas, where bg-muted vanished.
14
+ active ? "bg-foreground/6 text-foreground" : "bg-card text-muted-foreground hover:bg-foreground/3 hover:text-foreground",
15
+ className
16
+ );
4
17
  }
@@ -1,17 +1,21 @@
1
+ /** Heading anchors: one slug per heading, unique within a document. Pure. */
2
+ /** Lowercase, words joined by `-`, accents dropped. An empty result is `section`. */
1
3
  export function slugify(text) {
2
- const slug = text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
3
- return slug || "section";
4
+ const slug = text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
5
+ return slug || "section";
4
6
  }
7
+ /** One unique anchor per label, in order: a repeat becomes `name-2`, `name-3`. */
5
8
  export function anchorIds(labels) {
6
- const seen = new Map();
7
- const taken = new Set();
8
- return labels.map((label)=>{
9
- const base = slugify(label);
10
- let count = (seen.get(base) ?? 0) + 1;
11
- let id = count === 1 ? base : `${base}-${count}`;
12
- while(taken.has(id))id = `${base}-${++count}`;
13
- seen.set(base, count);
14
- taken.add(id);
15
- return id;
16
- });
9
+ const seen = new Map();
10
+ const taken = new Set();
11
+ return labels.map((label) => {
12
+ const base = slugify(label);
13
+ let count = (seen.get(base) ?? 0) + 1;
14
+ let id = count === 1 ? base : `${base}-${count}`;
15
+ // A heading literally called "Setup 2" must not steal the second "Setup".
16
+ while (taken.has(id)) id = `${base}-${++count}`;
17
+ seen.set(base, count);
18
+ taken.add(id);
19
+ return id;
20
+ });
17
21
  }