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
|
@@ -101,11 +101,11 @@ export interface TimeColumnProps extends Omit<HTMLAttributes<HTMLDivElement>, 'o
|
|
|
101
101
|
value?: number;
|
|
102
102
|
onselect?: (value: number) => void;
|
|
103
103
|
}
|
|
104
|
-
export interface DatePickerDialogProps extends Omit<HTMLAttributes<
|
|
104
|
+
export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps, MonthStepperLabelProps {
|
|
105
105
|
value?: ISODate | null;
|
|
106
106
|
displayMonth?: ISODate;
|
|
107
107
|
open?: boolean;
|
|
108
|
-
element?:
|
|
108
|
+
element?: HTMLDialogElement;
|
|
109
109
|
name?: string;
|
|
110
110
|
form?: string;
|
|
111
111
|
title?: string;
|
|
@@ -113,6 +113,9 @@ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElemen
|
|
|
113
113
|
modeToggle?: boolean;
|
|
114
114
|
label?: string;
|
|
115
115
|
supportingText?: string;
|
|
116
|
+
issues?: {
|
|
117
|
+
message: string;
|
|
118
|
+
}[];
|
|
116
119
|
selectYearLabel?: string;
|
|
117
120
|
calendarModeLabel?: string;
|
|
118
121
|
inputModeLabel?: string;
|
|
@@ -120,11 +123,15 @@ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElemen
|
|
|
120
123
|
onconfirm?: (value: ISODate | undefined) => void;
|
|
121
124
|
oncancel?: () => void;
|
|
122
125
|
}
|
|
123
|
-
export interface DateRangePickerProps extends Omit<HTMLAttributes<
|
|
126
|
+
export interface DateRangePickerProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps {
|
|
124
127
|
value?: DateRange;
|
|
125
128
|
open?: boolean;
|
|
126
|
-
element?:
|
|
129
|
+
element?: HTMLDialogElement;
|
|
127
130
|
title?: string;
|
|
131
|
+
headline?: string;
|
|
132
|
+
name?: string;
|
|
133
|
+
endName?: string;
|
|
134
|
+
form?: string;
|
|
128
135
|
startLabel?: string;
|
|
129
136
|
endLabel?: string;
|
|
130
137
|
onchange?: (value: DateRange) => void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Divider from '../divider/Divider.svelte'
|
|
3
|
+
import { syncOpenEffect } from '../popover.svelte.js'
|
|
3
4
|
import type { DialogProps } from './types.js'
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
element = $bindable(),
|
|
8
|
+
open = $bindable(),
|
|
7
9
|
quick = false,
|
|
8
10
|
children,
|
|
9
11
|
headline,
|
|
@@ -12,82 +14,63 @@
|
|
|
12
14
|
actions,
|
|
13
15
|
divider,
|
|
14
16
|
ontoggle,
|
|
15
|
-
// Naming attributes belong on the element that carries role="dialog", not on the popover
|
|
16
|
-
// container the rest of the attributes are spread onto.
|
|
17
|
-
'aria-label': ariaLabel,
|
|
18
17
|
'aria-labelledby': ariaLabelledby,
|
|
19
18
|
...attributes
|
|
20
19
|
}: DialogProps = $props()
|
|
21
20
|
|
|
22
21
|
const uid = $props.id()
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export const
|
|
29
|
-
element
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const hidePopover = () => {
|
|
33
|
-
element?.hidePopover()
|
|
23
|
+
/*
|
|
24
|
+
* A modal dialog can only be opened from script or from a `command` button; `<dialog open>` on
|
|
25
|
+
* its own is the non-modal kind, with no focus trap and no backdrop.
|
|
26
|
+
*/
|
|
27
|
+
export const show = () => {
|
|
28
|
+
if (element && !element.open) element.showModal()
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
previouslyFocused = document.activeElement as HTMLElement | undefined
|
|
39
|
-
for (let node: HTMLElement | null = element; node && node !== document.body;) {
|
|
40
|
-
const parent: HTMLElement | null = node.parentElement
|
|
41
|
-
if (!parent) break
|
|
42
|
-
for (const sibling of parent.children) {
|
|
43
|
-
if (sibling === node || !(sibling instanceof HTMLElement) || sibling.inert) continue
|
|
44
|
-
sibling.inert = true
|
|
45
|
-
inerted.push(sibling)
|
|
46
|
-
}
|
|
47
|
-
node = parent
|
|
48
|
-
}
|
|
49
|
-
dialogEl?.focus()
|
|
31
|
+
export const close = () => {
|
|
32
|
+
element?.close()
|
|
50
33
|
}
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
syncOpenEffect(
|
|
36
|
+
() => element,
|
|
37
|
+
() => open,
|
|
38
|
+
show,
|
|
39
|
+
close,
|
|
40
|
+
)
|
|
58
41
|
</script>
|
|
59
42
|
|
|
60
|
-
|
|
43
|
+
<!--
|
|
44
|
+
A `dialog`, not a popover. `showModal()` keeps everything outside inert for as long as the dialog
|
|
45
|
+
is open, including anything added to the page while it is open, and hands focus back on close.
|
|
46
|
+
`::backdrop` is the scrim and `closedby="any"` the click-outside-to-close. Where `closedby` is
|
|
47
|
+
unsupported the click handler below covers it: a click on the backdrop is reported against the
|
|
48
|
+
dialog itself.
|
|
49
|
+
-->
|
|
50
|
+
<dialog
|
|
61
51
|
bind:this={element}
|
|
62
52
|
{...attributes}
|
|
63
|
-
|
|
53
|
+
tabindex="-1"
|
|
54
|
+
closedby="any"
|
|
55
|
+
aria-labelledby={ariaLabelledby ?? (headline ? `${uid}-dialog-headline` : undefined)}
|
|
56
|
+
aria-describedby={supportingText ? `${uid}-dialog-supporting-text` : undefined}
|
|
57
|
+
class={['np-dialog-container', !quick && 'np-animate', attributes.class]}
|
|
64
58
|
ontoggle={(event) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
open = event.newState === 'open'
|
|
60
|
+
/*
|
|
61
|
+
* Without this the browser autofocuses the first focusable descendant, one of the actions,
|
|
62
|
+
* which can be a destructive one. Focus the dialog itself instead, so nothing is focused
|
|
63
|
+
* that the user didn't choose to interact with.
|
|
64
|
+
*/
|
|
65
|
+
if (event.newState === 'open') element?.focus()
|
|
70
66
|
ontoggle?.(event)
|
|
71
67
|
}}
|
|
72
|
-
|
|
68
|
+
onclick={(event) => {
|
|
69
|
+
attributes.onclick?.(event)
|
|
70
|
+
if (event.target === element) close()
|
|
71
|
+
}}
|
|
73
72
|
>
|
|
74
|
-
<div
|
|
75
|
-
role="none"
|
|
76
|
-
class="np-backdrop"
|
|
77
|
-
onclick={() => {
|
|
78
|
-
hidePopover()
|
|
79
|
-
}}
|
|
80
|
-
></div>
|
|
81
|
-
<div
|
|
82
|
-
bind:this={dialogEl}
|
|
83
|
-
class="np-dialog"
|
|
84
|
-
role="dialog"
|
|
85
|
-
tabindex="-1"
|
|
86
|
-
aria-modal="true"
|
|
87
|
-
aria-label={ariaLabel}
|
|
88
|
-
aria-labelledby={ariaLabelledby ?? (headline ? `${uid}-dialog-headline` : undefined)}
|
|
89
|
-
aria-describedby={supportingText ? `${uid}-dialog-supporting-text` : undefined}
|
|
90
|
-
>
|
|
73
|
+
<div class="np-dialog">
|
|
91
74
|
{#if icon}
|
|
92
75
|
<div class="np-dialog-icon">
|
|
93
76
|
{@render icon()}
|
|
@@ -119,19 +102,36 @@
|
|
|
119
102
|
</div>
|
|
120
103
|
{/if}
|
|
121
104
|
</div>
|
|
122
|
-
</
|
|
105
|
+
</dialog>
|
|
123
106
|
|
|
124
107
|
<style>
|
|
125
108
|
.np-dialog-container {
|
|
126
109
|
background: transparent;
|
|
127
110
|
border: none;
|
|
111
|
+
/* Focus lands here on open only to stay off the actions, not as something to show. */
|
|
112
|
+
outline: none;
|
|
128
113
|
margin: auto;
|
|
129
114
|
padding: var(--np-dialog-inset, 2rem 1rem);
|
|
130
115
|
box-sizing: border-box;
|
|
131
116
|
min-width: var(--np-dialog-container-min-width, 19.5rem);
|
|
132
117
|
width: var(--np-dialog-container-width, 37rem);
|
|
133
118
|
max-width: 100%;
|
|
119
|
+
max-height: none;
|
|
120
|
+
overflow: visible;
|
|
121
|
+
color: var(--np-color-on-surface);
|
|
122
|
+
}
|
|
123
|
+
/* A closed dialog is `display: none` from the user agent, which the enter transition needs. */
|
|
124
|
+
.np-dialog-container:not([open]) {
|
|
125
|
+
display: none;
|
|
134
126
|
}
|
|
127
|
+
/*
|
|
128
|
+
* The exit transition holds the dialog in the top layer after it has closed, where it would go on
|
|
129
|
+
* taking the clicks meant for the page behind it.
|
|
130
|
+
*/
|
|
131
|
+
.np-animate:not([open]) {
|
|
132
|
+
pointer-events: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
135
|
.np-dialog {
|
|
136
136
|
border: 0;
|
|
137
137
|
background-color: var(--np-dialog-container-color, var(--np-color-surface));
|
|
@@ -154,27 +154,36 @@
|
|
|
154
154
|
flex-direction: column;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
.np-
|
|
157
|
+
.np-dialog-container::backdrop {
|
|
158
|
+
background-color: var(--np-color-scrim);
|
|
159
|
+
opacity: 0.32;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.np-animate {
|
|
158
163
|
transition:
|
|
159
164
|
opacity var(--np-motion-expressive-slow-effects),
|
|
160
165
|
display var(--np-motion-expressive-slow-effects) allow-discrete,
|
|
161
166
|
overlay var(--np-motion-expressive-slow-effects) allow-discrete;
|
|
162
167
|
opacity: 0;
|
|
163
168
|
}
|
|
164
|
-
.np-animate[
|
|
169
|
+
.np-animate[open] {
|
|
165
170
|
opacity: 1;
|
|
166
171
|
@starting-style {
|
|
167
172
|
opacity: 0;
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
|
-
.np-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
+
.np-animate::backdrop {
|
|
176
|
+
transition:
|
|
177
|
+
opacity var(--np-motion-expressive-slow-effects),
|
|
178
|
+
display var(--np-motion-expressive-slow-effects) allow-discrete,
|
|
179
|
+
overlay var(--np-motion-expressive-slow-effects) allow-discrete;
|
|
180
|
+
opacity: 0;
|
|
175
181
|
}
|
|
176
|
-
.np-
|
|
177
|
-
|
|
182
|
+
.np-animate[open]::backdrop {
|
|
183
|
+
opacity: 0.32;
|
|
184
|
+
@starting-style {
|
|
185
|
+
opacity: 0;
|
|
186
|
+
}
|
|
178
187
|
}
|
|
179
188
|
|
|
180
189
|
.np-dialog-icon {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DialogProps } from './types.ts';
|
|
2
2
|
declare const Dialog: import("svelte").Component<DialogProps, {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}, "element">;
|
|
3
|
+
show: () => void;
|
|
4
|
+
close: () => void;
|
|
5
|
+
}, "element" | "open">;
|
|
6
6
|
type Dialog = ReturnType<typeof Dialog>;
|
|
7
7
|
export default Dialog;
|
package/dist/dialog/types.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
3
|
-
export interface DialogProps extends Omit<
|
|
2
|
+
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
3
|
+
export interface DialogProps extends Omit<HTMLDialogAttributes, 'open'> {
|
|
4
4
|
icon?: Snippet;
|
|
5
5
|
headline?: string;
|
|
6
6
|
supportingText?: string;
|
|
7
7
|
divider?: boolean;
|
|
8
8
|
actions?: Snippet;
|
|
9
9
|
quick?: boolean;
|
|
10
|
-
element?:
|
|
10
|
+
element?: HTMLDialogElement;
|
|
11
|
+
/** Bindable. Whether the dialog is showing. */
|
|
12
|
+
open?: boolean;
|
|
11
13
|
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import '../internal/focus-ring.css'
|
|
3
|
+
import { pressMorph } from '../press.svelte.js'
|
|
4
|
+
import Ripple from '../ripple/Ripple.svelte'
|
|
5
|
+
import Tooltip from '../tooltip/Tooltip.svelte'
|
|
6
|
+
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
7
|
+
import type { ExtendedFabProps } from './types.js'
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
variant = 'primary-container',
|
|
11
|
+
size = 's',
|
|
12
|
+
shape = 'round',
|
|
13
|
+
lowered = false,
|
|
14
|
+
disabled = false,
|
|
15
|
+
collapsed = false,
|
|
16
|
+
icon,
|
|
17
|
+
label,
|
|
18
|
+
element = $bindable(),
|
|
19
|
+
...attributes
|
|
20
|
+
}: ExtendedFabProps = $props()
|
|
21
|
+
|
|
22
|
+
const uid = $props.id()
|
|
23
|
+
|
|
24
|
+
const isLink = $derived(attributes.href != null && !disabled)
|
|
25
|
+
// Collapsed, the text is hidden, so the label has to be announced some other way.
|
|
26
|
+
const tooltipId = $derived(collapsed && label && !disabled ? uid : undefined)
|
|
27
|
+
|
|
28
|
+
const morph = pressMorph()
|
|
29
|
+
morph.supersededBy(() => shape)
|
|
30
|
+
|
|
31
|
+
const handlePress = () => {
|
|
32
|
+
if (disabled) return
|
|
33
|
+
morph.press()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const classes = $derived([
|
|
37
|
+
'np-extended-fab',
|
|
38
|
+
'np-focus-ring',
|
|
39
|
+
`np-fab-${variant}`,
|
|
40
|
+
size,
|
|
41
|
+
shape,
|
|
42
|
+
lowered && 'np-fab-lowered',
|
|
43
|
+
collapsed && 'np-fab-collapsed',
|
|
44
|
+
morph.pressed && 'pressed',
|
|
45
|
+
attributes.class,
|
|
46
|
+
])
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
{#snippet content()}
|
|
50
|
+
{#if !disabled}
|
|
51
|
+
<Ripple forElement={element} />
|
|
52
|
+
{/if}
|
|
53
|
+
{#if icon}
|
|
54
|
+
<span class="np-fab-icon">{@render icon()}</span>
|
|
55
|
+
{/if}
|
|
56
|
+
{#if label}
|
|
57
|
+
<span class="np-fab-label">{label}</span>
|
|
58
|
+
{/if}
|
|
59
|
+
{/snippet}
|
|
60
|
+
|
|
61
|
+
{#if isLink}
|
|
62
|
+
<a
|
|
63
|
+
{...attributes}
|
|
64
|
+
onclick={(event) => {
|
|
65
|
+
handlePress()
|
|
66
|
+
attributes.onclick?.(event)
|
|
67
|
+
}}
|
|
68
|
+
bind:this={element}
|
|
69
|
+
aria-describedby={tooltipId ?? attributes['aria-describedby']}
|
|
70
|
+
interestfor={tooltipId ?? attributes['interestfor']}
|
|
71
|
+
aria-label={collapsed ? label || attributes['aria-label'] : attributes['aria-label']}
|
|
72
|
+
class={classes}
|
|
73
|
+
>
|
|
74
|
+
{@render content()}
|
|
75
|
+
</a>
|
|
76
|
+
{:else}
|
|
77
|
+
<button
|
|
78
|
+
{...attributes as HTMLButtonAttributes}
|
|
79
|
+
onclick={(event) => {
|
|
80
|
+
handlePress()
|
|
81
|
+
attributes.onclick?.(event)
|
|
82
|
+
}}
|
|
83
|
+
bind:this={element}
|
|
84
|
+
aria-describedby={tooltipId ?? attributes['aria-describedby']}
|
|
85
|
+
interestfor={tooltipId ?? attributes['interestfor']}
|
|
86
|
+
aria-label={collapsed ? label || attributes['aria-label'] : attributes['aria-label']}
|
|
87
|
+
type={(attributes['type'] as HTMLButtonAttributes['type']) ?? 'button'}
|
|
88
|
+
{disabled}
|
|
89
|
+
class={classes}
|
|
90
|
+
>
|
|
91
|
+
{@render content()}
|
|
92
|
+
</button>
|
|
93
|
+
{/if}
|
|
94
|
+
|
|
95
|
+
{#if tooltipId}
|
|
96
|
+
<Tooltip id={tooltipId}>{label}</Tooltip>
|
|
97
|
+
{/if}
|
|
98
|
+
|
|
99
|
+
<style>
|
|
100
|
+
/*
|
|
101
|
+
* `interpolate-size` lets the intrinsic width animate, so collapsing is a plain CSS
|
|
102
|
+
* transition on `width` rather than a measured pixel value.
|
|
103
|
+
*/
|
|
104
|
+
.np-extended-fab {
|
|
105
|
+
interpolate-size: allow-keywords;
|
|
106
|
+
position: relative;
|
|
107
|
+
display: inline-flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: flex-start;
|
|
110
|
+
box-sizing: border-box;
|
|
111
|
+
border-width: 0;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
font: inherit;
|
|
114
|
+
font-weight: 500;
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
-webkit-tap-highlight-color: transparent;
|
|
119
|
+
box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
|
|
120
|
+
background-color: var(--np-fab-container-color, var(--_container-color));
|
|
121
|
+
color: var(--np-fab-icon-color, var(--_icon-color));
|
|
122
|
+
--np-ripple-hover-color: var(--np-fab-icon-color, var(--_icon-color));
|
|
123
|
+
--np-ripple-pressed-color: var(--np-fab-icon-color, var(--_icon-color));
|
|
124
|
+
width: auto;
|
|
125
|
+
min-width: var(--_height);
|
|
126
|
+
height: var(--_height);
|
|
127
|
+
gap: var(--_gap);
|
|
128
|
+
padding-inline: var(--_padding);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
* `calc(var(--_height) / 2)` renders identically to `--np-shape-corner-full` (9999px) at
|
|
133
|
+
* rest, since border-radius clamps an oversized radius down to exactly half the height
|
|
134
|
+
* anyway. It matters for the pressed transition: animating from a huge fixed value spends
|
|
135
|
+
* nearly the whole transition on radii that still render as fully round, so the morph never
|
|
136
|
+
* becomes visible during a normal, brief click. `50%` isn't a substitute here, since the box
|
|
137
|
+
* isn't square, it would resolve to an elliptical corner rather than a round pill end.
|
|
138
|
+
*/
|
|
139
|
+
.round {
|
|
140
|
+
border-radius: var(--np-fab-shape, calc(var(--_height) / 2));
|
|
141
|
+
}
|
|
142
|
+
.square {
|
|
143
|
+
border-radius: var(--np-fab-shape, var(--_square-radius));
|
|
144
|
+
}
|
|
145
|
+
/*
|
|
146
|
+
* A press morphs the corner towards the other shape. `:active` alone drops the moment the
|
|
147
|
+
* pointer lifts, which on a normal click is long before the morph has played, so the short
|
|
148
|
+
* `pressed` class holds the shape until the whole round trip is visible, the way Button does
|
|
149
|
+
* it. Both are gated on the motion preference, since without the transition below this is
|
|
150
|
+
* only an instant snap.
|
|
151
|
+
*/
|
|
152
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
153
|
+
.round:not(:disabled):is(:active, .pressed) {
|
|
154
|
+
border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
|
|
155
|
+
}
|
|
156
|
+
.square:not(:disabled):is(:active, .pressed) {
|
|
157
|
+
border-radius: var(--np-fab-pressed-shape, calc(var(--_height) / 2));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* The same three sizes as `Fab`, which is what M3 Expressive aligned the two on. */
|
|
162
|
+
.s {
|
|
163
|
+
--_height: 3.5rem;
|
|
164
|
+
--_square-radius: 1rem;
|
|
165
|
+
--_gap: 0.75rem;
|
|
166
|
+
--_padding: 1rem;
|
|
167
|
+
font-size: 1rem;
|
|
168
|
+
--np-icon-size: 1.5rem;
|
|
169
|
+
}
|
|
170
|
+
.m {
|
|
171
|
+
--_height: 5rem;
|
|
172
|
+
--_square-radius: 1.25rem;
|
|
173
|
+
--_gap: 1rem;
|
|
174
|
+
--_padding: 1.5rem;
|
|
175
|
+
font-size: 1.375rem;
|
|
176
|
+
--np-icon-size: 1.75rem;
|
|
177
|
+
}
|
|
178
|
+
.l {
|
|
179
|
+
--_height: 6rem;
|
|
180
|
+
--_square-radius: 1.75rem;
|
|
181
|
+
--_gap: 1rem;
|
|
182
|
+
--_padding: 2rem;
|
|
183
|
+
font-size: 1.5rem;
|
|
184
|
+
--np-icon-size: 2.25rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Collapsed is the icon-only FAB: square box, no text, no padding around the label. */
|
|
188
|
+
.np-fab-collapsed {
|
|
189
|
+
width: var(--_height);
|
|
190
|
+
padding-inline: 0;
|
|
191
|
+
justify-content: center;
|
|
192
|
+
gap: 0;
|
|
193
|
+
}
|
|
194
|
+
.np-fab-collapsed .np-fab-label {
|
|
195
|
+
opacity: 0;
|
|
196
|
+
max-width: 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.np-fab-label {
|
|
200
|
+
max-width: 20rem;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
text-overflow: ellipsis;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
206
|
+
.np-extended-fab {
|
|
207
|
+
transition:
|
|
208
|
+
width var(--np-motion-expressive-default-spatial),
|
|
209
|
+
gap var(--np-motion-expressive-default-spatial),
|
|
210
|
+
padding-inline var(--np-motion-expressive-default-spatial),
|
|
211
|
+
border-radius var(--np-motion-expressive-default-spatial),
|
|
212
|
+
box-shadow var(--np-motion-expressive-fast-effects),
|
|
213
|
+
background-color var(--np-motion-expressive-fast-effects);
|
|
214
|
+
}
|
|
215
|
+
.np-fab-label {
|
|
216
|
+
transition:
|
|
217
|
+
opacity var(--np-motion-expressive-fast-effects),
|
|
218
|
+
max-width var(--np-motion-expressive-default-spatial);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/*
|
|
223
|
+
* M3's colour styles, named after the tokens they map to. The tone styles are the stronger
|
|
224
|
+
* pair, the container styles the softer one. The surface style M3 Expressive dropped is gone.
|
|
225
|
+
*/
|
|
226
|
+
.np-fab-primary {
|
|
227
|
+
--_container-color: var(--np-color-primary);
|
|
228
|
+
--_icon-color: var(--np-color-on-primary);
|
|
229
|
+
}
|
|
230
|
+
.np-fab-secondary {
|
|
231
|
+
--_container-color: var(--np-color-secondary);
|
|
232
|
+
--_icon-color: var(--np-color-on-secondary);
|
|
233
|
+
}
|
|
234
|
+
.np-fab-tertiary {
|
|
235
|
+
--_container-color: var(--np-color-tertiary);
|
|
236
|
+
--_icon-color: var(--np-color-on-tertiary);
|
|
237
|
+
}
|
|
238
|
+
.np-fab-primary-container {
|
|
239
|
+
--_container-color: var(--np-color-primary-container);
|
|
240
|
+
--_icon-color: var(--np-color-on-primary-container);
|
|
241
|
+
}
|
|
242
|
+
.np-fab-secondary-container {
|
|
243
|
+
--_container-color: var(--np-color-secondary-container);
|
|
244
|
+
--_icon-color: var(--np-color-on-secondary-container);
|
|
245
|
+
}
|
|
246
|
+
.np-fab-tertiary-container {
|
|
247
|
+
--_container-color: var(--np-color-tertiary-container);
|
|
248
|
+
--_icon-color: var(--np-color-on-tertiary-container);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.np-fab-lowered {
|
|
252
|
+
box-shadow: var(--np-fab-elevation, var(--np-elevation-1));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@media (hover: hover) {
|
|
256
|
+
.np-extended-fab:not(:disabled):hover {
|
|
257
|
+
background-color: color-mix(
|
|
258
|
+
in srgb,
|
|
259
|
+
var(--np-fab-icon-color, var(--_icon-color)) 8%,
|
|
260
|
+
var(--np-fab-container-color, var(--_container-color))
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
.np-fab-lowered:not(:disabled):hover {
|
|
264
|
+
box-shadow: var(--np-fab-elevation, var(--np-elevation-2));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.np-extended-fab:disabled {
|
|
269
|
+
cursor: default;
|
|
270
|
+
box-shadow: none;
|
|
271
|
+
background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
|
|
272
|
+
color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.np-fab-icon {
|
|
276
|
+
display: flex;
|
|
277
|
+
flex: none;
|
|
278
|
+
fill: currentColor;
|
|
279
|
+
pointer-events: none;
|
|
280
|
+
}
|
|
281
|
+
</style>
|