@noxlovette/material 0.6.0 → 0.7.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 (176) hide show
  1. package/claude-skill/material-design/SKILL.md +5 -2
  2. package/claude-skill/material-design/references/accessibility-checklist.md +5 -0
  3. package/claude-skill/material-design/references/component-patterns.md +24 -3
  4. package/claude-skill/material-design/references/motion-guide.md +53 -11
  5. package/claude-skill/material-design/references/tokens-and-styles.md +4 -0
  6. package/dist/animation/TransitionPatterns.stories.svelte +50 -0
  7. package/dist/animation/containerTransform.js +10 -4
  8. package/dist/animation/reducedMotion.d.ts +2 -0
  9. package/dist/animation/reducedMotion.js +2 -0
  10. package/dist/animation/sharedAxisTransition.d.ts +20 -3
  11. package/dist/animation/sharedAxisTransition.js +67 -8
  12. package/dist/attachments/drag.d.ts +52 -0
  13. package/dist/attachments/drag.js +217 -0
  14. package/dist/attachments/index.d.ts +1 -0
  15. package/dist/attachments/index.js +1 -0
  16. package/dist/components/badge/theme.js +1 -1
  17. package/dist/components/buttons/button-group/theme.d.ts +1 -1
  18. package/dist/components/buttons/button-group/theme.js +1 -1
  19. package/dist/components/buttons/connected/theme.d.ts +1 -1
  20. package/dist/components/buttons/connected/theme.js +1 -1
  21. package/dist/components/buttons/split-button/SplitButton.svelte +1 -1
  22. package/dist/components/buttons/split-button/theme.d.ts +1 -1
  23. package/dist/components/buttons/split-button/theme.js +1 -1
  24. package/dist/components/buttons/theme.d.ts +1 -1
  25. package/dist/components/buttons/theme.js +3 -3
  26. package/dist/components/cards/theme.js +1 -1
  27. package/dist/components/chips/Chip.mdx +24 -11
  28. package/dist/components/chips/Chip.stories.svelte +10 -0
  29. package/dist/components/chips/Chip.svelte +58 -18
  30. package/dist/components/chips/Chip.svelte.d.ts +3 -1
  31. package/dist/components/chips/ChipGroup.stories.svelte +73 -0
  32. package/dist/components/chips/ChipGroup.stories.svelte.d.ts +4 -0
  33. package/dist/components/chips/ChipGroup.svelte +242 -0
  34. package/dist/components/chips/ChipGroup.svelte.d.ts +33 -0
  35. package/dist/components/chips/index.d.ts +1 -0
  36. package/dist/components/chips/index.js +1 -0
  37. package/dist/components/chips/theme.d.ts +183 -22
  38. package/dist/components/chips/theme.js +117 -26
  39. package/dist/components/chips/types.d.ts +36 -2
  40. package/dist/components/containers/action-rail/theme.d.ts +1 -1
  41. package/dist/components/containers/action-rail/theme.js +2 -2
  42. package/dist/components/containers/app/theme.d.ts +2 -2
  43. package/dist/components/containers/app/theme.js +2 -2
  44. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +1 -1
  45. package/dist/components/containers/bottom-sheet/theme.d.ts +1 -1
  46. package/dist/components/containers/bottom-sheet/theme.js +2 -2
  47. package/dist/components/containers/carousel/theme.d.ts +1 -1
  48. package/dist/components/containers/carousel/theme.js +1 -1
  49. package/dist/components/containers/context-menu/theme.d.ts +1 -1
  50. package/dist/components/containers/context-menu/theme.js +2 -2
  51. package/dist/components/containers/dialogue/Dialogue.svelte +1 -1
  52. package/dist/components/containers/dialogue/theme.d.ts +1 -1
  53. package/dist/components/containers/dialogue/theme.js +3 -3
  54. package/dist/components/containers/dialogue/types.d.ts +1 -1
  55. package/dist/components/containers/divider/theme.d.ts +1 -1
  56. package/dist/components/containers/divider/theme.js +1 -1
  57. package/dist/components/containers/link-preview/theme.d.ts +1 -1
  58. package/dist/components/containers/link-preview/theme.js +2 -2
  59. package/dist/components/containers/list/List.svelte +8 -2
  60. package/dist/components/containers/list/ListItem.mdx +6 -0
  61. package/dist/components/containers/list/ListItem.svelte +77 -2
  62. package/dist/components/containers/list/context.d.ts +14 -0
  63. package/dist/components/containers/list/theme.d.ts +7 -1
  64. package/dist/components/containers/list/theme.js +11 -3
  65. package/dist/components/containers/menu/Menu.svelte +1 -1
  66. package/dist/components/containers/menu/MenuSub.svelte +1 -1
  67. package/dist/components/containers/menu/theme.d.ts +1 -1
  68. package/dist/components/containers/menu/theme.js +1 -1
  69. package/dist/components/containers/pane/DraggablePane.svelte +25 -63
  70. package/dist/components/containers/pane/Pane.svelte +1 -1
  71. package/dist/components/containers/pane/theme.d.ts +1 -1
  72. package/dist/components/containers/pane/theme.js +35 -9
  73. package/dist/components/containers/popover/theme.d.ts +4 -4
  74. package/dist/components/containers/popover/theme.js +2 -2
  75. package/dist/components/containers/scroll-area/theme.js +1 -1
  76. package/dist/components/containers/side-sheet/theme.d.ts +1 -1
  77. package/dist/components/containers/side-sheet/theme.js +1 -1
  78. package/dist/components/date/theme.d.ts +7 -7
  79. package/dist/components/date/theme.js +1 -1
  80. package/dist/components/forms/checkbox/theme.d.ts +1 -1
  81. package/dist/components/forms/checkbox/theme.js +2 -2
  82. package/dist/components/forms/command/theme.d.ts +4 -4
  83. package/dist/components/forms/command/theme.js +1 -1
  84. package/dist/components/forms/pin/theme.d.ts +1 -1
  85. package/dist/components/forms/pin/theme.js +1 -1
  86. package/dist/components/forms/radio-group/theme.d.ts +1 -1
  87. package/dist/components/forms/radio-group/theme.js +1 -1
  88. package/dist/components/forms/search/theme.d.ts +1 -1
  89. package/dist/components/forms/search/theme.js +1 -1
  90. package/dist/components/forms/select/Select.svelte +2 -2
  91. package/dist/components/forms/select/theme.d.ts +1 -1
  92. package/dist/components/forms/select/theme.js +2 -2
  93. package/dist/components/forms/slider/theme.d.ts +1 -1
  94. package/dist/components/forms/slider/theme.js +1 -1
  95. package/dist/components/forms/switch/theme.d.ts +1 -1
  96. package/dist/components/forms/switch/theme.js +1 -1
  97. package/dist/components/forms/textfield/theme.d.ts +1 -1
  98. package/dist/components/forms/textfield/theme.js +1 -1
  99. package/dist/components/forms/toggle-group/theme.d.ts +1 -1
  100. package/dist/components/forms/toggle-group/theme.js +1 -1
  101. package/dist/components/forms/tooltip/theme.d.ts +1 -1
  102. package/dist/components/forms/tooltip/theme.js +2 -2
  103. package/dist/components/misc/Avatar.stories.svelte +56 -7
  104. package/dist/components/misc/Avatar.stories.svelte.d.ts +2 -17
  105. package/dist/components/misc/Avatar.svelte +82 -8
  106. package/dist/components/misc/Avatar.svelte.d.ts +5 -0
  107. package/dist/components/misc/ThemeSwitcher.svelte +1 -1
  108. package/dist/components/misc/theme.d.ts +6 -0
  109. package/dist/components/misc/theme.js +6 -4
  110. package/dist/components/misc/types.d.ts +17 -1
  111. package/dist/components/nav/appbar/theme.d.ts +1 -1
  112. package/dist/components/nav/appbar/theme.js +3 -2
  113. package/dist/components/nav/breadcrumb/theme.d.ts +1 -1
  114. package/dist/components/nav/breadcrumb/theme.js +1 -1
  115. package/dist/components/nav/currentRoute.d.ts +11 -0
  116. package/dist/components/nav/currentRoute.js +28 -0
  117. package/dist/components/nav/navbar/NavbarItem.svelte +3 -6
  118. package/dist/components/nav/navbar/theme.d.ts +3 -3
  119. package/dist/components/nav/navbar/theme.js +2 -2
  120. package/dist/components/nav/rail/Rail.mdx +65 -23
  121. package/dist/components/nav/rail/Rail.stories.svelte +24 -4
  122. package/dist/components/nav/rail/Rail.stories.svelte.d.ts +2 -17
  123. package/dist/components/nav/rail/Rail.svelte +165 -48
  124. package/dist/components/nav/rail/Rail.svelte.d.ts +6 -3
  125. package/dist/components/nav/rail/RailItem.svelte +122 -65
  126. package/dist/components/nav/rail/RailItem.svelte.d.ts +3 -2
  127. package/dist/components/nav/rail/theme.d.ts +70 -85
  128. package/dist/components/nav/rail/theme.js +80 -67
  129. package/dist/components/nav/rail/types.d.ts +4 -0
  130. package/dist/components/nav/tabs/Tabs.stories.svelte +2 -1
  131. package/dist/components/nav/tabs/theme.d.ts +1 -1
  132. package/dist/components/nav/tabs/theme.js +1 -1
  133. package/dist/components/pill/theme.d.ts +1 -1
  134. package/dist/components/pill/theme.js +1 -1
  135. package/dist/components/progress/LinearProgress.svelte +1 -1
  136. package/dist/components/snackbar/theme.d.ts +1 -1
  137. package/dist/components/snackbar/theme.js +2 -2
  138. package/dist/components/table/theme.d.ts +1 -1
  139. package/dist/components/table/theme.js +1 -1
  140. package/dist/components/time/TimeField.svelte +1 -1
  141. package/dist/components/time/theme.d.ts +1 -1
  142. package/dist/components/time/theme.js +1 -1
  143. package/dist/components/toolbar/theme.d.ts +1 -1
  144. package/dist/components/toolbar/theme.js +2 -2
  145. package/dist/components/typography/body/theme.d.ts +1 -1
  146. package/dist/components/typography/body/theme.js +1 -1
  147. package/dist/components/typography/display/theme.d.ts +1 -1
  148. package/dist/components/typography/display/theme.js +1 -1
  149. package/dist/components/typography/headline/theme.d.ts +1 -1
  150. package/dist/components/typography/headline/theme.js +1 -1
  151. package/dist/components/typography/kbd/theme.d.ts +1 -1
  152. package/dist/components/typography/kbd/theme.js +1 -1
  153. package/dist/components/typography/label/theme.js +1 -1
  154. package/dist/components/typography/title/theme.d.ts +1 -1
  155. package/dist/components/typography/title/theme.js +1 -1
  156. package/dist/index.css +3 -0
  157. package/dist/styles/components.css +50 -0
  158. package/dist/styles/icon.css +13 -0
  159. package/dist/styles/layers.css +33 -0
  160. package/dist/utils/icon/Icon.stories.svelte +97 -0
  161. package/dist/utils/icon/Icon.stories.svelte.d.ts +4 -0
  162. package/dist/utils/icon/Icon.svelte +101 -12
  163. package/dist/utils/icon/Icon.svelte.d.ts +9 -0
  164. package/dist/utils/icon/MaterialSymbolsProvider.svelte +11 -4
  165. package/dist/utils/icon/base-icons.d.ts +1 -1
  166. package/dist/utils/icon/base-icons.js +1 -1
  167. package/dist/utils/icon/icon-names.d.ts +2 -0
  168. package/dist/utils/icon/icon-names.js +2 -0
  169. package/dist/utils/icon/swap.d.ts +6 -0
  170. package/dist/utils/icon/swap.js +45 -0
  171. package/dist/utils/icon/theme.d.ts +1 -1
  172. package/dist/utils/icon/theme.js +1 -1
  173. package/dist/utils/icon/types.d.ts +59 -7
  174. package/dist/utils/tv.d.ts +5 -0
  175. package/dist/utils/tv.js +20 -0
  176. package/package.json +2 -1
