@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
@@ -1,13 +1,15 @@
1
1
  <!--
2
2
  @component
3
- An individual action within a FAB menu.
3
+ An individual action within a FAB menu. Wraps bits-ui's `DropdownMenu.Item`, so it gets the
4
+ `menuitem` role, arrow-key navigation, and closes the menu when selected. Must be rendered
5
+ inside a `FAB` with `withMenu`.
4
6
  -->
5
7
  <script lang="ts">
6
8
  import clsx from 'clsx';
7
9
  import { fabMenuItem } from './theme.js';
8
10
  import type { FABMenuItemProps } from './types.js';
9
11
  import { Icon, LoadingIndicator, Layer } from '../../utils/index.js';
10
- import { Button, type ButtonRootProps } from 'bits-ui';
12
+ import { Button, DropdownMenu, type DropdownMenuItemProps } from 'bits-ui';
11
13
 
12
14
  let {
13
15
  class: className,
@@ -16,6 +18,7 @@ An individual action within a FAB menu.
16
18
  children,
17
19
  iconProps,
18
20
  formaction,
21
+ disabled,
19
22
  ...restProps
20
23
  }: FABMenuItemProps = $props();
21
24
 
@@ -23,21 +26,21 @@ An individual action within a FAB menu.
23
26
  const btnCls = $derived(base({ class: clsx(className) }));
24
27
  </script>
25
28
 
