@urbicon-ui/blocks 6.24.0 → 6.26.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 (73) hide show
  1. package/README.md +14 -0
  2. package/dist/components/Calendar/CalendarTimeGrid.svelte +2 -1
  3. package/dist/components/Calendar/calendar.variants.d.ts +63 -63
  4. package/dist/components/Calendar/calendar.variants.js +5 -3
  5. package/dist/components/CommandPalette/CommandPalette.svelte +32 -16
  6. package/dist/components/CommandPalette/CommandPalette.svelte.d.ts +1 -1
  7. package/dist/components/CommandPalette/commandPalette.variants.d.ts +10 -0
  8. package/dist/components/CommandPalette/commandPalette.variants.js +9 -7
  9. package/dist/components/CommandPalette/index.d.ts +19 -0
  10. package/dist/components/CompositionBar/CompositionBar.svelte +2 -2
  11. package/dist/components/CompositionBar/composition-bar.variants.js +1 -1
  12. package/dist/components/Planner/planner.variants.js +1 -1
  13. package/dist/components/Sankey/sankey.variants.js +1 -1
  14. package/dist/i18n/index.d.ts +378 -2
  15. package/dist/internal/charts/variants.js +2 -2
  16. package/dist/internal/date-grid/DateGridScaffold.svelte +9 -2
  17. package/dist/internal/date-grid/date-grid.svelte.d.ts +4 -2
  18. package/dist/internal/date-grid/date-grid.svelte.js +63 -7
  19. package/dist/primitives/Alert/alert.variants.d.ts +8 -8
  20. package/dist/primitives/Alert/alert.variants.js +1 -1
  21. package/dist/primitives/Avatar/Avatar.svelte +38 -24
  22. package/dist/primitives/Avatar/index.d.ts +1 -1
  23. package/dist/primitives/Badge/badge.variants.js +1 -1
  24. package/dist/primitives/Button/Button.svelte +12 -25
  25. package/dist/primitives/Button/button.variants.js +2 -2
  26. package/dist/primitives/ButtonGroup/ButtonGroup.svelte +5 -1
  27. package/dist/primitives/Checkbox/checkbox.variants.js +2 -2
  28. package/dist/primitives/Collapsible/Collapsible.svelte +10 -0
  29. package/dist/primitives/Collapsible/index.d.ts +13 -2
  30. package/dist/primitives/ConfirmDialog/ConfirmDialog.svelte +14 -0
  31. package/dist/primitives/ConfirmDialog/index.d.ts +13 -2
  32. package/dist/primitives/Dialog/Dialog.svelte +36 -8
  33. package/dist/primitives/Drawer/Drawer.svelte +34 -7
  34. package/dist/primitives/Input/Input.svelte +7 -1
  35. package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +22 -22
  36. package/dist/primitives/JourneyTimeline/journey-timeline.variants.js +3 -3
  37. package/dist/primitives/Menu/Menu.svelte +8 -0
  38. package/dist/primitives/Menu/index.d.ts +11 -13
  39. package/dist/primitives/Popover/Popover.svelte +107 -4
  40. package/dist/primitives/Popover/index.d.ts +15 -2
  41. package/dist/primitives/Popover/index.js +1 -1
  42. package/dist/primitives/Popover/popover.variants.d.ts +25 -0
  43. package/dist/primitives/Popover/popover.variants.js +35 -0
  44. package/dist/primitives/Progress/progress.variants.js +1 -1
  45. package/dist/primitives/RadioGroup/radioGroup.variants.js +2 -2
  46. package/dist/primitives/Select/index.d.ts +8 -1
  47. package/dist/primitives/Slider/Slider.svelte +32 -3
  48. package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
  49. package/dist/primitives/Stepper/stepper.variants.d.ts +11 -11
  50. package/dist/primitives/Stepper/stepper.variants.js +2 -2
  51. package/dist/primitives/Textarea/Textarea.svelte +5 -1
  52. package/dist/primitives/Toast/toast.variants.d.ts +12 -12
  53. package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
  54. package/dist/primitives/Tooltip/tooltip.variants.js +14 -3
  55. package/dist/style/foundation.css +41 -7
  56. package/dist/style/interaction.css +19 -2
  57. package/dist/style/semantic.css +84 -1
  58. package/dist/style/themes/forest.css +3 -1
  59. package/dist/style/themes/neutral.css +10 -1
  60. package/dist/style/themes/ocean.css +5 -1
  61. package/dist/style/themes/sunset.css +3 -1
  62. package/dist/utils/compose-handlers.d.ts +72 -0
  63. package/dist/utils/compose-handlers.js +65 -0
  64. package/dist/utils/figma-token-export.js +6 -2
  65. package/dist/utils/guide.svelte.js +92 -29
  66. package/dist/utils/index.d.ts +1 -1
  67. package/dist/utils/index.js +1 -1
  68. package/dist/utils/overlay-tokens.d.ts +17 -0
  69. package/dist/utils/overlay-tokens.js +30 -0
  70. package/dist/utils/overlay.js +24 -2
  71. package/dist/utils/use-floating-panel.svelte.js +9 -3
  72. package/dist/utils/variants.js +5 -1
  73. package/package.json +3 -3