@@ -1,72 +1,163 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../utils/tv.js';
2
+ /*
3
+ Values from the M3 chip tokens (md.comp.{assist,filter,input,suggestion}-chip.*):
4
+ https://m3.material.io/components/chips/specs
5
+
6
+ - 32dp tall, 8dp corners, label-large. Padding is 16dp on each side, 8dp on a side that holds an
7
+ icon, and 4dp before an avatar; 8dp between elements. Icons are 18dp, avatars a 24dp circle.
8
+ - The 1dp outline is a border, so it sits inside the 32dp and stays visible (and darkens) while
9
+ the separate focus indicator is showing.
10
+ - Disabled: flat chips lose their fill-less outline to on-surface/12; elevated and selected
11
+ containers turn on-surface/12 with no shadow; label and icons are on-surface/38.
12
+
13
+ Chip doesn't use md-component-button-base: its disabled fill would paint flat chips, which M3
14
+ leaves unfilled.
15
+
16
+ An input chip is a container holding two sibling buttons, the primary `action` and the remove
17
+ button, since one can't nest inside the other. The container keeps the look (and shows the focus
18
+ indicator while the action has focus); the action carries the padding, and its state layer covers the whole chip.
19
+ */
2
20
  export const chip = tv({
3
21
  slots: {
4
- base: 'md-component-button-base group inline-flex h-spacing-400 max-w-max items-center justify-center gap-spacing-100 rounded-sm before:rounded-sm px-spacing-200 md-sys-typescale-label-large',
5
- icon: 'shrink-0',
22
+ // in-data-dragged: M3's dragged state, set by ChipGroup on the chip's slot: 8dp elevation and
23
+ // a 16% state layer.
24
+ base: 'in-data-dragged:shadow-elevation-4 in-data-dragged:before:bg-current/16 state-layer md-sys-state-focus-indicator relative inline-flex h-spacing-400 max-w-max shrink-0 items-center justify-center gap-spacing-100 rounded-sm border border-transparent px-spacing-200 md-sys-typescale-label-large select-none before:rounded-sm transition-[background-color,border-color,box-shadow] md-sys-motion-effects',
25
+ // 18dp is the chip spec's own icon size, between the 16 and 20 Icon presets.
26
+ icon: 'size-[18px] shrink-0 text-[18px]',
27
+ avatar: 'size-spacing-300 shrink-0 overflow-hidden rounded-full',
28
+ // Static, so its Layer (state layer and hit area) spans the whole chip; the remove button is
29
+ // positioned and later in the DOM, so it stays on top.
30
+ action: 'static flex h-full min-w-spacing-0 cursor-pointer items-center gap-spacing-100 self-stretch rounded-sm px-spacing-200 outline-none disabled:cursor-not-allowed',
6
31
  label: '',
7
- trailing: 'relative -mr-spacing-100 ml-spacing-50 inline-flex size-[18px] shrink-0 items-center justify-center rounded-full',
8
- checkIcon: 'size-spacing-200 shrink-0'
32
+ // 18dp glyph with a 48dp touch target around it (::after).
33
+ trailing: "relative inline-flex size-[18px] shrink-0 cursor-pointer items-center justify-center rounded-full after:absolute after:top-1/2 after:left-1/2 after:size-spacing-600 after:-translate-1/2 after:content-['']",
34
+ checkIcon: 'size-[18px] shrink-0'
9
35
  },
10
36
  variants: {
11
37
  variant: {
12
- assist: '',
13
- filter: '',
14
- input: '',
15
- suggestion: ''
38
+ assist: { base: 'text-md-sys-color-on-surface', icon: 'text-md-sys-color-primary' },
39
+ filter: { base: 'text-md-sys-color-on-surface-variant' },
40
+ input: { base: 'text-md-sys-color-on-surface-variant' },
41
+ suggestion: {
42
+ base: 'text-md-sys-color-on-surface-variant',
43
+ icon: 'text-md-sys-color-primary'
44
+ }
16
45
  },
17
46
  elevated: {
18
- true: '',
47
+ true: { base: 'shadow-elevation-1 hover:shadow-elevation-2' },
19
48
  false: ''
20
49
  },
21
50
  selected: {
22
- true: '',
51
+ true: {
52
+ base: 'bg-md-sys-color-secondary-container text-md-sys-color-on-secondary-container'
53
+ },
54
+ false: ''
55
+ },
56
+ leading: {
57
+ none: '',
58
+ icon: { base: 'pl-spacing-100', action: 'pl-spacing-100' },
59
+ avatar: { base: 'pl-spacing-50', action: 'pl-spacing-50' }
60
+ },
61
+ /** A trailing icon: the filter chip's `trailingIconProps`, or the input chip's remove button. */
62
+ trailing: {
63
+ true: { base: 'pr-spacing-100', action: 'pr-spacing-100' },
23
64
  false: ''
24
65
  },
25
66
  disabled: {
26
- true: '',
67
+ true: {
68
+ base: 'text-md-sys-color-on-surface/38 shadow-none hover:shadow-none',
69
+ icon: 'text-md-sys-color-on-surface/38',
70
+ avatar: 'opacity-38',
71
+ trailing: 'text-md-sys-color-on-surface/38',
72
+ checkIcon: 'text-md-sys-color-on-surface/38'
73
+ },
27
74
  false: ''
28
75
  }
29
76
  },
30
77
  compoundVariants: [
78
+ // Flat, unselected: no fill, outline-variant border that darkens on focus.
31
79
  {
32
80
  elevated: false,
33
81
  selected: false,
82
+ // The spec keeps a dragged flat chip unfilled, but one floating over its neighbours needs an
83
+ // opaque container, so it takes the elevated chip's.
34
84
  class: {
35
- base: 'bg-md-sys-color-surface text-md-sys-color-on-surface-variant outline outline-md-sys-color-outline-variant'
85
+ base: 'border-md-sys-color-outline-variant focus-visible:border-md-sys-color-on-surface-variant in-data-dragged:bg-md-sys-color-surface-container-low'
36
86
  }
37
87
  },
38
88
  {
39
- elevated: true,
40
- selected: false,
89
+ variant: 'assist',
90
+ elevated: false,
91
+ class: { base: 'focus-visible:border-md-sys-color-on-surface' }
92
+ },
93
+ { elevated: true, selected: false, class: { base: 'bg-md-sys-color-surface-container-low' } },
94
+ // A flat selected filter chip lifts on hover.
95
+ {
96
+ variant: 'filter',
97
+ elevated: false,
98
+ selected: true,
99
+ class: { base: 'hover:shadow-elevation-1' }
100
+ },
101
+ // Unselected filter icons are primary; selected ones inherit on-secondary-container.
102
+ { variant: 'filter', selected: false, class: { icon: 'text-md-sys-color-primary' } },
103
+ // Input chips are the reverse: on-surface-variant, primary once selected.
104
+ { variant: 'input', selected: true, class: { icon: 'text-md-sys-color-primary' } },
105
+ // The input chip's container has no padding of its own before the action, and needs the
106
+ // focus indicator (and darker border) while the action is focused.
107
+ {
108
+ variant: 'input',
41
109
  class: {
42
- base: 'bg-md-sys-color-surface-container-low text-md-sys-color-on-surface-variant shadow-elevation-1 hover:shadow-elevation-2'
110
+ base: 'pl-spacing-0 has-[[data-chip-action]:focus-visible]:outline-3 has-[[data-chip-action]:focus-visible]:outline-offset-2 has-[[data-chip-action]:focus-visible]:outline-md-sys-color-secondary has-[[data-chip-action]:focus-visible]:outline-solid'
43
111
  }
44
112
  },
113
+ { variant: 'input', trailing: false, class: { base: 'pr-spacing-0' } },
45
114
  {
46
- selected: true,
115
+ variant: 'input',
116
+ elevated: false,
117
+ selected: false,
47
118
  class: {
48
- base: 'bg-md-sys-color-secondary-container text-md-sys-color-on-secondary-container'
119
+ base: 'has-[[data-chip-action]:focus-visible]:border-md-sys-color-on-surface-variant'
49
120
  }
50
121
  },
51
122
  {
52
- variant: 'assist',
123
+ disabled: true,
124
+ elevated: false,
53
125
  selected: false,
54
- class: { icon: 'text-md-sys-color-primary' }
126
+ class: { base: 'border-md-sys-color-on-surface/12' }
55
127
  },
56
128
  {
57
129
  disabled: true,
58
- class: {
59
- base: 'cursor-not-allowed bg-md-sys-color-on-surface/10 text-md-sys-color-on-surface/38 outline-md-sys-color-on-surface/12 hover:shadow-elevation-1',
60
- icon: 'text-md-sys-color-on-surface/38',
61
- trailing: 'text-md-sys-color-on-surface/38 pointer-events-none',
62
- checkIcon: 'text-md-sys-color-on-surface/38'
63
- }
130
+ elevated: true,
131
+ class: { base: 'bg-md-sys-color-on-surface/12' }
132
+ },
133
+ {
134
+ disabled: true,
135
+ selected: true,
136
+ class: { base: 'bg-md-sys-color-on-surface/12 text-md-sys-color-on-surface/38' }
64
137
  }
65
138
  ],
66
139
  defaultVariants: {
67
140
  variant: 'assist',
68
141
  elevated: false,
69
142
  selected: false,
143
+ leading: 'none',
144
+ trailing: false,
70
145
  disabled: false
71
146
  }
72
147
  });
