@noxlovette/material 0.4.8 → 0.5.1

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/animation/Shapes.stories.svelte +56 -0
  2. package/dist/animation/Shapes.stories.svelte.d.ts +3 -0
  3. package/dist/animation/TransitionPatterns.stories.svelte +359 -0
  4. package/dist/animation/TransitionPatterns.stories.svelte.d.ts +3 -0
  5. package/dist/animation/containerTransform.d.ts +35 -12
  6. package/dist/animation/containerTransform.js +31 -138
  7. package/dist/animation/enterExit.d.ts +30 -3
  8. package/dist/animation/enterExit.js +69 -22
  9. package/dist/animation/index.d.ts +2 -1
  10. package/dist/animation/index.js +2 -1
  11. package/dist/animation/presence.svelte.d.ts +47 -0
  12. package/dist/animation/presence.svelte.js +84 -0
  13. package/dist/animation/shapeMorph.svelte.d.ts +52 -0
  14. package/dist/animation/shapeMorph.svelte.js +112 -0
  15. package/dist/animation/shapesAnimatableSmall.d.ts +28 -22
  16. package/dist/animation/shapesAnimatableSmall.js +32 -23
  17. package/dist/animation/sharedAxisTransition.d.ts +57 -37
  18. package/dist/animation/sharedAxisTransition.js +74 -22
  19. package/dist/animation/skeleton.d.ts +16 -0
  20. package/dist/animation/skeleton.js +28 -0
  21. package/dist/animation/spring.d.ts +61 -0
  22. package/dist/animation/spring.js +53 -0
  23. package/dist/animation/springValue.svelte.d.ts +22 -0
  24. package/dist/animation/springValue.svelte.js +42 -0
  25. package/dist/animation/transition.d.ts +3 -1
  26. package/dist/animation/transition.js +3 -1
  27. package/dist/attachments/clickOutside.d.ts +13 -0
  28. package/dist/attachments/clickOutside.js +18 -0
  29. package/dist/attachments/index.d.ts +1 -0
  30. package/dist/attachments/index.js +1 -0
  31. package/dist/components/buttons/FAB.svelte +23 -16
  32. package/dist/components/buttons/FABMenu.svelte +24 -29
  33. package/dist/components/buttons/FABMenu.svelte.d.ts +2 -1
  34. package/dist/components/buttons/FABMenuItem.svelte +23 -20
  35. package/dist/components/buttons/FABMenuItem.svelte.d.ts +5 -1
  36. package/dist/components/buttons/split-button/SplitButton.svelte +10 -17
  37. package/dist/components/buttons/split-button/theme.js +1 -1
  38. package/dist/components/buttons/types.d.ts +3 -3
  39. package/dist/components/cards/theme.js +1 -1
  40. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +8 -12
  41. package/dist/components/containers/bottom-sheet/BottomSheet.svelte +67 -55
  42. package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +1 -1
  43. package/dist/components/containers/bottom-sheet/types.d.ts +8 -2
  44. package/dist/components/containers/context-menu/ContextMenu.svelte +23 -36
  45. package/dist/components/containers/context-menu/theme.js +2 -2
  46. package/dist/components/containers/dialogue/Dialogue.svelte +49 -65
  47. package/dist/components/containers/link-preview/LinkPreview.svelte +12 -23
  48. package/dist/components/containers/link-preview/theme.d.ts +3 -3
  49. package/dist/components/containers/menu/Menu.svelte +14 -21
  50. package/dist/components/containers/menu/MenuSub.svelte +10 -17
  51. package/dist/components/containers/pane/DraggablePane.svelte +90 -5
  52. package/dist/components/containers/pane/DraggablePane.svelte.d.ts +1 -1
  53. package/dist/components/containers/pane/theme.js +2 -2
  54. package/dist/components/containers/popover/Popover.svelte +31 -42
  55. package/dist/components/containers/popover/theme.d.ts +3 -3
  56. package/dist/components/containers/scroll-area/theme.js +2 -2
  57. package/dist/components/containers/side-sheet/SideSheet.stories.svelte +10 -15
  58. package/dist/components/containers/side-sheet/SideSheet.svelte +44 -44
  59. package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +4 -3
  60. package/dist/components/containers/side-sheet/types.d.ts +8 -2
  61. package/dist/components/date/DateField.svelte +99 -92
  62. package/dist/components/date/DateField.svelte.d.ts +4 -5
  63. package/dist/components/date/DateRangeField.svelte +127 -155
  64. package/dist/components/date/DateRangeField.svelte.d.ts +4 -5
  65. package/dist/components/date/calendarMotion.d.ts +9 -0
  66. package/dist/components/date/calendarMotion.js +23 -0
  67. package/dist/components/date/theme.d.ts +95 -0
  68. package/dist/components/date/theme.js +103 -14
  69. package/dist/components/date/types.d.ts +13 -0
  70. package/dist/components/forms/checkbox/theme.js +2 -2
  71. package/dist/components/forms/command/theme.d.ts +3 -3
  72. package/dist/components/forms/command/theme.js +1 -1
  73. package/dist/components/forms/pin/theme.js +2 -2
  74. package/dist/components/forms/radio-group/theme.js +2 -2
  75. package/dist/components/forms/select/Select.svelte +42 -54
  76. package/dist/components/forms/select/theme.js +4 -4
  77. package/dist/components/forms/slider/Slider.svelte +144 -126
  78. package/dist/components/forms/slider/Slider.svelte.d.ts +3 -0
  79. package/dist/components/forms/slider/theme.d.ts +154 -48
  80. package/dist/components/forms/slider/theme.js +60 -20
  81. package/dist/components/forms/slider/types.d.ts +1 -1
  82. package/dist/components/forms/switch/theme.js +4 -4
  83. package/dist/components/forms/textfield/Textfield.svelte +1 -1
  84. package/dist/components/forms/textfield/theme.d.ts +12 -0
  85. package/dist/components/forms/textfield/theme.js +30 -10
  86. package/dist/components/forms/toggle-group/theme.js +1 -1
  87. package/dist/components/forms/tooltip/Tooltip.svelte +24 -35
  88. package/dist/components/nav/breadcrumb/theme.js +1 -1
  89. package/dist/components/nav/navbar/theme.js +2 -2
  90. package/dist/components/nav/rail/Rail.svelte +4 -5
  91. package/dist/components/nav/rail/theme.js +7 -7
  92. package/dist/components/nav/tabs/TabHolder.svelte +29 -3
  93. package/dist/components/nav/tabs/TabHolder.svelte.d.ts +3 -1
  94. package/dist/components/nav/tabs/theme.js +1 -1
  95. package/dist/components/progress/CircularProgress.svelte +78 -4
  96. package/dist/components/progress/CircularProgress.svelte.d.ts +5 -0
  97. package/dist/components/progress/LinearProgress.svelte +70 -7
  98. package/dist/components/progress/LinearProgress.svelte.d.ts +5 -0
  99. package/dist/components/progress/WavyLinearProgress.svelte +27 -3
  100. package/dist/components/progress/WavyLinearProgress.svelte.d.ts +3 -0
  101. package/dist/components/progress/_wavy.d.ts +2 -0
  102. package/dist/components/progress/_wavy.js +3 -1
  103. package/dist/components/snackbar/Snackbar.svelte +13 -17
  104. package/dist/components/table/TableBody.svelte +3 -16
  105. package/dist/components/table/TableBody.svelte.d.ts +1 -5
  106. package/dist/components/table/TableHeader.svelte +1 -1
  107. package/dist/components/table/theme.js +1 -1
  108. package/dist/components/table/types.d.ts +0 -7
  109. package/dist/components/time/TimeField.stories.svelte +41 -0
  110. package/dist/components/time/TimeField.stories.svelte.d.ts +19 -0
  111. package/dist/components/time/TimeField.svelte +69 -62
  112. package/dist/components/time/TimeField.svelte.d.ts +6 -0
  113. package/dist/components/time/TimepickerInput.svelte +8 -4
  114. package/dist/components/time/types.d.ts +11 -1
  115. package/dist/components/toolbar/theme.js +2 -2
  116. package/dist/index.d.ts +1 -1
  117. package/dist/index.js +1 -1
  118. package/dist/styles/component.css +21 -22
  119. package/dist/styles/motion.css +317 -38
  120. package/dist/utils/Layer.svelte +16 -29
  121. package/dist/utils/icon/LoadingIndicator.svelte +61 -22
  122. package/dist/utils/icon/loadingShapes.d.ts +1 -0
  123. package/dist/utils/icon/loadingShapes.js +16 -0
  124. package/package.json +5 -2
  125. package/dist/actions/clickOutside.d.ts +0 -3
  126. package/dist/actions/clickOutside.js +0 -13
  127. package/dist/actions/floating.d.ts +0 -20
  128. package/dist/actions/floating.js +0 -87
  129. package/dist/actions/index.d.ts +0 -6
  130. package/dist/actions/index.js +0 -6
  131. package/dist/actions/keyboard.d.ts +0 -3
  132. package/dist/actions/keyboard.js +0 -14
  133. package/dist/actions/portal.d.ts +0 -3
  134. package/dist/actions/portal.js +0 -8
  135. package/dist/actions/positionFloating.d.ts +0 -10
  136. package/dist/actions/positionFloating.js +0 -59
  137. package/dist/actions/rovingFocusGroup.d.ts +0 -20
  138. package/dist/actions/rovingFocusGroup.js +0 -82
  139. package/dist/animation/easing.d.ts +0 -6
  140. package/dist/animation/easing.js +0 -78
  141. package/dist/animation/outroClass.d.ts +0 -3
  142. package/dist/animation/outroClass.js +0 -20
  143. package/dist/animation/transitionTypes.d.ts +0 -8
  144. package/dist/animation/transitionTypes.js +0 -1
