noph-ui 0.44.0 → 0.45.1

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 (136) hide show
  1. package/README.md +10 -1
  2. package/dist/animation.d.ts +2 -0
  3. package/dist/animation.js +33 -0
  4. package/dist/app-bar/AppBar.svelte +230 -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 +13 -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 +6 -1
  14. package/dist/badge/types.d.ts +0 -1
  15. package/dist/button/Button.svelte +5 -15
  16. package/dist/button/IconButton.svelte +5 -15
  17. package/dist/button/SegmentedButton.svelte +15 -1
  18. package/dist/button/SplitButton.svelte +75 -0
  19. package/dist/button/SplitButton.svelte.d.ts +4 -0
  20. package/dist/button/index.d.ts +1 -0
  21. package/dist/button/index.js +1 -0
  22. package/dist/button/types.d.ts +17 -4
  23. package/dist/card/types.d.ts +1 -1
  24. package/dist/checkbox/Checkbox.svelte +47 -58
  25. package/dist/chip/AssistChip.svelte +5 -5
  26. package/dist/chip/ChipSet.svelte +3 -11
  27. package/dist/chip/FilterChip.svelte +33 -27
  28. package/dist/chip/InputChip.svelte +13 -2
  29. package/dist/chip/SuggestionChip.svelte +29 -0
  30. package/dist/chip/SuggestionChip.svelte.d.ts +4 -0
  31. package/dist/chip/index.d.ts +1 -0
  32. package/dist/chip/index.js +1 -0
  33. package/dist/chip/types.d.ts +16 -9
  34. package/dist/date-picker/Calendar.svelte +1 -1
  35. package/dist/date-picker/DatePickerDialog.svelte +23 -11
  36. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -1
  37. package/dist/date-picker/DateRangePicker.svelte +40 -10
  38. package/dist/date-picker/DateRangePicker.svelte.d.ts +4 -1
  39. package/dist/date-picker/DockedDatePicker.svelte +13 -10
  40. package/dist/date-picker/DockedDatePicker.svelte.d.ts +4 -1
  41. package/dist/date-picker/DockedDateTimePicker.svelte +13 -7
  42. package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +4 -1
  43. package/dist/date-picker/SelectionList.svelte +6 -32
  44. package/dist/date-picker/SelectionList.svelte.d.ts +2 -12
  45. package/dist/date-picker/TimeColumn.svelte +4 -29
  46. package/dist/date-picker/exitVisibility.svelte.d.ts +5 -0
  47. package/dist/date-picker/exitVisibility.svelte.js +23 -0
  48. package/dist/date-picker/optionList.d.ts +4 -0
  49. package/dist/date-picker/optionList.js +31 -0
  50. package/dist/date-picker/types.d.ts +11 -4
  51. package/dist/dialog/Dialog.svelte +55 -68
  52. package/dist/dialog/Dialog.svelte.d.ts +3 -3
  53. package/dist/dialog/types.d.ts +4 -3
  54. package/dist/fab/ExtendedFab.svelte +255 -0
  55. package/dist/fab/ExtendedFab.svelte.d.ts +5 -0
  56. package/dist/fab/Fab.svelte +211 -0
  57. package/dist/fab/Fab.svelte.d.ts +5 -0
  58. package/dist/fab/FabMenu.svelte +207 -0
  59. package/dist/fab/FabMenu.svelte.d.ts +7 -0
  60. package/dist/fab/index.d.ts +3 -0
  61. package/dist/fab/index.js +3 -0
  62. package/dist/fab/types.d.ts +34 -0
  63. package/dist/fab/types.js +1 -0
  64. package/dist/icons/ArrowBackIcon.svelte +13 -0
  65. package/dist/icons/ArrowBackIcon.svelte.d.ts +7 -0
  66. package/dist/icons/ChevronDownIcon.svelte +3 -0
  67. package/dist/icons/ChevronDownIcon.svelte.d.ts +26 -0
  68. package/dist/icons/SearchIcon.svelte +15 -0
  69. package/dist/icons/SearchIcon.svelte.d.ts +7 -0
  70. package/dist/icons/index.d.ts +3 -0
  71. package/dist/icons/index.js +3 -0
  72. package/dist/index.d.ts +6 -0
  73. package/dist/index.js +6 -0
  74. package/dist/internal/focus-ring.css +12 -0
  75. package/dist/loading-indicator/shapes.js +0 -7
  76. package/dist/menu/Menu.svelte +12 -7
  77. package/dist/menu/Menu.svelte.d.ts +2 -2
  78. package/dist/menu/types.d.ts +0 -2
  79. package/dist/navigation-bar/NavigationBar.svelte +59 -0
  80. package/dist/navigation-bar/NavigationBar.svelte.d.ts +4 -0
  81. package/dist/navigation-bar/NavigationBarItem.svelte +239 -0
  82. package/dist/navigation-bar/NavigationBarItem.svelte.d.ts +5 -0
  83. package/dist/navigation-bar/index.d.ts +2 -0
  84. package/dist/navigation-bar/index.js +2 -0
  85. package/dist/navigation-bar/types.d.ts +19 -0
  86. package/dist/navigation-bar/types.js +1 -0
  87. package/dist/navigation-drawer/NavigationDrawer.svelte +107 -76
  88. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +4 -1
  89. package/dist/navigation-drawer/NavigationDrawerItem.svelte +10 -3
  90. package/dist/navigation-drawer/types.d.ts +3 -1
  91. package/dist/navigation-rail/NavigationRailItem.svelte +16 -0
  92. package/dist/navigation-rail/types.d.ts +3 -0
  93. package/dist/popover.svelte.d.ts +5 -0
  94. package/dist/popover.svelte.js +22 -0
  95. package/dist/press.svelte.d.ts +6 -0
  96. package/dist/press.svelte.js +32 -0
  97. package/dist/radio/Radio.svelte +26 -7
  98. package/dist/radio/types.d.ts +3 -0
  99. package/dist/search/Search.svelte +391 -0
  100. package/dist/search/Search.svelte.d.ts +5 -0
  101. package/dist/search/index.d.ts +1 -0
  102. package/dist/search/index.js +1 -0
  103. package/dist/search/types.d.ts +19 -0
  104. package/dist/search/types.js +1 -0
  105. package/dist/select/Select.svelte +48 -87
  106. package/dist/sheet/Sheet.svelte +235 -0
  107. package/dist/sheet/Sheet.svelte.d.ts +7 -0
  108. package/dist/sheet/index.d.ts +1 -0
  109. package/dist/sheet/index.js +1 -0
  110. package/dist/sheet/types.d.ts +11 -0
  111. package/dist/sheet/types.js +1 -0
  112. package/dist/slider/Slider.svelte +118 -30
  113. package/dist/slider/types.d.ts +3 -0
  114. package/dist/snackbar/Snackbar.svelte +32 -35
  115. package/dist/snackbar/Snackbar.svelte.d.ts +2 -2
  116. package/dist/snackbar/types.d.ts +3 -2
  117. package/dist/switch/Switch.svelte +51 -20
  118. package/dist/switch/types.d.ts +3 -0
  119. package/dist/tabs/Tab.svelte +4 -12
  120. package/dist/tabs/Tabs.svelte +5 -5
  121. package/dist/text-field/TextField.svelte +2 -4
  122. package/dist/toolbar/Toolbar.svelte +121 -0
  123. package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
  124. package/dist/toolbar/index.d.ts +1 -0
  125. package/dist/toolbar/index.js +1 -0
  126. package/dist/toolbar/types.d.ts +8 -0
  127. package/dist/toolbar/types.js +1 -0
  128. package/dist/tooltip/RichTooltip.svelte +18 -12
  129. package/dist/tooltip/RichTooltip.svelte.d.ts +2 -2
  130. package/dist/tooltip/Tooltip.svelte +22 -11
  131. package/dist/tooltip/Tooltip.svelte.d.ts +4 -1
  132. package/dist/types.d.ts +6 -0
  133. package/dist/types.js +6 -0
  134. package/package.json +13 -13
  135. package/dist/date-picker/enterAnimation.d.ts +0 -1
  136. 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,2 @@