148
+ /** Layout of a set of chips: 8dp apart, wrapping onto new lines. */
149
+ export const chipGroup = tv({
150
+ slots: {
151
+ base: 'relative flex flex-wrap gap-spacing-100',
152
+ // Its own stacking context only while dragged, so the chip floats over its neighbours.
153
+ item: 'relative flex touch-manipulation data-dragged:z-10 data-dragged:cursor-grabbing',
154
+ hint: 'sr-only',
155
+ live: 'sr-only'
156
+ },
157
+ variants: {
158
+ reorderable: {
159
+ true: { item: 'cursor-grab select-none [-webkit-touch-callout:none]' },
160
+ false: ''
161
+ }
162
+ }
163
+ });
@@ -26,18 +26,52 @@ export type ChipProps = ChipVariants & AnchorButtonAttributes & {
26
26
  disabled?: boolean;
27
27
  /** Configuration for the leading icon. */
28
28
  iconProps?: IconProps;
29
+ /**
30
+ * Filter chips only: a trailing icon, e.g. `arrow_drop_down` on a chip that opens a menu.
31
+ */
32
+ trailingIconProps?: IconProps;
29
33
  /**
30
34
  * Custom leading content (e.g. an avatar image) rendered instead of `iconProps`.
31
35
  * Typically used by 'input' chips to represent a contact or entity.
32
36
  */
33
37
  avatar?: Snippet;
34
38
  /**
35
- * Called when the trailing remove icon is activated.
36
- * Providing this renders the trailing remove affordance on 'input' chips.
39
+ * Called when the trailing remove icon is activated, or on Backspace/Delete while the chip
40
+ * has focus. Providing this renders the trailing remove affordance on 'input' chips. Move
41
+ * focus to a neighbouring chip (or the input) afterwards, or it falls back to the page.
37
42
  */
38
43
  onRemove?: () => void;
44
+ /**
45
+ * Accessible name of the input chip's remove button. Name the item, e.g. `Remove Ada`.
46
+ * @default 'Remove'
47
+ */
48
+ removeLabel?: string;
39
49
  /** The label content of the chip. */
40
50
  children: Snippet;
41
51
  /** The `formaction` attribute for the chip when used in a form. */
42
52
  formaction?: string;
43
53
  };
