noph-ui 0.51.2 → 0.53.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 (187) hide show
  1. package/dist/animation.d.ts +14 -1
  2. package/dist/animation.js +55 -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 +53 -30
  11. package/dist/button/ButtonGroup.svelte +139 -19
  12. package/dist/button/ButtonGroup.svelte.d.ts +1 -1
  13. package/dist/button/IconButton.svelte +51 -33
  14. package/dist/button/SegmentedButton.svelte +52 -15
  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/card/Card.svelte +3 -19
  23. package/dist/card/types.d.ts +12 -2
  24. package/dist/carousel/Carousel.svelte +77 -14
  25. package/dist/carousel/CarouselItem.svelte +26 -14
  26. package/dist/carousel/carouselMorph.js +1 -1
  27. package/dist/carousel/carouselStrategy.js +25 -27
  28. package/dist/checkbox/Checkbox.svelte +6 -8
  29. package/dist/chip/ChipSet.svelte +28 -2
  30. package/dist/chip/FilterChip.svelte +33 -17
  31. package/dist/chip/InputChip.svelte +127 -26
  32. package/dist/chip/SuggestionChip.svelte +6 -22
  33. package/dist/chip/SuggestionChip.svelte.d.ts +1 -2
  34. package/dist/chip/types.d.ts +8 -1
  35. package/dist/date-picker/Calendar.svelte +34 -31
  36. package/dist/date-picker/Calendar.svelte.d.ts +2 -0
  37. package/dist/date-picker/CalendarHeader.svelte +84 -62
  38. package/dist/date-picker/CalendarHeader.svelte.d.ts +3 -0
  39. package/dist/date-picker/DatePickerDialog.svelte +135 -75
  40. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -3
  41. package/dist/date-picker/DateRangePicker.svelte +227 -78
  42. package/dist/date-picker/DateRangePicker.svelte.d.ts +3 -3
  43. package/dist/date-picker/DockedDatePicker.svelte +76 -457
  44. package/dist/date-picker/DockedDatePicker.svelte.d.ts +3 -3
  45. package/dist/date-picker/DockedDateTimePicker.svelte +128 -518
  46. package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +3 -3
  47. package/dist/date-picker/DockedPickerField.svelte +221 -0
  48. package/dist/date-picker/DockedPickerField.svelte.d.ts +9 -0
  49. package/dist/date-picker/MonthPanel.svelte +291 -0
  50. package/dist/date-picker/MonthPanel.svelte.d.ts +12 -0
  51. package/dist/date-picker/SelectionList.svelte +7 -6
  52. package/dist/date-picker/SelectionList.svelte.d.ts +1 -0
  53. package/dist/date-picker/TimeColumn.svelte +2 -6
  54. package/dist/date-picker/TimeColumn.svelte.d.ts +1 -0
  55. package/dist/date-picker/YearGrid.svelte +6 -13
  56. package/dist/date-picker/YearGrid.svelte.d.ts +1 -0
  57. package/dist/date-picker/dateUtils.d.ts +4 -0
  58. package/dist/date-picker/dateUtils.js +15 -88
  59. package/dist/date-picker/exitVisibility.svelte.js +13 -6
  60. package/dist/date-picker/formatters.d.ts +16 -0
  61. package/dist/date-picker/formatters.js +48 -15
  62. package/dist/date-picker/internalTypes.d.ts +59 -1
  63. package/dist/date-picker/shortcuts.d.ts +11 -0
  64. package/dist/date-picker/shortcuts.js +40 -0
  65. package/dist/date-picker/timeUtils.d.ts +8 -3
  66. package/dist/date-picker/timeUtils.js +33 -51
  67. package/dist/date-picker/types.d.ts +22 -0
  68. package/dist/dialog/Dialog.svelte +171 -33
  69. package/dist/dialog/Dialog.svelte.d.ts +2 -1
  70. package/dist/dialog/types.d.ts +6 -0
  71. package/dist/divider/Divider.svelte +36 -10
  72. package/dist/divider/types.d.ts +1 -0
  73. package/dist/fab/ExtendedFab.svelte +26 -60
  74. package/dist/fab/Fab.svelte +13 -48
  75. package/dist/fab/FabMenu.svelte +102 -44
  76. package/dist/fab/FabMenu.svelte.d.ts +2 -1
  77. package/dist/fab/FabMenuItem.svelte +8 -0
  78. package/dist/fab/FabMenuItem.svelte.d.ts +4 -0
  79. package/dist/fab/index.d.ts +1 -0
  80. package/dist/fab/index.js +1 -0
  81. package/dist/fab/types.d.ts +6 -4
  82. package/dist/icons/Icon.svelte +1 -4
  83. package/dist/icons/MenuIcon.svelte +13 -0
  84. package/dist/icons/MenuIcon.svelte.d.ts +7 -0
  85. package/dist/icons/MenuOpenIcon.svelte +15 -0
  86. package/dist/icons/MenuOpenIcon.svelte.d.ts +7 -0
  87. package/dist/icons/index.d.ts +2 -0
  88. package/dist/icons/index.js +2 -0
  89. package/dist/internal/exit.css +33 -0
  90. package/dist/internal/field.css +337 -0
  91. package/dist/internal/fieldTypes.d.ts +6 -0
  92. package/dist/internal/focus-ring.css +25 -6
  93. package/dist/internal/interest.css +3 -1
  94. package/dist/internal/pop-in.css +19 -0
  95. package/dist/internal/visually-hidden.css +11 -0
  96. package/dist/keyboard-nav.d.ts +12 -1
  97. package/dist/keyboard-nav.js +104 -14
  98. package/dist/list/Item.svelte +84 -24
  99. package/dist/list/List.svelte +35 -2
  100. package/dist/list/ListItem.svelte +16 -2
  101. package/dist/list/context.d.ts +6 -0
  102. package/dist/list/context.js +4 -0
  103. package/dist/list/types.d.ts +7 -0
  104. package/dist/media.d.ts +1 -0
  105. package/dist/media.js +1 -0
  106. package/dist/menu/Menu.svelte +53 -20
  107. package/dist/menu/Menu.svelte.d.ts +3 -1
  108. package/dist/menu/MenuItem.svelte +9 -3
  109. package/dist/menu/types.d.ts +7 -1
  110. package/dist/navigation-bar/NavigationBar.svelte +9 -3
  111. package/dist/navigation-bar/NavigationBarItem.svelte +31 -39
  112. package/dist/navigation-drawer/NavigationDrawer.svelte +96 -20
  113. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +2 -1
  114. package/dist/navigation-drawer/NavigationDrawerItem.svelte +10 -12
  115. package/dist/navigation-drawer/NavigationDrawerSection.svelte +49 -0
  116. package/dist/navigation-drawer/NavigationDrawerSection.svelte.d.ts +4 -0
  117. package/dist/navigation-drawer/index.d.ts +1 -0
  118. package/dist/navigation-drawer/index.js +1 -0
  119. package/dist/navigation-drawer/types.d.ts +12 -0
  120. package/dist/navigation-rail/NavigationRail.svelte +367 -7
  121. package/dist/navigation-rail/NavigationRail.svelte.d.ts +1 -1
  122. package/dist/navigation-rail/NavigationRailItem.svelte +185 -59
  123. package/dist/navigation-rail/NavigationRailSection.svelte +57 -0
  124. package/dist/navigation-rail/NavigationRailSection.svelte.d.ts +4 -0
  125. package/dist/navigation-rail/context.d.ts +6 -0
  126. package/dist/navigation-rail/context.js +5 -0
  127. package/dist/navigation-rail/index.d.ts +1 -0
  128. package/dist/navigation-rail/index.js +1 -0
  129. package/dist/navigation-rail/morph.d.ts +14 -0
  130. package/dist/navigation-rail/morph.js +78 -0
  131. package/dist/navigation-rail/types.d.ts +21 -0
  132. package/dist/press.svelte.d.ts +0 -1
  133. package/dist/press.svelte.js +0 -11
  134. package/dist/progress/CircularProgress.svelte +28 -82
  135. package/dist/progress/CircularProgress.svelte.d.ts +1 -0
  136. package/dist/progress/LinearProgress.svelte +67 -171
  137. package/dist/progress/LinearProgress.svelte.d.ts +1 -0
  138. package/dist/progress/four-color.css +34 -0
  139. package/dist/progress/types.d.ts +1 -0
  140. package/dist/radio/Radio.svelte +13 -10
  141. package/dist/ripple/Ripple.svelte +8 -16
  142. package/dist/search/Search.svelte +238 -58
  143. package/dist/search/Search.svelte.d.ts +1 -0
  144. package/dist/search/types.d.ts +4 -0
  145. package/dist/select/Check.svelte +8 -31
  146. package/dist/select/Check.svelte.d.ts +1 -1
  147. package/dist/select/NativeSelect.svelte +113 -374
  148. package/dist/select/NativeSelect.svelte.d.ts +1 -0
  149. package/dist/select/Option.svelte +2 -11
  150. package/dist/select/Select.svelte +170 -858
  151. package/dist/select/Select.svelte.d.ts +1 -0
  152. package/dist/select/VirtualList.svelte +16 -2
  153. package/dist/select/VirtualList.svelte.d.ts +6 -2
  154. package/dist/sheet/Sheet.svelte +229 -22
  155. package/dist/sheet/Sheet.svelte.d.ts +3 -1
  156. package/dist/sheet/types.d.ts +10 -0
  157. package/dist/slider/Slider.svelte +14 -64
  158. package/dist/slider/Slider.svelte.d.ts +1 -0
  159. package/dist/snackbar/Snackbar.svelte +3 -0
  160. package/dist/snackbar/Snackbar.svelte.d.ts +2 -1
  161. package/dist/switch/Switch.svelte +53 -19
  162. package/dist/tabs/Tab.svelte +30 -18
  163. package/dist/tabs/Tabs.svelte +28 -4
  164. package/dist/tabs/types.d.ts +3 -0
  165. package/dist/text-field/TextField.svelte +124 -616
  166. package/dist/text-field/TextField.svelte.d.ts +1 -0
  167. package/dist/time-picker/ClockDial.svelte +8 -25
  168. package/dist/time-picker/ClockDial.svelte.d.ts +2 -0
  169. package/dist/time-picker/DockedTimePicker.svelte +102 -239
  170. package/dist/time-picker/DockedTimePicker.svelte.d.ts +3 -3
  171. package/dist/time-picker/PeriodSelector.svelte +8 -7
  172. package/dist/time-picker/PeriodSelector.svelte.d.ts +1 -0
  173. package/dist/time-picker/TimeInputs.svelte +13 -2
  174. package/dist/time-picker/TimePickerDialog.svelte +33 -26
  175. package/dist/time-picker/TimePickerDialog.svelte.d.ts +3 -3
  176. package/dist/time-picker/TimePickerPanel.svelte +26 -4
  177. package/dist/time-picker/TimeSelectors.svelte +4 -9
  178. package/dist/time-picker/TimeSelectors.svelte.d.ts +1 -0
  179. package/dist/time-picker/internalTypes.d.ts +2 -0
  180. package/dist/time-picker/timePickerState.svelte.js +3 -9
  181. package/dist/toolbar/Toolbar.svelte +21 -11
  182. package/dist/toolbar/types.d.ts +1 -0
  183. package/dist/tooltip/RichTooltip.svelte +2 -23
  184. package/dist/tooltip/RichTooltip.svelte.d.ts +2 -1
  185. package/dist/tooltip/Tooltip.svelte +5 -25
  186. package/dist/tooltip/Tooltip.svelte.d.ts +2 -1
  187. package/package.json +12 -12