@@ -5,11 +5,11 @@ Link Previews display a preview of a link's content when hovered.
5
5
  @see https://bits-ui.com/docs/components/link-preview
6
6
  -->
7
7
  <script lang="ts">
8
+ import { enterExit, presence } from '../../../animation/index.js';
8
9
  import { LinkPreview } from 'bits-ui';
9
10
  import clsx from 'clsx';
10
11
  import { linkPreview } from './theme.js';
11
12
  import type { LinkPreviewProps } from './types.js';
12
- import { enterExit, easeEmphasizedDecel, easeEmphasizedAccel } from '../../../animation/index.js';
13
13
  import { Layer } from '../../../utils/index.js';
14
14
 
15
15
  let {
@@ -33,31 +33,20 @@ Link Previews display a preview of a link's content when hovered.
33
33
  </LinkPreview.Trigger>
34
34
 
35
35
  <LinkPreview.Portal>
36
- <LinkPreview.Content forceMount {side} {align} {sideOffset}>
36
+ <LinkPreview.Content {side} {align} {sideOffset}>
37
37
  {#snippet child({ wrapperProps, props, open: isOpen })}
38
- {#if isOpen}
39
- <div {...wrapperProps}>
40
- <div
41
- {...props}
42
- class={base({ class: clsx(className) })}
43
- in:enterExit={{
44
- duration: 200,
45
- easing: easeEmphasizedDecel,
46
- mode: 'scale'
47
- }}
48
- out:enterExit={{
49
- duration: 150,
50
- easing: easeEmphasizedAccel,
51
- mode: 'scale'
52
- }}
53
- >
54
- <Layer />
55
- <div class={content()}>
56
- {@render children()}
57
- </div>
38
+ <div {...wrapperProps}>
39
+ <div
40
+ {...props}
41
+ class={base({ class: clsx(className) })}
42
+ {@attach presence(() => isOpen, enterExit.scale)}
43
+ >
44
+ <Layer />
45
+ <div class={content()}>
46
+ {@render children()}
58
47
  </div>
59
48
  </div>
60
- {/if}
49
+ </div>
61
50
  {/snippet}
62
51
  </LinkPreview.Content>
63
52
  </LinkPreview.Portal>
@@ -3,15 +3,15 @@ export type LinkPreviewVariants = VariantProps<typeof linkPreview>;
3
3
  export declare const linkPreview: import("tailwind-variants").TVReturnType<{
4
4
  [key: string]: {
5
5
  [key: string]: import("tailwind-variants").ClassValue | {
6
- base?: import("tailwind-variants").ClassValue;
7
6
  content?: import("tailwind-variants").ClassValue;
7
+ base?: import("tailwind-variants").ClassValue;
8
8
  };
9
9
  };
10
10
  } | {
11
11
  [x: string]: {
12
12
  [x: string]: import("tailwind-variants").ClassValue | {
13
- base?: import("tailwind-variants").ClassValue;
14
13
  content?: import("tailwind-variants").ClassValue;
14
+ base?: import("tailwind-variants").ClassValue;
15
15
  };
16
16
  };
17
17
  } | {}, {
@@ -20,8 +20,8 @@ export declare const linkPreview: import("tailwind-variants").TVReturnType<{
20
20
  }, undefined, {
21
21
  [key: string]: {
22
22
  [key: string]: import("tailwind-variants").ClassValue | {
23
- base?: import("tailwind-variants").ClassValue;
24
23
  content?: import("tailwind-variants").ClassValue;
24
+ base?: import("tailwind-variants").ClassValue;
25
25
  };
26
26
  };
27
27
  } | {}, {
@@ -16,11 +16,10 @@ Contrast with `MenuItem` (a single interactive row inside the panel) and
16
16
  @see https://m3.material.io/components/menus/guidelines
17
17
  -->
18
18
  <script lang="ts">
19
+ import { enterExit, presence } from '../../../animation/index.js';
19
20
  import { DropdownMenu } from 'bits-ui';
20
21
  import clsx from 'clsx';
21
22
  import Button from '../../buttons/Button.svelte';
22
- import { enterExit } from '../../../animation/enterExit.js';
23
- import { easeEmphasizedDecel } from '../../../animation/easing.js';
24
23
  import type { MenuProps } from './types.js';
25
24
 
26
25
  let {
@@ -45,27 +44,21 @@ Contrast with `MenuItem` (a single interactive row inside the panel) and
45
44
  </DropdownMenu.Trigger>
46
45
 
47
46
  <DropdownMenu.Portal>
48
- <DropdownMenu.Content forceMount {align} sideOffset={4} class="z-100">
47
+ <DropdownMenu.Content {align} sideOffset={4} class="z-100">
49
48
  {#snippet child({ wrapperProps, props, open: isOpen })}
50
- {#if isOpen}
51
- <div {...wrapperProps} class={wrapperProps.class as any}>
52
- <div
53
- {...props}
54
- class={clsx(
55
- 'bg-md-sys-color-surface-container-high shadow-elevation-3 ring-md-sys-color-outline/10 max-w-sm min-w-48 gap-1 overflow-y-auto rounded-lg px-2 py-1',
56
- props.class as any,
57
- contentClass
58
- )}
59
- transition:enterExit={{
60
- duration: 200,
61
- easing: easeEmphasizedDecel,
62
- mode: 'scale'
63
- }}
64
- >
65
- {@render children()}
66
- </div>
49
+ <div {...wrapperProps} class={wrapperProps.class as any}>
50
+ <div
51
+ {...props}
52
+ class={clsx(
53
+ 'bg-md-sys-color-surface-container-high shadow-elevation-3 ring-md-sys-color-outline/10 max-w-sm min-w-48 gap-1 overflow-y-auto rounded-lg px-2 py-1',
54
+ props.class as any,
55
+ contentClass
56
+ )}
57
+ {@attach presence(() => isOpen, enterExit.scale)}
58
+ >
59
+ {@render children()}
67
60
  </div>
68
- {/if}
61
+ </div>
69
62
  {/snippet}
70
63
  </DropdownMenu.Content>
71
64
  </DropdownMenu.Portal>
@@ -25,13 +25,12 @@ trigger row (`iconProps`/`helper`/`children` label) behaves like a
25
25
  @see https://m3.material.io/components/menus/guidelines
26
26
  -->
27
27
  <script lang="ts">
28
+ import { enterExit, presence } from '../../../animation/index.js';
28
29
  import { DropdownMenu } from 'bits-ui';
29
30
  import clsx from 'clsx';
30
31
  import Icon from '../../../utils/icon/Icon.svelte';
31
32
  import Layer from '../../../utils/Layer.svelte';
32
33
  import { menu as menuCls } from './theme.js';
33
- import { enterExit } from '../../../animation/enterExit.js';
34
- import { easeEmphasizedDecel } from '../../../animation/easing.js';
35
34
  import type { MenuSubProps } from './types.js';
36
35
 
37
36
  let {
@@ -68,23 +67,17 @@ trigger row (`iconProps`/`helper`/`children` label) behaves like a
68
67
  </DropdownMenu.SubTrigger>
69
68
 
70
69
  <DropdownMenu.Portal>
71
- <DropdownMenu.SubContent forceMount sideOffset={4} class="z-100">
70
+ <DropdownMenu.SubContent sideOffset={4} class="z-100">
72
71
  {#snippet child({ wrapperProps, props, open: isOpen })}
73
- {#if isOpen}
74
- <div {...wrapperProps} class={wrapperProps.class as any}>
75
- <div
76
- {...props}
77
- class={clsx(cls.content(), props.class as any, submenuClass)}
78
- transition:enterExit={{
79
- duration: 200,
80
- easing: easeEmphasizedDecel,
81
- mode: 'scale'
82
- }}
83
- >
84
- {@render submenu()}
85
- </div>
72
+ <div {...wrapperProps} class={wrapperProps.class as any}>
73
+ <div
74
+ {...props}
75
+ class={clsx(cls.content(), props.class as any, submenuClass)}
76
+ {@attach presence(() => isOpen, enterExit.scale)}
77
+ >
78
+ {@render submenu()}
86
79
  </div>
87
- {/if}
80
+ </div>
88
81
  {/snippet}
89
82
  </DropdownMenu.SubContent>
90
83
  </DropdownMenu.Portal>
@@ -27,9 +27,11 @@ the pane itself stays `position: fixed` and doesn't move with either.
27
27
  <script lang="ts">
28
28
  import clsx from 'clsx';
29
29
  import { onMount } from 'svelte';
30
+ import { animate, type AnimationPlaybackControls } from 'motion';
31
+ import { springTokens, springTransition } from '../../../animation/spring.js';
30
32
  import { Icon, Layer } from '../../../utils/index.js';
31
33
  import { ButtonIcon } from '../../buttons/index.js';
32
- import { clickOutside } from '../../../actions/index.js';
34
+ import { clickOutside } from '../../../attachments/index.js';
33
35
  import { draggablePane, type ResizeEdge } from './theme.js';
34
36
  import { dragPositions } from './dragStore.svelte.js';
35
37
  import type { DraggablePaneProps } from './types.js';
@@ -269,7 +271,10 @@ the pane itself stays `position: fixed` and doesn't move with either.
269
271
  persist();
270
272
 
271
273
  window.addEventListener('resize', recomputeBounds);
272
- return () => window.removeEventListener('resize', recomputeBounds);
274
+ return () => {
275
+ window.removeEventListener('resize', recomputeBounds);
276
+ stopSettle();
277
+ };
273
278
  });
274
279
 
275
280
  // `bounds` can be an element that resizes or scrolls independently of the
@@ -301,28 +306,106 @@ the pane itself stays `position: fixed` and doesn't move with either.
301
306
  }
302
307
  });
303
308
 
309
+ /*
310
+ Expressive drag: the pane tracks the pointer 1:1 (direct manipulation never lags behind a
311
+ spring), rubber-bands past `bounds` instead of hitting a wall, and on release springs back
312
+ inside on the fast spatial spring, carrying the pointer's velocity. Keyboard nudges glide on
313
+ the same spring. Grabbing the pane mid-spring stops it where it is.
314
+ */
315
+ const SETTLE_SPRING = springTransition(springTokens.fastSpatial);
316
+ /* Share of the overshoot still applied at the edge, shrinking the further it's pulled. The
317
+ UIScrollView rubber-band constant — M3 has no token for this; tune by eye. */
318
+ const RUBBER_BAND = 0.55;
319
+ /* Pointer samples older than this don't count towards the release velocity. */
320
+ const VELOCITY_WINDOW_MS = 100;
321
+
322
+ let settleX: AnimationPlaybackControls | undefined;
323
+ let settleY: AnimationPlaybackControls | undefined;
324
+ /** Where the running spring is headed; undefined once it's done or stopped. */
325
+ let settleTarget: { x: number; y: number } | undefined;
326
+
327
+ function stopSettle() {
328
+ settleX?.stop();
329
+ settleY?.stop();
330
+ settleTarget = undefined;
331
+ }
332
+
333
+ /** Springs the pane to `target` (clamped), starting with `velocity` px/s. */
334
+ function settleTo(targetX: number, targetY: number, velocity = { x: 0, y: 0 }) {
335
+ stopSettle();
336
+ const next = clamp(targetX, targetY);
337
+ const fromX = x ?? initialX;
338
+ const fromY = y ?? initialY;
339
+ settleTarget = next;
340
+ settleX = animate(fromX, next.x, {
341
+ ...SETTLE_SPRING,
342
+ velocity: velocity.x,
343
+ onUpdate: (value) => (x = value),
344
+ onComplete: () => (settleTarget = undefined)
345
+ });
346
+ settleY = animate(fromY, next.y, {
347
+ ...SETTLE_SPRING,
348
+ velocity: velocity.y,
349
+ onUpdate: (value) => (y = value)
350
+ });
351
+ // Persist the resting position up front — the spring only animates towards it.
352
+ if (persistKey) dragPositions.set(persistKey, { x: next.x, y: next.y, width, height });
353
+ }
354
+
355
+ /** Resisted overshoot: the further past the edge, the less the pane follows. */
356
+ function rubberBand(overshoot: number, limit: number) {
357
+ return (1 - 1 / ((overshoot * RUBBER_BAND) / limit + 1)) * limit;
358
+ }
359
+
360
+ function resist(value: number, min: number, max: number, size: number) {
361
+ if (value < min) return min - rubberBand(min - value, size);
362
+ if (value > max) return max + rubberBand(value - max, size);
363
+ return value;
364
+ }
365
+
304
366
  let dragStartX = 0;
305
367
  let dragStartY = 0;
306
368
  let originX = 0;
307
369
  let originY = 0;
370
+ let samples: { x: number; y: number; t: number }[] = [];
308
371
 
309
372
  function startDrag(event: PointerEvent) {
310
373
  if (disableDrag) return;
374
+ stopSettle();
311
375
  dragging = true;
312
376
  dragStartX = event.clientX;
313
377
  dragStartY = event.clientY;
314
378
  originX = x ?? initialX;
315
379
  originY = y ?? initialY;
380
+ samples = [{ x: event.clientX, y: event.clientY, t: event.timeStamp }];
316
381
  (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
317
382
  }
318
383
 
319
384
  function moveDrag(event: PointerEvent) {
320
385
  if (!dragging) return;
321
- moveTo(originX + (event.clientX - dragStartX), originY + (event.clientY - dragStartY));
386
+ const rawX = originX + (event.clientX - dragStartX);
387
+ const rawY = originY + (event.clientY - dragStartY);
388
+ const rect = boundsRect();
389
+ const { x: minX, y: minY } = clamp(-Infinity, -Infinity);
390
+ const { x: maxX, y: maxY } = clamp(Infinity, Infinity);
391
+ x = resist(rawX, minX, maxX, rect.right - rect.left);
392
+ y = resist(rawY, minY, maxY, rect.bottom - rect.top);
393
+ samples.push({ x: event.clientX, y: event.clientY, t: event.timeStamp });
394
+ samples = samples.filter((sample) => event.timeStamp - sample.t <= VELOCITY_WINDOW_MS);
395
+ }
396
+
397
+ function releaseVelocity() {
398
+ const first = samples[0];
399
+ const last = samples[samples.length - 1];
400
+ const seconds = first && last ? (last.t - first.t) / 1000 : 0;
401
+ if (!seconds) return { x: 0, y: 0 };
402
+ return { x: (last.x - first.x) / seconds, y: (last.y - first.y) / seconds };
322
403
  }
323
404
 
324
405
  function endDrag() {
406
+ if (!dragging) return;
325
407
  dragging = false;
408
+ settleTo(x ?? initialX, y ?? initialY, releaseVelocity());
326
409
  }
327
410
 
328
411
  const NUDGE = 8;
@@ -339,7 +422,9 @@ the pane itself stays `position: fixed` and doesn't move with either.
339
422
  const delta = deltas[event.key];
340
423
  if (!delta) return;
341
424
  event.preventDefault();
342
- moveTo((x ?? initialX) + delta[0], (y ?? initialY) + delta[1]);
425
+ // Retargets a running nudge, so holding an arrow key glides instead of stepping.
426
+ const from = settleTarget ?? clamp(x ?? initialX, y ?? initialY);
427
+ settleTo(from.x + delta[0], from.y + delta[1]);
343
428
  }
344
429
 
345
430
  let resizeStartClientX = 0;
@@ -440,7 +525,7 @@ the pane itself stays `position: fixed` and doesn't move with either.
440
525
  {style}
441
526
  role="group"
442
527
  aria-label={title}
443
- use:clickOutside={() => onClickOutside?.()}
528
+ {@attach onClickOutside && clickOutside(onClickOutside)}
444
529
  {...rest}
445
530
  >
446
531
  <div
@@ -24,6 +24,6 @@ import type { DraggablePaneProps } from './types.js';
24
24
  * element resizes or scrolls (itself or via any scrolling ancestor), since
25
25
  * the pane itself stays `position: fixed` and doesn't move with either.
26
26
  */
27
- declare const DraggablePane: import("svelte").Component<DraggablePaneProps, {}, "x" | "y" | "width" | "height" | "collapsed">;
27
+ declare const DraggablePane: import("svelte").Component<DraggablePaneProps, {}, "height" | "width" | "x" | "y" | "collapsed">;
28
28
  type DraggablePane = ReturnType<typeof DraggablePane>;
29
29
  export default DraggablePane;
@@ -117,7 +117,7 @@ export const pane = tv({
117
117
  export const paneHandle = tv({
118
118
  slots: {
119
119
  base: 'group relative flex shrink-0 w-3 self-stretch cursor-col-resize touch-none items-center justify-center rounded-full bg-transparent hover:bg-md-sys-color-outline/20',
120
- grip: 'rounded-full transition-[background-color,height,width] duration-150'
120
+ grip: 'rounded-full transition-[background-color,height,width] md-sys-motion-fast-spatial'
121
121
  },
122
122
  variants: {
123
123
  dragging: {
@@ -135,7 +135,7 @@ export const paneHandle = tv({
135
135
  });
136
136
  export const draggablePane = tv({
137
137
  slots: {
138
- base: 'fixed z-[100] flex min-w-72 flex-col rounded-md bg-md-sys-color-surface-container-highest shadow-elevation-3',
138
+ base: 'fixed z-[100] flex min-w-72 flex-col rounded-md bg-md-sys-color-surface-container-highest shadow-elevation-3 transition-shadow md-sys-motion-effects',
139
139
  headerBar: 'relative flex shrink-0 items-center gap-2 rounded-t-2xl px-3 py-2 touch-none select-none md-sys-state-focus-indicator',
140
140
  grip: 'shrink-0 text-md-sys-color-on-surface-variant',
141
141
  headline: 'flex-1 truncate md-sys-typescale-title-small text-md-sys-color-on-surface',
@@ -5,11 +5,11 @@ Popovers display rich content in a non-modal overlay anchored to a trigger eleme
5
5
  @see https://m3.material.io/components/dialogs/overview (non-modal surface variant)
6
6
  -->
7
7
  <script lang="ts">
8
+ import { enterExit, presence } from '../../../animation/index.js';
8
9
  import { Popover } from 'bits-ui';
9
10
  import clsx from 'clsx';
10
11
  import { popover } from './theme.js';
11
12
  import type { PopoverProps } from './types.js';
12
- import { enterExit, easeEmphasizedDecel, easeEmphasizedAccel } from '../../../animation/index.js';
13
13
  import ButtonIcon from '../../buttons/ButtonIcon.svelte';
14
14
  import Title from '../../typography/title/Title.svelte';
15
15
  let {
@@ -35,49 +35,38 @@ Popovers display rich content in a non-modal overlay anchored to a trigger eleme
35
35
  </Popover.Trigger>
36
36
 
37
37
  <Popover.Portal>
38
- <Popover.Content forceMount {side} {align} {sideOffset}>
38
+ <Popover.Content {side} {align} {sideOffset}>
39
39
  {#snippet child({ wrapperProps, props, open: isOpen })}
40
- {#if isOpen}
41
- <div {...wrapperProps} class={wrapperProps.class as any}>
42
- <div
43
- {...props}
44
- class={base({ class: clsx(className, props.class as any) })}
45
- in:enterExit={{
46
- duration: 200,
47
- easing: easeEmphasizedDecel,
48
- mode: 'scale'
49
- }}
50
- out:enterExit={{
51
- duration: 150,
52
- easing: easeEmphasizedAccel,
53
- mode: 'scale'
54
- }}
55
- >
56
- {#if title || showClose}
57
- <div class={header()}>
58
- {#if title}
59
- <Title class={titleCls()}>{title}</Title>
60
- {/if}
61
- {#if showClose}
62
- <Popover.Close>
63
- {#snippet child({ props: closeProps })}
64
- <div class={close()}>
65
- <ButtonIcon
66
- {...closeProps}
67
- iconProps={{ name: 'close' }}
68
- variant="text"
69
- size="sm"
70
- />
71
- </div>
72
- {/snippet}
73
- </Popover.Close>
74
- {/if}
75
- </div>
76
- {/if}
77
- {@render children()}
78
- </div>
40
+ <div {...wrapperProps} class={wrapperProps.class as any}>
41
+ <div
42
+ {...props}
43
+ class={base({ class: clsx(className, props.class as any) })}
44
+ {@attach presence(() => isOpen, enterExit.scale)}
45
+ >
46
+ {#if title || showClose}
47
+ <div class={header()}>
48
+ {#if title}
49
+ <Title class={titleCls()}>{title}</Title>
50
+ {/if}
51
+ {#if showClose}
52
+ <Popover.Close>
53
+ {#snippet child({ props: closeProps })}
54
+ <div class={close()}>
55
+ <ButtonIcon
56
+ {...closeProps}
57
+ iconProps={{ name: 'close' }}
58
+ variant="text"
59
+ size="sm"
60
+ />
61
+ </div>
62
+ {/snippet}
63
+ </Popover.Close>
64
+ {/if}
65
+ </div>
66
+ {/if}
67
+ {@render children()}
79
68
  </div>
80
- {/if}
69
+ </div>
81
70
  {/snippet}
82
71
  </Popover.Content>
83
72
  </Popover.Portal>
@@ -3,21 +3,21 @@ export type PopoverVariants = VariantProps<typeof popover>;
3
3
  export declare const popover: import("tailwind-variants").TVReturnType<{
4
4
  [key: string]: {
5
5
  [key: string]: import("tailwind-variants").ClassValue | {
6
+ title?: import("tailwind-variants").ClassValue;
6
7
  close?: import("tailwind-variants").ClassValue;
7
8
  base?: import("tailwind-variants").ClassValue;
8
9
  body?: import("tailwind-variants").ClassValue;
9
10
  header?: import("tailwind-variants").ClassValue;
10
- title?: import("tailwind-variants").ClassValue;
11
11
  };
12
12
  };
13
13
  } | {
14
14
  [x: string]: {
15
15
  [x: string]: import("tailwind-variants").ClassValue | {
16
+ title?: import("tailwind-variants").ClassValue;
16
17
  close?: import("tailwind-variants").ClassValue;
17
18
  base?: import("tailwind-variants").ClassValue;
18
19
  body?: import("tailwind-variants").ClassValue;
19
20
  header?: import("tailwind-variants").ClassValue;
20
- title?: import("tailwind-variants").ClassValue;
21
21
  };
22
22
  };
23
23
  } | {}, {
@@ -29,11 +29,11 @@ export declare const popover: import("tailwind-variants").TVReturnType<{
29
29
  }, undefined, {
30
30
  [key: string]: {
31
31
  [key: string]: import("tailwind-variants").ClassValue | {
32
+ title?: import("tailwind-variants").ClassValue;
32
33
  close?: import("tailwind-variants").ClassValue;
33
34
  base?: import("tailwind-variants").ClassValue;
34
35
  body?: import("tailwind-variants").ClassValue;
35
36
  header?: import("tailwind-variants").ClassValue;
36
- title?: import("tailwind-variants").ClassValue;
37
37
  };
38
38
  };
39
39
  } | {}, {
@@ -3,8 +3,8 @@ export const scrollArea = tv({
3
3
  slots: {
4
4
  root: 'relative overflow-hidden',
5
5
  viewport: 'size-full rounded-[inherit]',
6
- scrollbar: 'flex touch-none select-none transition-colors duration-150 ease-out',
7
- thumb: 'relative flex-1 rounded-full bg-md-sys-color-outline-variant hover:bg-md-sys-color-secondary active:bg-md-sys-color-secondary transition-colors duration-150',
6
+ scrollbar: 'flex touch-none select-none transition-colors md-sys-motion-fast-effects',
7
+ thumb: 'relative flex-1 rounded-full bg-md-sys-color-outline-variant hover:bg-md-sys-color-secondary active:bg-md-sys-color-secondary transition-colors md-sys-motion-fast-effects',
8
8
  corner: 'bg-md-sys-color-surface-container-highest'
9
9
  },
10
10
  variants: {
@@ -1,9 +1,6 @@
1
1
  <!--
2
- SideSheet has no `open` prop of its own — mounting it calls the native <dialog>'s showModal()
3
- immediately, so visibility is controlled by conditionally rendering the component
4
- ({#if open}<SideSheet ...>{/if}), same as BottomSheet. `close` is a required callback the
5
- sheet invokes on Esc or backdrop click — the consumer is expected to flip its own `open` flag
6
- to false in response.
2
+ SideSheet is controlled with `bind:open`: it dismisses itself (close button, Esc, backdrop
3
+ click) by setting `open` to false, and stays mounted until its exit animation finishes.
7
4
  -->
8
5
  <script module lang="ts">
9
6
  import { defineMeta } from '@storybook/addon-svelte-csf';
@@ -31,16 +28,14 @@
31
28
  {#snippet template(args)}
32
29
  <div class="p-6">
33
30
  <Button onclick={() => (playgroundOpen = true)}>Show side sheet</Button>
34
- {#if playgroundOpen}
35
- <SideSheet headline={args.headline} close={() => (playgroundOpen = false)}>
36
- <div class="flex flex-col gap-3 px-6 pb-6">
37
- <Body class="text-md-sys-color-on-surface-variant">
38
- Supplementary content and actions, anchored to the edge of the screen while the main
39
- content stays visible.
40
- </Body>
41
- </div>
42
- </SideSheet>
43
- {/if}
31
+ <SideSheet headline={args.headline} bind:open={playgroundOpen}>
32
+ <div class="flex flex-col gap-3 px-6 pb-6">
33
+ <Body class="text-md-sys-color-on-surface-variant">
34
+ Supplementary content and actions, anchored to the edge of the screen while the main
35
+ content stays visible.
36
+ </Body>
37
+ </div>
38
+ </SideSheet>
44
39
  </div>
45
40
  {/snippet}
46
41
  </Story>