54
+ export type ChipGroupProps<T> = {
55
+ /** The items, one chip each. With `reorderable`, dragging rewrites their order. Bindable. */
56
+ items: T[];
57
+ /** A stable, unique key per item. */
58
+ key: (item: T) => string;
59
+ /** Renders one item's `Chip`. */
60
+ chip: Snippet<[item: T, index: number]>;
61
+ /**
62
+ * Lets people reorder the chips: drag one (on touch, press and hold first), or focus one and
63
+ * press Alt+Arrow. The dragged chip takes M3's dragged state and its neighbours make room.
64
+ * @default false
65
+ */
66
+ reorderable?: boolean;
67
+ /** Called with the new order after a drag or a keyboard move. */
68
+ onReorder?: (items: T[]) => void;
69
+ /**
70
+ * The item's name in the announcement after a move ("Ada, moved to 2 of 5"). Defaults to its
71
+ * `key`.
72
+ */
73
+ itemLabel?: (item: T) => string;
74
+ /** Accessible name of the group. */
75
+ 'aria-label'?: string;
76
+ class?: string;
77
+ };
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type ActionRailVariants = VariantProps<typeof actionRail>;
3
3
  export declare const actionRail: import("tailwind-variants").TVReturnType<{
4
4
  anchor: {
@@ -1,7 +1,7 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const actionRail = tv({
3
3
  slots: {
4
- base: 'z-30 flex flex-col items-center justify-center right-spacing-0'
4
+ base: 'z-layer-bar flex flex-col items-center justify-center right-spacing-0'
5
5
  },
6
6
  variants: {
7
7
  anchor: {
@@ -1,3 +1,3 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type AppVariants = VariantProps<typeof app>;
3
- export declare const app: import("tailwind-variants").TVReturnType<{} | {} | {}, undefined, "bg-md-sys-color-surface-container text-md-sys-color-on-background min-h-dvh caret-md-sys-color-primary selection:bg-md-sys-color-primary-container selection:text-md-sys-color-on-primary-container relative min-h-dvh pt-[env(safe-area-inset-top)] pr-[env(safe-area-inset-right)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] antialiased", {} | {}, undefined, import("tailwind-variants").TVReturnTypeLike<unknown, undefined>>;
3
+ export declare const app: import("tailwind-variants").TVReturnType<{} | {} | {}, undefined, "md-app bg-md-sys-color-surface-container text-md-sys-color-on-background min-h-dvh caret-md-sys-color-primary selection:bg-md-sys-color-primary-container selection:text-md-sys-color-on-primary-container relative min-h-dvh pt-[env(safe-area-inset-top)] pr-[env(safe-area-inset-right)] pb-[env(safe-area-inset-bottom)] pl-[calc(env(safe-area-inset-left)+var(--md-rail-inset,0px))] antialiased", {} | {}, undefined, import("tailwind-variants").TVReturnTypeLike<unknown, undefined>>;
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const app = tv({
3
- base: 'bg-md-sys-color-surface-container text-md-sys-color-on-background min-h-dvh caret-md-sys-color-primary selection:bg-md-sys-color-primary-container selection:text-md-sys-color-on-primary-container relative min-h-dvh pt-[env(safe-area-inset-top)] pr-[env(safe-area-inset-right)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] antialiased'
3
+ base: 'md-app bg-md-sys-color-surface-container text-md-sys-color-on-background min-h-dvh caret-md-sys-color-primary selection:bg-md-sys-color-primary-container selection:text-md-sys-color-on-primary-container relative min-h-dvh pt-[env(safe-area-inset-top)] pr-[env(safe-area-inset-right)] pb-[env(safe-area-inset-bottom)] pl-[calc(env(safe-area-inset-left)+var(--md-rail-inset,0px))] antialiased'
4
4
  });
@@ -23,7 +23,7 @@
23
23
  { name: 'download', label: 'Download' },
24
24
  { name: 'edit', label: 'Rename' },
25
25
  { name: 'delete', label: 'Move to trash' }
26
- ];
26
+ ] as const;
27
27
  </script>
28
28
 
29
29
  <script lang="ts">
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type BottomSheetVariants = VariantProps<typeof bottomSheet>;
3
3
  export declare const bottomSheet: import("tailwind-variants").TVReturnType<{
4
4
  variant: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  /*
3
3
  M3 bottom sheet (https://m3.material.io/components/bottom-sheets/specs): `surface-container-low`
4
4
  at elevation 1, 28dp top corners. Full window width up to 640dp; past 640dp it keeps 56dp side
@@ -11,7 +11,7 @@ import { tv } from 'tailwind-variants';
11
11
  export const bottomSheet = tv({
12
12
  slots: {
13
13
  base: [
14
- 'fixed inset-x-spacing-0 top-auto bottom-spacing-0 z-40 mx-auto mt-auto mb-spacing-0 p-spacing-0',
14
+ 'fixed inset-x-spacing-0 top-auto bottom-spacing-0 z-layer-sheet mx-auto mt-auto mb-spacing-0 p-spacing-0',
15
15
  'w-full max-w-160 sm:w-[calc(100%-7rem)]',
16
16
  'max-h-[calc(100dvh-4.5rem)] sm:max-h-[calc(100dvh-3.5rem)]',
17
17
  'overflow-visible bg-transparent outline-none'
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type CarouselVariants = VariantProps<typeof carousel>;
3
3
  export declare const carousel: import("tailwind-variants").TVReturnType<{
4
4
  layout: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  /*
3
3
  M3 carousel (https://m3.material.io/components/carousel/specs): 28dp items on `surface`, 8dp
4
4
  apart. Multi-browse and hero layouts pad 16dp on both sides and 8dp top/bottom; uncontained
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type ContextMenuVariants = VariantProps<typeof contextMenu>;
3
3
  export declare const contextMenu: import("tailwind-variants").TVReturnType<{
4
4
  selected: {
@@ -1,8 +1,8 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const contextMenu = tv({
3
3
  slots: {
4
4
  content: `
5
- z-[100] min-w-48 max-w-sm gap-spacing-50 overflow-y-auto rounded-lg
5
+ z-layer-popup min-w-48 max-w-sm gap-spacing-50 overflow-y-auto rounded-lg
6
6
  bg-md-sys-color-surface-container-high px-spacing-100 py-spacing-50
7
7
  shadow-elevation-3 ring-md-sys-color-outline/10
8
8
  `,
@@ -22,7 +22,7 @@ on screen until confirmed, dismissed, or a required action has been taken.
22
22
  headline,
23
23
  supportingText,
24
24
  confirmText,
25
- cancelText = 'Отмена',
25
+ cancelText = 'Cancel',
26
26
  confirmAction,
27
27
  loading = false,
28
28
  portalDisabled = false,
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type DialogueVariants = VariantProps<typeof dialogue>;
3
3
  export declare const dialogue: import("tailwind-variants").TVReturnType<{
4
4
  [key: string]: {
@@ -1,8 +1,8 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const dialogue = tv({
3
3
  slots: {
4
- base: 'fixed inset-spacing-0 z-40 bg-md-sys-color-scrim/32',
5
- inner: 'fixed inset-spacing-0 z-50 m-auto h-max w-full outline-none bg-md-sys-color-surface-container-high shadow-elevation-3 flex flex-col rounded-[28px] p-spacing-300 gap-spacing-200',
4
+ base: 'fixed inset-spacing-0 z-layer-modal bg-md-sys-color-scrim/32',
5
+ inner: 'fixed inset-spacing-0 z-layer-modal m-auto h-max w-full outline-none bg-md-sys-color-surface-container-high shadow-elevation-3 flex flex-col rounded-[28px] p-spacing-300 gap-spacing-200',
6
6
  headlineContainer: 'md-sys-typescale-headline-small text-md-sys-color-on-surface',
7
7
  supportingTextContainer: 'md-sys-typescale-body-medium text-md-sys-color-on-surface-variant',
8
8
  buttonContainer: 'mt-spacing-100 flex items-center justify-end gap-spacing-100'
@@ -18,7 +18,7 @@ export type DialogueProps = DialogueVariants & Dialog.RootProps & {
18
18
  headline?: string;
19
19
  /** The label for the confirmation button. */
20
20
  confirmText: string;
21
- /** The label for the cancel button. Defaults to 'Отмена'. */
21
+ /** The label for the cancel button. Defaults to 'Cancel'. */
22
22
  cancelText?: string;
23
23
  /**
24
24
  * Optional additional content to be rendered between the supporting text
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type HrVariants = VariantProps<typeof hr>;
3
3
  export declare const hr: import("tailwind-variants").TVReturnType<{
4
4
  orientation: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const hr = tv({
3
3
  base: 'bg-md-sys-color-outline-variant',
4
4
  variants: {
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type LinkPreviewVariants = VariantProps<typeof linkPreview>;
3
3
  export declare const linkPreview: import("tailwind-variants").TVReturnType<{
4
4
  [key: string]: {
@@ -1,7 +1,7 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const linkPreview = tv({
3
3
  slots: {
4
- base: 'relative z-[100] min-w-48 max-w-sm rounded-2xl bg-md-sys-color-surface-container-high p-spacing-200 shadow-elevation-3 ring-1 ring-md-sys-color-outline/20 overflow-hidden',
4
+ base: 'relative z-layer-popup min-w-48 max-w-sm rounded-2xl bg-md-sys-color-surface-container-high p-spacing-200 shadow-elevation-3 ring-1 ring-md-sys-color-outline/20 overflow-hidden',
5
5
  content: 'flex flex-col gap-spacing-100'
6
6
  }
7
7
  });
@@ -6,13 +6,19 @@
6
6
 
7
7
  let { variant = 'standard', class: className, children, ...restProps }: ListProps = $props();
8
8
 
9
+ let element = $state<HTMLElement>();
10
+
9
11
  setListContext({
10
12
  get variant() {
11
13
  return variant;
12
- }
14
+ },
15
+ get element() {
16
+ return element;
17
+ },
18
+ from: null
13
19
  });
14
20
  </script>
15
21
 
16
- <ul class={list({ variant, class: clsx(className) })} {...restProps}>
22
+ <ul bind:this={element} class={list({ variant, class: clsx(className) })} {...restProps}>
17
23
  {@render children?.()}
18
24
  </ul>
@@ -91,6 +91,12 @@ chevron, `aria-expanded` and `aria-controls`. The nested items are `inert` while
91
91
  content to 38%. Hover, focus and press tints come from the shared state layer. The keyboard focus
92
92
  ring is drawn inside the item, so segments don't overlap.
93
93
 
94
+ When the selection moves inside one `List` (a nav list following the route, a single-select
95
+ list), the fill travels: it springs from where the old one was drawn to the newly selected item,
96
+ up or down, on the `spatial` spring. It passes over the items in between, and a selection that
97
+ moves again mid-flight picks up from where the fill is. When several items change at once, or
98
+ under reduced motion, the fill just appears on the selected item.
99
+
94
100
  <Canvas of={ListStories.Disabled} />
95
101
 
96
102
  ## Where this differs from the tokens
@@ -2,7 +2,10 @@
2
2
  import { Icon, Layer } from '../../../utils/index.js';
3
3
  import clsx from 'clsx';
4
4
  import { listItem } from './theme.js';
5
- import { getListContext } from './context.js';
5
+ import { getListContext, type SelectionRect } from './context.js';
6
+ import { untrack } from 'svelte';
7
+ import { animate, type AnimationPlaybackControlsWithThen } from 'motion';
8
+ import { springTokens, springTransition } from '../../../animation/spring.js';
6
9
  import type { ListitemProps } from './types.js';
7
10
  import Badge from '../../badge/Badge.svelte';
8
11
 
@@ -53,6 +56,71 @@
53
56
  })
54
57
  );
55
58
 
59
+ /*
60
+ The selected fill travels. Before an update, the item losing the selection leaves where its
61
+ fill is drawn (mid-flight too) in the List's context; after it, the item gaining the
62
+ selection springs its new fill over from there, on spatial: a sizeable move, and part of a
63
+ navigation, so no fastSpatial bounce. Without a List, or with several items selected or
64
+ deselected at once, the fill just appears where it belongs. Reduced motion skips the ride.
65
+ */
66
+ const showFill = $derived(!!selected && !disabled);
67
+ let fill = $state<HTMLElement>();
68
+ let travelling = $state(false);
69
+ let wasShown = untrack(() => showFill);
70
+ let ride: AnimationPlaybackControlsWithThen | undefined;
71
+
72
+ const measure = (el: HTMLElement): SelectionRect | null => {
73
+ const box = ctx?.element?.getBoundingClientRect();
74
+ if (!box) return null;
75
+ const r = el.getBoundingClientRect();
76
+ return {
77
+ top: r.top - box.top,
78
+ bottom: r.bottom - box.top,
79
+ left: r.left - box.left,
80
+ right: r.right - box.left
81
+ };
82
+ };
83
+
84
+ $effect.pre(() => {
85
+ const shown = showFill;
86
+ untrack(() => {
87
+ if (!wasShown || shown || !ctx || !fill) return;
88
+ ctx.from = measure(fill);
89
+ ride?.stop();
90
+ travelling = false;
91
+ // Only for this update: a deselect with no select after it leaves nothing behind.
92
+ queueMicrotask(() => (ctx.from = null));
93
+ });
94
+ });
95
+
96
+ $effect(() => {
97
+ const shown = showFill;
98
+ untrack(() => {
99
+ const gained = shown && !wasShown;
100
+ wasShown = shown;
101
+ const from = ctx?.from;
102
+ if (!gained || !from || !fill) return;
103
+ ctx!.from = null;
104
+ const to = measure(fill);
105
+ if (!to || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
106
+ // Above the items it passes over, which are the item's siblings.
107
+ travelling = true;
108
+ ride = animate(
109
+ fill,
110
+ {
111
+ top: [`${from.top - to.top}px`, '0px'],
112
+ bottom: [`${to.bottom - from.bottom}px`, '0px'],
113
+ left: [`${from.left - to.left}px`, '0px'],
114
+ right: [`${to.right - from.right}px`, '0px']
115
+ },
116
+ springTransition(springTokens.spatial)
117
+ );
118
+ ride.then(() => (travelling = false));
119
+ });
120
+ });
121
+
122
+ $effect(() => () => ride?.stop());
123
+
56
124
  const attrs = $derived.by(() => {
57
125
  const { label: _, href, onclick, ...extra } = rest;
58
126
  const a: Record<string, unknown> = { ...extra };
@@ -81,6 +149,9 @@
81
149
  </script>
82
150
 
83
151
  {#snippet content()}
152
+ {#if showFill}
153
+ <span bind:this={fill} class={cls.selection()} aria-hidden="true"></span>
154
+ {/if}
84
155
  {#if interactive}
85
156
  <Layer />
86
157
  {/if}
@@ -121,7 +192,11 @@
121
192
  {/snippet}
122
193
 
123
194
  {#snippet item()}
124
- <svelte:element this={tag} class={cls.base({ class: clsx(className) })} {...attrs}>
195
+ <svelte:element
196
+ this={tag}
197
+ class={cls.base({ class: clsx(className, travelling && 'z-10') })}
198
+ {...attrs}
199
+ >
125
200
  {@render content()}
126
201
  </svelte:element>
127
202
  {#if children}
@@ -1,5 +1,19 @@
1
+ /** Where the outgoing selection was drawn, relative to the list's box. */
2
+ export type SelectionRect = {
3
+ top: number;
4
+ bottom: number;
5
+ left: number;
6
+ right: number;
7
+ };
1
8
  type ListContext = {
2
9
  readonly variant: 'standard' | 'segmented';
10
+ /** The list's own element, which selection rects are measured against. */
11
+ readonly element: HTMLElement | undefined;
12
+ /**
13
+ * Handed from the item losing the selection to the one gaining it within one update, so the
14
+ * selected fill travels between them. Cleared once that update settles.
15
+ */
16
+ from: SelectionRect | null;
3
17
  };
4
18
  export declare const setListContext: (ctx: ListContext) => ListContext;
5
19
  export declare const getListContext: () => ListContext | undefined;
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type ListVariants = VariantProps<typeof list>;
3
3
  export type ListMediaVariants = VariantProps<typeof listMedia>;
4
4
  export declare const list: import("tailwind-variants").TVReturnType<{
@@ -85,6 +85,7 @@ export declare const listItem: import("tailwind-variants").TVReturnType<{
85
85
  };
86
86
  }, {
87
87
  base: string[];
88
+ selection: string;
88
89
  leading: string;
89
90
  body: string;
90
91
  overline: string;
@@ -165,6 +166,7 @@ export declare const listItem: import("tailwind-variants").TVReturnType<{
165
166
  };
166
167
  }, {
167
168
  base: string[];
169
+ selection: string;
168
170
  leading: string;
169
171
  body: string;
170
172
  overline: string;
@@ -245,6 +247,7 @@ export declare const listItem: import("tailwind-variants").TVReturnType<{
245
247
  };
246
248
  }, {
247
249
  base: string[];
250
+ selection: string;
248
251
  leading: string;
249
252
  body: string;
250
253
  overline: string;
@@ -327,6 +330,7 @@ export declare const listiem: import("tailwind-variants").TVReturnType<{
327
330
  };
328
331
  }, {
329
332
  base: string[];
333
+ selection: string;
330
334
  leading: string;
331
335
  body: string;
332
336
  overline: string;
@@ -407,6 +411,7 @@ export declare const listiem: import("tailwind-variants").TVReturnType<{
407
411
  };
408
412
  }, {
409
413
  base: string[];
414
+ selection: string;
410
415
  leading: string;
411
416
  body: string;
412
417
  overline: string;
@@ -487,6 +492,7 @@ export declare const listiem: import("tailwind-variants").TVReturnType<{
487
492
  };
488
493
  }, {
489
494
  base: string[];
495
+ selection: string;
490
496
  leading: string;
491
497
  body: string;
492
498
  overline: string;