noph-ui 0.43.1 → 0.45.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 (175) hide show
  1. package/README.md +10 -1
  2. package/dist/animation.d.ts +17 -0
  3. package/dist/animation.js +48 -0
  4. package/dist/app-bar/AppBar.svelte +280 -0
  5. package/dist/app-bar/AppBar.svelte.d.ts +4 -0
  6. package/dist/app-bar/index.d.ts +1 -0
  7. package/dist/app-bar/index.js +1 -0
  8. package/dist/app-bar/types.d.ts +35 -0
  9. package/dist/app-bar/types.js +1 -0
  10. package/dist/autocomplete/AutoComplete.svelte +10 -11
  11. package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
  12. package/dist/autocomplete/types.d.ts +1 -1
  13. package/dist/badge/Badge.svelte +11 -1
  14. package/dist/badge/Badge.svelte.d.ts +2 -5
  15. package/dist/badge/types.d.ts +5 -0
  16. package/dist/badge/types.js +1 -0
  17. package/dist/button/Button.svelte +5 -15
  18. package/dist/button/IconButton.svelte +5 -15
  19. package/dist/button/SegmentedButton.svelte +15 -1
  20. package/dist/button/SplitButton.svelte +92 -0
  21. package/dist/button/SplitButton.svelte.d.ts +4 -0
  22. package/dist/button/index.d.ts +1 -0
  23. package/dist/button/index.js +1 -0
  24. package/dist/button/types.d.ts +23 -4
  25. package/dist/card/Card.svelte +1 -1
  26. package/dist/card/types.d.ts +1 -1
  27. package/dist/checkbox/Checkbox.svelte +50 -58
  28. package/dist/chip/AssistChip.svelte +5 -5
  29. package/dist/chip/ChipSet.svelte +3 -10
  30. package/dist/chip/ChipSet.svelte.d.ts +1 -1
  31. package/dist/chip/FilterChip.svelte +35 -27
  32. package/dist/chip/FilterChip.svelte.d.ts +1 -1
  33. package/dist/chip/InputChip.svelte +13 -2
  34. package/dist/chip/SuggestionChip.svelte +29 -0
  35. package/dist/chip/SuggestionChip.svelte.d.ts +4 -0
  36. package/dist/chip/index.d.ts +1 -0
  37. package/dist/chip/index.js +1 -0
  38. package/dist/chip/types.d.ts +26 -10
  39. package/dist/date-picker/Calendar.svelte +1 -1
  40. package/dist/date-picker/DatePickerDialog.svelte +30 -8
  41. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -1
  42. package/dist/date-picker/DateRangePicker.svelte +45 -10
  43. package/dist/date-picker/DateRangePicker.svelte.d.ts +4 -1
  44. package/dist/date-picker/DockedDatePicker.svelte +18 -4
  45. package/dist/date-picker/DockedDatePicker.svelte.d.ts +4 -1
  46. package/dist/date-picker/DockedDateTimePicker.svelte +18 -4
  47. package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +4 -1
  48. package/dist/date-picker/SelectionList.svelte +6 -32
  49. package/dist/date-picker/SelectionList.svelte.d.ts +2 -12
  50. package/dist/date-picker/TimeColumn.svelte +4 -29
  51. package/dist/date-picker/exitVisibility.svelte.d.ts +10 -0
  52. package/dist/date-picker/exitVisibility.svelte.js +30 -0
  53. package/dist/date-picker/optionList.d.ts +16 -0
  54. package/dist/date-picker/optionList.js +43 -0
  55. package/dist/date-picker/types.d.ts +11 -4
  56. package/dist/dialog/Dialog.svelte +77 -68
  57. package/dist/dialog/Dialog.svelte.d.ts +3 -3
  58. package/dist/dialog/types.d.ts +5 -3
  59. package/dist/divider/Divider.svelte +6 -2
  60. package/dist/divider/Divider.svelte.d.ts +1 -1
  61. package/dist/divider/types.d.ts +1 -0
  62. package/dist/fab/ExtendedFab.svelte +281 -0
  63. package/dist/fab/ExtendedFab.svelte.d.ts +5 -0
  64. package/dist/fab/Fab.svelte +239 -0
  65. package/dist/fab/Fab.svelte.d.ts +5 -0
  66. package/dist/fab/FabMenu.svelte +255 -0
  67. package/dist/fab/FabMenu.svelte.d.ts +7 -0
  68. package/dist/fab/index.d.ts +3 -0
  69. package/dist/fab/index.js +3 -0
  70. package/dist/fab/types.d.ts +58 -0
  71. package/dist/fab/types.js +1 -0
  72. package/dist/icons/ArrowBackIcon.svelte +13 -0
  73. package/dist/icons/ArrowBackIcon.svelte.d.ts +7 -0
  74. package/dist/icons/ChevronDownIcon.svelte +3 -0
  75. package/dist/icons/ChevronDownIcon.svelte.d.ts +26 -0
  76. package/dist/icons/Icon.svelte +1 -6
  77. package/dist/icons/Icon.svelte.d.ts +1 -6
  78. package/dist/icons/SearchIcon.svelte +15 -0
  79. package/dist/icons/SearchIcon.svelte.d.ts +7 -0
  80. package/dist/icons/index.d.ts +3 -0
  81. package/dist/icons/index.js +3 -0
  82. package/dist/icons/types.d.ts +6 -0
  83. package/dist/icons/types.js +1 -0
  84. package/dist/index.d.ts +6 -0
  85. package/dist/index.js +6 -0
  86. package/dist/internal/focus-ring.css +12 -0
  87. package/dist/list/Item.svelte +5 -0
  88. package/dist/list/Item.svelte.d.ts +1 -1
  89. package/dist/list/ListItem.svelte +2 -2
  90. package/dist/list/ListItem.svelte.d.ts +1 -1
  91. package/dist/list/types.d.ts +1 -0
  92. package/dist/loading-indicator/LoadingIndicator.svelte +7 -2
  93. package/dist/loading-indicator/LoadingIndicator.svelte.d.ts +1 -1
  94. package/dist/loading-indicator/types.d.ts +3 -2
  95. package/dist/menu/Menu.svelte +13 -8
  96. package/dist/menu/Menu.svelte.d.ts +2 -2
  97. package/dist/menu/MenuItem.svelte +2 -2
  98. package/dist/menu/MenuItem.svelte.d.ts +1 -1
  99. package/dist/menu/types.d.ts +0 -2
  100. package/dist/navigation-bar/NavigationBar.svelte +66 -0
  101. package/dist/navigation-bar/NavigationBar.svelte.d.ts +4 -0
  102. package/dist/navigation-bar/NavigationBarItem.svelte +261 -0
  103. package/dist/navigation-bar/NavigationBarItem.svelte.d.ts +5 -0
  104. package/dist/navigation-bar/index.d.ts +2 -0
  105. package/dist/navigation-bar/index.js +2 -0
  106. package/dist/navigation-bar/types.d.ts +29 -0
  107. package/dist/navigation-bar/types.js +1 -0
  108. package/dist/navigation-drawer/NavigationDrawer.svelte +128 -76
  109. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +4 -1
  110. package/dist/navigation-drawer/NavigationDrawerItem.svelte +13 -3
  111. package/dist/navigation-drawer/NavigationDrawerItem.svelte.d.ts +1 -1
  112. package/dist/navigation-drawer/types.d.ts +7 -1
  113. package/dist/navigation-rail/NavigationRail.svelte +9 -3
  114. package/dist/navigation-rail/NavigationRail.svelte.d.ts +1 -1
  115. package/dist/navigation-rail/NavigationRailItem.svelte +25 -1
  116. package/dist/navigation-rail/NavigationRailItem.svelte.d.ts +1 -1
  117. package/dist/navigation-rail/types.d.ts +7 -1
  118. package/dist/popover.svelte.d.ts +14 -0
  119. package/dist/popover.svelte.js +31 -0
  120. package/dist/press.svelte.d.ts +19 -0
  121. package/dist/press.svelte.js +45 -0
  122. package/dist/progress/CircularProgress.svelte +4 -1
  123. package/dist/progress/CircularProgress.svelte.d.ts +1 -1
  124. package/dist/progress/LinearProgress.svelte +9 -1
  125. package/dist/progress/LinearProgress.svelte.d.ts +1 -1
  126. package/dist/progress/types.d.ts +3 -2
  127. package/dist/radio/Radio.svelte +26 -7
  128. package/dist/radio/types.d.ts +3 -0
  129. package/dist/search/Search.svelte +472 -0
  130. package/dist/search/Search.svelte.d.ts +5 -0
  131. package/dist/search/index.d.ts +1 -0
  132. package/dist/search/index.js +1 -0
  133. package/dist/search/types.d.ts +39 -0
  134. package/dist/search/types.js +1 -0
  135. package/dist/select/NativeSelect.svelte +13 -16
  136. package/dist/select/NativeSelect.svelte.d.ts +2 -12
  137. package/dist/select/Option.svelte +1 -7
  138. package/dist/select/Option.svelte.d.ts +1 -6
  139. package/dist/select/Select.svelte +48 -87
  140. package/dist/select/Select.svelte.d.ts +1 -1
  141. package/dist/select/types.d.ts +15 -1
  142. package/dist/sheet/Sheet.svelte +260 -0
  143. package/dist/sheet/Sheet.svelte.d.ts +7 -0
  144. package/dist/sheet/index.d.ts +1 -0
  145. package/dist/sheet/index.js +1 -0
  146. package/dist/sheet/types.d.ts +19 -0
  147. package/dist/sheet/types.js +1 -0
  148. package/dist/slider/Slider.svelte +154 -30
  149. package/dist/slider/Slider.svelte.d.ts +1 -1
  150. package/dist/slider/types.d.ts +3 -0
  151. package/dist/snackbar/Snackbar.svelte +48 -37
  152. package/dist/snackbar/Snackbar.svelte.d.ts +2 -2
  153. package/dist/snackbar/types.d.ts +3 -2
  154. package/dist/switch/Switch.svelte +51 -20
  155. package/dist/switch/types.d.ts +3 -0
  156. package/dist/tabs/Tab.svelte +8 -12
  157. package/dist/tabs/Tabs.svelte +3 -3
  158. package/dist/tabs/Tabs.svelte.d.ts +1 -1
  159. package/dist/text-field/TextField.svelte +19 -12
  160. package/dist/text-field/TextField.svelte.d.ts +1 -1
  161. package/dist/toolbar/Toolbar.svelte +133 -0
  162. package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
  163. package/dist/toolbar/index.d.ts +1 -0
  164. package/dist/toolbar/index.js +1 -0
  165. package/dist/toolbar/types.d.ts +15 -0
  166. package/dist/toolbar/types.js +1 -0
  167. package/dist/tooltip/RichTooltip.svelte +25 -12
  168. package/dist/tooltip/RichTooltip.svelte.d.ts +2 -2
  169. package/dist/tooltip/Tooltip.svelte +22 -11
  170. package/dist/tooltip/Tooltip.svelte.d.ts +4 -1
  171. package/dist/types.d.ts +8 -0
  172. package/dist/types.js +8 -0
  173. package/package.json +13 -13
  174. package/dist/date-picker/enterAnimation.d.ts +0 -1
  175. package/dist/date-picker/enterAnimation.js +0 -10