26
- <Button.Root
27
- {formaction}
28
- class={btnCls}
29
- data-cy="m3-fab-menu-item"
30
- {...restProps as ButtonRootProps}
31
- >
32
- {#if iconProps}
33
- {#if loading}
34
- <LoadingIndicator />
35
- {:else}
36
- <Icon class={icon()} {...iconProps} />
37
- {/if}
38
- {:else if loading}
39
- <LoadingIndicator />
40
- {/if}
41
- {@render children?.()}
42
- <Layer />
43
- </Button.Root>
29
+ <!-- Spread onto the Item, not the button: bits-ui merges (and chains handlers of) these into `props`. -->
30
+ <DropdownMenu.Item {disabled} {...restProps as DropdownMenuItemProps}>
31
+ {#snippet child({ props })}
32
+ <Button.Root {formaction} {disabled} data-cy="m3-fab-menu-item" {...props} class={btnCls}>
33
+ {#if iconProps}
34
+ {#if loading}
35
+ <LoadingIndicator />
36
+ {:else}
37
+ <Icon class={icon()} {...iconProps} />
38
+ {/if}
39
+ {:else if loading}
40
+ <LoadingIndicator />
41
+ {/if}
42
+ {@render children?.()}
43
+ <Layer />
44
+ </Button.Root>
45
+ {/snippet}
46
+ </DropdownMenu.Item>
@@ -1,5 +1,9 @@
1
1
  import type { FABMenuItemProps } from './types.js';
2
- /** An individual action within a FAB menu. */
2
+ /**
3
+ * An individual action within a FAB menu. Wraps bits-ui's `DropdownMenu.Item`, so it gets the
4
+ * `menuitem` role, arrow-key navigation, and closes the menu when selected. Must be rendered
5
+ * inside a `FAB` with `withMenu`.
6
+ */
3
7
  declare const FABMenuItem: import("svelte").Component<FABMenuItemProps, {}, "">;
4
8
  type FABMenuItem = ReturnType<typeof FABMenuItem>;
5
9
  export default FABMenuItem;
@@ -15,10 +15,9 @@ selectable items, no menu).
15
15
  @see https://m3.material.io/components/buttons/guidelines#834f5f31-e363-4d0f-bc4d-abac128f5f0e
16
16
  -->
17
17
  <script lang="ts">
18
+ import { enterExit, presence } from '../../../animation/index.js';
18
19
  import { DropdownMenu } from 'bits-ui';
19
20
  import clsx from 'clsx';
20
- import { easeEmphasizedDecel } from '../../../animation/easing.js';
21
- import { enterExit } from '../../../animation/enterExit.js';
22
21
  import Button from '../Button.svelte';
23
22
  import ButtonIcon from '../ButtonIcon.svelte';
24
23
  import { buttonIcon } from '../theme.js';
@@ -80,23 +79,17 @@ selectable items, no menu).
80
79
  </DropdownMenu.Trigger>
81
80
 
82
81
  <DropdownMenu.Portal>
83
- <DropdownMenu.Content forceMount {align} sideOffset={4} class="z-100">
82
+ <DropdownMenu.Content {align} sideOffset={4} class="z-100">
84
83
  {#snippet child({ wrapperProps, props, open: isOpen })}
85
- {#if isOpen}
86
- <div {...wrapperProps} class={wrapperProps.class as any}>
87
- <div
88
- {...props}
89
- class="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"
90
- transition:enterExit={{
91
- duration: 200,
92
- easing: easeEmphasizedDecel,
93
- mode: 'scale'
94
- }}
95
- >
96
- {@render menuChildren()}
97
- </div>
84
+ <div {...wrapperProps} class={wrapperProps.class as any}>
85
+ <div
86
+ {...props}
87
+ class="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"
88
+ {@attach presence(() => isOpen, enterExit.scale)}
89
+ >
90
+ {@render menuChildren()}
98
91
  </div>
99
- {/if}
92
+ </div>
100
93
  {/snippet}
101
94
  </DropdownMenu.Content>
102
95
  </DropdownMenu.Portal>
@@ -12,7 +12,7 @@ export const SPLIT_BUTTON_INNER_RADIUS = {
12
12
  export const splitButton = tv({
13
13
  slots: {
14
14
  root: 'inline-flex items-center gap-0.5',
15
- icon: 'transition-transform duration-200'
15
+ icon: 'transition-transform md-sys-motion-fast-spatial'
16
16
  },
17
17
  variants: {
18
18
  open: {
@@ -146,9 +146,7 @@ export type FABProps = FABVariants & AnchorButtonAttributes & {
146
146
  */
147
147
  children?: Snippet;
148
148
  };
149
- export type FABMenuProps = HTMLAttributes<HTMLUListElement> & {
150
- /** The FAB button element to position the menu relative to. */
151
- anchorEl: HTMLElement;
149
+ export type FABMenuProps = HTMLAttributes<HTMLDivElement> & {
152
150
  /**
153
151
  * The menu items. Use `FABMenuItem` components.
154
152
  */
@@ -159,6 +157,8 @@ export type FABMenuItemProps = FABMenuItemVariants & AnchorButtonAttributes & {
159
157
  * The visual variant of the menu item.
160
158
  */
161
159
  variant?: FABMenuItemVariants['variant'];
160
+ /** Whether the menu item is disabled (skipped by keyboard navigation). */
161
+ disabled?: boolean;
162
162
  /** Whether the menu item is in a loading state. */
163
163
  loading?: boolean;
164
164
  /**
@@ -1,7 +1,7 @@
1
1
  import { tv } from 'tailwind-variants';
2
2
  export const card = tv({
3
3
  slots: {
4
- base: 'relative flex flex-col rounded-lg disabled:opacity-[0.38] disabled:bg-md-sys-color-surface-variant state-layer before:rounded-lg [transition:box-shadow_var(--md-sys-motion-duration,200ms)_var(--md-sys-motion-timing-function,cubic-bezier(0.34,0.8,0.34,1))]',
4
+ base: 'relative flex flex-col rounded-lg disabled:opacity-[0.38] disabled:bg-md-sys-color-surface-variant state-layer before:rounded-lg transition-shadow md-sys-motion-effects',
5
5
  icon: 'size-6 text-[24px] text-md-sys-color-primary'
6
6
  },
7
7
  variants: {
@@ -1,9 +1,7 @@
1
1
  <!--
2
- BottomSheet has no `open` prop of its own — mounting it calls the native <dialog>'s
3
- showModal() immediately, so visibility is controlled by conditionally rendering the component
4
- ({#if open}<BottomSheet ...>{/if}). `close` is a required callback the sheet invokes on Esc,
5
- backdrop click, or dragging the handle down below a minimum height ('esc' | 'click' | 'low') —
6
- the consumer is expected to flip its own `open` flag to false in response.
2
+ BottomSheet is controlled with `bind:open`: it dismisses itself on Esc, backdrop click, or
3
+ dragging the handle below a minimum height, and stays mounted until its exit animation
4
+ finishes. The optional `close` callback reports why ('esc' | 'click' | 'low').
7
5
  -->
8
6
  <script module lang="ts">
9
7
  import { defineMeta } from '@storybook/addon-svelte-csf';
@@ -24,12 +22,10 @@
24
22
  <Story name="Playground" asChild>
25
23
  <div class="p-6">
26
24
  <Button onclick={() => (playgroundOpen = true)}>Open bottom sheet</Button>
27
- {#if playgroundOpen}
28
- <BottomSheet close={() => (playgroundOpen = false)}>
29
- <MenuItem iconProps={{ name: 'share' }}>Share</MenuItem>
30
- <MenuItem iconProps={{ name: 'link' }}>Copy link</MenuItem>
31
- <MenuItem iconProps={{ name: 'download' }}>Download</MenuItem>
32
- </BottomSheet>
33
- {/if}
25
+ <BottomSheet bind:open={playgroundOpen}>
26
+ <MenuItem iconProps={{ name: 'share' }}>Share</MenuItem>
27
+ <MenuItem iconProps={{ name: 'link' }}>Copy link</MenuItem>
28
+ <MenuItem iconProps={{ name: 'download' }}>Download</MenuItem>
29
+ </BottomSheet>
34
30
  </div>
35
31
  </Story>
@@ -9,31 +9,37 @@ while keeping the main screen content visible.
9
9
  -->
10
10
  <script lang="ts">
11
11
  import type { BottomSheetProps } from './types.js';
12
- import type { TransitionConfig } from 'svelte/transition';
13
- import { easeEmphasizedAccel, easeEmphasizedDecel } from '../../../animation/easing.js';
14
- import { outroClass } from '../../../animation/outroClass.js';
12
+ import { untrack } from 'svelte';
13
+ import { enterExit, Presence } from '../../../animation/index.js';
15
14
 
16
- let { children, close }: BottomSheetProps = $props();
15
+ const DEFAULT_HEIGHT = 480;
16
+ const DISMISS_HEIGHT = 48;
17
17
 
18
- let height = $state(480);
18
+ let { children, open = $bindable(true), close }: BottomSheetProps = $props();
19
+
20
+ let height = $state(DEFAULT_HEIGHT);
19
21
  let container: HTMLDivElement | undefined = $state();
20
22
  let isDragging = $state(false);
21
23
  let startY = $state(0);
22
24
 
23
- const open = (node: HTMLDialogElement) => node.showModal();
25
+ const sheet = new Presence(() => open);
26
+
27
+ // untrack: re-running this attachment on every drag would call showModal() on an open dialog.
28
+ const showModal = (node: HTMLDialogElement) => {
29
+ untrack(() => node.clientHeight < height && (height = node.clientHeight));
30
+ node.showModal();
31
+ };
24
32
 
25
- const heightAnim = (
26
- node: HTMLDialogElement,
27
- options: { duration: number; easing: typeof easeEmphasizedDecel }
28
- ): TransitionConfig => {
29
- if (node.clientHeight < height) height = node.clientHeight;
30
- return {
31
- duration: options.duration,
32
- easing: options.easing,
33
- css: (t) => `max-height: ${t * height}px`
34
- };
33
+ const dismiss = (reason: 'esc' | 'click' | 'low') => {
34
+ open = false;
35
+ close?.(reason);
35
36
  };
36
37
 
38
+ // A sheet dragged shut keeps its collapsed height through the exit; restore it on reopen.
39
+ $effect.pre(() => {
40
+ if (open) untrack(() => height < DISMISS_HEIGHT && (height = DEFAULT_HEIGHT));
41
+ });
42
+
37
43
  const moveWheel = (e: WheelEvent) => {
38
44
  e.preventDefault();
39
45
  height += e.deltaY;
@@ -49,7 +55,7 @@ while keeping the main screen content visible.
49
55
  };
50
56
 
51
57
  $effect(() => {
52
- if (height < 48) close('low');
58
+ if (height < DISMISS_HEIGHT && untrack(() => open)) dismiss('low');
53
59
  });
54
60
  </script>
55
61
 
@@ -60,51 +66,57 @@ while keeping the main screen content visible.
60
66
  ontouchend={() => (isDragging = false)}
61
67
  />
62
68
 
63
- <dialog
64
- class="bg-md-sys-color-surface-container-low text-md-sys-color-on-surface mx-auto mt-auto w-full max-w-2xl overflow-hidden rounded-t-md"
65
- style:max-height="{height}px"
66
- use:open
67
- use:outroClass
68
- oncancel={(e) => {
69
- e.preventDefault();
70
- close('esc');
71
- }}
72
- onmousedown={(e) => {
73
- if (e.target != e.currentTarget) return;
74
- close('click');
75
- }}
76
- onwheel={moveWheel}
77
- in:heightAnim={{ easing: easeEmphasizedDecel, duration: 400 }}
78
- out:heightAnim={{ easing: easeEmphasizedAccel, duration: 300 }}
79
- >
80
- <div class="px-4" bind:this={container}>
81
- <!-- svelte-ignore a11y_no_static_element_interactions -->
82
- <div
83
- class="flex h-12 w-full cursor-grab items-center justify-center"
84
- onmousedown={(e) => {
85
- e.preventDefault();
86
- isDragging = true;
87
- startY = e.clientY;
88
- }}
89
- ontouchstart={(e) => {
90
- isDragging = true;
91
- startY = e.touches[0].clientY;
92
- }}
93
- >
94
- <div class="bg-md-sys-color-on-surface-variant/40 h-1 w-8 rounded-full"></div>
69
+ {#if sheet.mounted}
70
+ <dialog
71
+ class="bg-md-sys-color-surface-container-low text-md-sys-color-on-surface mx-auto mt-auto w-full max-w-2xl overflow-hidden rounded-t-md"
72
+ style:max-height="{height}px"
73
+ data-state={open ? 'open' : 'closed'}
74
+ {@attach showModal}
75
+ {@attach sheet.attach(enterExit.bottomSheet)}
76
+ oncancel={(e) => {
77
+ e.preventDefault();
78
+ dismiss('esc');
79
+ }}
80
+ onmousedown={(e) => {
81
+ if (e.target != e.currentTarget) return;
82
+ dismiss('click');
83
+ }}
84
+ onwheel={moveWheel}
85
+ >
86
+ <div class="px-4" bind:this={container}>
87
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
88
+ <div
89
+ class="flex h-12 w-full cursor-grab items-center justify-center"
90
+ onmousedown={(e) => {
91
+ e.preventDefault();
92
+ isDragging = true;
93
+ startY = e.clientY;
94
+ }}
95
+ ontouchstart={(e) => {
96
+ isDragging = true;
97
+ startY = e.touches[0].clientY;
98
+ }}
99
+ >
100
+ <div class="bg-md-sys-color-on-surface-variant/40 h-1 w-8 rounded-full"></div>
101
+ </div>
102
+ {@render children()}
95
103
  </div>
96
- {@render children()}
97
- </div>
98
- </dialog>
104
+ </dialog>
105
+ {/if}
99
106
 
100
107
  <style>
101
108
  dialog::backdrop {
102
109
  background-color: rgba(0, 0, 0, 0.5);
103
- animation: backdrop 400ms;
110
+ animation: backdrop var(--md-sys-motion-duration-effects-spring)
111
+ var(--md-sys-motion-timing-function-effects-spring);
112
+ }
113
+ dialog[data-state='closed'] {
114
+ pointer-events: none;
104
115
  }
105
- dialog:global(.leaving)::backdrop {
116
+ dialog[data-state='closed']::backdrop {
106
117
  background-color: transparent;
107
- animation: backdropReverse 400ms;
118
+ animation: backdropReverse var(--md-sys-motion-duration-fast-effects-spring)
119
+ var(--md-sys-motion-timing-function-fast-effects-spring);
108
120
  }
109
121
  @keyframes backdrop {
110
122
  from {
@@ -7,6 +7,6 @@ import type { BottomSheetProps } from './types.js';
7
7
  *
8
8
  * @see https://m3.material.io/components/bottom-sheets/overview
9
9
  */
10
- declare const BottomSheet: import("svelte").Component<BottomSheetProps, {}, "">;
10
+ declare const BottomSheet: import("svelte").Component<BottomSheetProps, {}, "open">;
11
11
  type BottomSheet = ReturnType<typeof BottomSheet>;
12
12
  export default BottomSheet;
@@ -9,8 +9,14 @@ export interface BottomSheetProps {
9
9
  */
10
10
  children: Snippet;
11
11
  /**
12
- * Callback function to close the bottom sheet.
12
+ * Whether the sheet is shown. Bind it (`bind:open`) so the sheet can play its exit animation
13
+ * before unmounting.
14
+ * @default true
15
+ */
16
+ open?: boolean;
17
+ /**
18
+ * Called when the user dismisses the sheet.
13
19
  * @param reason The reason for closing ('esc' key, 'click' outside, or 'low' drag height).
14
20
  */
15
- close: (reason: 'esc' | 'click' | 'low') => void;
21
+ close?: (reason: 'esc' | 'click' | 'low') => void;
16
22
  }
@@ -30,13 +30,12 @@ you need it (e.g. a table row):
30
30
  @see https://m3.material.io/components/menus/guidelines
31
31
  -->
32
32
  <script lang="ts">
33
+ import { enterExit, presence } from '../../../animation/index.js';
33
34
  import { ContextMenu as BitsContextMenu } from 'bits-ui';
34
35
  import { contextMenu } from './theme.js';
35
36
  import type { ContextMenuProps, ContextMenuDataItem } from './types.js';
36
37
  import Icon from '../../../utils/icon/Icon.svelte';
37
38
  import Divider from '../divider/Divider.svelte';
38
- import { enterExit } from '../../../animation/enterExit.js';
39
- import { easeEmphasizedDecel } from '../../../animation/easing.js';
40
39
 
41
40
  let {
42
41
  items = [],
@@ -75,25 +74,19 @@ you need it (e.g. a table row):
75
74
  <Icon name="chevron_right" class={theme.rightSlot()} />
76
75
  </BitsContextMenu.SubTrigger>
77
76
  <BitsContextMenu.Portal>
78
- <BitsContextMenu.SubContent forceMount class={theme.content()}>
77
+ <BitsContextMenu.SubContent class={theme.content()}>
79
78
  {#snippet child({ wrapperProps, props, open })}
80
- {#if open}
81
- <div {...wrapperProps} class={wrapperProps.class as any}>
82
- <div
83
- {...props}
84
- class={props.class as any}
85
- transition:enterExit={{
86
- duration: 200,
87
- easing: easeEmphasizedDecel,
88
- mode: 'scale'
89
- }}
90
- >
91
- {#each item.items as subItem (subItem)}
92
- {@render MenuItem(subItem)}
93
- {/each}
94
- </div>
79
+ <div {...wrapperProps} class={wrapperProps.class as any}>
80
+ <div
81
+ {...props}
82
+ class={props.class as any}
83
+ {@attach presence(() => open, enterExit.scale)}
84
+ >
85
+ {#each item.items as subItem (subItem)}
86
+ {@render MenuItem(subItem)}
87
+ {/each}
95
88
  </div>
96
- {/if}
89
+ </div>
97
90
  {/snippet}
98
91
  </BitsContextMenu.SubContent>
99
92
  </BitsContextMenu.Portal>
@@ -142,25 +135,19 @@ you need it (e.g. a table row):
142
135
  </BitsContextMenu.Trigger>
143
136
 
144
137
  <BitsContextMenu.Portal>
145
- <BitsContextMenu.Content forceMount class={theme.content()}>
138
+ <BitsContextMenu.Content class={theme.content()}>
146
139
  {#snippet child({ wrapperProps, props, open })}
147
- {#if open}
148
- <div {...wrapperProps} class={wrapperProps.class as any}>
149
- <div
150
- {...props}
151
- class={props.class as any}
152
- transition:enterExit={{
153
- duration: 200,
154
- easing: easeEmphasizedDecel,
155
- mode: 'scale'
156
- }}
157
- >
158
- {#each items as item (item)}
159
- {@render MenuItem(item)}
160
- {/each}
161
- </div>
140
+ <div {...wrapperProps} class={wrapperProps.class as any}>
141
+ <div
142
+ {...props}
143
+ class={props.class as any}
144
+ {@attach presence(() => open, enterExit.scale)}
145
+ >
146
+ {#each items as item (item)}
147
+ {@render MenuItem(item)}
148
+ {/each}
162
149
  </div>
163
- {/if}
150
+ </div>
164
151
  {/snippet}
165
152
  </BitsContextMenu.Content>
166
153
  </BitsContextMenu.Portal>
@@ -9,14 +9,14 @@ export const contextMenu = tv({
9
9
  item: `
10
10
  rounded-sm relative flex w-full cursor-pointer select-none items-center gap-2
11
11
  px-3 py-2 text-left md-sys-typescale-body-large text-md-sys-color-on-surface
12
- outline-none transition-colors duration-150
12
+ outline-none transition-colors md-sys-motion-fast-effects
13
13
  hover:bg-md-sys-color-on-surface/8 data-[highlighted]:bg-md-sys-color-on-surface/8
14
14
  data-[disabled]:cursor-not-allowed data-[disabled]:opacity-38
15
15
  `,
16
16
  subTrigger: `
17
17
  rounded-sm relative flex w-full cursor-pointer select-none items-center gap-2
18
18
  px-3 py-2 text-left md-sys-typescale-body-large text-md-sys-color-on-surface
19
- outline-none transition-colors duration-150
19
+ outline-none transition-colors md-sys-motion-fast-effects
20
20
  hover:bg-md-sys-color-on-surface/8 data-[state=open]:bg-md-sys-color-on-surface/8
21
21
  data-[highlighted]:bg-md-sys-color-on-surface/8
22
22
  data-[disabled]:cursor-not-allowed data-[disabled]:opacity-38
@@ -13,8 +13,7 @@ on screen until confirmed, dismissed, or a required action has been taken.
13
13
  import { dialogue } from './theme.js';
14
14
  import type { DialogueProps } from './types.js';
15
15
  import { Dialog } from 'bits-ui';
16
- import { enterExit } from '../../../animation/enterExit.js';
17
- import { easeEmphasized, easeEmphasizedDecel } from '../../../animation/easing.js';
16
+ import { enterExit, presence } from '../../../animation/index.js';
18
17
  import { Button } from '../../buttons/index.js';
19
18
 
20
19
  let {
@@ -44,79 +43,64 @@ on screen until confirmed, dismissed, or a required action has been taken.
44
43
 
45
44
  <Dialog.Root bind:open {...rootRest}>
46
45
  <Dialog.Portal disabled={portalDisabled}>
47
- <Dialog.Overlay forceMount>
46
+ <Dialog.Overlay>
48
47
  {#snippet child({ props, open })}
49
- {#if open}
50
- <div
51
- {...props}
52
- class={base()}
53
- transition:enterExit={{
54
- duration: 500,
55
- easing: easeEmphasizedDecel
56
- }}
57
- ></div>
58
- {/if}
48
+ <div {...props} class={base()} {@attach presence(() => open, enterExit.fade)}></div>
59
49
  {/snippet}
60
50
  </Dialog.Overlay>
61
- <Dialog.Content forceMount>
51
+ <Dialog.Content>
62
52
  {#snippet child({ props, open: isOpen })}
63
- {#if isOpen}
64
- <form
65
- method="POST"
66
- action={confirmAction}
67
- use:enhance
68
- {...props}
69
- {...formProps}
70
- class={inner({ class: className })}
71
- style:min-width={minWidth}
72
- style:max-width={maxWidth}
73
- transition:enterExit={{
74
- duration: 500,
75
- easing: easeEmphasized,
76
- mode: 'dialog-m3'
77
- }}
78
- >
79
- {#if headline}
80
- <Dialog.Title class={headlineContainer()}>
81
- {headline}
82
- </Dialog.Title>
83
- {/if}
84
- {#if supportingText}
85
- <Dialog.Description class={supportingTextContainer()}>
86
- {supportingText}
87
- </Dialog.Description>
88
- {/if}
53
+ <form
54
+ method="POST"
55
+ action={confirmAction}
56
+ use:enhance
57
+ {...props}
58
+ {...formProps}
59
+ class={inner({ class: className })}
60
+ style:min-width={minWidth}
61
+ style:max-width={maxWidth}
62
+ {@attach presence(() => isOpen, enterExit.dialog)}
63
+ >
64
+ {#if headline}
65
+ <Dialog.Title class={headlineContainer()}>
66
+ {headline}
67
+ </Dialog.Title>
68
+ {/if}
69
+ {#if supportingText}
70
+ <Dialog.Description class={supportingTextContainer()}>
71
+ {supportingText}
72
+ </Dialog.Description>
73
+ {/if}
89
74
 
90
- {#if children}
91
- <div class="flex w-full flex-col gap-4">
92
- {@render children()}
93
- </div>
94
- {/if}
75
+ {#if children}
76
+ <div class="flex w-full flex-col gap-4">
77
+ {@render children()}
78
+ </div>
79
+ {/if}
95
80
 
96
- <div class={buttonContainer()}>
97
- <Button
98
- type="button"
99
- variant="text"
100
- data-cy="dialogue-cancel"
101
- onclick={() => (open = false)}
102
- >
103
- {cancelText}
104
- </Button>
81
+ <div class={buttonContainer()}>
82
+ <Button
83
+ type="button"
84
+ variant="text"
85
+ data-cy="dialogue-cancel"
86
+ onclick={() => (open = false)}
87
+ >
88
+ {cancelText}
89
+ </Button>
105
90
 
106
- {#if delegateClose}
91
+ {#if delegateClose}
92
+ <Button type="submit" {disabled} {loading} data-cy="dialogue-confirm">
93
+ {confirmText}
94
+ </Button>
95
+ {:else}
96
+ <Dialog.Close>
107
97
  <Button type="submit" {disabled} {loading} data-cy="dialogue-confirm">
108
98
  {confirmText}
109
99
  </Button>
110
- {:else}
111
- <Dialog.Close>
112
- <Button type="submit" {disabled} {loading} data-cy="dialogue-confirm">
113
- {confirmText}
114
- </Button>
115
- </Dialog.Close>
116
- {/if}
117
- </div>
118
- </form>
119
- {/if}
100
+ </Dialog.Close>
101
+ {/if}
102
+ </div>
103
+ </form>
120
104
  {/snippet}
121
105
  </Dialog.Content>
122
106
  </Dialog.Portal>