noph-ui 0.44.0 → 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.
- package/README.md +10 -1
- package/dist/animation.d.ts +17 -0
- package/dist/animation.js +48 -0
- package/dist/app-bar/AppBar.svelte +280 -0
- package/dist/app-bar/AppBar.svelte.d.ts +4 -0
- package/dist/app-bar/index.d.ts +1 -0
- package/dist/app-bar/index.js +1 -0
- package/dist/app-bar/types.d.ts +35 -0
- package/dist/app-bar/types.js +1 -0
- package/dist/autocomplete/AutoComplete.svelte +10 -11
- package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
- package/dist/autocomplete/types.d.ts +1 -1
- package/dist/badge/Badge.svelte +6 -1
- package/dist/badge/types.d.ts +0 -1
- package/dist/button/Button.svelte +5 -15
- package/dist/button/IconButton.svelte +5 -15
- package/dist/button/SegmentedButton.svelte +15 -1
- package/dist/button/SplitButton.svelte +92 -0
- package/dist/button/SplitButton.svelte.d.ts +4 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +1 -0
- package/dist/button/types.d.ts +23 -4
- package/dist/card/types.d.ts +1 -1
- package/dist/checkbox/Checkbox.svelte +49 -58
- package/dist/chip/AssistChip.svelte +5 -5
- package/dist/chip/ChipSet.svelte +3 -11
- package/dist/chip/FilterChip.svelte +35 -27
- package/dist/chip/InputChip.svelte +13 -2
- package/dist/chip/SuggestionChip.svelte +29 -0
- package/dist/chip/SuggestionChip.svelte.d.ts +4 -0
- package/dist/chip/index.d.ts +1 -0
- package/dist/chip/index.js +1 -0
- package/dist/chip/types.d.ts +24 -9
- package/dist/date-picker/Calendar.svelte +1 -1
- package/dist/date-picker/DatePickerDialog.svelte +30 -8
- package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -1
- package/dist/date-picker/DateRangePicker.svelte +45 -10
- package/dist/date-picker/DateRangePicker.svelte.d.ts +4 -1
- package/dist/date-picker/DockedDatePicker.svelte +18 -4
- package/dist/date-picker/DockedDatePicker.svelte.d.ts +4 -1
- package/dist/date-picker/DockedDateTimePicker.svelte +18 -4
- package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +4 -1
- package/dist/date-picker/SelectionList.svelte +6 -32
- package/dist/date-picker/SelectionList.svelte.d.ts +2 -12
- package/dist/date-picker/TimeColumn.svelte +4 -29
- package/dist/date-picker/exitVisibility.svelte.d.ts +10 -0
- package/dist/date-picker/exitVisibility.svelte.js +30 -0
- package/dist/date-picker/optionList.d.ts +16 -0
- package/dist/date-picker/optionList.js +43 -0
- package/dist/date-picker/types.d.ts +11 -4
- package/dist/dialog/Dialog.svelte +77 -68
- package/dist/dialog/Dialog.svelte.d.ts +3 -3
- package/dist/dialog/types.d.ts +5 -3
- package/dist/fab/ExtendedFab.svelte +281 -0
- package/dist/fab/ExtendedFab.svelte.d.ts +5 -0
- package/dist/fab/Fab.svelte +239 -0
- package/dist/fab/Fab.svelte.d.ts +5 -0
- package/dist/fab/FabMenu.svelte +255 -0
- package/dist/fab/FabMenu.svelte.d.ts +7 -0
- package/dist/fab/index.d.ts +3 -0
- package/dist/fab/index.js +3 -0
- package/dist/fab/types.d.ts +58 -0
- package/dist/fab/types.js +1 -0
- package/dist/icons/ArrowBackIcon.svelte +13 -0
- package/dist/icons/ArrowBackIcon.svelte.d.ts +7 -0
- package/dist/icons/ChevronDownIcon.svelte +3 -0
- package/dist/icons/ChevronDownIcon.svelte.d.ts +26 -0
- package/dist/icons/SearchIcon.svelte +15 -0
- package/dist/icons/SearchIcon.svelte.d.ts +7 -0
- package/dist/icons/index.d.ts +3 -0
- package/dist/icons/index.js +3 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/internal/focus-ring.css +12 -0
- package/dist/menu/Menu.svelte +12 -7
- package/dist/menu/Menu.svelte.d.ts +2 -2
- package/dist/menu/types.d.ts +0 -2
- package/dist/navigation-bar/NavigationBar.svelte +66 -0
- package/dist/navigation-bar/NavigationBar.svelte.d.ts +4 -0
- package/dist/navigation-bar/NavigationBarItem.svelte +261 -0
- package/dist/navigation-bar/NavigationBarItem.svelte.d.ts +5 -0
- package/dist/navigation-bar/index.d.ts +2 -0
- package/dist/navigation-bar/index.js +2 -0
- package/dist/navigation-bar/types.d.ts +29 -0
- package/dist/navigation-bar/types.js +1 -0
- package/dist/navigation-drawer/NavigationDrawer.svelte +128 -76
- package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +4 -1
- package/dist/navigation-drawer/NavigationDrawerItem.svelte +10 -3
- package/dist/navigation-drawer/types.d.ts +6 -1
- package/dist/navigation-rail/NavigationRailItem.svelte +16 -0
- package/dist/navigation-rail/types.d.ts +3 -0
- package/dist/popover.svelte.d.ts +14 -0
- package/dist/popover.svelte.js +31 -0
- package/dist/press.svelte.d.ts +19 -0
- package/dist/press.svelte.js +45 -0
- package/dist/radio/Radio.svelte +26 -7
- package/dist/radio/types.d.ts +3 -0
- package/dist/search/Search.svelte +472 -0
- package/dist/search/Search.svelte.d.ts +5 -0
- package/dist/search/index.d.ts +1 -0
- package/dist/search/index.js +1 -0
- package/dist/search/types.d.ts +39 -0
- package/dist/search/types.js +1 -0
- package/dist/select/Select.svelte +48 -87
- package/dist/sheet/Sheet.svelte +260 -0
- package/dist/sheet/Sheet.svelte.d.ts +7 -0
- package/dist/sheet/index.d.ts +1 -0
- package/dist/sheet/index.js +1 -0
- package/dist/sheet/types.d.ts +19 -0
- package/dist/sheet/types.js +1 -0
- package/dist/slider/Slider.svelte +154 -30
- package/dist/slider/types.d.ts +3 -0
- package/dist/snackbar/Snackbar.svelte +48 -37
- package/dist/snackbar/Snackbar.svelte.d.ts +2 -2
- package/dist/snackbar/types.d.ts +3 -2
- package/dist/switch/Switch.svelte +51 -20
- package/dist/switch/types.d.ts +3 -0
- package/dist/tabs/Tab.svelte +8 -12
- package/dist/tabs/Tabs.svelte +3 -3
- package/dist/text-field/TextField.svelte +2 -2
- package/dist/toolbar/Toolbar.svelte +133 -0
- package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
- package/dist/toolbar/index.d.ts +1 -0
- package/dist/toolbar/index.js +1 -0
- package/dist/toolbar/types.d.ts +15 -0
- package/dist/toolbar/types.js +1 -0
- package/dist/tooltip/RichTooltip.svelte +25 -12
- package/dist/tooltip/RichTooltip.svelte.d.ts +2 -2
- package/dist/tooltip/Tooltip.svelte +22 -11
- package/dist/tooltip/Tooltip.svelte.d.ts +4 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.js +6 -0
- package/package.json +13 -13
- package/dist/date-picker/enterAnimation.d.ts +0 -1
- package/dist/date-picker/enterAnimation.js +0 -10
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { arrowKeyNav, rovingTabindex } from '../keyboard-nav.js'
|
|
3
|
+
import { syncOpenEffect } from '../popover.svelte.js'
|
|
3
4
|
import type { NavigationDrawerProps } from './types.js'
|
|
5
|
+
|
|
4
6
|
let {
|
|
5
7
|
modal = false,
|
|
6
8
|
backdrop = false,
|
|
9
|
+
open = $bindable(false),
|
|
7
10
|
element = $bindable(),
|
|
8
11
|
direction = 'ltr',
|
|
9
|
-
popover,
|
|
10
12
|
children,
|
|
11
13
|
onkeydown: userKeydown,
|
|
12
14
|
ontoggle,
|
|
@@ -21,83 +23,123 @@
|
|
|
21
23
|
if (!event.defaultPrevented) arrowHandler(event)
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
/* Only a modal drawer is a dialog; a standard one is a nav that is always in the layout. */
|
|
27
|
+
const asDialog = () => (element instanceof HTMLDialogElement ? element : undefined)
|
|
26
28
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
for (let node: HTMLElement | null = element; node && node !== document.body;) {
|
|
31
|
-
const parent: HTMLElement | null = node.parentElement
|
|
32
|
-
if (!parent) break
|
|
33
|
-
for (const sibling of parent.children) {
|
|
34
|
-
if (sibling === node || !(sibling instanceof HTMLElement) || sibling.inert) continue
|
|
35
|
-
sibling.inert = true
|
|
36
|
-
inerted.push(sibling)
|
|
37
|
-
}
|
|
38
|
-
node = parent
|
|
39
|
-
}
|
|
40
|
-
element.querySelector<HTMLElement>('.np-navigation-drawer-item')?.focus()
|
|
29
|
+
export const show = () => {
|
|
30
|
+
const dialog = asDialog()
|
|
31
|
+
if (dialog && !dialog.open) dialog.showModal()
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
inerted = []
|
|
46
|
-
previouslyFocused?.focus?.()
|
|
47
|
-
previouslyFocused = undefined
|
|
34
|
+
export const close = () => {
|
|
35
|
+
asDialog()?.close()
|
|
48
36
|
}
|
|
37
|
+
|
|
38
|
+
syncOpenEffect(
|
|
39
|
+
() => (modal ? element : undefined),
|
|
40
|
+
() => open,
|
|
41
|
+
show,
|
|
42
|
+
close,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const start = $derived(direction === 'ltr' ? 'translateX(-100%)' : 'translateX(100%)')
|
|
49
46
|
</script>
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
48
|
+
<!--
|
|
49
|
+
A modal drawer is a `dialog` holding a nav landmark, so the browser keeps the page behind it
|
|
50
|
+
inert for as long as it is open and hands focus back on close. `::backdrop` is the scrim and
|
|
51
|
+
`closedby="any"` the tap-outside-to-close. Where `closedby` is unsupported the click handler
|
|
52
|
+
below covers it: a click on the backdrop is reported against the dialog itself.
|
|
53
|
+
-->
|
|
54
|
+
{#if modal}
|
|
55
|
+
<dialog
|
|
56
|
+
{...attributes}
|
|
57
|
+
{@attach attach}
|
|
58
|
+
bind:this={element}
|
|
59
|
+
tabindex="-1"
|
|
60
|
+
aria-label={null}
|
|
61
|
+
aria-labelledby={null}
|
|
62
|
+
closedby="any"
|
|
63
|
+
style="--np-navigation-drawer-start: {start}; {attributes.style ?? ''}"
|
|
64
|
+
class={[
|
|
65
|
+
'np-navigation-drawer-container',
|
|
66
|
+
'np-navigation-drawer-container-modal',
|
|
67
|
+
backdrop && 'np-navigation-drawer-backdrop',
|
|
68
|
+
attributes.class,
|
|
69
|
+
]}
|
|
70
|
+
onkeydown={handleKeydown}
|
|
71
|
+
ontoggle={(event) => {
|
|
72
|
+
open = event.newState === 'open'
|
|
73
|
+
/*
|
|
74
|
+
* Without this the browser autofocuses the first focusable descendant, the first nav
|
|
75
|
+
* item. Focus the drawer itself instead, so nothing is focused that the user didn't
|
|
76
|
+
* choose to interact with.
|
|
77
|
+
*/
|
|
78
|
+
if (event.newState === 'open') element?.focus()
|
|
79
|
+
ontoggle?.(event)
|
|
80
|
+
}}
|
|
81
|
+
onclick={(event) => {
|
|
82
|
+
attributes.onclick?.(event)
|
|
83
|
+
if (event.target === element) close()
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<!--
|
|
87
|
+
The label belongs to the navigation landmark, not to the dialog wrapping it, so it is
|
|
88
|
+
pulled back off the dialog above and put here. Everything else stays on the dialog: it
|
|
89
|
+
is the container the layout and the custom properties are written against, and the
|
|
90
|
+
element `commandfor` has to reach.
|
|
91
|
+
-->
|
|
92
|
+
<nav
|
|
93
|
+
aria-label={attributes['aria-label']}
|
|
94
|
+
aria-labelledby={attributes['aria-labelledby']}
|
|
95
|
+
class="np-navigation-wrapper np-navigation-drawer-shade"
|
|
96
|
+
>
|
|
97
|
+
<div class="np-navigation-drawer">
|
|
98
|
+
{@render children?.()}
|
|
99
|
+
</div>
|
|
100
|
+
</nav>
|
|
101
|
+
</dialog>
|
|
102
|
+
{:else}
|
|
103
|
+
<nav
|
|
104
|
+
{...attributes}
|
|
105
|
+
{@attach attach}
|
|
106
|
+
bind:this={element}
|
|
107
|
+
class={['np-navigation-drawer-container', attributes.class]}
|
|
108
|
+
onkeydown={handleKeydown}
|
|
109
|
+
>
|
|
110
|
+
<div class="np-navigation-wrapper">
|
|
111
|
+
<div class="np-navigation-drawer">
|
|
112
|
+
{@render children?.()}
|
|
113
|
+
</div>
|
|
88
114
|
</div>
|
|
89
|
-
</
|
|
90
|
-
|
|
115
|
+
</nav>
|
|
116
|
+
{/if}
|
|
91
117
|
|
|
92
118
|
<style>
|
|
93
119
|
.np-navigation-drawer-container {
|
|
94
120
|
color: var(--np-color-on-surface-variant);
|
|
95
121
|
width: calc(var(--np-navigation-drawer-width, 22.5rem) + 3px);
|
|
96
122
|
border: 0;
|
|
123
|
+
/* Focus lands here on open only to stay off the first nav item, not as something to show. */
|
|
124
|
+
outline: none;
|
|
97
125
|
margin: 0;
|
|
98
126
|
padding: 0;
|
|
99
127
|
background-color: transparent;
|
|
128
|
+
max-width: none;
|
|
129
|
+
max-height: none;
|
|
100
130
|
}
|
|
131
|
+
|
|
132
|
+
/* The modal drawer is docked to the inline edge rather than centred as a dialog would be. */
|
|
133
|
+
.np-navigation-drawer-container-modal {
|
|
134
|
+
position: fixed;
|
|
135
|
+
inset-block: 0;
|
|
136
|
+
height: 100dvh;
|
|
137
|
+
overflow: visible;
|
|
138
|
+
}
|
|
139
|
+
.np-navigation-drawer-container-modal:not([open]) {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
101
143
|
.np-navigation-wrapper {
|
|
102
144
|
background-color: var(--np-navigation-drawer-background, var(--np-color-surface-container-low));
|
|
103
145
|
border-start-end-radius: var(--np-shape-corner-large);
|
|
@@ -106,33 +148,32 @@
|
|
|
106
148
|
height: var(--np-navigation-drawer-height, 100dvh);
|
|
107
149
|
overflow-y: auto;
|
|
108
150
|
scrollbar-width: thin;
|
|
151
|
+
display: block;
|
|
109
152
|
}
|
|
110
153
|
|
|
111
|
-
.np-navigation-drawer-container
|
|
154
|
+
.np-navigation-drawer-container-modal .np-navigation-wrapper {
|
|
112
155
|
transform: var(--np-navigation-drawer-start, translateX(-100%));
|
|
113
156
|
}
|
|
114
|
-
|
|
115
|
-
.np-navigation-drawer-container:popover-open .np-navigation-wrapper {
|
|
157
|
+
.np-navigation-drawer-container-modal[open] .np-navigation-wrapper {
|
|
116
158
|
transform: translateX(0);
|
|
117
159
|
}
|
|
118
160
|
|
|
119
161
|
@media (prefers-reduced-motion: no-preference) {
|
|
120
|
-
.np-navigation-drawer-container
|
|
162
|
+
.np-navigation-drawer-container-modal .np-navigation-wrapper {
|
|
121
163
|
transition: transform var(--np-motion-standard-slow-spatial);
|
|
122
164
|
}
|
|
123
|
-
|
|
124
|
-
.np-navigation-drawer-container[popover] {
|
|
165
|
+
.np-navigation-drawer-container-modal {
|
|
125
166
|
transition:
|
|
126
167
|
overlay var(--np-motion-standard-slow-spatial) allow-discrete,
|
|
127
168
|
display var(--np-motion-standard-slow-spatial) allow-discrete;
|
|
128
169
|
}
|
|
129
|
-
|
|
130
|
-
.np-navigation-drawer-container:popover-open .np-navigation-wrapper {
|
|
170
|
+
.np-navigation-drawer-container-modal[open] .np-navigation-wrapper {
|
|
131
171
|
@starting-style {
|
|
132
172
|
transform: var(--np-navigation-drawer-start, translateX(-100%));
|
|
133
173
|
}
|
|
134
174
|
}
|
|
135
175
|
}
|
|
176
|
+
|
|
136
177
|
.np-navigation-drawer {
|
|
137
178
|
display: flex;
|
|
138
179
|
padding: var(--np-navigation-drawer-padding, 1.25rem 0.75rem);
|
|
@@ -142,17 +183,28 @@
|
|
|
142
183
|
box-shadow: var(--np-elevation-1);
|
|
143
184
|
}
|
|
144
185
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
186
|
+
/* `backdrop` decides whether the scrim is painted; a modal dialog always has the layer. */
|
|
187
|
+
.np-navigation-drawer-container-modal::backdrop {
|
|
188
|
+
background-color: transparent;
|
|
189
|
+
}
|
|
190
|
+
.np-navigation-drawer-backdrop::backdrop {
|
|
148
191
|
background-color: var(--np-color-scrim);
|
|
149
192
|
opacity: 0;
|
|
150
|
-
transition: opacity var(--np-motion-expressive-slow-effects);
|
|
151
193
|
}
|
|
152
|
-
.np-navigation-drawer-backdrop[
|
|
194
|
+
.np-navigation-drawer-backdrop[open]::backdrop {
|
|
153
195
|
opacity: 0.32;
|
|
154
|
-
|
|
155
|
-
|
|
196
|
+
}
|
|
197
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
198
|
+
.np-navigation-drawer-backdrop::backdrop {
|
|
199
|
+
transition:
|
|
200
|
+
opacity var(--np-motion-expressive-slow-effects),
|
|
201
|
+
overlay var(--np-motion-expressive-slow-effects) allow-discrete,
|
|
202
|
+
display var(--np-motion-expressive-slow-effects) allow-discrete;
|
|
203
|
+
}
|
|
204
|
+
.np-navigation-drawer-backdrop[open]::backdrop {
|
|
205
|
+
@starting-style {
|
|
206
|
+
opacity: 0;
|
|
207
|
+
}
|
|
156
208
|
}
|
|
157
209
|
}
|
|
158
210
|
</style>
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { NavigationDrawerProps } from './types.ts';
|
|
2
|
-
declare const NavigationDrawer: import("svelte").Component<NavigationDrawerProps, {
|
|
2
|
+
declare const NavigationDrawer: import("svelte").Component<NavigationDrawerProps, {
|
|
3
|
+
show: () => void;
|
|
4
|
+
close: () => void;
|
|
5
|
+
}, "element" | "open">;
|
|
3
6
|
type NavigationDrawer = ReturnType<typeof NavigationDrawer>;
|
|
4
7
|
export default NavigationDrawer;
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
let {
|
|
8
8
|
selected = false,
|
|
9
9
|
label,
|
|
10
|
-
|
|
10
|
+
badgeLabel,
|
|
11
|
+
badgeAriaLabel,
|
|
11
12
|
icon,
|
|
12
13
|
element = $bindable(),
|
|
13
14
|
...attributes
|
|
@@ -24,8 +25,14 @@
|
|
|
24
25
|
{/if}
|
|
25
26
|
</div>
|
|
26
27
|
<div class="np-navigation-drawer-item-label">{label}</div>
|
|
27
|
-
{#if
|
|
28
|
-
<div
|
|
28
|
+
{#if badgeLabel}
|
|
29
|
+
<div
|
|
30
|
+
class="np-navigation-drawer-item-badge"
|
|
31
|
+
role={badgeAriaLabel ? 'status' : undefined}
|
|
32
|
+
aria-label={badgeAriaLabel}
|
|
33
|
+
>
|
|
34
|
+
{badgeLabel}
|
|
35
|
+
</div>
|
|
29
36
|
{/if}
|
|
30
37
|
{/snippet}
|
|
31
38
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
3
3
|
export interface NavigationDrawerProps extends HTMLAttributes<HTMLElement> {
|
|
4
|
+
/** A modal drawer is a `dialog` over the page; a standard one is part of the layout. */
|
|
4
5
|
modal?: boolean;
|
|
6
|
+
/** Paints the scrim behind a modal drawer. */
|
|
5
7
|
backdrop?: boolean;
|
|
8
|
+
/** Bindable. Whether a modal drawer is showing. Ignored by a standard drawer. */
|
|
9
|
+
open?: boolean;
|
|
6
10
|
element?: HTMLElement;
|
|
7
11
|
direction?: 'rtl' | 'ltr';
|
|
8
12
|
}
|
|
@@ -11,7 +15,8 @@ export interface NavigationDrawerItemProps extends HTMLAttributes<NavigationDraw
|
|
|
11
15
|
icon?: Snippet;
|
|
12
16
|
label: string;
|
|
13
17
|
selected?: boolean;
|
|
14
|
-
|
|
18
|
+
badgeLabel?: string;
|
|
19
|
+
badgeAriaLabel?: string;
|
|
15
20
|
type?: 'submit' | 'reset' | 'button' | (string & {}) | null;
|
|
16
21
|
element?: NavigationDrawerItemElement;
|
|
17
22
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import '../internal/focus-ring.css'
|
|
3
|
+
import Badge from '../badge/Badge.svelte'
|
|
3
4
|
import Ripple from '../ripple/Ripple.svelte'
|
|
4
5
|
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
5
6
|
import type { NavigationRailItemProps } from './types.js'
|
|
@@ -8,6 +9,9 @@
|
|
|
8
9
|
selected,
|
|
9
10
|
icon,
|
|
10
11
|
label,
|
|
12
|
+
badge = false,
|
|
13
|
+
badgeLabel,
|
|
14
|
+
badgeAriaLabel,
|
|
11
15
|
element = $bindable(),
|
|
12
16
|
...attributes
|
|
13
17
|
}: NavigationRailItemProps = $props()
|
|
@@ -20,6 +24,11 @@
|
|
|
20
24
|
<div class="np-navigation-action-icon">
|
|
21
25
|
<Ripple forElement={touchEl} />
|
|
22
26
|
{@render icon()}
|
|
27
|
+
{#if badge}
|
|
28
|
+
<div class="np-navigation-action-badge">
|
|
29
|
+
<Badge label={badgeLabel} aria-label={badgeAriaLabel} />
|
|
30
|
+
</div>
|
|
31
|
+
{/if}
|
|
23
32
|
</div>
|
|
24
33
|
<div class="np-navigation-action-label">{label}</div>
|
|
25
34
|
<span class="np-touch" bind:this={touchEl}></span>
|
|
@@ -92,6 +101,13 @@
|
|
|
92
101
|
transition: color var(--np-motion-expressive-default-effects);
|
|
93
102
|
color: var(--np-color-on-surface-variant);
|
|
94
103
|
}
|
|
104
|
+
.np-navigation-action-badge {
|
|
105
|
+
--np-badge-top: 0;
|
|
106
|
+
--np-badge-end: 0.75rem;
|
|
107
|
+
position: absolute;
|
|
108
|
+
inset: 0;
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
}
|
|
95
111
|
.np-navigation-action-selected .np-navigation-action-icon {
|
|
96
112
|
color: var(--np-color-on-secondary-container);
|
|
97
113
|
--np-icon-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
|
@@ -8,6 +8,9 @@ export interface NavigationRailItemProps extends HTMLAttributes<NavigationRailIt
|
|
|
8
8
|
icon: Snippet;
|
|
9
9
|
label: string;
|
|
10
10
|
selected?: boolean;
|
|
11
|
+
badge?: boolean;
|
|
12
|
+
badgeLabel?: string | number;
|
|
13
|
+
badgeAriaLabel?: string;
|
|
11
14
|
type?: 'submit' | 'reset' | 'button' | (string & {}) | null;
|
|
12
15
|
element?: NavigationRailItemElement;
|
|
13
16
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `show()`/`close()` for a popover-backed overlay, guarded both ways. `showPopover()` throws if
|
|
3
|
+
* the popover is already showing and `hidePopover()` throws if it is already hidden, so both
|
|
4
|
+
* directions need the `:popover-open` check, not just the one that happens to get exercised first.
|
|
5
|
+
*/
|
|
6
|
+
export declare const popoverController: (getElement: () => HTMLElement | undefined) => {
|
|
7
|
+
show: () => void;
|
|
8
|
+
close: () => void;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Keeps a bindable `open` prop in sync with an overlay's own `show`/`close`, so a caller can drive
|
|
12
|
+
* the overlay either by calling the exported methods or by writing to the bound prop.
|
|
13
|
+
*/
|
|
14
|
+
export declare const syncOpenEffect: (getElement: () => HTMLElement | undefined, getOpen: () => boolean | undefined, show: () => void, close: () => void) => void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `show()`/`close()` for a popover-backed overlay, guarded both ways. `showPopover()` throws if
|
|
3
|
+
* the popover is already showing and `hidePopover()` throws if it is already hidden, so both
|
|
4
|
+
* directions need the `:popover-open` check, not just the one that happens to get exercised first.
|
|
5
|
+
*/
|
|
6
|
+
export const popoverController = (getElement) => ({
|
|
7
|
+
show: () => {
|
|
8
|
+
const element = getElement();
|
|
9
|
+
if (element && !element.matches(':popover-open'))
|
|
10
|
+
element.showPopover();
|
|
11
|
+
},
|
|
12
|
+
close: () => {
|
|
13
|
+
const element = getElement();
|
|
14
|
+
if (element?.matches(':popover-open'))
|
|
15
|
+
element.hidePopover();
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Keeps a bindable `open` prop in sync with an overlay's own `show`/`close`, so a caller can drive
|
|
20
|
+
* the overlay either by calling the exported methods or by writing to the bound prop.
|
|
21
|
+
*/
|
|
22
|
+
export const syncOpenEffect = (getElement, getOpen, show, close) => {
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (!getElement())
|
|
25
|
+
return;
|
|
26
|
+
if (getOpen() === true)
|
|
27
|
+
show();
|
|
28
|
+
else if (getOpen() === false)
|
|
29
|
+
close();
|
|
30
|
+
});
|
|
31
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const PRESS_DURATION = 100;
|
|
2
|
+
/**
|
|
3
|
+
* The corner morph a Button, an IconButton or a FAB plays when it is activated. `:active` covers
|
|
4
|
+
* the press itself, but it drops the moment the pointer lifts, so a quick click would show only a
|
|
5
|
+
* frame or two of the morph. The `pressed` this returns is held for as long as the round trip
|
|
6
|
+
* takes to be seen, and is set from `click`, which arrives for the keyboard as well as the pointer.
|
|
7
|
+
*/
|
|
8
|
+
export declare const pressMorph: () => {
|
|
9
|
+
readonly pressed: boolean;
|
|
10
|
+
press: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* A change of `shape` supersedes the morph. Both describe the same corner, and the pressed
|
|
13
|
+
* radius is the shape the element is not, so holding the press across a shape change cancels
|
|
14
|
+
* the change out and stalls it until the timer runs down. FabMenu's trigger does exactly
|
|
15
|
+
* that, flipping to `square` on the click that opens the menu; dropping the press there
|
|
16
|
+
* leaves that click one continuous morph.
|
|
17
|
+
*/
|
|
18
|
+
supersededBy: (shape: () => unknown) => void;
|
|
19
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { onMount } from 'svelte';
|
|
2
|
+
export const PRESS_DURATION = 100;
|
|
3
|
+
/**
|
|
4
|
+
* The corner morph a Button, an IconButton or a FAB plays when it is activated. `:active` covers
|
|
5
|
+
* the press itself, but it drops the moment the pointer lifts, so a quick click would show only a
|
|
6
|
+
* frame or two of the morph. The `pressed` this returns is held for as long as the round trip
|
|
7
|
+
* takes to be seen, and is set from `click`, which arrives for the keyboard as well as the pointer.
|
|
8
|
+
*/
|
|
9
|
+
export const pressMorph = () => {
|
|
10
|
+
let pressed = $state(false);
|
|
11
|
+
let timeout;
|
|
12
|
+
const cancel = () => {
|
|
13
|
+
clearTimeout(timeout);
|
|
14
|
+
pressed = false;
|
|
15
|
+
};
|
|
16
|
+
onMount(() => cancel);
|
|
17
|
+
return {
|
|
18
|
+
get pressed() {
|
|
19
|
+
return pressed;
|
|
20
|
+
},
|
|
21
|
+
press: () => {
|
|
22
|
+
clearTimeout(timeout);
|
|
23
|
+
pressed = true;
|
|
24
|
+
timeout = setTimeout(() => (pressed = false), PRESS_DURATION);
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* A change of `shape` supersedes the morph. Both describe the same corner, and the pressed
|
|
28
|
+
* radius is the shape the element is not, so holding the press across a shape change cancels
|
|
29
|
+
* the change out and stalls it until the timer runs down. FabMenu's trigger does exactly
|
|
30
|
+
* that, flipping to `square` on the click that opens the menu; dropping the press there
|
|
31
|
+
* leaves that click one continuous morph.
|
|
32
|
+
*/
|
|
33
|
+
supersededBy: (shape) => {
|
|
34
|
+
let previous;
|
|
35
|
+
$effect(() => {
|
|
36
|
+
const next = shape();
|
|
37
|
+
const was = previous;
|
|
38
|
+
previous = next;
|
|
39
|
+
if (was === undefined || was === next)
|
|
40
|
+
return;
|
|
41
|
+
cancel();
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
package/dist/radio/Radio.svelte
CHANGED
|
@@ -9,14 +9,20 @@
|
|
|
9
9
|
element = $bindable(),
|
|
10
10
|
group = $bindable(),
|
|
11
11
|
style,
|
|
12
|
+
issues,
|
|
12
13
|
...attributes
|
|
13
14
|
}: RadioProps = $props()
|
|
14
15
|
|
|
15
16
|
let inputEl = $state<HTMLInputElement>()
|
|
16
17
|
const uid = $props.id()
|
|
18
|
+
let hasError = $derived(!!issues?.length)
|
|
17
19
|
</script>
|
|
18
20
|
|
|
19
|
-
<div
|
|
21
|
+
<div
|
|
22
|
+
{style}
|
|
23
|
+
class={['np-radio-container', hasError && !attributes.disabled && 'np-error', attributes.class]}
|
|
24
|
+
bind:this={element}
|
|
25
|
+
>
|
|
20
26
|
{#if !attributes.disabled}
|
|
21
27
|
<Ripple forElement={inputEl} class="np-radio-ripple" />
|
|
22
28
|
{/if}
|
|
@@ -70,7 +76,7 @@
|
|
|
70
76
|
-webkit-tap-highlight-color: transparent;
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
.np-container {
|
|
79
|
+
.np-radio-container {
|
|
74
80
|
margin: max(0px, (40px - var(--np-radio-icon-size, 20px))/2);
|
|
75
81
|
display: inline-flex;
|
|
76
82
|
vertical-align: top;
|
|
@@ -84,7 +90,7 @@
|
|
|
84
90
|
cursor: pointer;
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
.np-container:has(input:disabled) {
|
|
93
|
+
.np-radio-container:has(input:disabled) {
|
|
88
94
|
cursor: default;
|
|
89
95
|
}
|
|
90
96
|
|
|
@@ -94,11 +100,11 @@
|
|
|
94
100
|
position: absolute;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
.np-container:has(input:checked) .np-radio-icon {
|
|
103
|
+
.np-radio-container:has(input:checked) .np-radio-icon {
|
|
98
104
|
fill: var(--np-radio-selected-icon-color, var(--np-color-primary));
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
.np-container:has(input:disabled) .np-radio-icon {
|
|
107
|
+
.np-radio-container:has(input:disabled) .np-radio-icon {
|
|
102
108
|
fill: var(--np-color-on-surface);
|
|
103
109
|
opacity: 0.38;
|
|
104
110
|
}
|
|
@@ -109,7 +115,7 @@
|
|
|
109
115
|
transition: opacity 50ms linear;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
.np-container:has(input:checked) .inner.circle {
|
|
118
|
+
.np-radio-container:has(input:checked) .inner.circle {
|
|
113
119
|
animation: 300ms cubic-bezier(0.05, 0.7, 0.1, 1) 0s 1 normal none running inner-circle-grow;
|
|
114
120
|
opacity: 1;
|
|
115
121
|
}
|
|
@@ -123,7 +129,20 @@
|
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
.np-
|
|
132
|
+
.np-error .np-radio-icon {
|
|
133
|
+
fill: var(--np-color-error);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.np-error:has(input:checked) .np-radio-icon {
|
|
137
|
+
fill: var(--np-color-error);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.np-error {
|
|
141
|
+
--np-ripple-hover-color: var(--np-color-error);
|
|
142
|
+
--np-ripple-pressed-color: var(--np-color-error);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.np-radio-container:has(input:focus-visible) {
|
|
127
146
|
outline-style: solid;
|
|
128
147
|
outline-color: var(--np-color-secondary);
|
|
129
148
|
outline-width: 3px;
|
package/dist/radio/types.d.ts
CHANGED