1
+ export declare const afterTwoFrames: (fn: () => void) => () => void;
2
+ export declare const afterExit: (element: Element | undefined, fn: () => void) => () => void;
@@ -0,0 +1,33 @@
1
+ export const afterTwoFrames = (fn) => {
2
+ let inner = 0;
3
+ const outer = requestAnimationFrame(() => {
4
+ inner = requestAnimationFrame(fn);
5
+ });
6
+ return () => {
7
+ cancelAnimationFrame(outer);
8
+ cancelAnimationFrame(inner);
9
+ };
10
+ };
11
+ export const afterExit = (element, fn) => {
12
+ if (!element) {
13
+ fn();
14
+ return () => { };
15
+ }
16
+ let cancelled = false;
17
+ const finish = () => {
18
+ if (!cancelled)
19
+ fn();
20
+ };
21
+ const cancelFrames = afterTwoFrames(() => {
22
+ const animations = element.getAnimations();
23
+ if (animations.length === 0) {
24
+ finish();
25
+ return;
26
+ }
27
+ Promise.allSettled(animations.map(({ finished }) => finished)).then(finish);
28
+ });
29
+ return () => {
30
+ cancelled = true;
31
+ cancelFrames();
32
+ };
33
+ };
@@ -0,0 +1,230 @@
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
+ const collapses = $derived(collapsible && twoLine)
21
+ </script>
22
+
23
+ {#snippet titles(inline: boolean)}
24
+ <div class={['np-app-bar-titles', inline && 'np-app-bar-titles-inline']}>
25
+ <div class="np-app-bar-headline">{headline}</div>
26
+ {#if subtitle}
27
+ <div class="np-app-bar-subtitle">{subtitle}</div>
28
+ {/if}
29
+ </div>
30
+ {/snippet}
31
+
32
+ <header
33
+ {...attributes}
34
+ bind:this={element}
35
+ class={[
36
+ 'np-app-bar',
37
+ `np-app-bar-${variant}`,
38
+ collapses && 'np-app-bar-collapsible',
39
+ collapses && scroller === 'nearest' && 'np-app-bar-scroller-nearest',
40
+ attributes.class,
41
+ ]}
42
+ >
43
+ <div class="np-app-bar-row">
44
+ {#if leading}
45
+ <div class="np-app-bar-leading">{@render leading()}</div>
46
+ {/if}
47
+ {#if isSearch}
48
+ <div class="np-app-bar-search-field">{@render search?.()}</div>
49
+ {:else if twoLine}
50
+ <div class="np-app-bar-inline" aria-hidden="true">{@render titles(true)}</div>
51
+ {:else}
52
+ {@render titles(false)}
53
+ {/if}
54
+ {#if trailing}
55
+ <div class="np-app-bar-trailing">{@render trailing()}</div>
56
+ {/if}
57
+ </div>
58
+ {#if twoLine}
59
+ <div class="np-app-bar-second-row">
60
+ {@render titles(false)}
61
+ </div>
62
+ {/if}
63
+ {@render children?.()}
64
+ </header>
65
+
66
+ <style>
67
+ .np-app-bar {
68
+ position: sticky;
69
+ inset-block-start: 0;
70
+ z-index: 8;
71
+ box-sizing: border-box;
72
+ display: flex;
73
+ flex-direction: column;
74
+ width: 100%;
75
+ background-color: var(--np-app-bar-container-color, var(--np-color-surface));
76
+ color: var(--np-color-on-surface);
77
+ --np-icon-button-icon-color: var(--np-color-on-surface-variant);
78
+ }
79
+
80
+ .np-app-bar:has(:global(.np-search-expanded)) {
81
+ z-index: 9;
82
+ }
83
+
84
+ .np-app-bar-row {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.5rem;
88
+ flex: none;
89
+ min-height: 4rem;
90
+ padding-inline: 0.25rem;
91
+ }
92
+
93
+ .np-app-bar-leading,
94
+ .np-app-bar-trailing {
95
+ display: flex;
96
+ align-items: center;
97
+ flex: none;
98
+ gap: 0.25rem;
99
+ }
100
+ .np-app-bar-trailing {
101
+ margin-inline-start: auto;
102
+ }
103
+
104
+ .np-app-bar-titles {
105
+ display: flex;
106
+ flex-direction: column;
107
+ justify-content: center;
108
+ gap: 0.125rem;
109
+ min-width: 0;
110
+ }
111
+
112
+ .np-app-bar-subtitle {
113
+ font-size: 0.875rem;
114
+ line-height: 1.25rem;
115
+ font-weight: 500;
116
+ color: var(--np-app-bar-subtitle-color, var(--np-color-on-surface-variant));
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ }
121
+ .np-app-bar-titles-inline .np-app-bar-headline {
122
+ font-size: 1.375rem;
123
+ }
124
+
125
+ .np-app-bar-headline {
126
+ font-size: var(--_headline-size);
127
+ line-height: 1.2;
128
+ font-weight: 400;
129
+ color: var(--np-app-bar-headline-color, var(--np-color-on-surface));
130
+ overflow: hidden;
131
+ text-overflow: ellipsis;
132
+ white-space: nowrap;
133
+ min-width: 0;
134
+ }
135
+
136
+ .np-app-bar-small,
137
+ .np-app-bar-search {
138
+ --_headline-size: 1.375rem;
139
+ }
140
+
141
+ .np-app-bar-search-field {
142
+ flex: 1;
143
+ min-width: 0;
144
+ display: flex;
145
+ align-items: center;
146
+ padding-inline: 0.25rem;
147
+ }
148
+ .np-app-bar-small .np-app-bar-titles {
149
+ padding-inline-start: 0.75rem;
150
+ flex: 1;
151
+ }
152
+
153
+ .np-app-bar-medium {
154
+ --_headline-size: 1.5rem;
155
+ --_second-row-height: 3rem;
156
+ }
157
+ .np-app-bar-large {
158
+ --_headline-size: 1.75rem;
159
+ --_second-row-height: 5.5rem;
160
+ }
161
+
162
+ .np-app-bar-second-row {
163
+ interpolate-size: allow-keywords;
164
+ display: flex;
165
+ align-items: flex-end;
166
+ box-sizing: border-box;
167
+ height: auto;
168
+ min-height: var(--_second-row-height);
169
+ padding-inline: 1rem;
170
+ padding-block-end: 1rem;
171
+ overflow: hidden;
172
+ }
173
+
174
+ .np-app-bar-inline {
175
+ font-size: 1.375rem;
176
+ line-height: 1.2;
177
+ font-weight: 400;
178
+ color: var(--np-app-bar-headline-color, var(--np-color-on-surface));
179
+ padding-inline-start: 0.75rem;
180
+ flex: 1;
181
+ min-width: 0;
182
+ overflow: hidden;
183
+ text-overflow: ellipsis;
184
+ white-space: nowrap;
185
+ opacity: 0;
186
+ }
187
+
188
+ @supports (animation-timeline: scroll()) {
189
+ :global(html:has(.np-app-bar-collapsible)) {
190
+ overflow-anchor: none;
191
+ }
192
+ :global(:has(.np-app-bar-scroller-nearest)) {
193
+ overflow-anchor: none;
194
+ }
195
+
196
+ .np-app-bar-collapsible .np-app-bar-second-row {
197
+ animation: np-app-bar-collapse linear both;
198
+ animation-timeline: scroll(root block);
199
+ animation-range: 0 var(--_second-row-height);
200
+ }
201
+ .np-app-bar-collapsible .np-app-bar-inline {
202
+ animation: np-app-bar-reveal linear both;
203
+ animation-timeline: scroll(root block);
204
+ animation-range: 0 var(--_second-row-height);
205
+ }
206
+ .np-app-bar-scroller-nearest .np-app-bar-second-row,
207
+ .np-app-bar-scroller-nearest .np-app-bar-inline {
208
+ animation-timeline: scroll(nearest block);
209
+ }
210
+ }
211
+
212
+ @keyframes np-app-bar-collapse {
213
+ to {
214
+ height: 0;
215
+ min-height: 0;
216
+ padding-block-end: 0;
217
+ opacity: 0;
218
+ }
219
+ }
220
+
221
+ @keyframes np-app-bar-reveal {
222
+ 0%,
223
+ 50% {
224
+ opacity: 0;
225
+ }
226
+ to {
227
+ opacity: 1;
228
+ }
229
+ }
230
+ </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,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ export interface AppBarProps extends HTMLAttributes<HTMLElement> {
4
+ variant?: 'search' | 'small' | 'medium' | 'large';
5
+ headline?: string;
6
+ subtitle?: string;
7
+ leading?: Snippet;
8
+ search?: Snippet;
9
+ trailing?: Snippet;
10
+ collapsible?: boolean;
11
+ scroller?: 'root' | 'nearest';
12
+ element?: HTMLElement;
13
+ }
@@ -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, {}, "element" | "value" | "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,7 +1,12 @@
1
1
  <script lang="ts">
2
2
  import type { BadgeProps } from './types.js'
3
3
 
4
- let { label, ariaLabel, element = $bindable(), ...attributes }: BadgeProps = $props()
4
+ let {
5
+ label,
6
+ 'aria-label': ariaLabel,
7
+ element = $bindable(),
8
+ ...attributes
9
+ }: BadgeProps = $props()
5
10
  </script>
6
11
 
7
12
  <div
@@ -1,6 +1,5 @@
1
1
  import type { HTMLAttributes } from 'svelte/elements';
2
2
  export interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
3
3
  label?: string | number;
4
- ariaLabel?: string;
5
4
  element?: HTMLDivElement;
6
5
  }
@@ -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
  >
@@ -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 { IconButtonProps } from './types.js'
9
9
 
@@ -29,22 +29,12 @@
29
29
  const isLink = $derived(attributes.href != null && !disabled && !loading)
30
30
  const tooltipId = $derived(title && !disabled && !loading ? uid : undefined)
31
31
 
32
- let pressed = $state(false)
33
- let pressedTimeout: ReturnType<typeof setTimeout>
32
+ const morph = pressMorph()
34
33
 
35
34
  const handlePress = () => {
36
35
  if (disabled || loading) return
37
- pressed = true
38
- pressedTimeout = setTimeout(() => {
39
- pressed = false
40
- }, 100)
36
+ morph.press()
41
37
  }
42
-
43
- onMount(() => {
44
- return () => {
45
- clearTimeout(pressedTimeout)
46
- }
47
- })
48
38
  </script>
49
39
 
50
40
  {#snippet content()}
@@ -86,7 +76,7 @@
86
76
  'enabled',
87
77
  toggle && 'toggle',
88
78
  selected && 'selected',
89
- pressed && 'pressed',
79
+ morph.pressed && 'pressed',
90
80
  attributes.class,
91
81
  ].filter(Boolean)}
92
82
  >
@@ -119,7 +109,7 @@
119
109
  disabled || loading ? `${variant}-disabled disabled` : `${variant} enabled`,
120
110
  toggle && 'toggle',
121
111
  selected && 'selected',
122
- pressed && 'pressed',
112
+ morph.pressed && 'pressed',
123
113
  attributes.class,
124
114
  ]}
125
115
  >
@@ -10,13 +10,16 @@
10
10
  multiSelect = false,
11
11
  element = $bindable(),
12
12
  group = $bindable(),
13
+ issues,
13
14
  ...attributes
14
15
  }: SegmentedButtonProps = $props()
16
+
17
+ let hasError = $derived(!!issues?.length)
15
18
  </script>
16
19
 
17
20
  <div
18
21
  {...attributes}
19
- class={['np-segmented-buttons', attributes.class]}
22
+ class={['np-segmented-buttons', hasError && 'np-error', attributes.class]}
20
23
  bind:this={element}
21
24
  style="{attributes.style};grid-template-columns: repeat({options.length}, minmax(max-content, 1fr));"
22
25
  >
@@ -95,6 +98,17 @@
95
98
  overflow-x: auto;
96
99
  }