@@ -13,8 +13,9 @@
13
13
  disabled = false,
14
14
  label = '',
15
15
  icon,
16
+ trailingIcon,
16
17
  element = $bindable(),
17
- removeAriaLabel = 'Remove',
18
+ removeAriaLabel,
18
19
  onremove,
19
20
  name,
20
21
  value,
@@ -51,6 +52,7 @@
51
52
  bind:this={element}
52
53
  class={[
53
54
  'np-filter-chip',
55
+ 'np-focus-ring-within',
54
56
  variant === 'elevated' ? 'np-filter-chip-elevated' : 'np-filter-chip-default',
55
57
  icon ? 'np-filter-chip-icon' : '',
56
58
  removable ? 'np-filter-chip-removable' : '',
@@ -69,10 +71,22 @@
69
71
  <CheckIcon width={18} height={18} />
70
72
  </div>
71
73
  <div class="np-chip-label">{label}</div>
74
+ {#if trailingIcon && !removable}
75
+ <div class="np-chip-trailing-icon" aria-hidden="true">
76
+ {@render trailingIcon()}
77
+ </div>
78
+ {/if}
79
+ <span class="np-touch"></span>
72
80
  <input
73
81
  type="checkbox"
74
82
  checked={isChecked}
75
83
  {onchange}
84
+ onkeydown={(event) => {
85
+ // Enter selects like Space, instead of submitting the form.
86
+ if (event.key !== 'Enter') return
87
+ event.preventDefault()
88
+ event.currentTarget.click()
89
+ }}
76
90
  {value}
77
91
  {name}
78
92
  {disabled}
@@ -89,7 +103,7 @@
89
103
  type="button"
90
104
  size="xs"
91
105
  --np-icon-button-icon-size="1.125rem"
92
- aria-label={removeAriaLabel}
106
+ aria-label={removeAriaLabel ?? `Remove ${label}`}
93
107
  onclick={onremove}
94
108
  >
95
109
  <CloseIcon />
@@ -99,6 +113,7 @@
99
113
 
100
114
  <style>
101
115
  .np-filter-chip {
116
+ box-sizing: border-box;
102
117
  position: relative;
103
118
  display: inline-flex;
104
119
  align-items: center;
@@ -107,7 +122,6 @@
107
122
  --np-icon-button-icon-color: var(--np-color-on-surface-variant);
108
123
  --np-icon-size: 1.125rem;
109
124
  --np-ripple-pressed-opacity: 0.1;
110
- min-width: 0;
111
125
  transition: box-shadow var(--np-motion-expressive-slow-effects);
112
126
  }
113
127
  .np-filter-chip-label input {
@@ -117,6 +131,7 @@
117
131
  }
118
132
  .np-filter-chip-label {
119
133
  cursor: pointer;
134
+ position: relative;
120
135
  display: inline-flex;
121
136
  align-items: center;
122
137
  height: 2rem;
@@ -126,7 +141,19 @@
126
141
  gap: 0.5rem;
127
142
  z-index: 1;
128
143
  padding-inline: 1rem;
129
- overflow: hidden;
144
+ }
145
+ .np-chip-trailing-icon {
146
+ display: flex;
147
+ margin-inline-end: -0.5rem;
148
+ }
149
+ /* The chip keeps a 48px tall target. */
150
+ .np-touch {
151
+ position: absolute;
152
+ inset-inline: 0;
153
+ top: 50%;
154
+ translate: 0 -50%;
155
+ height: 3rem;
156
+ z-index: -1;
130
157
  }
131
158
  .np-chip-icon-checked {
132
159
  display: flex;
@@ -154,8 +181,10 @@
154
181
  .np-filter-chip-icon .np-filter-chip-label {
155
182
  padding-inline-start: 0.5rem;
156
183
  }
184
+ /* Removable, selecting and removing each keep a 48px target side by side. */
157
185
  .np-filter-chip-removable {
158
186
  padding-inline-end: 1px;
187
+ min-width: 5.5rem;
159
188
  }
160
189
  .np-filter-chip-removable .np-filter-chip-label {
161
190
  padding-inline-end: 1px;
@@ -229,16 +258,6 @@
229
258
  .np-filter-chip:has(input:checked) .np-filter-chip-label {
230
259
  color: var(--np-color-on-secondary-container);
231
260
  }
232
- .np-filter-chip-label:focus-visible {
233
- outline-width: 0;
234
- }
235
- .np-filter-chip:has(input:focus-visible) {
236
- outline-style: solid;
237
- outline-color: var(--np-color-secondary);
238
- outline-width: 3px;
239
- outline-offset: 2px;
240
- }
241
-
242
261
  .np-filter-chip-disabled .np-filter-chip-label {
243
262
  cursor: default;
244
263
  color: var(--np-color-on-surface) !important;
@@ -270,8 +289,5 @@
270
289
  .np-filter-chip-icon .np-chip-icon-checked {
271
290
  transition: none;
272
291
  }
273
- .np-filter-chip:has(input:focus-visible) {
274
- animation: focusAnimation var(--np-motion-expressive-slow-effects) forwards;
275
- }
276
292
  }
277
293
  </style>
@@ -10,9 +10,11 @@
10
10
  disabled = false,
11
11
  label = '',
12
12
  icon,
13
+ avatar,
14
+ variant = 'outlined',
13
15
  element = $bindable(),
14
16
  actionElement = $bindable(),
15
- removeAriaLabel = 'Remove',
17
+ removeAriaLabel,
16
18
  onclick,
17
19
  onremove,
18
20
  name,
@@ -22,46 +24,88 @@
22
24
  }: InputChipProps = $props()
23
25
 
24
26
  let hasError = $derived(!!issues?.length)
27
+ let content = $derived(label || `${value ?? ''}`)
28
+ // With nothing to do but remove, the chip is one focusable element: its remove button.
29
+ let hasAction = $derived(!!onclick)
30
+ let removeElement: HTMLElement | undefined = $state()
31
+
32
+ // Backspace and Delete remove the focused chip. Focus moves on to a neighbour first, so it is
33
+ // not lost when the chip leaves the page.
34
+ const removeWithKey = (event: KeyboardEvent) => {
35
+ if (disabled || (event.key !== 'Backspace' && event.key !== 'Delete')) return
36
+ event.preventDefault()
37
+ const set = element?.closest('.np-chip-set')
38
+ if (set && element) {
39
+ const chips = [
40
+ ...set.querySelectorAll<HTMLElement>('.np-input-chip:not(.np-input-chip-disabled)'),
41
+ ]
42
+ const index = chips.indexOf(element)
43
+ const neighbour = chips[index + 1] ?? chips[index - 1]
44
+ neighbour?.querySelector<HTMLElement>('button')?.focus()
45
+ }
46
+ removeElement?.click()
47
+ }
25
48
  </script>
26
49
 
27
50
  <div
28
51
  {...attributes}
29
52
  bind:this={element}
30
53
  aria-disabled={disabled}
54
+ onkeydown={(event) => {
55
+ attributes.onkeydown?.(event)
56
+ if (!event.defaultPrevented) removeWithKey(event)
57
+ }}
31
58
  class={[
32
59
  'np-input-chip',
33
- icon ? 'np-input-chip-icon' : '',
60
+ variant === 'elevated' && 'np-input-chip-elevated',
61
+ hasAction && 'np-input-chip-action',
62
+ avatar ? 'np-input-chip-avatar' : icon ? 'np-input-chip-icon' : '',
34
63
  disabled ? 'np-input-chip-disabled' : '',
35
64
  selected ? 'np-input-chip-selected' : '',
36
65
  hasError && !disabled && 'np-chip-error',
37
66
  attributes.class,
38
67
  ]}
39
68
  >
40
- <button
41
- bind:this={actionElement}
42
- type="button"
43
- class="np-input-chip-label"
44
- aria-pressed={selected === undefined ? undefined : selected}
45
- {disabled}
46
- {onclick}
47
- >
48
- {#if icon}
49
- <div class="np-chip-icon">
69
+ {#snippet chipContent()}
70
+ {#if avatar}
71
+ <div class="np-chip-avatar" aria-hidden="true">
72
+ {@render avatar()}
73
+ </div>
74
+ {:else if icon}
75
+ <div class="np-chip-icon" aria-hidden="true">
50
76
  {@render icon()}
51
77
  </div>
52
78
  {/if}
53
- <div class="np-chip-label">{label || value}</div>
54
- </button>
55
- <input type="hidden" {value} {name} {disabled} />
56
- {#if !disabled}
57
- <Ripple forElement={actionElement} />
79
+ <div class="np-chip-label">{content}</div>
80
+ {/snippet}
81
+ {#if hasAction}
82
+ <button
83
+ bind:this={actionElement}
84
+ type="button"
85
+ class="np-input-chip-label"
86
+ aria-pressed={selected === undefined ? undefined : selected}
87
+ {disabled}
88
+ {onclick}
89
+ >
90
+ {@render chipContent()}
91
+ <span class="np-touch"></span>
92
+ </button>
93
+ {#if !disabled}
94
+ <Ripple forElement={actionElement} />
95
+ {/if}
96
+ {:else}
97
+ <div class="np-input-chip-label">
98
+ {@render chipContent()}
99
+ </div>
58
100
  {/if}
101
+ <input type="hidden" {value} {name} {disabled} />
59
102
  <IconButton
60
103
  {disabled}
61
104
  type="button"
62
105
  size="xs"
63
106
  --np-icon-button-icon-size="1.125rem"
64
- aria-label={removeAriaLabel}
107
+ aria-label={removeAriaLabel ?? `Remove ${content}`}
108
+ bind:element={removeElement}
65
109
  onclick={onremove}
66
110
  >
67
111
  <CloseIcon />
@@ -70,6 +114,7 @@
70
114
 
71
115
  <style>
72
116
  .np-input-chip {
117
+ box-sizing: border-box;
73
118
  position: relative;
74
119
  display: inline-flex;
75
120
  align-items: center;
@@ -78,8 +123,12 @@
78
123
  --np-icon-button-icon-color: var(--np-color-on-surface-variant);
79
124
  --np-icon-size: 1.125rem;
80
125
  --np-ripple-pressed-opacity: 0.1;
126
+ --_state-color: var(--np-color-on-surface-variant);
81
127
  padding-inline-end: 1px;
82
- min-width: 0;
128
+ }
129
+ /* With an action besides remove, both keep a 48px target side by side. */
130
+ .np-input-chip-action {
131
+ min-width: 5.5rem;
83
132
  }
84
133
  .np-chip-error {
85
134
  --np-input-chip-outline-color: var(--np-color-error);
@@ -97,6 +146,7 @@
97
146
  padding-block: 0;
98
147
  padding-inline-end: 0;
99
148
  cursor: pointer;
149
+ position: relative;
100
150
  display: inline-flex;
101
151
  align-items: center;
102
152
  height: 2rem;
@@ -119,9 +169,36 @@
119
169
  .np-input-chip-selected .np-chip-icon {
120
170
  color: var(--np-color-primary);
121
171
  }
172
+ div.np-input-chip-label {
173
+ cursor: default;
174
+ }
122
175
  .np-input-chip-icon .np-input-chip-label {
123
176
  padding-inline-start: 0.5rem;
124
177
  }
178
+ .np-input-chip-avatar .np-input-chip-label {
179
+ padding-inline-start: 0.25rem;
180
+ }
181
+ /* A leading image is larger than an icon, with rounded corners. */
182
+ .np-chip-avatar {
183
+ display: flex;
184
+ flex: none;
185
+ width: 1.5rem;
186
+ height: 1.5rem;
187
+ border-radius: 0.75rem;
188
+ overflow: hidden;
189
+ }
190
+ .np-chip-avatar > :global(*) {
191
+ width: 100%;
192
+ height: 100%;
193
+ object-fit: cover;
194
+ }
195
+ .np-touch {
196
+ position: absolute;
197
+ inset-inline: 0;
198
+ top: 50%;
199
+ translate: 0 -50%;
200
+ height: 3rem;
201
+ }
125
202
  .np-chip-label {
126
203
  line-height: 1.25rem;
127
204
  font-size: 0.875rem;
@@ -150,17 +227,47 @@
150
227
  outline-color: transparent;
151
228
  background-color: var(--np-color-secondary-container);
152
229
  }
230
+ .np-input-chip-elevated {
231
+ box-shadow: var(--np-elevation-1);
232
+ }
233
+ .np-input-chip-elevated:not(.np-input-chip-selected)::before {
234
+ outline-color: transparent;
235
+ background-color: var(--np-color-surface-container-low);
236
+ }
237
+ .np-input-chip-elevated.np-input-chip-disabled {
238
+ box-shadow: none;
239
+ }
153
240
  .np-input-chip-selected {
154
241
  --np-icon-button-icon-color: var(--np-color-on-secondary-container);
242
+ --_state-color: var(--np-color-on-secondary-container);
155
243
  }
156
244
  .np-input-chip-selected .np-input-chip-label {
157
245
  color: var(--np-color-on-secondary-container);
158
246
  }
159
- .np-input-chip:has(:focus-visible) {
247
+ /* The whole chip shows focus, with its ring and a 10% state layer: when its body is focused, or its
248
+ remove button when that is all the chip has. A remove button next to a body shows its own. */
249
+ .np-input-chip:is(
250
+ :has(> .np-input-chip-label:focus-visible),
251
+ :not(.np-input-chip-action):has(:global(:focus-visible))
252
+ ) {
160
253
  outline-style: solid;
161
254
  outline-color: var(--np-color-secondary);
162
255
  outline-width: 3px;
163
256
  outline-offset: 2px;
257
+ &::before {
258
+ --_state-layer: color-mix(in srgb, var(--_state-color) 10%, transparent);
259
+ background-image: linear-gradient(var(--_state-layer), var(--_state-layer));
260
+ }
261
+ @media (prefers-reduced-motion: no-preference) {
262
+ animation: focusAnimation var(--np-motion-expressive-slow-effects) forwards;
263
+ }
264
+ }
265
+ .np-input-chip:not(.np-input-chip-action) {
266
+ --np-ripple-focus-opacity: 0;
267
+ }
268
+ .np-input-chip:not(.np-input-chip-action) :global(.np-icon-button:focus-visible) {
269
+ outline: none;
270
+ animation: none;
164
271
  }
165
272
 
166
273
  .np-input-chip-disabled .np-input-chip-label {
@@ -176,10 +283,4 @@
176
283
  outline-color: transparent;
177
284
  background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
178
285
  }
179
-
180
- @media (prefers-reduced-motion: no-preference) {
181
- .np-input-chip:has(:focus-visible) {
182
- animation: focusAnimation var(--np-motion-expressive-slow-effects) forwards;
183
- }
184
- }
185
286
  </style>
@@ -1,29 +1,13 @@
1
1
  <script lang="ts">
2
- import Button from '../button/Button.svelte'
2
+ import AssistChip from './AssistChip.svelte'
3
3
  import type { SuggestionChipProps } from './types.js'
4
4
 
5
- let {
6
- variant = 'outlined',
7
- disabled = false,
8
- label = '',
9
- element = $bindable(),
10
- ...attributes
11
- }: SuggestionChipProps = $props()
5
+ let { element = $bindable(), ...attributes }: SuggestionChipProps = $props()
12
6
  </script>
13
7
 
14
- <Button
15
- --np-elevated-button-label-text-color="var(--np-suggestion-chip-label-text-color, var(--np-color-on-surface-variant))"
16
- --np-outlined-button-label-text-color="var(--np-suggestion-chip-label-text-color, var(--np-color-on-surface-variant))"
17
- --np-outlined-button-outline-color="var(--np-suggestion-chip-outline-color, var(--np-color-outline-variant))"
18
- --np-button-padding="1rem"
19
- --np-button-shape="0.5rem"
20
- style="letter-spacing: 0.006rem"
8
+ <AssistChip
9
+ --np-assist-chip-label-text-color="var(--np-suggestion-chip-label-text-color, var(--np-color-on-surface-variant))"
10
+ --np-assist-chip-outline-color="var(--np-suggestion-chip-outline-color, var(--np-color-outline-variant))"
21
11
  bind:element
22
12
  {...attributes}
23
- size="xs"
24
- shape="square"
25
- {variant}
26
- {disabled}
27
- >
28
- {label}
29
- </Button>
13
+ />
@@ -1,4 +1,3 @@
1
- import type { SuggestionChipProps } from './types.js';
2
- declare const SuggestionChip: import("svelte").Component<SuggestionChipProps, {}, "element">;
1
+ declare const SuggestionChip: import("svelte").Component<import("./types.js").AssistChipProps, {}, "element">;
3
2
  type SuggestionChip = ReturnType<typeof SuggestionChip>;
4
3
  export default SuggestionChip;
@@ -13,7 +13,7 @@ export interface AssistChipProps extends HTMLAttributes<ButtonElement>, Omit<HTM
13
13
  element?: HTMLElement;
14
14
  type?: 'submit' | 'reset' | 'button' | (string & {}) | null;
15
15
  }
16
- export type SuggestionChipProps = Omit<AssistChipProps, 'icon'>;
16
+ export type SuggestionChipProps = AssistChipProps;
17
17
  export interface FilterChipProps extends HTMLAttributes<HTMLDivElement> {
18
18
  selected?: boolean;
19
19
  removable?: boolean;
@@ -21,6 +21,9 @@ export interface FilterChipProps extends HTMLAttributes<HTMLDivElement> {
21
21
  variant?: 'outlined' | 'elevated';
22
22
  label?: string;
23
23
  icon?: Snippet;
24
+ /** Trailing icon, such as an arrow for a chip that opens a menu. Not shown when `removable`. */
25
+ trailingIcon?: Snippet;
26
+ /** Defaults to "Remove" and the chip's label. */
24
27
  removeAriaLabel?: string;
25
28
  element?: HTMLDivElement;
26
29
  name?: string;
@@ -33,8 +36,12 @@ export interface FilterChipProps extends HTMLAttributes<HTMLDivElement> {
33
36
  export interface InputChipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onclick'> {
34
37
  selected?: boolean;
35
38
  disabled?: boolean | null;
39
+ variant?: 'outlined' | 'elevated';
36
40
  label?: string;
37
41
  icon?: Snippet;
42
+ /** A 24px leading image, such as a person's photo. Takes the place of `icon`. */
43
+ avatar?: Snippet;
44
+ /** Defaults to "Remove" and the chip's label. */
38
45
  removeAriaLabel?: string;
39
46
  element?: HTMLDivElement;
40
47
  actionElement?: HTMLButtonElement;
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
+ import '../internal/focus-ring.css'
3
+ import '../internal/visually-hidden.css'
2
4
  import Ripple from '../ripple/Ripple.svelte'
3
- import { tick } from 'svelte'
5
+ import { flushSync } from 'svelte'
4
6
  import type { Attachment } from 'svelte/attachments'
5
7
  import {
6
8
  addDays,
@@ -16,6 +18,7 @@
16
18
  isSameDay,
17
19
  isWithin,
18
20
  MAX_CALENDAR_ROWS,
21
+ parseISODate,
19
22
  startOfDay,
20
23
  toISODate,
21
24
  today as getToday,
@@ -42,6 +45,7 @@
42
45
  tabStopDate,
43
46
  focusRoot,
44
47
  onselect,
48
+ onconfirm,
45
49
  onfocusday,
46
50
  onmonthstep,
47
51
  monthSubhead,
@@ -100,22 +104,32 @@
100
104
  )
101
105
  }
102
106
 
103
- const moveFocus = async (grid: HTMLElement, date: Date) => {
107
+ // The month changes and focus moves in one step, so no key lands on the page in between.
108
+ const moveFocus = (grid: HTMLElement, date: Date) => {
104
109
  const target = startOfDay(clampDate(date, min, max))
105
- onfocusday?.(target)
106
- await tick()
110
+ flushSync(() => onfocusday?.(target))
107
111
  findDay(focusRoot ?? grid, target)?.focus()
108
112
  }
109
113
 
110
114
  const handleKeydown = (event: KeyboardEvent & { currentTarget: EventTarget & HTMLElement }) => {
111
115
  const grid = event.currentTarget
112
116
  const from = focusedDate ?? tabStop
117
+ if (event.key === 'Enter' && onconfirm) {
118
+ const day = (event.target as HTMLElement).closest<HTMLElement>('.np-calendar-day')
119
+ const date = parseISODate(day?.dataset.date)
120
+ if (!day || !date || day.getAttribute('aria-disabled') === 'true') return
121
+ event.preventDefault()
122
+ onselect?.(date)
123
+ onconfirm(date)
124
+ return
125
+ }
126
+ const forward = getComputedStyle(grid).direction === 'rtl' ? -1 : 1
113
127
  switch (event.key) {
114
128
  case 'ArrowLeft':
115
- moveFocus(grid, addDays(from, -1))
129
+ moveFocus(grid, addDays(from, -forward))
116
130
  break
117
131
  case 'ArrowRight':
118
- moveFocus(grid, addDays(from, 1))
132
+ moveFocus(grid, addDays(from, forward))
119
133
  break
120
134
  case 'ArrowUp':
121
135
  moveFocus(grid, addDays(from, -DAYS_IN_WEEK))
@@ -195,12 +209,14 @@
195
209
  aria-label={monthLabel}
196
210
  onkeydown={handleKeydown}
197
211
  >
198
- <thead class={weekdays ? undefined : 'np-calendar-head-hidden'}>
212
+ <thead class={weekdays ? undefined : 'np-visually-hidden'}>
199
213
  <tr>
200
214
  {#each weekdayLabels as weekday (weekday.long)}
201
215
  <th scope="col" abbr={weekday.long} class="np-calendar-weekday">
202
- <span aria-hidden="true">{weekday.narrow}</span>
203
- <span class="np-calendar-sr-only">{weekday.long}</span>
216
+ <span class="np-calendar-weekday-short" aria-hidden="true" title={weekday.long}
217
+ >{weekday.narrow}</span
218
+ >
219
+ <span class="np-visually-hidden">{weekday.long}</span>
204
220
  </th>
205
221
  {/each}
206
222
  </tr>
@@ -254,7 +270,7 @@
254
270
  if (event.currentTarget.matches(':focus-visible')) onfocusday?.(date)
255
271
  }}
256
272
  >
257
- <span class="np-calendar-day-container">
273
+ <span class="np-calendar-day-container np-focus-ring-part">
258
274
  {date.getDate()}
259
275
  {#if enabled}
260
276
  <Ripple />
@@ -273,27 +289,17 @@
273
289
 
274
290
  <style>
275
291
  .np-calendar-body.dynamic {
292
+ box-sizing: content-box;
276
293
  overflow: hidden;
277
294
  height: calc(var(--np-calendar-head, 2.5rem) + var(--np-calendar-rows) * 3rem);
295
+ padding: 0 0.25rem 0.25rem;
296
+ margin: 0 -0.25rem -0.25rem;
278
297
  }
279
298
 
280
299
  .np-calendar.swipeable {
281
300
  touch-action: pan-y;
282
301
  }
283
302
 
284
- .np-calendar-sr-only,
285
- .np-calendar-head-hidden {
286
- position: absolute;
287
- width: 1px;
288
- height: 1px;
289
- padding: 0;
290
- margin: -1px;
291
- overflow: hidden;
292
- clip-path: inset(50%);
293
- white-space: nowrap;
294
- border-width: 0;
295
- }
296
-
297
303
  .np-calendar-grid {
298
304
  display: table;
299
305
  border-collapse: collapse;
@@ -352,6 +358,10 @@
352
358
  vertical-align: middle;
353
359
  }
354
360
 
361
+ .np-calendar-weekday-short {
362
+ display: block;
363
+ }
364
+
355
365
  .np-calendar-weekday {
356
366
  box-sizing: content-box;
357
367
  height: 1.5rem;
@@ -406,9 +416,7 @@
406
416
  }
407
417
 
408
418
  .np-calendar-day:not(.between) .np-calendar-day-container {
409
- transition:
410
- background-color var(--np-motion-expressive-default-effects),
411
- outline-color var(--np-motion-expressive-default-effects);
419
+ transition: background-color var(--np-motion-expressive-default-effects);
412
420
  }
413
421
  }
414
422
 
@@ -456,11 +464,6 @@
456
464
  outline: none;
457
465
  }
458
466
 
459
- .np-calendar-day:focus-visible .np-calendar-day-container {
460
- outline: 3px solid var(--np-color-secondary);
461
- outline-offset: 2px;
462
- }
463
-
464
467
  .np-calendar-cell.in-range::before,
465
468
  .np-calendar-cell.range-start::before,
466
469
  .np-calendar-cell.range-end::before {
@@ -1,3 +1,5 @@
1
+ import '../internal/focus-ring.css';
2
+ import '../internal/visually-hidden.css';
1
3
  import type { CalendarProps } from './types.js';
2
4
  declare const Calendar: import("svelte").Component<CalendarProps, {}, "">;
3
5
  type Calendar = ReturnType<typeof Calendar>;