package/README.md CHANGED
@@ -54,8 +54,12 @@ Live demos and usage examples for every component are available on [noph.dev](ht
54
54
 
55
55
  **Actions**
56
56
  [Buttons](https://noph.dev/components/button) ·
57
+ [Button group](https://noph.dev/components/button-group) ·
58
+ [FAB](https://noph.dev/components/fab) ·
57
59
  [Icon buttons](https://noph.dev/components/icon-button) ·
58
60
  [Segmented buttons](https://noph.dev/components/segmented-button) ·
61
+ [Split button](https://noph.dev/components/split-button) ·
62
+ [Toolbar](https://noph.dev/components/toolbar) ·
59
63
  [Ripple](https://noph.dev/components/ripple)
60
64
 
61
65
  **Inputs & selection**
@@ -65,6 +69,7 @@ Live demos and usage examples for every component are available on [noph.dev](ht
65
69
  [DatePicker](https://noph.dev/components/date-picker) ·
66
70
  [DateTimePicker](https://noph.dev/components/date-time-picker) ·
67
71
  [Radio](https://noph.dev/components/radio) ·
72
+ [Search](https://noph.dev/components/search) ·
68
73
  [Select](https://noph.dev/components/select) ·
69
74
  [Slider](https://noph.dev/components/slider) ·
70
75
  [Switch](https://noph.dev/components/switch) ·
@@ -72,8 +77,10 @@ Live demos and usage examples for every component are available on [noph.dev](ht
72
77
 
73
78
  **Navigation**
74
79
  [Menus](https://noph.dev/components/menu) ·
80
+ [Navigation Bar](https://noph.dev/components/navigation-bar) ·
75
81
  [Navigation Drawer](https://noph.dev/components/navigation-drawer) ·
76
82
  [Navigation Rail](https://noph.dev/components/navigation-rail) ·
83
+ [App bar](https://noph.dev/components/app-bar) ·
77
84
  [Tabs](https://noph.dev/components/tabs)
78
85
 
79
86
  **Communication**
@@ -82,9 +89,11 @@ Live demos and usage examples for every component are available on [noph.dev](ht
82
89
  [Loading indicator](https://noph.dev/components/loading-indicator) ·
83
90
  [Progress indicators](https://noph.dev/components/progress) ·
84
91
  [Snackbar](https://noph.dev/components/snackbar) ·
85
- [Tooltips](https://noph.dev/components/tooltip)
92
+ [Tooltips](https://noph.dev/components/tooltip) ·
93
+ [Rich tooltip](https://noph.dev/components/tooltip#rich-tooltip)
86
94
 
87
95
  **Containment**
88
96
  [Cards](https://noph.dev/components/card) ·
89
97
  [Divider](https://noph.dev/components/divider) ·
98
+ [Sheets](https://noph.dev/components/sheet) ·
90
99
  [Lists](https://noph.dev/components/list)
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Runs `fn` after two frames, so a class or attribute change written just before it has been through
3
+ * one style recalculation. Returns a canceller.
4
+ */
5
+ export declare const afterTwoFrames: (fn: () => void) => () => void;
6
+ /**
7
+ * Runs `fn` once `element` has finished the transitions it is playing, so content gated on `open`
8
+ * can stay mounted for as long as its container is still on screen. A container that animates its
9
+ * exit is still painted after `open` has flipped to `false`, and content dropped on that flip empties
10
+ * the container a frame before it starts to leave.
11
+ *
12
+ * The wait starts two frames in because a transition does not exist until the browser has
13
+ * recalculated styles, so `getAnimations()` reads empty straight after the change that starts it.
14
+ * An element with no exit animation therefore finishes on the second frame. Returns a canceller for
15
+ * the container reopening mid exit.
16
+ */
17
+ export declare const afterExit: (element: Element | undefined, fn: () => void) => () => void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Runs `fn` after two frames, so a class or attribute change written just before it has been through
3
+ * one style recalculation. Returns a canceller.
4
+ */
5
+ export const afterTwoFrames = (fn) => {
6
+ let inner = 0;
7
+ const outer = requestAnimationFrame(() => {
8
+ inner = requestAnimationFrame(fn);
9
+ });
10
+ return () => {
11
+ cancelAnimationFrame(outer);
12
+ cancelAnimationFrame(inner);
13
+ };
14
+ };
15
+ /**
16
+ * Runs `fn` once `element` has finished the transitions it is playing, so content gated on `open`
17
+ * can stay mounted for as long as its container is still on screen. A container that animates its
18
+ * exit is still painted after `open` has flipped to `false`, and content dropped on that flip empties
19
+ * the container a frame before it starts to leave.
20
+ *
21
+ * The wait starts two frames in because a transition does not exist until the browser has
22
+ * recalculated styles, so `getAnimations()` reads empty straight after the change that starts it.
23
+ * An element with no exit animation therefore finishes on the second frame. Returns a canceller for
24
+ * the container reopening mid exit.
25
+ */
26
+ export const afterExit = (element, fn) => {
27
+ if (!element) {
28
+ fn();
29
+ return () => { };
30
+ }
31
+ let cancelled = false;
32
+ const finish = () => {
33
+ if (!cancelled)
34
+ fn();
35
+ };
36
+ const cancelFrames = afterTwoFrames(() => {
37
+ const animations = element.getAnimations();
38
+ if (animations.length === 0) {
39
+ finish();
40
+ return;
41
+ }
42
+ Promise.allSettled(animations.map(({ finished }) => finished)).then(finish);
43
+ });
44
+ return () => {
45
+ cancelled = true;
46
+ cancelFrames();
47
+ };
48
+ };
@@ -0,0 +1,280 @@
1
+ <script lang="ts">
2
+ import type { AppBarProps } from './types.js'
3
+
4
+ let {
5
+ variant = 'small',
6
+ headline,
7
+ subtitle,
8
+ leading,
9
+ search,
10
+ trailing,
11
+ collapsible = false,
12
+ scroller = 'root',
13
+ children,
14
+ element = $bindable(),
15
+ ...attributes
16
+ }: AppBarProps = $props()
17
+
18
+ const twoLine = $derived(variant === 'medium' || variant === 'large')
19
+ const isSearch = $derived(variant === 'search')
20
+ // Only a two-line bar has a second row to collapse.
21
+ const collapses = $derived(collapsible && twoLine)
22
+ </script>
23
+
24
+ {#snippet titles(inline: boolean)}
25
+ <div class={['np-app-bar-titles', inline && 'np-app-bar-titles-inline']}>
26
+ <div class="np-app-bar-headline">{headline}</div>
27
+ {#if subtitle}
28
+ <div class="np-app-bar-subtitle">{subtitle}</div>
29
+ {/if}
30
+ </div>
31
+ {/snippet}
32
+
33
+ <header
34
+ {...attributes}
35
+ bind:this={element}
36
+ class={[
37
+ 'np-app-bar',
38
+ `np-app-bar-${variant}`,
39
+ collapses && 'np-app-bar-collapsible',
40
+ collapses && scroller === 'nearest' && 'np-app-bar-scroller-nearest',
41
+ attributes.class,
42
+ ]}
43
+ >
44
+ <div class="np-app-bar-row">
45
+ {#if leading}
46
+ <div class="np-app-bar-leading">{@render leading()}</div>
47
+ {/if}
48
+ {#if isSearch}
49
+ <!-- A search app bar carries a field in place of a headline. -->
50
+ <div class="np-app-bar-search-field">{@render search?.()}</div>
51
+ {:else if twoLine}
52
+ <!--
53
+ The collapsed copy of the headline and subtitle. Hidden from assistive technology so they
54
+ are announced once; the real ones below stay in the accessibility tree even at height zero.
55
+ -->
56
+ <div class="np-app-bar-inline" aria-hidden="true">{@render titles(true)}</div>
57
+ {:else}
58
+ {@render titles(false)}
59
+ {/if}
60
+ {#if trailing}
61
+ <div class="np-app-bar-trailing">{@render trailing()}</div>
62
+ {/if}
63
+ </div>
64
+ {#if twoLine}
65
+ <div class="np-app-bar-second-row">
66
+ {@render titles(false)}
67
+ </div>
68
+ {/if}
69
+ {@render children?.()}
70
+ </header>
71
+
72
+ <style>
73
+ .np-app-bar {
74
+ position: sticky;
75
+ inset-block-start: 0;
76
+ z-index: 8;
77
+ box-sizing: border-box;
78
+ display: flex;
79
+ flex-direction: column;
80
+ width: 100%;
81
+ background-color: var(--np-app-bar-container-color, var(--np-color-surface));
82
+ color: var(--np-color-on-surface);
83
+ --np-icon-button-icon-color: var(--np-color-on-surface-variant);
84
+ }
85
+
86
+ /*
87
+ * An expanded search floats past the bar's own bounds, into the space of whatever comes
88
+ * after it in the document. Sibling bars share the same z-index, so without this the one
89
+ * holding the expanded search would lose to a later sibling and the view would open behind
90
+ * it rather than over it.
91
+ */
92
+ .np-app-bar:has(:global(.np-search-expanded)) {
93
+ z-index: 9;
94
+ }
95
+
96
+ .np-app-bar-row {
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 0.5rem;
100
+ flex: none;
101
+ /* `min-height` rather than `height`, so a subtitle on a one-row bar grows it. */
102
+ min-height: 4rem;
103
+ padding-inline: 0.25rem;
104
+ }
105
+
106
+ .np-app-bar-leading,
107
+ .np-app-bar-trailing {
108
+ display: flex;
109
+ align-items: center;
110
+ flex: none;
111
+ gap: 0.25rem;
112
+ }
113
+ .np-app-bar-trailing {
114
+ margin-inline-start: auto;
115
+ }
116
+
117
+ .np-app-bar-titles {
118
+ display: flex;
119
+ flex-direction: column;
120
+ justify-content: center;
121
+ gap: 0.125rem;
122
+ min-width: 0;
123
+ }
124
+
125
+ .np-app-bar-subtitle {
126
+ font-size: 0.875rem;
127
+ line-height: 1.25rem;
128
+ font-weight: 500;
129
+ color: var(--np-app-bar-subtitle-color, var(--np-color-on-surface-variant));
130
+ overflow: hidden;
131
+ text-overflow: ellipsis;
132
+ white-space: nowrap;
133
+ }
134
+ /* The collapsed copy sits on one row, so its subtitle stays at the small size. */
135
+ .np-app-bar-titles-inline .np-app-bar-headline {
136
+ font-size: 1.375rem;
137
+ }
138
+
139
+ .np-app-bar-headline {
140
+ font-size: var(--_headline-size);
141
+ line-height: 1.2;
142
+ font-weight: 400;
143
+ color: var(--np-app-bar-headline-color, var(--np-color-on-surface));
144
+ overflow: hidden;
145
+ text-overflow: ellipsis;
146
+ white-space: nowrap;
147
+ min-width: 0;
148
+ }
149
+
150
+ /* One-row bars: the headline sits in the action row. */
151
+ .np-app-bar-small,
152
+ .np-app-bar-search {
153
+ --_headline-size: 1.375rem;
154
+ }
155
+
156
+ /*
157
+ * The field takes the row, so a search bar has no headline to size. Named `-search-field`
158
+ * rather than `-search`, which is already the variant class on the header.
159
+ */
160
+ .np-app-bar-search-field {
161
+ flex: 1;
162
+ min-width: 0;
163
+ display: flex;
164
+ align-items: center;
165
+ padding-inline: 0.25rem;
166
+ }
167
+ .np-app-bar-small .np-app-bar-titles {
168
+ padding-inline-start: 0.75rem;
169
+ flex: 1;
170
+ }
171
+
172
+ /* Two-row bars: the headline sits below, and the inline copy is revealed on collapse. */
173
+ .np-app-bar-medium {
174
+ --_headline-size: 1.5rem;
175
+ --_second-row-height: 3rem; /* medium: 64 + 48 = 112 */
176
+ }
177
+ .np-app-bar-large {
178
+ --_headline-size: 1.75rem;
179
+ --_second-row-height: 5.5rem; /* large: 64 + 88 = 152 */
180
+ }
181
+
182
+ /*
183
+ * Sized by its content with a floor, not a fixed height, so a headline plus a subtitle is not
184
+ * clipped. `interpolate-size` lets the collapse animate that intrinsic height down to zero,
185
+ * the same way the extended FAB animates its width.
186
+ */
187
+ .np-app-bar-second-row {
188
+ interpolate-size: allow-keywords;
189
+ display: flex;
190
+ align-items: flex-end;
191
+ box-sizing: border-box;
192
+ height: auto;
193
+ min-height: var(--_second-row-height);
194
+ padding-inline: 1rem;
195
+ padding-block-end: 1rem;
196
+ overflow: hidden;
197
+ }
198
+
199
+ .np-app-bar-inline {
200
+ font-size: 1.375rem;
201
+ line-height: 1.2;
202
+ font-weight: 400;
203
+ color: var(--np-app-bar-headline-color, var(--np-color-on-surface));
204
+ padding-inline-start: 0.75rem;
205
+ flex: 1;
206
+ min-width: 0;
207
+ overflow: hidden;
208
+ text-overflow: ellipsis;
209
+ white-space: nowrap;
210
+ /* Hidden until the bar collapses. */
211
+ opacity: 0;
212
+ }
213
+
214
+ /*
215
+ * The collapse. `animation-timeline: scroll()` drives these keyframes from the scroll position
216
+ * itself, so there is no scroll listener, no measurement and no state. `animation-range` ends
217
+ * the collapse once the page has scrolled past the second row's height.
218
+ */
219
+ @supports (animation-timeline: scroll()) {
220
+ /*
221
+ * Scroll anchoring has to be off on whichever element scrolls, or the collapse cannot
222
+ * happen at all: the bar shrinking above the viewport is exactly the content shift that
223
+ * anchoring exists to compensate for, so the browser pushes the scroll position back and
224
+ * the two fight to a standstill. `:has()` reaches the scroller so a consumer does not have
225
+ * to know this, and only ever matches when a collapsible bar is actually present.
226
+ */
227
+ :global(html:has(.np-app-bar-collapsible)) {
228
+ overflow-anchor: none;
229
+ }
230
+ /*
231
+ * `scroll(nearest block)` resolves to the nearest scrollable ancestor at any depth, and CSS
232
+ * cannot single that one out, so every ancestor of the bar is covered. Turning anchoring off
233
+ * on an ancestor that does not scroll costs nothing.
234
+ */
235
+ :global(:has(.np-app-bar-scroller-nearest)) {
236
+ overflow-anchor: none;
237
+ }
238
+
239
+ /*
240
+ * Deliberately not gated on `prefers-reduced-motion`. The collapse is a layout state tied
241
+ * one to one to the scroll position, the way `position: sticky` is, not motion that plays on
242
+ * its own. Turning it off would leave the bar permanently eating a third of a small screen
243
+ * rather than removing an animation.
244
+ */
245
+ .np-app-bar-collapsible .np-app-bar-second-row {
246
+ animation: np-app-bar-collapse linear both;
247
+ animation-timeline: scroll(root block);
248
+ animation-range: 0 var(--_second-row-height);
249
+ }
250
+ .np-app-bar-collapsible .np-app-bar-inline {
251
+ animation: np-app-bar-reveal linear both;
252
+ animation-timeline: scroll(root block);
253
+ animation-range: 0 var(--_second-row-height);
254
+ }
255
+ .np-app-bar-scroller-nearest .np-app-bar-second-row,
256
+ .np-app-bar-scroller-nearest .np-app-bar-inline {
257
+ animation-timeline: scroll(nearest block);
258
+ }
259
+ }
260
+
261
+ @keyframes np-app-bar-collapse {
262
+ to {
263
+ height: 0;
264
+ min-height: 0;
265
+ padding-block-end: 0;
266
+ opacity: 0;
267
+ }
268
+ }
269
+
270
+ @keyframes np-app-bar-reveal {
271
+ /* Held hidden for the first half, so the two headlines cross rather than overlap. */
272
+ 0%,
273
+ 50% {
274
+ opacity: 0;
275
+ }
276
+ to {
277
+ opacity: 1;
278
+ }
279
+ }
280
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { AppBarProps } from './types.ts';
2
+ declare const AppBar: import("svelte").Component<AppBarProps, {}, "element">;
3
+ type AppBar = ReturnType<typeof AppBar>;
4
+ export default AppBar;
@@ -0,0 +1 @@
1
+ export { default as AppBar } from './AppBar.svelte';
@@ -0,0 +1 @@
1
+ export { default as AppBar } from './AppBar.svelte';
@@ -0,0 +1,35 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ export interface AppBarProps extends HTMLAttributes<HTMLElement> {
4
+ /**
5
+ * M3 Expressive's four: `'search'` carries a search field instead of a headline and is the
6
+ * emphasised, global entry point to search. `'small'` is one row. `'medium'` and `'large'` are
7
+ * the flexible variants, putting the headline on a second line below the action row.
8
+ */
9
+ variant?: 'search' | 'small' | 'medium' | 'large';
10
+ /** Named `headline` rather than `title`, which is the native tooltip attribute. */
11
+ headline?: string;
12
+ /** Secondary line under the headline. */
13
+ subtitle?: string;
14
+ /** Leading action, usually a navigation or back icon button. */
15
+ leading?: Snippet;
16
+ /**
17
+ * The search field for `variant="search"`. Put a `Search` here. Replaces the headline, which a
18
+ * search app bar does not show.
19
+ */
20
+ search?: Snippet;
21
+ /** Trailing actions. */
22
+ trailing?: Snippet;
23
+ /**
24
+ * Collapses a `medium` or `large` bar down to one row as the page scrolls. Driven by
25
+ * `animation-timeline: scroll()`, so there is no scroll listener. Where that is unsupported the
26
+ * bar simply stays expanded.
27
+ */
28
+ collapsible?: boolean;
29
+ /**
30
+ * The element the collapse is tied to. `'root'` (the default) follows the page scroller; pass a
31
+ * different scroll container's axis with `'nearest'` when the bar sits inside one.
32
+ */
33
+ scroller?: 'root' | 'nearest';
34
+ element?: HTMLElement;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -10,7 +10,7 @@
10
10
  value = $bindable(),
11
11
  variant = 'outlined',
12
12
  element = $bindable(),
13
- menuOpen = $bindable(false),
13
+ open = $bindable(false),
14
14
  populated,
15
15
  clampMenuWidth = false,
16
16
  optionsFilter,
@@ -19,6 +19,7 @@
19
19
  finalPopulated = populated
20
20
  menuElement.hidePopover()
21
21
  },
22
+ style,
22
23
  onkeydown,
23
24
  onclick,
24
25
  oninput,
@@ -36,7 +37,6 @@
36
37
  let displayOptions = $derived(query === '' && !optionsFilter ? options : options.filter(filterFn))
37
38
  let useVirtualList = $derived(displayOptions.length > virtualThreshold)
38
39
  let widthProp = $derived(clampMenuWidth || useVirtualList ? 'width' : 'min-width')
39
- let clientWidth = $state(0)
40
40
  let menuElement = $state<HTMLDivElement>()
41
41
  let finalPopulated = $derived(populated)
42
42
  let activeIndex = $state(NO_INDEX)
@@ -74,7 +74,7 @@
74
74
  activeIndex = NO_INDEX
75
75
  return
76
76
  }
77
- if (!menuOpen || activeIndex < 0) return
77
+ if (!open || activeIndex < 0) return
78
78
  const id = `${uid}-opt-${activeIndex}`
79
79
  const optEl = document.getElementById(id)
80
80
  if (optEl) {
@@ -135,14 +135,13 @@
135
135
  autocomplete="off"
136
136
  {variant}
137
137
  populated={finalPopulated}
138
- bind:clientWidth
139
138
  bind:value
140
- style="anchor-name:--{uid};"
139
+ style="anchor-name:--{uid};{style ?? ''}"
141
140
  role="combobox"
142
141
  aria-controls="listbox-{uid}"
143
- aria-expanded={menuOpen}
142
+ aria-expanded={open}
144
143
  aria-autocomplete="list"
145
- aria-activedescendant={menuOpen && activeIndex >= 0 ? `${uid}-opt-${activeIndex}` : undefined}
144
+ aria-activedescendant={open && activeIndex >= 0 ? `${uid}-opt-${activeIndex}` : undefined}
146
145
  aria-haspopup="listbox"
147
146
  onclick={(event) => {
148
147
  finalPopulated = true
@@ -159,7 +158,7 @@
159
158
  menuElement?.hidePopover()
160
159
  return
161
160
  }
162
- if (event.key === 'Escape' && menuOpen) {
161
+ if (event.key === 'Escape' && open) {
163
162
  menuElement?.hidePopover()
164
163
  activeIndex = NO_INDEX
165
164
  event.preventDefault()
@@ -189,7 +188,7 @@
189
188
  event.preventDefault()
190
189
  return
191
190
  }
192
- if (event.key === 'Enter' && menuOpen && activeIndex >= 0) {
191
+ if (event.key === 'Enter' && open && activeIndex >= 0) {
193
192
  const opt = displayOptions[activeIndex]
194
193
  if (opt) {
195
194
  selectOption(opt)
@@ -203,7 +202,7 @@
203
202
  />
204
203
  <Menu
205
204
  id="listbox-{uid}"
206
- style="position-anchor:--{uid};{widthProp}:{clientWidth}px"
205
+ style="position-anchor:--{uid};{widthProp}:anchor-size(width)"
207
206
  role="listbox"
208
207
  class={[!displayOptions.length && 'np-auto-complete-empty']}
209
208
  coverAnchor={false}
@@ -214,7 +213,7 @@
214
213
  ? 'var(--np-outlined-select-text-field-container-shape)'
215
214
  : 'var(--np-filled-select-text-field-container-shape)'}
216
215
  anchor={element}
217
- bind:open={menuOpen}
216
+ bind:open
218
217
  ontoggle={(e) => {
219
218
  if (e.newState === 'closed') {
220
219
  activeIndex = NO_INDEX
@@ -1,4 +1,4 @@
1
1
  import type { AutoCompleteProps } from './types.ts';
2
- declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "value" | "element" | "menuOpen">;
2
+ declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value" | "open">;
3
3
  type AutoComplete = ReturnType<typeof AutoComplete>;
4
4
  export default AutoComplete;
@@ -13,5 +13,5 @@ export interface AutoCompleteProps extends Omit<TextFieldProps, 'clientWidth' |
13
13
  onoptionselect?: (option: AutoCompleteOption, menuElement: HTMLDivElement) => void;
14
14
  clampMenuWidth?: boolean;
15
15
  virtualThreshold?: number;
16
- menuOpen?: boolean;
16
+ open?: boolean;
17
17
  }
@@ -1,14 +1,24 @@
1
1
  <script lang="ts">
2
- let { label, ariaLabel }: { label?: string | number; ariaLabel?: string } = $props()
2
+ import type { BadgeProps } from './types.js'
3
+
4
+ let {
5
+ label,
6
+ 'aria-label': ariaLabel,
7
+ element = $bindable(),
8
+ ...attributes
9
+ }: BadgeProps = $props()
3
10
  </script>
4
11
 
5
12
  <div
13
+ {...attributes}
6
14
  aria-hidden={ariaLabel ? undefined : 'true'}
7
15
  role={ariaLabel ? 'status' : undefined}
8
16
  aria-label={ariaLabel}
17
+ bind:this={element}
9
18
  class={[
10
19
  'np-badge-container',
11
20
  label === undefined ? 'np-badge-container-no-label' : 'np-badge-container-label',
21
+ attributes.class,
12
22
  ]}
13
23
  >
14
24
  {#if label !== undefined}
@@ -1,7 +1,4 @@
1
- type $$ComponentProps = {
2
- label?: string | number;
3
- ariaLabel?: string;
4
- };
5
- declare const Badge: import("svelte").Component<$$ComponentProps, {}, "">;
1
+ import type { BadgeProps } from './types.ts';
2
+ declare const Badge: import("svelte").Component<BadgeProps, {}, "element">;
6
3
  type Badge = ReturnType<typeof Badge>;
7
4
  export default Badge;
@@ -0,0 +1,5 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
3
+ label?: string | number;
4
+ element?: HTMLDivElement;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import '../internal/focus-ring.css'
3
+ import { pressMorph } from '../press.svelte.js'
3
4
  import CircularProgress from '../progress/CircularProgress.svelte'
4
5
  import Ripple from '../ripple/Ripple.svelte'
5
6
  import Tooltip from '../tooltip/Tooltip.svelte'
6
- import { onMount } from 'svelte'
7
7
  import type { HTMLButtonAttributes } from 'svelte/elements'
8
8
  import type { ButtonProps } from './types.js'
9
9
 
@@ -30,22 +30,12 @@
30
30
 
31
31
  const tooltipId = $derived(title && !disabled && !loading ? uid : undefined)
32
32
 
33
- let pressed = $state(false)
34
- let pressedTimeout: ReturnType<typeof setTimeout>
33
+ const morph = pressMorph()
35
34
 
36
35
  const handlePress = () => {
37
36
  if (disabled || loading) return
38
- pressed = true
39
- pressedTimeout = setTimeout(() => {
40
- pressed = false
41
- }, 100)
37
+ morph.press()
42
38
  }
43
-
44
- onMount(() => {
45
- return () => {
46
- clearTimeout(pressedTimeout)
47
- }
48
- })
49
39
  </script>
50
40
 
51
41
  {#snippet content()}
@@ -95,7 +85,7 @@
95
85
  selected && 'selected',
96
86
  'enabled',
97
87
  variant,
98
- pressed && 'pressed',
88
+ morph.pressed && 'pressed',
99
89
  attributes.class,
100
90
  ]}
101
91
  >
@@ -128,7 +118,7 @@
128
118
  selected && 'selected',
129
119
  loading && 'np-loading',
130
120
  disabled || loading ? `${variant}-disabled disabled` : `${variant} enabled`,
131
- pressed && 'pressed',
121
+ morph.pressed && 'pressed',
132
122
  attributes.class,
133
123
  ]}
134
124
  >