noph-ui 0.51.2 → 0.52.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 (130) hide show
  1. package/dist/animation.d.ts +26 -1
  2. package/dist/animation.js +50 -2
  3. package/dist/app-bar/AppBar.svelte +71 -7
  4. package/dist/app-bar/types.d.ts +6 -0
  5. package/dist/autocomplete/AutoComplete.svelte +6 -31
  6. package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
  7. package/dist/badge/Badge.svelte +12 -4
  8. package/dist/badge/description.d.ts +4 -0
  9. package/dist/badge/description.js +4 -0
  10. package/dist/button/Button.svelte +55 -20
  11. package/dist/button/ButtonGroup.svelte +139 -19
  12. package/dist/button/ButtonGroup.svelte.d.ts +1 -1
  13. package/dist/button/IconButton.svelte +49 -18
  14. package/dist/button/SegmentedButton.svelte +44 -4
  15. package/dist/button/SplitButton.svelte +77 -3
  16. package/dist/button/SplitButton.svelte.d.ts +1 -1
  17. package/dist/button/buttonGroup.d.ts +0 -4
  18. package/dist/button/buttonGroup.js +0 -9
  19. package/dist/button/groupContext.d.ts +10 -0
  20. package/dist/button/groupContext.js +4 -0
  21. package/dist/button/types.d.ts +19 -0
  22. package/dist/checkbox/Checkbox.svelte +5 -0
  23. package/dist/chip/ChipSet.svelte +28 -2
  24. package/dist/chip/FilterChip.svelte +32 -4
  25. package/dist/chip/InputChip.svelte +127 -26
  26. package/dist/chip/SuggestionChip.svelte +6 -22
  27. package/dist/chip/SuggestionChip.svelte.d.ts +1 -2
  28. package/dist/chip/types.d.ts +8 -1
  29. package/dist/date-picker/Calendar.svelte +4 -2
  30. package/dist/date-picker/DatePickerDialog.svelte +1 -0
  31. package/dist/date-picker/DatePickerDialog.svelte.d.ts +1 -1
  32. package/dist/date-picker/DateRangePicker.svelte +1 -0
  33. package/dist/date-picker/DateRangePicker.svelte.d.ts +1 -1
  34. package/dist/date-picker/DockedDatePicker.svelte +12 -5
  35. package/dist/date-picker/DockedDatePicker.svelte.d.ts +1 -1
  36. package/dist/date-picker/DockedDateTimePicker.svelte +12 -5
  37. package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +1 -1
  38. package/dist/date-picker/YearGrid.svelte +4 -2
  39. package/dist/dialog/Dialog.svelte +171 -33
  40. package/dist/dialog/Dialog.svelte.d.ts +2 -1
  41. package/dist/dialog/types.d.ts +6 -0
  42. package/dist/divider/Divider.svelte +36 -10
  43. package/dist/divider/types.d.ts +1 -0
  44. package/dist/fab/ExtendedFab.svelte +26 -60
  45. package/dist/fab/Fab.svelte +13 -48
  46. package/dist/fab/FabMenu.svelte +102 -44
  47. package/dist/fab/FabMenu.svelte.d.ts +2 -1
  48. package/dist/fab/FabMenuItem.svelte +8 -0
  49. package/dist/fab/FabMenuItem.svelte.d.ts +4 -0
  50. package/dist/fab/index.d.ts +1 -0
  51. package/dist/fab/index.js +1 -0
  52. package/dist/fab/types.d.ts +6 -4
  53. package/dist/icons/MenuIcon.svelte +13 -0
  54. package/dist/icons/MenuIcon.svelte.d.ts +7 -0
  55. package/dist/icons/MenuOpenIcon.svelte +15 -0
  56. package/dist/icons/MenuOpenIcon.svelte.d.ts +7 -0
  57. package/dist/icons/index.d.ts +2 -0
  58. package/dist/icons/index.js +2 -0
  59. package/dist/internal/exit.css +47 -0
  60. package/dist/internal/fieldTypes.d.ts +6 -0
  61. package/dist/internal/focus-ring.css +4 -0
  62. package/dist/internal/interest.css +3 -1
  63. package/dist/keyboard-nav.d.ts +12 -1
  64. package/dist/keyboard-nav.js +104 -14
  65. package/dist/list/Item.svelte +52 -6
  66. package/dist/list/List.svelte +35 -2
  67. package/dist/list/ListItem.svelte +16 -2
  68. package/dist/list/context.d.ts +6 -0
  69. package/dist/list/context.js +4 -0
  70. package/dist/list/types.d.ts +7 -0
  71. package/dist/media.d.ts +1 -0
  72. package/dist/media.js +1 -0
  73. package/dist/menu/Menu.svelte +70 -18
  74. package/dist/menu/Menu.svelte.d.ts +2 -1
  75. package/dist/menu/MenuItem.svelte +9 -3
  76. package/dist/menu/types.d.ts +7 -1
  77. package/dist/navigation-bar/NavigationBar.svelte +9 -3
  78. package/dist/navigation-bar/NavigationBarItem.svelte +29 -13
  79. package/dist/navigation-drawer/NavigationDrawer.svelte +96 -20
  80. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +2 -1
  81. package/dist/navigation-drawer/NavigationDrawerItem.svelte +8 -3
  82. package/dist/navigation-drawer/NavigationDrawerSection.svelte +49 -0
  83. package/dist/navigation-drawer/NavigationDrawerSection.svelte.d.ts +4 -0
  84. package/dist/navigation-drawer/index.d.ts +1 -0
  85. package/dist/navigation-drawer/index.js +1 -0
  86. package/dist/navigation-drawer/types.d.ts +12 -0
  87. package/dist/navigation-rail/NavigationRail.svelte +367 -7
  88. package/dist/navigation-rail/NavigationRail.svelte.d.ts +1 -1
  89. package/dist/navigation-rail/NavigationRailItem.svelte +190 -53
  90. package/dist/navigation-rail/NavigationRailSection.svelte +57 -0
  91. package/dist/navigation-rail/NavigationRailSection.svelte.d.ts +4 -0
  92. package/dist/navigation-rail/context.d.ts +6 -0
  93. package/dist/navigation-rail/context.js +5 -0
  94. package/dist/navigation-rail/index.d.ts +1 -0
  95. package/dist/navigation-rail/index.js +1 -0
  96. package/dist/navigation-rail/morph.d.ts +14 -0
  97. package/dist/navigation-rail/morph.js +78 -0
  98. package/dist/navigation-rail/types.d.ts +21 -0
  99. package/dist/progress/CircularProgress.svelte +2 -1
  100. package/dist/progress/LinearProgress.svelte +48 -20
  101. package/dist/progress/types.d.ts +1 -0
  102. package/dist/radio/Radio.svelte +7 -1
  103. package/dist/search/Search.svelte +245 -58
  104. package/dist/search/types.d.ts +4 -0
  105. package/dist/select/NativeSelect.svelte +20 -12
  106. package/dist/select/Option.svelte +1 -2
  107. package/dist/select/Select.svelte +59 -140
  108. package/dist/select/VirtualList.svelte +16 -1
  109. package/dist/select/VirtualList.svelte.d.ts +6 -2
  110. package/dist/sheet/Sheet.svelte +223 -5
  111. package/dist/sheet/Sheet.svelte.d.ts +3 -1
  112. package/dist/sheet/types.d.ts +10 -0
  113. package/dist/slider/Slider.svelte +1 -27
  114. package/dist/snackbar/Snackbar.svelte +3 -0
  115. package/dist/snackbar/Snackbar.svelte.d.ts +2 -1
  116. package/dist/switch/Switch.svelte +51 -11
  117. package/dist/tabs/Tab.svelte +28 -14
  118. package/dist/tabs/Tabs.svelte +28 -4
  119. package/dist/tabs/types.d.ts +3 -0
  120. package/dist/text-field/TextField.svelte +49 -13
  121. package/dist/time-picker/DockedTimePicker.svelte +6 -1
  122. package/dist/time-picker/DockedTimePicker.svelte.d.ts +1 -1
  123. package/dist/time-picker/TimePickerDialog.svelte +1 -0
  124. package/dist/time-picker/TimePickerDialog.svelte.d.ts +1 -1
  125. package/dist/toolbar/Toolbar.svelte +21 -11
  126. package/dist/toolbar/types.d.ts +1 -0
  127. package/dist/tooltip/RichTooltip.svelte.d.ts +1 -1
  128. package/dist/tooltip/Tooltip.svelte +3 -1
  129. package/dist/tooltip/Tooltip.svelte.d.ts +1 -1
  130. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import '../internal/focus-ring.css'