97
100
 
101
+ .np-segmented-buttons.np-error {
102
+ --np-ripple-hover-color: var(--np-color-error);
103
+ --np-ripple-pressed-color: var(--np-color-error);
104
+ border-color: var(--np-color-error);
105
+ color: var(--np-color-error);
106
+ }
107
+
108
+ .np-error .np-segmented-button {
109
+ border-inline-end-color: var(--np-color-error);
110
+ }
111
+
98
112
  .np-segmented-button {
99
113
  flex: 1;
100
114
  padding: 0.5rem 1.75rem;
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import ChevronDownIcon from '../icons/ChevronDownIcon.svelte'
3
+ import Menu from '../menu/Menu.svelte'
4
+ import Button from './Button.svelte'
5
+ import ButtonGroup from './ButtonGroup.svelte'
6
+ import type { SplitButtonProps } from './types.js'
7
+
8
+ let {
9
+ label = '',
10
+ icon,
11
+ menu,
12
+ variant = 'filled',
13
+ size = 's',
14
+ disabled = false,
15
+ open = $bindable(false),
16
+ menuLabel = 'More options',
17
+ onclick,
18
+ element = $bindable(),
19
+ ...attributes
20
+ }: SplitButtonProps = $props()
21
+
22
+ const uid = $props.id()
23
+ const menuId = `np-split-button-menu-${uid}`
24
+
25
+ let trigger: HTMLElement | undefined = $state()
26
+ </script>
27
+
28
+ {#snippet caret()}
29
+ <ChevronDownIcon />
30
+ {/snippet}
31
+
32
+ <ButtonGroup
33
+ {...attributes}
34
+ bind:element
35
+ variant="connected"
36
+ class={['np-split-button', attributes.class]}
37
+ >
38
+ <Button {variant} {size} {disabled} {onclick} start={icon}>{label}</Button>
39
+ <Button
40
+ {variant}
41
+ {size}
42
+ {disabled}
43
+ bind:element={trigger}
44
+ class="np-split-button-trigger"
45
+ command="toggle-popover"
46
+ commandfor={menuId}
47
+ aria-haspopup="menu"
48
+ aria-expanded={open}
49
+ aria-label={menuLabel}
50
+ start={caret}
51
+ />
52
+ <Menu id={menuId} anchor={trigger} bind:open coverAnchor={false}>
53
+ {@render menu?.(menuId)}
54
+ </Menu>
55
+ </ButtonGroup>
56
+
57
+ <style>
58
+ :global(.np-split-button-trigger svg) {
59
+ transition: rotate var(--np-motion-expressive-fast-effects);
60
+ }
61
+ :global(.np-split-button-trigger[aria-expanded='true'] svg) {
62
+ rotate: 180deg;
63
+ }
64
+ :global(.np-split-button > .np-split-button-trigger) {
65
+ --np-button-padding: 0;
66
+ aspect-ratio: 1;
67
+ justify-content: center;
68
+ }
69
+ :global(.np-split-button > .np-split-button-trigger[aria-expanded='true']) {
70
+ --np-button-group-inner-corner: var(--_round-radius, var(--np-shape-corner-full));
71
+ }
72
+ :global(.np-split-button-trigger[aria-expanded='true'] .np-ripple-surface::before) {
73
+ opacity: var(--np-ripple-hover-opacity, 0.08);
74
+ }
75
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { SplitButtonProps } from './types.ts';
2
+ declare const SplitButton: import("svelte").Component<SplitButtonProps, {}, "element" | "open">;
3
+ type SplitButton = ReturnType<typeof SplitButton>;
4
+ export default SplitButton;