@noxlovette/material 0.4.8 → 0.5.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 (131) hide show
  1. package/dist/animation/TransitionPatterns.stories.svelte +357 -0
  2. package/dist/animation/TransitionPatterns.stories.svelte.d.ts +3 -0
  3. package/dist/animation/containerTransform.d.ts +26 -12
  4. package/dist/animation/containerTransform.js +22 -138
  5. package/dist/animation/enterExit.d.ts +21 -3
  6. package/dist/animation/enterExit.js +69 -22
  7. package/dist/animation/index.d.ts +1 -1
  8. package/dist/animation/index.js +1 -1
  9. package/dist/animation/presence.svelte.d.ts +47 -0
  10. package/dist/animation/presence.svelte.js +84 -0
  11. package/dist/animation/sharedAxisTransition.d.ts +35 -37
  12. package/dist/animation/sharedAxisTransition.js +50 -22
  13. package/dist/animation/skeleton.d.ts +11 -0
  14. package/dist/animation/skeleton.js +23 -0
  15. package/dist/animation/spring.d.ts +61 -0
  16. package/dist/animation/spring.js +53 -0
  17. package/dist/animation/springValue.svelte.d.ts +22 -0
  18. package/dist/animation/springValue.svelte.js +42 -0
  19. package/dist/animation/transition.d.ts +3 -1
  20. package/dist/animation/transition.js +3 -1
  21. package/dist/attachments/clickOutside.d.ts +13 -0
  22. package/dist/attachments/clickOutside.js +18 -0
  23. package/dist/attachments/index.d.ts +1 -0
  24. package/dist/attachments/index.js +1 -0
  25. package/dist/components/buttons/FAB.svelte +23 -16
  26. package/dist/components/buttons/FABMenu.svelte +24 -29
  27. package/dist/components/buttons/FABMenu.svelte.d.ts +2 -1
  28. package/dist/components/buttons/FABMenuItem.svelte +23 -20
  29. package/dist/components/buttons/FABMenuItem.svelte.d.ts +5 -1
  30. package/dist/components/buttons/split-button/SplitButton.svelte +10 -17
  31. package/dist/components/buttons/split-button/theme.js +1 -1
  32. package/dist/components/buttons/types.d.ts +3 -3
  33. package/dist/components/cards/theme.js +1 -1
  34. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +8 -12
  35. package/dist/components/containers/bottom-sheet/BottomSheet.svelte +67 -55
  36. package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +1 -1
  37. package/dist/components/containers/bottom-sheet/types.d.ts +8 -2
  38. package/dist/components/containers/context-menu/ContextMenu.svelte +23 -36
  39. package/dist/components/containers/context-menu/theme.js +2 -2
  40. package/dist/components/containers/dialogue/Dialogue.svelte +49 -65
  41. package/dist/components/containers/link-preview/LinkPreview.svelte +12 -23
  42. package/dist/components/containers/link-preview/theme.d.ts +3 -3
  43. package/dist/components/containers/menu/Menu.svelte +14 -21
  44. package/dist/components/containers/menu/MenuSub.svelte +10 -17
  45. package/dist/components/containers/pane/DraggablePane.svelte +90 -5
  46. package/dist/components/containers/pane/DraggablePane.svelte.d.ts +1 -1
  47. package/dist/components/containers/pane/theme.js +2 -2
  48. package/dist/components/containers/popover/Popover.svelte +31 -42
  49. package/dist/components/containers/popover/theme.d.ts +3 -3
  50. package/dist/components/containers/scroll-area/theme.js +2 -2
  51. package/dist/components/containers/side-sheet/SideSheet.stories.svelte +10 -15
  52. package/dist/components/containers/side-sheet/SideSheet.svelte +44 -44
  53. package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +4 -3
  54. package/dist/components/containers/side-sheet/types.d.ts +8 -2
  55. package/dist/components/date/DateField.svelte +85 -92
  56. package/dist/components/date/DateField.svelte.d.ts +2 -5
  57. package/dist/components/date/DateRangeField.svelte +113 -155
  58. package/dist/components/date/DateRangeField.svelte.d.ts +2 -5
  59. package/dist/components/date/theme.d.ts +95 -0
  60. package/dist/components/date/theme.js +99 -14
  61. package/dist/components/date/types.d.ts +13 -0
  62. package/dist/components/forms/checkbox/theme.js +2 -2
  63. package/dist/components/forms/command/theme.d.ts +3 -3
  64. package/dist/components/forms/command/theme.js +1 -1
  65. package/dist/components/forms/pin/theme.js +2 -2
  66. package/dist/components/forms/radio-group/theme.js +2 -2
  67. package/dist/components/forms/select/Select.svelte +42 -54
  68. package/dist/components/forms/select/theme.js +4 -4
  69. package/dist/components/forms/slider/Slider.svelte +144 -126
  70. package/dist/components/forms/slider/Slider.svelte.d.ts +3 -0
  71. package/dist/components/forms/slider/theme.d.ts +154 -48
  72. package/dist/components/forms/slider/theme.js +60 -20
  73. package/dist/components/forms/slider/types.d.ts +1 -1
  74. package/dist/components/forms/switch/theme.js +4 -4
  75. package/dist/components/forms/textfield/Textfield.svelte +1 -1
  76. package/dist/components/forms/textfield/theme.d.ts +9 -0
  77. package/dist/components/forms/textfield/theme.js +26 -9
  78. package/dist/components/forms/toggle-group/theme.js +1 -1
  79. package/dist/components/forms/tooltip/Tooltip.svelte +24 -35
  80. package/dist/components/nav/breadcrumb/theme.js +1 -1
  81. package/dist/components/nav/navbar/theme.js +2 -2
  82. package/dist/components/nav/rail/Rail.svelte +4 -5
  83. package/dist/components/nav/rail/theme.js +7 -7
  84. package/dist/components/nav/tabs/theme.js +1 -1
  85. package/dist/components/progress/CircularProgress.svelte +78 -4
  86. package/dist/components/progress/CircularProgress.svelte.d.ts +5 -0
  87. package/dist/components/progress/LinearProgress.svelte +70 -7
  88. package/dist/components/progress/LinearProgress.svelte.d.ts +5 -0
  89. package/dist/components/progress/WavyLinearProgress.svelte +27 -3
  90. package/dist/components/progress/WavyLinearProgress.svelte.d.ts +3 -0
  91. package/dist/components/progress/_wavy.d.ts +2 -0
  92. package/dist/components/progress/_wavy.js +3 -1
  93. package/dist/components/snackbar/Snackbar.svelte +13 -17
  94. package/dist/components/table/TableBody.svelte +3 -16
  95. package/dist/components/table/TableBody.svelte.d.ts +1 -5
  96. package/dist/components/table/TableHeader.svelte +1 -1
  97. package/dist/components/table/theme.js +1 -1
  98. package/dist/components/table/types.d.ts +0 -7
  99. package/dist/components/time/TimeField.stories.svelte +41 -0
  100. package/dist/components/time/TimeField.stories.svelte.d.ts +19 -0
  101. package/dist/components/time/TimeField.svelte +62 -61
  102. package/dist/components/toolbar/theme.js +2 -2
  103. package/dist/index.d.ts +1 -1
  104. package/dist/index.js +1 -1
  105. package/dist/styles/component.css +21 -22
  106. package/dist/styles/motion.css +317 -38
  107. package/dist/utils/Layer.svelte +16 -29
  108. package/dist/utils/icon/LoadingIndicator.svelte +61 -22
  109. package/dist/utils/icon/loadingShapes.d.ts +1 -0
  110. package/dist/utils/icon/loadingShapes.js +15 -0
  111. package/package.json +5 -2
  112. package/dist/actions/clickOutside.d.ts +0 -3
  113. package/dist/actions/clickOutside.js +0 -13
  114. package/dist/actions/floating.d.ts +0 -20
  115. package/dist/actions/floating.js +0 -87
  116. package/dist/actions/index.d.ts +0 -6
  117. package/dist/actions/index.js +0 -6
  118. package/dist/actions/keyboard.d.ts +0 -3
  119. package/dist/actions/keyboard.js +0 -14
  120. package/dist/actions/portal.d.ts +0 -3
  121. package/dist/actions/portal.js +0 -8
  122. package/dist/actions/positionFloating.d.ts +0 -10
  123. package/dist/actions/positionFloating.js +0 -59
  124. package/dist/actions/rovingFocusGroup.d.ts +0 -20
  125. package/dist/actions/rovingFocusGroup.js +0 -82
  126. package/dist/animation/easing.d.ts +0 -6
  127. package/dist/animation/easing.js +0 -78
  128. package/dist/animation/outroClass.d.ts +0 -3
  129. package/dist/animation/outroClass.js +0 -20
  130. package/dist/animation/transitionTypes.d.ts +0 -8
  131. package/dist/animation/transitionTypes.js +0 -1
@@ -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>
@@ -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',