3
- import { pressMorph } from '../press.svelte.js'
4
3
  import Ripple from '../ripple/Ripple.svelte'
5
4
  import Tooltip from '../tooltip/Tooltip.svelte'
6
5
  import type { HTMLButtonAttributes } from 'svelte/elements'
@@ -9,9 +8,8 @@
9
8
  let {
10
9
  variant = 'primary-container',
11
10
  size = 's',
12
- shape = 'round',
11
+ shape = 'square',
13
12
  lowered = false,
14
- disabled = false,
15
13
  icon,
16
14
  label,
17
15
  element = $bindable(),
@@ -20,16 +18,9 @@
20
18
 
21
19
  const uid = $props.id()
22
20
 
23
- let isLink = $derived(attributes.href != null && !disabled)
24
- let tooltipId = $derived(label && !disabled ? uid : undefined)
25
-
26
- const morph = pressMorph()
27
- morph.supersededBy(() => shape)
28
-
29
- const handlePress = () => {
30
- if (disabled) return
31
- morph.press()
32
- }
21
+ let isLink = $derived(attributes.href != null)
22
+ let tooltipId = $derived(label ? uid : undefined)
23
+ let tooltipNames = $derived(!!tooltipId && !attributes['aria-label'])
33
24
 
34
25
  let classes = $derived([
35
26
  'np-fab',
@@ -38,15 +29,12 @@
38
29
  size,
39
30
  shape,
40
31
  lowered && 'np-fab-lowered',
41
- morph.pressed && 'pressed',
42
32
  attributes.class,
43
33
  ])
44
34
  </script>
45
35
 
46
36
  {#snippet content()}
47
- {#if !disabled}
48
- <Ripple forElement={element} />
49
- {/if}
37
+ <Ripple forElement={element} />
50
38
  {#if icon}
51
39
  <span class="np-fab-icon">{@render icon()}</span>
52
40
  {/if}
@@ -55,14 +43,10 @@
55
43
  {#if isLink}
56
44
  <a
57
45
  {...attributes}
58
- onclick={(event) => {
59
- handlePress()
60
- attributes.onclick?.(event)
61
- }}
62
46
  bind:this={element}
63
- aria-describedby={tooltipId ?? attributes['aria-describedby']}
47
+ aria-describedby={(!tooltipNames && tooltipId) || attributes['aria-describedby']}
64
48
  interestfor={tooltipId ?? attributes['interestfor']}
65
- aria-label={label || attributes['aria-label']}
49
+ aria-label={attributes['aria-label'] ?? label}
66
50
  class={classes}
67
51
  >
68
52
  {@render content()}
@@ -70,16 +54,11 @@
70
54
  {:else}
71
55
  <button
72
56
  {...attributes as HTMLButtonAttributes}
73
- onclick={(event) => {
74
- handlePress()
75
- attributes.onclick?.(event)
76
- }}
77
57
  bind:this={element}
78
- aria-describedby={tooltipId ?? attributes['aria-describedby']}
58
+ aria-describedby={(!tooltipNames && tooltipId) || attributes['aria-describedby']}
79
59
  interestfor={tooltipId ?? attributes['interestfor']}
80
- aria-label={label || attributes['aria-label']}
60
+ aria-label={attributes['aria-label'] ?? label}
81
61
  type={(attributes['type'] as HTMLButtonAttributes['type']) ?? 'button'}
82
- {disabled}
83
62
  class={classes}
84
63
  >
85
64
  {@render content()}
@@ -116,18 +95,11 @@
116
95
  border-radius: var(--np-fab-shape, var(--_square-radius));
117
96
  }
118
97
 
119
- @media (prefers-reduced-motion: no-preference) {
120
- .round:not(:disabled):is(:active, .pressed) {
121
- border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
122
- }
123
- .square:not(:disabled):is(:active, .pressed) {
124
- border-radius: var(--np-fab-pressed-shape, 50%);
125
- }
126
- }
127
-
128
98
  @media (prefers-reduced-motion: no-preference) {
129
99
  .np-fab {
130
100
  transition:
101
+ width var(--np-motion-expressive-default-spatial),
102
+ height var(--np-motion-expressive-default-spatial),
131
103
  border-radius var(--np-motion-expressive-default-spatial),
132
104
  box-shadow var(--np-motion-expressive-fast-effects),
133
105
  background-color var(--np-motion-expressive-fast-effects);
@@ -183,21 +155,14 @@
183
155
  }
184
156
 
185
157
  @media (hover: hover) {
186
- .np-fab:not(:disabled):hover {
158
+ .np-fab:hover {
187
159
  box-shadow: var(--np-fab-elevation, var(--np-elevation-4));
188
160
  }
189
- .np-fab-lowered:not(:disabled):hover {
161
+ .np-fab-lowered:hover {
190
162
  box-shadow: var(--np-fab-elevation, var(--np-elevation-2));
191
163
  }
192
164
  }
193
165
 
194
- .np-fab:disabled {
195
- cursor: default;
196
- box-shadow: none;
197
- background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
198
- color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
199
- }
200
-
201
166
  .np-fab-icon {
202
167
  display: flex;
203
168
  fill: currentColor;
@@ -1,10 +1,9 @@
1
1
  <script lang="ts">
2
- import { reducedMotion } from '../media.js'
2
+ import '../internal/exit.css'
3
+ import { exitAnimation } from '../animation.js'
3
4
  import CloseIcon from '../icons/CloseIcon.svelte'
4
- import { arrowKeyNav, rovingTabindex } from '../keyboard-nav.js'
5
+ import { arrowKeyNav, focusableItems, rovingTabindex, typeahead } from '../keyboard-nav.js'
5
6
  import { popoverController, syncOpenEffect } from '../popover.svelte.js'
6
- import { PRESS_DURATION } from '../press.svelte.js'
7
- import { onMount } from 'svelte'
8
7
  import Fab from './Fab.svelte'
9
8
  import type { FabMenuProps } from './types.js'
10
9
 
@@ -16,7 +15,6 @@
16
15
  variant = 'primary-container',
17
16
  size = 's',
18
17
  placement = 'block-start',
19
- closeOnSelect = true,
20
18
  open = $bindable(false),
21
19
  element = $bindable(),
22
20
  ...attributes
@@ -28,38 +26,33 @@
28
26
 
29
27
  let menuElement: HTMLDivElement | undefined = $state()
30
28
 
29
+ let colorSet = $derived(variant.replace('-container', '') as 'primary' | 'secondary' | 'tertiary')
30
+
31
31
  const ITEMS = 'button, a[href], [role="menuitem"]'
32
32
  const attach = rovingTabindex(ITEMS)
33
33
  let arrowHandler = $derived(
34
34
  arrowKeyNav(ITEMS, placement.startsWith('inline') ? 'horizontal' : 'vertical'),
35
35
  )
36
+ const typeaheadHandler = typeahead(
37
+ ITEMS,
38
+ (item) => (item.querySelector('.children-wrapper') ?? item).textContent ?? '',
39
+ )
36
40
 
37
41
  const controller = popoverController(() => menuElement)
38
42
 
39
43
  export const show = () => controller.show()
40
44
 
41
- export const close = () => {
42
- clearTimeout(closeTimeout)
43
- controller.close()
44
- }
45
-
46
- let closeTimeout: ReturnType<typeof setTimeout>
45
+ export const close = () => controller.close()
47
46
 
48
- const closeAfterPress = () => {
49
- if (reducedMotion.current) {
50
- close()
51
- return
52
- }
53
- clearTimeout(closeTimeout)
54
- closeTimeout = setTimeout(close, PRESS_DURATION)
47
+ const openWithArrow = (event: KeyboardEvent) => {
48
+ if (!menuElement || (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')) return
49
+ event.preventDefault()
50
+ show()
51
+ focusableItems(menuElement, ITEMS)
52
+ .at(event.key === 'ArrowDown' ? 0 : -1)
53
+ ?.focus()
55
54
  }
56
55
 
57
- onMount(() => {
58
- return () => {
59
- clearTimeout(closeTimeout)
60
- }
61
- })
62
-
63
56
  syncOpenEffect(
64
57
  () => menuElement,
65
58
  () => open,
@@ -83,19 +76,26 @@
83
76
  {...attributes}
84
77
  bind:this={element}
85
78
  style="anchor-name: {anchorName}; {attributes.style ?? ''}"
86
- class={['np-fab-menu', `np-fab-menu-${placement}`, attributes.class]}
79
+ class={[
80
+ 'np-fab-menu',
81
+ `np-fab-menu-${placement}`,
82
+ `np-fab-menu-${size}`,
83
+ `np-fab-menu-set-${colorSet}`,
84
+ attributes.class,
85
+ ]}
87
86
  >
88
87
  <Fab
89
- {variant}
90
- {size}
88
+ variant={open ? colorSet : variant}
89
+ size={open ? 's' : size}
90
+ shape={open ? 'round' : undefined}
91
91
  {label}
92
- shape={open ? 'square' : 'round'}
93
92
  command="toggle-popover"
94
93
  commandfor={menuId}
95
94
  aria-haspopup="menu"
96
95
  aria-expanded={open}
97
96
  class="np-fab-menu-trigger"
98
97
  icon={triggerIcon}
98
+ onkeydown={openWithArrow}
99
99
  />
100
100
 
101
101
  <div
@@ -108,11 +108,10 @@
108
108
  aria-label={label}
109
109
  class="np-fab-menu-list"
110
110
  {@attach attach}
111
- onkeydown={arrowHandler}
112
- onclick={(event) => {
113
- if (!closeOnSelect) return
114
- const target = event.target as HTMLElement
115
- if (target.closest(ITEMS)) closeAfterPress()
111
+ {@attach exitAnimation}
112
+ onkeydown={(event) => {
113
+ arrowHandler(event)
114
+ if (!event.defaultPrevented) typeaheadHandler(event)
116
115
  }}
117
116
  ontoggle={(event) => {
118
117
  open = event.newState === 'open'
@@ -126,6 +125,35 @@
126
125
  .np-fab-menu {
127
126
  position: relative;
128
127
  display: inline-flex;
128
+ width: var(--_size);
129
+ height: var(--_size);
130
+ }
131
+ .np-fab-menu-s {
132
+ --_size: 3.5rem;
133
+ }
134
+ .np-fab-menu-m {
135
+ --_size: 5rem;
136
+ }
137
+ .np-fab-menu-l {
138
+ --_size: 6rem;
139
+ }
140
+ .np-fab-menu > :global(.np-fab.np-fab-menu-trigger) {
141
+ position: absolute;
142
+ inset-block-start: 0;
143
+ inset-inline-end: 0;
144
+ }
145
+
146
+ .np-fab-menu-set-primary {
147
+ --np-tonal-button-container-color: var(--np-color-primary-container);
148
+ --np-tonal-button-label-text-color: var(--np-color-on-primary-container);
149
+ }
150
+ .np-fab-menu-set-secondary {
151
+ --np-tonal-button-container-color: var(--np-color-secondary-container);
152
+ --np-tonal-button-label-text-color: var(--np-color-on-secondary-container);
153
+ }
154
+ .np-fab-menu-set-tertiary {
155
+ --np-tonal-button-container-color: var(--np-color-tertiary-container);
156
+ --np-tonal-button-label-text-color: var(--np-color-on-tertiary-container);
129
157
  }
130
158
 
131
159
  .np-fab-menu-icons {
@@ -152,49 +180,79 @@
152
180
 
153
181
  .np-fab-menu-list {
154
182
  position: absolute;
183
+ box-sizing: border-box;
184
+ max-height: 100%;
155
185
  margin: 0;
156
- padding: 0;
186
+ padding: 0.5rem;
157
187
  border: none;
158
188
  background: none;
159
189
  flex-direction: column;
160
190
  align-items: flex-end;
161
191
  gap: 0.25rem;
162
- overflow: visible;
192
+ overflow: auto;
193
+ scrollbar-width: none;
163
194
  }
164
195
 
165
196
  .np-fab-menu-list:popover-open {
166
197
  display: flex;
167
198
  }
199
+ .np-fab-menu-list {
200
+ --np-exit-display: flex;
201
+ }
202
+ .np-fab-menu-list > :global(:focus-visible) {
203
+ z-index: 1;
204
+ }
168
205
 
169
206
  .np-fab-menu-block-start .np-fab-menu-list {
170
- position-area: block-start;
171
- margin-block-end: 0.5rem;
172
- flex-direction: column-reverse;
207
+ position-area: block-start span-inline-start;
173
208
  }
174
209
  .np-fab-menu-block-end .np-fab-menu-list {
175
- position-area: block-end;
176
- margin-block-start: 0.5rem;
210
+ position-area: block-end span-inline-start;
177
211
  }
178
212
  .np-fab-menu-inline-start .np-fab-menu-list {
179
213
  position-area: inline-start;
180
- margin-inline-end: 0.5rem;
181
- flex-direction: row-reverse;
182
- align-items: center;
183
214
  }
184
215
  .np-fab-menu-inline-end .np-fab-menu-list {
185
216
  position-area: inline-end;
186
- margin-inline-start: 0.5rem;
217
+ }
218
+ :is(.np-fab-menu-block-start, .np-fab-menu-block-end) .np-fab-menu-list {
219
+ margin-inline-end: -0.5rem;
220
+ }
221
+ :is(.np-fab-menu-inline-start, .np-fab-menu-inline-end) .np-fab-menu-list {
187
222
  flex-direction: row;
188
223
  align-items: center;
189
224
  }
190
225
 
191
226
  @media (prefers-reduced-motion: no-preference) {
227
+ .np-fab-menu-list {
228
+ opacity: 0;
229
+ transition:
230
+ opacity var(--np-motion-expressive-fast-effects),
231
+ display var(--np-motion-expressive-fast-effects) allow-discrete,
232
+ overlay var(--np-motion-expressive-fast-effects) allow-discrete;
233
+ }
234
+ .np-fab-menu-list:popover-open {
235
+ opacity: 1;
236
+ }
192
237
  .np-fab-menu-list:popover-open > :global(*) {
193
238
  --_stagger-window: 210ms;
194
239
  --_delay: calc((sibling-index() - 1) / sibling-count() * var(--_stagger-window));
195
240
  animation: np-fab-menu-item-in var(--np-motion-expressive-default-spatial) both;
196
241
  animation-delay: var(--_delay);
197
242
  }
243
+ .np-fab-menu-list:popover-open > :global(:focus-visible) {
244
+ animation:
245
+ np-fab-menu-item-in var(--np-motion-expressive-default-spatial) both,
246
+ focusAnimation var(--np-motion-expressive-slow-effects) forwards;
247
+ animation-delay: var(--_delay), 0s;
248
+ }
249
+ :is(.np-fab-menu-block-start, .np-fab-menu-inline-start)
250
+ .np-fab-menu-list:popover-open
251
+ > :global(*) {
252
+ --_delay: calc(
253
+ (sibling-count() - sibling-index()) / sibling-count() * var(--_stagger-window)
254
+ );
255
+ }
198
256
  }
199
257
 
200
258
  @keyframes np-fab-menu-item-in {
@@ -1,7 +1,8 @@
1
+ import '../internal/exit.css';
1
2
  import type { FabMenuProps } from './types.js';
2
3
  declare const FabMenu: import("svelte").Component<FabMenuProps, {
3
4
  show: () => void;
4
5
  close: () => void;
5
- }, "element" | "open">;
6
+ }, "open" | "element">;
6
7
  type FabMenu = ReturnType<typeof FabMenu>;
7
8
  export default FabMenu;
@@ -0,0 +1,8 @@
1
+ <script lang="ts">
2
+ import Button from '../button/Button.svelte'
3
+ import type { FabMenuItemProps } from './types.js'
4
+
5
+ let { icon, element = $bindable(), ...attributes }: FabMenuItemProps = $props()
6
+ </script>
7
+
8
+ <Button {...attributes} bind:element role="menuitem" variant="tonal" size="m" start={icon} />
@@ -0,0 +1,4 @@
1
+ import type { FabMenuItemProps } from './types.js';
2
+ declare const FabMenuItem: import("svelte").Component<FabMenuItemProps, {}, "element">;
3
+ type FabMenuItem = ReturnType<typeof FabMenuItem>;
4
+ export default FabMenuItem;
@@ -1,3 +1,4 @@
1
1
  export { default as ExtendedFab } from './ExtendedFab.svelte';
2
2
  export { default as Fab } from './Fab.svelte';
3
3
  export { default as FabMenu } from './FabMenu.svelte';
4
+ export { default as FabMenuItem } from './FabMenuItem.svelte';
package/dist/fab/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as ExtendedFab } from './ExtendedFab.svelte';
2
2
  export { default as Fab } from './Fab.svelte';
3
3
  export { default as FabMenu } from './FabMenu.svelte';
4
+ export { default as FabMenuItem } from './FabMenuItem.svelte';
@@ -1,14 +1,13 @@
1
- import type { ButtonElement } from '../button/types.js';
1
+ import type { ButtonElement, ButtonProps } from '../button/types.js';
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
4
4
  export type FabElement = ButtonElement;
5
- interface BaseFabProps extends HTMLAttributes<FabElement>, Omit<HTMLButtonAttributes, keyof HTMLAttributes<HTMLButtonElement> | 'type'>, Omit<HTMLAnchorAttributes, keyof HTMLAttributes<HTMLAnchorElement> | 'type'> {
5
+ interface BaseFabProps extends HTMLAttributes<FabElement>, Omit<HTMLButtonAttributes, keyof HTMLAttributes<HTMLButtonElement> | 'type' | 'disabled'>, Omit<HTMLAnchorAttributes, keyof HTMLAttributes<HTMLAnchorElement> | 'type'> {
6
6
  element?: FabElement;
7
7
  variant?: 'primary' | 'secondary' | 'tertiary' | 'primary-container' | 'secondary-container' | 'tertiary-container';
8
8
  size?: 's' | 'm' | 'l';
9
9
  shape?: 'round' | 'square';
10
10
  lowered?: boolean;
11
- disabled?: boolean | null;
12
11
  type?: 'submit' | 'reset' | 'button' | (string & {}) | null;
13
12
  }
14
13
  export interface FabProps extends BaseFabProps {
@@ -20,6 +19,10 @@ export interface ExtendedFabProps extends BaseFabProps {
20
19
  label?: string;
21
20
  collapsed?: boolean;
22
21
  }
22
+ export interface FabMenuItemProps extends Omit<ButtonProps, 'variant' | 'size' | 'start' | 'end' | 'toggle' | 'selected' | 'role'> {
23
+ /** Leading icon. Keep it, it tells the actions apart at a glance. */
24
+ icon?: Snippet;
25
+ }
23
26
  export interface FabMenuProps extends HTMLAttributes<HTMLDivElement> {
24
27
  label?: string;
25
28
  icon?: Snippet;
@@ -28,7 +31,6 @@ export interface FabMenuProps extends HTMLAttributes<HTMLDivElement> {
28
31
  size?: FabProps['size'];
29
32
  placement?: 'block-start' | 'block-end' | 'inline-start' | 'inline-end';
30
33
  open?: boolean;
31
- closeOnSelect?: boolean;
32
34
  element?: HTMLDivElement;
33
35
  }
34
36
  export {};
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ let {
3
+ height = 24,
4
+ width = 24,
5
+ }: {
6
+ height?: number
7
+ width?: number
8
+ } = $props()
9
+ </script>
10
+
11
+ <svg xmlns="http://www.w3.org/2000/svg" {height} viewBox="0 -960 960 960" {width}
12
+ ><path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z" /></svg
13
+ >
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ height?: number;
3
+ width?: number;
4
+ };
5
+ declare const MenuIcon: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type MenuIcon = ReturnType<typeof MenuIcon>;
7
+ export default MenuIcon;
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ let {
3
+ height = 24,
4
+ width = 24,
5
+ }: {
6
+ height?: number
7
+ width?: number
8
+ } = $props()
9
+ </script>
10
+
11
+ <svg xmlns="http://www.w3.org/2000/svg" {height} viewBox="0 -960 960 960" {width}
12
+ ><path
13
+ d="M120-240v-80h520v80H120Zm664-40L584-480l200-200 56 56-144 144 144 144-56 56ZM120-440v-80h400v80H120Zm0-200v-80h520v80H120Z"
14
+ /></svg
15
+ >
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ height?: number;
3
+ width?: number;
4
+ };
5
+ declare const MenuOpenIcon: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type MenuOpenIcon = ReturnType<typeof MenuOpenIcon>;
7
+ export default MenuOpenIcon;
@@ -9,5 +9,7 @@ export { default as CloseIcon } from './CloseIcon.svelte';
9
9
  export { default as EditCalendarIcon } from './EditCalendarIcon.svelte';
10
10
  export { default as Icon } from './Icon.svelte';
11
11
  export { default as KeyboardIcon } from './KeyboardIcon.svelte';
12
+ export { default as MenuIcon } from './MenuIcon.svelte';
13
+ export { default as MenuOpenIcon } from './MenuOpenIcon.svelte';
12
14
  export { default as ScheduleIcon } from './ScheduleIcon.svelte';
13
15
  export { default as SearchIcon } from './SearchIcon.svelte';
@@ -9,5 +9,7 @@ export { default as CloseIcon } from './CloseIcon.svelte';
9
9
  export { default as EditCalendarIcon } from './EditCalendarIcon.svelte';
10
10
  export { default as Icon } from './Icon.svelte';
11
11
  export { default as KeyboardIcon } from './KeyboardIcon.svelte';
12
+ export { default as MenuIcon } from './MenuIcon.svelte';
13
+ export { default as MenuOpenIcon } from './MenuOpenIcon.svelte';
12
14
  export { default as ScheduleIcon } from './ScheduleIcon.svelte';
13
15
  export { default as SearchIcon } from './SearchIcon.svelte';
@@ -0,0 +1,47 @@
1
+ /* Browsers without an `overlay` transition, such as Safari, drop a closing dialog or popover from
2
+ the top layer at once, so its exit animation would never show. While it animates out it carries
3
+ data-np-exiting (see exitAnimation in animation.ts) and these rules keep it shown, over the page
4
+ but not blocking it. Browsers with the transition keep it in the top layer themselves. */
5
+ @supports not (overlay: auto) {
6
+ /* Important, as each component hides its closed state with a rule of its own, scoped and so
7
+ more specific than this one. It only ever holds for the moment of leaving. */
8
+ [popover][data-np-exiting]:not(:popover-open),
9
+ dialog[data-np-exiting]:not([open]) {
10
+ display: var(--np-exit-display, block) !important;
11
+ z-index: 1000 !important;
12
+ pointer-events: none !important;
13
+ }
14
+
15
+ /* A closed dialog is no longer :modal and would lose its place. Components that place their
16
+ dialog themselves win over this. */
17
+ :where(dialog[data-np-exiting]:not([open])) {
18
+ position: fixed;
19
+ inset-block: 0;
20
+ }
21
+
22
+ /* Its ::backdrop is gone as well, so a dialog with a scrim draws one of its own that fades: a
23
+ wide shadow around it, and one inside for a dialog whose box is transparent, as it sits under
24
+ the content. A shadow rather than a pseudo element covers the screen even while the dialog
25
+ moves. A dialog that is its own surface turns the inner one off with
26
+ --np-exit-scrim-inner: 0 0 transparent. */
27
+ dialog.np-exit-scrim[data-np-exiting]:not([open]) {
28
+ --np-exit-scrim-color: color-mix(in srgb, var(--np-color-scrim) 32%, transparent);
29
+ box-shadow:
30
+ 0 0 0 100vmax var(--np-exit-scrim-color),
31
+ var(--np-exit-scrim-inner, inset 0 0 0 100vmax var(--np-exit-scrim-color));
32
+ animation: np-exit-scrim var(--np-exit-scrim-motion, var(--np-motion-expressive-slow-effects))
33
+ forwards;
34
+ }
35
+ @keyframes np-exit-scrim {
36
+ to {
37
+ --np-exit-scrim-color: transparent;
38
+ }
39
+ }
40
+ }
41
+
42
+ /* Registered, so the scrim's colour can fade. */
43
+ @property --np-exit-scrim-color {
44
+ syntax: '<color>';
45
+ inherits: false;
46
+ initial-value: transparent;
47
+ }
@@ -7,6 +7,12 @@ export interface FieldProps {
7
7
  issues?: Issue[];
8
8
  prefixText?: string;
9
9
  suffixText?: string;
10
+ /** Spoken in place of `prefixText`, for example "Euro" for "€". */
11
+ prefixLabel?: string;
12
+ /** Spoken in place of `suffixText`, for example "at gmail dot com". */
13
+ suffixLabel?: string;
14
+ /** Spoken before the character counter. */
15
+ counterLabel?: string;
10
16
  variant?: 'outlined' | 'filled';
11
17
  start?: Snippet;
12
18
  end?: Snippet;
@@ -31,6 +31,10 @@
31
31
  outline-width: 3px;
32
32
  outline-offset: 2px;
33
33
  }
34
+ /* The focused state layer, on top of the ring. */
35
+ .np-focus-ring:focus-visible .np-ripple-surface::before {
36
+ opacity: var(--np-ripple-focus-opacity, 0.1);
37
+ }
34
38
  @media (prefers-reduced-motion: no-preference) {
35
39
  .np-focus-ring:focus-visible {
36
40
  animation: focusAnimation var(--np-motion-expressive-slow-effects) forwards;
@@ -1,4 +1,6 @@
1
- [interestfor][aria-describedby] {
1
+ /* A tooltip shows at once and lingers a little. Its anchor either points at it with
2
+ aria-describedby or, when the tooltip text is the anchor's name, is followed by it. */
3
+ [interestfor]:is([aria-describedby], :has(+ [role='tooltip'])) {
2
4
  interest-delay-start: 0s;
3
5
  interest-delay-end: 500ms;
4
6
  }
@@ -1,10 +1,21 @@
1
1
  import type { Attachment } from 'svelte/attachments';
2
+ export declare const focusableItems: (node: ParentNode, itemSelector: string) => HTMLElement[];
3
+ export declare const focusedItem: (node: HTMLElement, itemSelector: string, items: HTMLElement[]) => HTMLElement | null;
2
4
  export declare const rovingTabindex: (itemSelector: string, options?: {
3
5
  currentAttr?: string;
4
6
  currentValue?: string;
7
+ /** Picks the current item by selector instead of `currentAttr` and `currentValue`. */
8
+ current?: string;
5
9
  }) => Attachment<HTMLElement>;
6
- export declare const arrowKeyNav: (itemSelector: string, orientation?: "vertical" | "horizontal", { wrap }?: {
10
+ export declare const arrowKeyNav: (itemSelector: string, orientation?: "vertical" | "horizontal" | "both", { wrap }?: {
7
11
  wrap?: boolean;
8
12
  }) => (event: KeyboardEvent & {
9
13
  currentTarget: EventTarget & HTMLElement;
10
14
  }) => void;
15
+ export declare const typeaheadBuffer: (timeout?: number) => (event: KeyboardEvent) => string | undefined;
16
+ export declare const typeaheadMatch: (labels: string[], current: number, query: string) => number;
17
+ export declare const typeahead: (itemSelector: string, labelOf: (item: HTMLElement) => string, { timeout }?: {
18
+ timeout?: number;
19
+ }) => (event: KeyboardEvent & {
20
+ currentTarget: EventTarget & HTMLElement;
21
+ }) => void;