@@ -2,7 +2,7 @@ import { type SlotNames, type VariantProps } from '../../utils/variants.js';
2
2
  export declare const journeyTimelineVariants: ((props?: {
3
3
  orientation?: "horizontal" | "vertical" | undefined;
4
4
  size?: "sm" | "md" | "lg" | undefined;
5
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
5
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
6
6
  focused?: boolean | undefined;
7
7
  interactive?: boolean | undefined;
8
8
  travelled?: boolean | undefined;
@@ -13,7 +13,7 @@ export declare const journeyTimelineVariants: ((props?: {
13
13
  base: (props?: ({
14
14
  orientation?: "horizontal" | "vertical" | undefined;
15
15
  size?: "sm" | "md" | "lg" | undefined;
16
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
16
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
17
17
  focused?: boolean | undefined;
18
18
  interactive?: boolean | undefined;
19
19
  travelled?: boolean | undefined;
@@ -26,7 +26,7 @@ export declare const journeyTimelineVariants: ((props?: {
26
26
  rail: (props?: ({
27
27
  orientation?: "horizontal" | "vertical" | undefined;
28
28
  size?: "sm" | "md" | "lg" | undefined;
29
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
29
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
30
30
  focused?: boolean | undefined;
31
31
  interactive?: boolean | undefined;
32
32
  travelled?: boolean | undefined;
@@ -39,7 +39,7 @@ export declare const journeyTimelineVariants: ((props?: {
39
39
  node: (props?: ({
40
40
  orientation?: "horizontal" | "vertical" | undefined;
41
41
  size?: "sm" | "md" | "lg" | undefined;
42
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
42
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
43
43
  focused?: boolean | undefined;
44
44
  interactive?: boolean | undefined;
45
45
  travelled?: boolean | undefined;
@@ -52,7 +52,7 @@ export declare const journeyTimelineVariants: ((props?: {
52
52
  metaColumn: (props?: ({
53
53
  orientation?: "horizontal" | "vertical" | undefined;
54
54
  size?: "sm" | "md" | "lg" | undefined;
55
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
55
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
56
56
  focused?: boolean | undefined;
57
57
  interactive?: boolean | undefined;
58
58
  travelled?: boolean | undefined;
@@ -65,7 +65,7 @@ export declare const journeyTimelineVariants: ((props?: {
65
65
  meta: (props?: ({
66
66
  orientation?: "horizontal" | "vertical" | undefined;
67
67
  size?: "sm" | "md" | "lg" | undefined;
68
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
68
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
69
69
  focused?: boolean | undefined;
70
70
  interactive?: boolean | undefined;
71
71
  travelled?: boolean | undefined;
@@ -78,7 +78,7 @@ export declare const journeyTimelineVariants: ((props?: {
78
78
  markerColumn: (props?: ({
79
79
  orientation?: "horizontal" | "vertical" | undefined;
80
80
  size?: "sm" | "md" | "lg" | undefined;
81
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
81
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
82
82
  focused?: boolean | undefined;
83
83
  interactive?: boolean | undefined;
84
84
  travelled?: boolean | undefined;
@@ -91,7 +91,7 @@ export declare const journeyTimelineVariants: ((props?: {
91
91
  marker: (props?: ({
92
92
  orientation?: "horizontal" | "vertical" | undefined;
93
93
  size?: "sm" | "md" | "lg" | undefined;
94
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
94
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
95
95
  focused?: boolean | undefined;
96
96
  interactive?: boolean | undefined;
97
97
  travelled?: boolean | undefined;
@@ -104,7 +104,7 @@ export declare const journeyTimelineVariants: ((props?: {
104
104
  connector: (props?: ({
105
105
  orientation?: "horizontal" | "vertical" | undefined;
106
106
  size?: "sm" | "md" | "lg" | undefined;
107
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
107
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
108
108
  focused?: boolean | undefined;
109
109
  interactive?: boolean | undefined;
110
110
  travelled?: boolean | undefined;
@@ -117,7 +117,7 @@ export declare const journeyTimelineVariants: ((props?: {
117
117
  content: (props?: ({
118
118
  orientation?: "horizontal" | "vertical" | undefined;
119
119
  size?: "sm" | "md" | "lg" | undefined;
120
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
120
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
121
121
  focused?: boolean | undefined;
122
122
  interactive?: boolean | undefined;
123
123
  travelled?: boolean | undefined;
@@ -130,7 +130,7 @@ export declare const journeyTimelineVariants: ((props?: {
130
130
  card: (props?: ({
131
131
  orientation?: "horizontal" | "vertical" | undefined;
132
132
  size?: "sm" | "md" | "lg" | undefined;
133
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
133
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
134
134
  focused?: boolean | undefined;
135
135
  interactive?: boolean | undefined;
136
136
  travelled?: boolean | undefined;
@@ -143,7 +143,7 @@ export declare const journeyTimelineVariants: ((props?: {
143
143
  header: (props?: ({
144
144
  orientation?: "horizontal" | "vertical" | undefined;
145
145
  size?: "sm" | "md" | "lg" | undefined;
146
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
146
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
147
147
  focused?: boolean | undefined;
148
148
  interactive?: boolean | undefined;
149
149
  travelled?: boolean | undefined;
@@ -156,7 +156,7 @@ export declare const journeyTimelineVariants: ((props?: {
156
156
  trigger: (props?: ({
157
157
  orientation?: "horizontal" | "vertical" | undefined;
158
158
  size?: "sm" | "md" | "lg" | undefined;
159
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
159
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
160
160
  focused?: boolean | undefined;
161
161
  interactive?: boolean | undefined;
162
162
  travelled?: boolean | undefined;
@@ -169,7 +169,7 @@ export declare const journeyTimelineVariants: ((props?: {
169
169
  trailing: (props?: ({
170
170
  orientation?: "horizontal" | "vertical" | undefined;
171
171
  size?: "sm" | "md" | "lg" | undefined;
172
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
172
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
173
173
  focused?: boolean | undefined;
174
174
  interactive?: boolean | undefined;
175
175
  travelled?: boolean | undefined;
@@ -182,7 +182,7 @@ export declare const journeyTimelineVariants: ((props?: {
182
182
  labelGroup: (props?: ({
183
183
  orientation?: "horizontal" | "vertical" | undefined;
184
184
  size?: "sm" | "md" | "lg" | undefined;
185
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
185
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
186
186
  focused?: boolean | undefined;
187
187
  interactive?: boolean | undefined;
188
188
  travelled?: boolean | undefined;
@@ -195,7 +195,7 @@ export declare const journeyTimelineVariants: ((props?: {
195
195
  title: (props?: ({
196
196
  orientation?: "horizontal" | "vertical" | undefined;
197
197
  size?: "sm" | "md" | "lg" | undefined;
198
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
198
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
199
199
  focused?: boolean | undefined;
200
200
  interactive?: boolean | undefined;
201
201
  travelled?: boolean | undefined;
@@ -208,7 +208,7 @@ export declare const journeyTimelineVariants: ((props?: {
208
208
  subtitle: (props?: ({
209
209
  orientation?: "horizontal" | "vertical" | undefined;
210
210
  size?: "sm" | "md" | "lg" | undefined;
211
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
211
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
212
212
  focused?: boolean | undefined;
213
213
  interactive?: boolean | undefined;
214
214
  travelled?: boolean | undefined;
@@ -221,7 +221,7 @@ export declare const journeyTimelineVariants: ((props?: {
221
221
  segment: (props?: ({
222
222
  orientation?: "horizontal" | "vertical" | undefined;
223
223
  size?: "sm" | "md" | "lg" | undefined;
224
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
224
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
225
225
  focused?: boolean | undefined;
226
226
  interactive?: boolean | undefined;
227
227
  travelled?: boolean | undefined;
@@ -234,7 +234,7 @@ export declare const journeyTimelineVariants: ((props?: {
234
234
  detail: (props?: ({
235
235
  orientation?: "horizontal" | "vertical" | undefined;
236
236
  size?: "sm" | "md" | "lg" | undefined;
237
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
237
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
238
238
  focused?: boolean | undefined;
239
239
  interactive?: boolean | undefined;
240
240
  travelled?: boolean | undefined;
@@ -247,7 +247,7 @@ export declare const journeyTimelineVariants: ((props?: {
247
247
  detailInner: (props?: ({
248
248
  orientation?: "horizontal" | "vertical" | undefined;
249
249
  size?: "sm" | "md" | "lg" | undefined;
250
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
250
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
251
251
  focused?: boolean | undefined;
252
252
  interactive?: boolean | undefined;
253
253
  travelled?: boolean | undefined;
@@ -260,7 +260,7 @@ export declare const journeyTimelineVariants: ((props?: {
260
260
  detailContent: (props?: ({
261
261
  orientation?: "horizontal" | "vertical" | undefined;
262
262
  size?: "sm" | "md" | "lg" | undefined;
263
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
263
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
264
264
  focused?: boolean | undefined;
265
265
  interactive?: boolean | undefined;
266
266
  travelled?: boolean | undefined;
@@ -273,7 +273,7 @@ export declare const journeyTimelineVariants: ((props?: {
273
273
  panel: (props?: ({
274
274
  orientation?: "horizontal" | "vertical" | undefined;
275
275
  size?: "sm" | "md" | "lg" | undefined;
276
- status?: "active" | "blocked" | "attention" | "complete" | "pending" | "skipped" | undefined;
276
+ status?: "complete" | "active" | "pending" | "attention" | "blocked" | "skipped" | undefined;
277
277
  focused?: boolean | undefined;
278
278
  interactive?: boolean | undefined;
279
279
  travelled?: boolean | undefined;
@@ -111,13 +111,13 @@ export const journeyTimelineVariants = tv({
111
111
  },
112
112
  size: {
113
113
  sm: {
114
- meta: 'text-[11px]',
114
+ meta: 'text-2xs',
115
115
  marker: 'size-2.5',
116
116
  node: 'gap-x-2.5',
117
117
  card: '-mx-1.5 px-1.5 py-1.5',
118
118
  title: 'text-xs',
119
- subtitle: 'text-[11px]',
120
- segment: 'text-[11px]',
119
+ subtitle: 'text-2xs',
120
+ segment: 'text-2xs',
121
121
  content: 'pb-4',
122
122
  detailContent: 'text-xs pt-2',
123
123
  panel: 'p-3 text-xs'
@@ -8,6 +8,7 @@
8
8
  import { getTierContext } from '../../utils/tier-context';
9
9
  const ChevronDownIcon = resolveIcon('chevronDown', ChevronDownIconDefault);
10
10
  import Popover from '../Popover/Popover.svelte';
11
+ import { popoverMotion } from '../Popover/popover.variants';
11
12
  import { setMenuContext, type MenuContext, type MenuRegistryItem } from './menu.context';
12
13
  import MenuItemComp from './MenuItem.svelte';
13
14
  import MenuSubmenu from './MenuSubmenu.svelte';
@@ -471,6 +472,12 @@
471
472
  only painted layer. Without this, the panel renders as two stacked
472
473
  surfaces (Popover wrapper + Menu content) and shows a near-empty box
473
474
  with double borders.
475
+
476
+ The stripped default ALSO carried the enter/exit motion, so the exact
477
+ fragment (`popoverMotion`) is re-applied via `class` — it survives
478
+ Popover's `unstyled` by design and animates the transparent wrapper,
479
+ which fades/scales the Menu chrome inside it. A Menu-level `unstyled`
480
+ strips it again; consumers rebuild on the panel's `data-state`.
474
481
  -->
475
482
  <Popover
476
483
  bind:open
@@ -479,6 +486,7 @@
479
486
  {usePortal}
480
487
  autoTrigger={false}
481
488
  unstyled
489
+ class={unstyled ? undefined : popoverMotion}
482
490
  syncMinWidth={contextTrigger ? false : syncWidth}
483
491
  offsetDistance={effectiveTier === 'commit' ? 8 : 4}
484
492
  trigger={contextTrigger ? undefined : triggerContent}
@@ -7,18 +7,9 @@ import type { Placement } from '../../utils/floating.js';
7
7
  import type { InteractiveTier } from '../../utils/tier-context.js';
8
8
  import type { MenuSlots } from './menu.variants.js';
9
9
  /**
10
- * @description Action menu (`role="menu"`) triggered by a button. Items are
11
- * verbs the user can invoke Edit, Delete, Share, Export and dispatch an
12
- * `onSelect` callback when activated. For picking a value from a list use
13
- * `Select` (or `Combobox` for searchable). Menu and Select are deliberately
14
- * disjoint: Menu's ARIA semantics, keyboard model (Tab through items, Enter
15
- * triggers), and visual chrome match the Action surface family; Select's
16
- * `role="listbox"` + arrow-navigation match the Form surface family.
17
- *
18
- * @tag action
19
- * @related Select
20
- * @related Combobox
21
- * @related Popover
10
+ * Menu-specific props. The catalog JSDoc (`@description`/`@tag`/`@related`)
11
+ * lives on `MenuProps` belowthe interface docs-gen actually picks up
12
+ * so it is not duplicated here.
22
13
  *
23
14
  * @example Items array with `onSelect` callbacks
24
15
  * ```svelte
@@ -172,7 +163,14 @@ export interface MenuSpecificProps<TItem extends MenuItemType = MenuItemType> {
172
163
  mint?: MintProp;
173
164
  }
174
165
  /**
175
- * @description Action menu triggered by a button with nested items, icons, separators, and keyboard navigation.
166
+ * @description Action menu (`role="menu"`) triggered by a button, with nested
167
+ * submenus, sections, icons, and separators. Items are verbs the user can
168
+ * invoke — Edit, Delete, Share, Export — and dispatch an `onSelect` callback
169
+ * when activated; Menu holds no selection state. For picking a value from a
170
+ * list use `Select` (or `Combobox` for searchable). Menu and Select are
171
+ * deliberately disjoint: Menu's `role="menu"`/`menuitem` semantics with
172
+ * arrow-key roving and Action-family chrome versus Select's `role="listbox"`
173
+ * value commitment with Form-family chrome.
176
174
  *
177
175
  * @tag action
178
176
  * @related Select
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
- import { useFloatingPanel, floatingPanelHidden } from '../../utils';
4
+ import { useFloatingPanel, floatingPanelHidden, maxTransitionDurationMs } from '../../utils';
5
5
  import { popoverVariants } from './popover.variants';
6
6
  import type { PopoverProps } from './index';
7
7
 
@@ -21,6 +21,9 @@
21
21
  autoTrigger = true,
22
22
  size = 'md',
23
23
 
24
+ transitionDuration,
25
+ transitionEasing,
26
+
24
27
  onOpenChange,
25
28
  onClickOutside: onClickOutsideProp,
26
29
  onEscape: onEscapeProp,
@@ -47,11 +50,64 @@
47
50
  resolveSlotClasses(blocksConfig, 'Popover', preset, { size }, slotClassesProp)
48
51
  );
49
52
 
53
+ // Per-instance motion overrides. Set the shared popover CSS variables inline
54
+ // only when a prop is provided, so the unset default keeps inheriting the
55
+ // reduced-motion-aware token (mirrors Tooltip's transitionDuration path).
56
+ const popoverDurationInline = $derived(
57
+ transitionDuration != null ? `${transitionDuration}ms` : undefined
58
+ );
59
+
50
60
  let internalTriggerElement = $state<HTMLElement | null>(null);
51
61
  let popoverElement = $state<HTMLElement | null>(null);
52
62
 
53
63
  const effectiveTriggerElement = $derived(triggerElement || internalTriggerElement);
54
64
 
65
+ // ── Exit-motion lag (ACC-3 rest) ───────────────────────────
66
+ //
67
+ // Closing used to tear three things down in one flush: `open` flips, the
68
+ // children block unmounts, and `useFloatingPanel` hides the panel. The CSS
69
+ // exit transition (see `popoverMotion` in popover.variants.ts) keeps the
70
+ // *panel element* painted via `allow-discrete`, but the children must
71
+ // outlive `open` or the panel fades out empty. `exiting` lags the teardown
72
+ // by the panel's actual computed transition duration — read from the live
73
+ // style so per-instance props, theme token overrides, and reduced motion all
74
+ // shorten it automatically. A zero duration (jsdom/node tests, browsers
75
+ // without the CSS, `unstyled` without rebuilt motion) tears down
76
+ // synchronously: exactly the pre-motion behaviour.
77
+ //
78
+ // `$effect.pre` matters: the flag must be `true` in the SAME flush that
79
+ // renders `open === false`, otherwise the children unmount one frame before
80
+ // the lag starts. The `prevOpenForExit` tracker (non-reactive, seeded via
81
+ // `untrack` like `prevPopoverMode` below) keeps the effect keyed on real
82
+ // transitions — without it, the `bind:this` assignment would re-run the
83
+ // effect on mount and flash a closed-by-default popover open for one lag.
84
+ const EXIT_MOTION_BUFFER_MS = 50;
85
+ let exiting = $state(false);
86
+ let exitTimer: ReturnType<typeof setTimeout> | undefined;
87
+ let prevOpenForExit = untrack(() => open);
88
+ $effect.pre(() => {
89
+ if (open === prevOpenForExit) return;
90
+ prevOpenForExit = open;
91
+ if (open) {
92
+ clearTimeout(exitTimer);
93
+ exiting = false;
94
+ return;
95
+ }
96
+ const ms = maxTransitionDurationMs(untrack(() => popoverElement));
97
+ if (ms <= 0) return;
98
+ exiting = true;
99
+ exitTimer = setTimeout(() => {
100
+ exiting = false;
101
+ }, ms + EXIT_MOTION_BUFFER_MS);
102
+ });
103
+
104
+ // While `exiting`, the panel is still fading: keep children mounted and (in
105
+ // the in-place modes) keep `display` un-hidden. `useFloatingPanel` stays on
106
+ // the raw `open` on purpose — `hidePopover()` fires immediately and the
107
+ // discrete transition carries the visual exit, so a re-open during the fade
108
+ // reverses smoothly instead of waiting out the lag.
109
+ const panelVisible = $derived(open || exiting);
110
+
55
111
  // ── Floating UI positioning + native show/hide ─────────────
56
112
  //
57
113
  // Delegated to the shared `useFloatingPanel` helper — the same positioning
@@ -291,6 +347,27 @@
291
347
  target?.focus();
292
348
  }
293
349
 
350
+ // Un-arm `dismissedByTrigger` at the start of the NEXT pointer gesture,
351
+ // wherever it lands. Registered as a one-shot capture listener when the
352
+ // guard arms: capture order (document before trigger) guarantees a stale
353
+ // flag is cleared before any new arm, and `once` keeps at most one listener
354
+ // pending. This closes the aborted-click hole (pointerdown on the trigger,
355
+ // pointer released elsewhere → no click ever consumed the flag → the next
356
+ // trigger click was swallowed once).
357
+ function disarmDismissedByTrigger() {
358
+ dismissedByTrigger = false;
359
+ }
360
+
361
+ // Unmount cleanup for the imperative leftovers: a pending exit-lag timer
362
+ // and a still-armed disarm listener. Dependency-free → runs once,
363
+ // teardown on destroy.
364
+ $effect(() => {
365
+ return () => {
366
+ clearTimeout(exitTimer);
367
+ document.removeEventListener('pointerdown', disarmDismissedByTrigger, true);
368
+ };
369
+ });
370
+
294
371
  function handleTriggerPointerDown() {
295
372
  // Arm the "this pointerdown already dismissed it" guard only in auto
296
373
  // mode, where the browser's light dismiss really closes the popover
@@ -298,7 +375,15 @@
298
375
  // re-opening it). In manual mode nothing light-dismisses — the click
299
376
  // itself must toggle-close, so arming the guard there left the trigger
300
377
  // unable to close its own popover.
301
- if (open && popoverMode === 'auto') dismissedByTrigger = true;
378
+ if (open && popoverMode === 'auto') {
379
+ dismissedByTrigger = true;
380
+ // This event has already passed the document's capture phase, so the
381
+ // listener can only fire on a LATER pointerdown.
382
+ document.addEventListener('pointerdown', disarmDismissedByTrigger, {
383
+ capture: true,
384
+ once: true
385
+ });
386
+ }
302
387
  }
303
388
 
304
389
  function handleTriggerClick(event: MouseEvent) {
@@ -361,6 +446,20 @@
361
446
  drops over a modal dialog (Codeberg #23).
362
447
  -->
363
448
 
449
+ <!--
450
+ `data-state` sits after `{...restProps}` like the other load-bearing
451
+ attributes: it drives the enter/exit motion CSS (popoverMotion) and is the
452
+ documented styling hook for consumers rebuilding motion under `unstyled`.
453
+ `{#if panelVisible}` (not `open`) keeps the children mounted while the exit
454
+ transition plays; `style:display` follows the same lagged flag so the
455
+ in-place modes can fade out before they hide.
456
+
457
+ `inert` while closed (GuidePanel's pattern): the exit-fading children are
458
+ still mounted and displayed, so without it a Tab right after dismiss would
459
+ focus into the visually dismissed panel and Enter could re-fire a consumer
460
+ action; pointer-events-none (popoverMotion) only covers mouse/touch. Also
461
+ drops the fading subtree from the a11y tree immediately.
462
+ -->
364
463
  <div
365
464
  bind:this={popoverElement}
366
465
  class={popoverClasses}
@@ -370,12 +469,16 @@
370
469
  style:position={panel.strategy}
371
470
  style:inset="auto"
372
471
  style:margin="0"
373
- style:display={floatingPanelHidden(panel, open) ? 'none' : null}
472
+ style:display={floatingPanelHidden(panel, panelVisible) ? 'none' : null}
473
+ style:--blocks-popover-duration={popoverDurationInline}
474
+ style:--blocks-popover-easing={transitionEasing}
475
+ data-state={open ? 'open' : 'closed'}
476
+ inert={!open || undefined}
374
477
  {role}
375
478
  aria-modal={ariaModal || undefined}
376
479
  {id}
377
480
  >
378
- {#if open}
481
+ {#if panelVisible}
379
482
  {@render children()}
380
483
  {/if}
381
484
  </div>
@@ -70,6 +70,19 @@ export interface PopoverProps extends PopoverVariants, Omit<HTMLAttributes<HTMLD
70
70
  open?: boolean;
71
71
  /** When true (default), the trigger wrapper handles click and keyboard to toggle the popover. Set to `false` to manage `open` yourself. */
72
72
  autoTrigger?: boolean;
73
+ /**
74
+ * Override the enter/exit fade duration in ms. Defaults to the shared token
75
+ * `--blocks-popover-duration` (150ms; collapses to 1ms under
76
+ * `prefers-reduced-motion`).
77
+ */
78
+ transitionDuration?: number;
79
+ /**
80
+ * Override the enter/exit easing as a CSS `<easing-function>` (e.g.
81
+ * `'linear'`, `'ease-out'`, `'cubic-bezier(0.4,0,0.2,1)'`). Defaults to the
82
+ * token `--blocks-popover-easing`. A CSS string, not a JS easing fn, because
83
+ * the popover motion is a pure CSS transition.
84
+ */
85
+ transitionEasing?: string;
73
86
  /**
74
87
  * Whether the popover closes on Escape key. Default `true`.
75
88
  * Set to `false` for cases where Escape should be intercepted by an
@@ -98,7 +111,7 @@ export interface PopoverProps extends PopoverVariants, Omit<HTMLAttributes<HTMLD
98
111
  onEscape?: () => void;
99
112
  /** Extra classes merged onto the floating panel element. */
100
113
  class?: string;
101
- /** Strip all default tv() classes. Combine with `class` or `slotClasses` for full custom styling. */
114
+ /** Strip all default tv() classes (including the enter/exit motion). Combine with `class` or `slotClasses` for full custom styling; the panel always carries `data-state="open" | "closed"`, so custom motion can rebuild on that hook (see `popoverMotion` in popover.variants.ts for the reference implementation). */
102
115
  unstyled?: boolean;
103
116
  /** Per-slot class overrides. Available slots: `base` (the floating panel). */
104
117
  slotClasses?: Partial<Record<'base', string>>;
@@ -111,4 +124,4 @@ export interface PopoverProps extends PopoverVariants, Omit<HTMLAttributes<HTMLD
111
124
  preset?: string;
112
125
  }
113
126
  export { default as Popover } from './Popover.svelte';
114
- export { type PopoverVariants, popoverVariants } from './popover.variants.js';
127
+ export { type PopoverVariants, popoverMotion, popoverVariants } from './popover.variants.js';
@@ -1,2 +1,2 @@
1
1
  export { default as Popover } from './Popover.svelte';
2
- export { popoverVariants } from './popover.variants.js';
2
+ export { popoverMotion, popoverVariants } from './popover.variants.js';
@@ -1,4 +1,29 @@
1
1
  import { type VariantProps } from '../../utils/variants.js';
2
+ /**
3
+ * CSS-native enter/exit motion for the floating panel (ACC-3 rest), keyed on
4
+ * the `data-state` attribute Popover always stamps. Kept as its own fragment
5
+ * because two call sites need exactly these classes: `popoverVariants.base`
6
+ * below, and Menu — which renders its inner Popover `unstyled` (to avoid a
7
+ * double surface) and re-applies the fragment via Popover's `class` prop.
8
+ *
9
+ * How the two halves work:
10
+ * - **Enter** — the panel un-hides via `showPopover()` (top layer) or a
11
+ * `display` flip (in-place mode), so a plain transition has no before-state;
12
+ * `starting:` (`@starting-style`) supplies it.
13
+ * - **Exit** — `hidePopover()` / a native light dismiss yank the panel to
14
+ * `display: none` in the same style recalc that flips `data-state`;
15
+ * `transition-discrete` (`transition-behavior: allow-discrete`) on
16
+ * `display`/`overlay` keeps it painted (and in the top layer) until the
17
+ * fade lands. Popover lags the children-teardown to match — see the
18
+ * exit-motion block in Popover.svelte.
19
+ *
20
+ * Browsers without `@starting-style`/`allow-discrete` simply skip the motion
21
+ * and keep today's instant toggle. Duration/easing resolve through the
22
+ * `--blocks-popover-*` tokens (interaction.css), which reduced motion
23
+ * collapses to 1ms; `motion-reduce:duration-[1ms]` guards the inline
24
+ * per-instance override path, which can't see the media query.
25
+ */
26
+ export declare const popoverMotion: string;
2
27
  export declare const popoverVariants: ((props?: import("../../utils/variants.js").TVProps<{
3
28
  size: {
4
29
  sm: string;
@@ -1,9 +1,44 @@
1
1
  import { tv } from '../../utils/variants.js';
2
+ /**
3
+ * CSS-native enter/exit motion for the floating panel (ACC-3 rest), keyed on
4
+ * the `data-state` attribute Popover always stamps. Kept as its own fragment
5
+ * because two call sites need exactly these classes: `popoverVariants.base`
6
+ * below, and Menu — which renders its inner Popover `unstyled` (to avoid a
7
+ * double surface) and re-applies the fragment via Popover's `class` prop.
8
+ *
9
+ * How the two halves work:
10
+ * - **Enter** — the panel un-hides via `showPopover()` (top layer) or a
11
+ * `display` flip (in-place mode), so a plain transition has no before-state;
12
+ * `starting:` (`@starting-style`) supplies it.
13
+ * - **Exit** — `hidePopover()` / a native light dismiss yank the panel to
14
+ * `display: none` in the same style recalc that flips `data-state`;
15
+ * `transition-discrete` (`transition-behavior: allow-discrete`) on
16
+ * `display`/`overlay` keeps it painted (and in the top layer) until the
17
+ * fade lands. Popover lags the children-teardown to match — see the
18
+ * exit-motion block in Popover.svelte.
19
+ *
20
+ * Browsers without `@starting-style`/`allow-discrete` simply skip the motion
21
+ * and keep today's instant toggle. Duration/easing resolve through the
22
+ * `--blocks-popover-*` tokens (interaction.css), which reduced motion
23
+ * collapses to 1ms; `motion-reduce:duration-[1ms]` guards the inline
24
+ * per-instance override path, which can't see the media query.
25
+ */
26
+ export const popoverMotion = [
27
+ 'transition-[opacity,scale,display,overlay] transition-discrete',
28
+ 'duration-[var(--blocks-popover-duration)] ease-[var(--blocks-popover-easing)]',
29
+ 'motion-reduce:duration-[1ms]',
30
+ // pointer-events-none while closed: during the exit fade the children are
31
+ // still mounted and painted — without this, a quick click on the fading
32
+ // panel would fire consumer handlers from a visually dismissed surface.
33
+ 'data-[state=closed]:opacity-0 data-[state=closed]:scale-[0.98] data-[state=closed]:pointer-events-none',
34
+ 'starting:data-[state=open]:opacity-0 starting:data-[state=open]:scale-[0.98]'
35
+ ].join(' ');
2
36
  export const popoverVariants = tv({
3
37
  // tier: contain — floating panel surface.
4
38
  base: [
5
39
  'bg-surface-elevated border border-border-hairline rounded-contain',
6
40
  'shadow-[var(--blocks-shadow-md)] backdrop-blur-sm',
41
+ popoverMotion,
7
42
  // `calc(100dvh-4rem)` is the static design cap; Floating UI's `size`
8
43
  // middleware (via useFloatingPanel) narrows it to the room actually left
9
44
  // between the anchor and the visual viewport edge through
@@ -35,7 +35,7 @@ export const progressVariants = tv({
35
35
  xs: {
36
36
  track: 'h-1',
37
37
  fill: 'h-1',
38
- circularLabel: 'text-[10px]'
38
+ circularLabel: 'text-3xs'
39
39
  },
40
40
  sm: {
41
41
  track: 'h-1.5',
@@ -66,7 +66,7 @@ export const radioItemVariants = tv({
66
66
  indicator: 'w-3.5 h-3.5 mt-px',
67
67
  dot: 'w-1.5 h-1.5',
68
68
  label: 'text-xs',
69
- description: 'text-[10px]'
69
+ description: 'text-3xs'
70
70
  },
71
71
  sm: {
72
72
  item: 'gap-2',
@@ -157,7 +157,7 @@ export const radioItemVariants = tv({
157
157
  {
158
158
  checked: true,
159
159
  intent: 'warning',
160
- class: { indicator: 'bg-warning border-warning', dot: 'bg-neutral-900' }
160
+ class: { indicator: 'bg-warning border-warning', dot: 'bg-text-on-warning' }
161
161
  },
162
162
  {
163
163
  checked: true,
@@ -256,7 +256,14 @@ interface SelectBaseProps<T extends SelectValue = string> extends Omit<SelectVar
256
256
  * and make the custom look reusable across the project.
257
257
  */
258
258
  preset?: string;
259
- /** Explicit `id` for the component. Auto-generated if omitted. */
259
+ /**
260
+ * Explicit `id` for the component. Auto-generated if omitted.
261
+ *
262
+ * The id lands on the component's wrapper, which is not a labelable element.
263
+ * The focusable trigger is derived from it as `` `${id}-trigger` `` — that is
264
+ * the id an external `<label for>` has to address, since a `for` pointing at
265
+ * the wrapper focuses nothing.
266
+ */
260
267
  id?: string;
261
268
  }
262
269
  /**