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
|
@@ -0,0 +1,239 @@
|
|
|
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 { FabProps } from './types.js'
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
variant = 'primary-container',
|
|
11
|
+
size = 's',
|
|
12
|
+
shape = 'round',
|
|
13
|
+
lowered = false,
|
|
14
|
+
disabled = false,
|
|
15
|
+
icon,
|
|
16
|
+
label,
|
|
17
|
+
element = $bindable(),
|
|
18
|
+
...attributes
|
|
19
|
+
}: FabProps = $props()
|
|
20
|
+
|
|
21
|
+
const uid = $props.id()
|
|
22
|
+
|
|
23
|
+
const isLink = $derived(attributes.href != null && !disabled)
|
|
24
|
+
const tooltipId = $derived(label && !disabled ? uid : undefined)
|
|
25
|
+
|
|
26
|
+
const morph = pressMorph()
|
|
27
|
+
morph.supersededBy(() => shape)
|
|
28
|
+
|
|
29
|
+
const handlePress = () => {
|
|
30
|
+
if (disabled) return
|
|
31
|
+
morph.press()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const classes = $derived([
|
|
35
|
+
'np-fab',
|
|
36
|
+
'np-focus-ring',
|
|
37
|
+
`np-fab-${variant}`,
|
|
38
|
+
size,
|
|
39
|
+
shape,
|
|
40
|
+
lowered && 'np-fab-lowered',
|
|
41
|
+
morph.pressed && 'pressed',
|
|
42
|
+
attributes.class,
|
|
43
|
+
])
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
{#snippet content()}
|
|
47
|
+
{#if !disabled}
|
|
48
|
+
<Ripple forElement={element} />
|
|
49
|
+
{/if}
|
|
50
|
+
{#if icon}
|
|
51
|
+
<span class="np-fab-icon">{@render icon()}</span>
|
|
52
|
+
{/if}
|
|
53
|
+
{/snippet}
|
|
54
|
+
|
|
55
|
+
{#if isLink}
|
|
56
|
+
<a
|
|
57
|
+
{...attributes}
|
|
58
|
+
onclick={(event) => {
|
|
59
|
+
handlePress()
|
|
60
|
+
attributes.onclick?.(event)
|
|
61
|
+
}}
|
|
62
|
+
bind:this={element}
|
|
63
|
+
aria-describedby={tooltipId ?? attributes['aria-describedby']}
|
|
64
|
+
interestfor={tooltipId ?? attributes['interestfor']}
|
|
65
|
+
aria-label={label || attributes['aria-label']}
|
|
66
|
+
class={classes}
|
|
67
|
+
>
|
|
68
|
+
{@render content()}
|
|
69
|
+
</a>
|
|
70
|
+
{:else}
|
|
71
|
+
<button
|
|
72
|
+
{...attributes as HTMLButtonAttributes}
|
|
73
|
+
onclick={(event) => {
|
|
74
|
+
handlePress()
|
|
75
|
+
attributes.onclick?.(event)
|
|
76
|
+
}}
|
|
77
|
+
bind:this={element}
|
|
78
|
+
aria-describedby={tooltipId ?? attributes['aria-describedby']}
|
|
79
|
+
interestfor={tooltipId ?? attributes['interestfor']}
|
|
80
|
+
aria-label={label || attributes['aria-label']}
|
|
81
|
+
type={(attributes['type'] as HTMLButtonAttributes['type']) ?? 'button'}
|
|
82
|
+
{disabled}
|
|
83
|
+
class={classes}
|
|
84
|
+
>
|
|
85
|
+
{@render content()}
|
|
86
|
+
</button>
|
|
87
|
+
{/if}
|
|
88
|
+
|
|
89
|
+
{#if tooltipId}
|
|
90
|
+
<Tooltip id={tooltipId}>{label}</Tooltip>
|
|
91
|
+
{/if}
|
|
92
|
+
|
|
93
|
+
<style>
|
|
94
|
+
.np-fab {
|
|
95
|
+
position: relative;
|
|
96
|
+
display: inline-flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
box-sizing: border-box;
|
|
100
|
+
border-width: 0;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
font: inherit;
|
|
103
|
+
text-decoration: none;
|
|
104
|
+
-webkit-tap-highlight-color: transparent;
|
|
105
|
+
box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
|
|
106
|
+
background-color: var(--np-fab-container-color, var(--_container-color));
|
|
107
|
+
color: var(--np-fab-icon-color, var(--_icon-color));
|
|
108
|
+
--np-ripple-hover-color: var(--np-fab-icon-color, var(--_icon-color));
|
|
109
|
+
--np-ripple-pressed-color: var(--np-fab-icon-color, var(--_icon-color));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* `round` is fully rounded and `square` is the smaller radius, the same way Button reads.
|
|
114
|
+
* The FAB box is always square, so `50%` renders identically to `--np-shape-corner-full`
|
|
115
|
+
* (9999px) at rest. It matters for the pressed transition below: animating `border-radius`
|
|
116
|
+
* from a huge fixed value spends nearly the whole transition on radii that still render as
|
|
117
|
+
* fully round, so the morph never becomes visible during a normal, brief click.
|
|
118
|
+
*/
|
|
119
|
+
.round {
|
|
120
|
+
border-radius: var(--np-fab-shape, 50%);
|
|
121
|
+
}
|
|
122
|
+
.square {
|
|
123
|
+
border-radius: var(--np-fab-shape, var(--_square-radius));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* A press morphs the corner towards the other shape. `:active` alone drops the moment the
|
|
128
|
+
* pointer lifts, which on a normal click is long before the morph has played, so the short
|
|
129
|
+
* `pressed` class holds the shape until the whole round trip is visible, the way Button does
|
|
130
|
+
* it. Both are gated on the motion preference, since without the transition below this is
|
|
131
|
+
* only an instant snap.
|
|
132
|
+
*/
|
|
133
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
134
|
+
.round:not(:disabled):is(:active, .pressed) {
|
|
135
|
+
border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
|
|
136
|
+
}
|
|
137
|
+
.square:not(:disabled):is(:active, .pressed) {
|
|
138
|
+
border-radius: var(--np-fab-pressed-shape, 50%);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
143
|
+
.np-fab {
|
|
144
|
+
transition:
|
|
145
|
+
border-radius var(--np-motion-expressive-default-spatial),
|
|
146
|
+
box-shadow var(--np-motion-expressive-fast-effects),
|
|
147
|
+
background-color var(--np-motion-expressive-fast-effects);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* M3's three recommended sizes, matching the extended FAB's. The 40px FAB it used to also
|
|
153
|
+
* define is gone: M3 Expressive no longer recommends it. Each square radius is the shape token
|
|
154
|
+
* that goes with the size, large, large-increased and extra-large.
|
|
155
|
+
*/
|
|
156
|
+
.s {
|
|
157
|
+
width: 3.5rem;
|
|
158
|
+
height: 3.5rem;
|
|
159
|
+
--_square-radius: 1rem;
|
|
160
|
+
--np-icon-size: 1.5rem;
|
|
161
|
+
}
|
|
162
|
+
.m {
|
|
163
|
+
width: 5rem;
|
|
164
|
+
height: 5rem;
|
|
165
|
+
--_square-radius: 1.25rem;
|
|
166
|
+
--np-icon-size: 1.75rem;
|
|
167
|
+
}
|
|
168
|
+
.l {
|
|
169
|
+
width: 6rem;
|
|
170
|
+
height: 6rem;
|
|
171
|
+
--_square-radius: 1.75rem;
|
|
172
|
+
--np-icon-size: 2.25rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* M3's colour styles, named after the tokens they map to. The tone styles are the stronger
|
|
177
|
+
* pair, the container styles the softer one. The surface style M3 Expressive dropped is gone.
|
|
178
|
+
*/
|
|
179
|
+
.np-fab-primary {
|
|
180
|
+
--_container-color: var(--np-color-primary);
|
|
181
|
+
--_icon-color: var(--np-color-on-primary);
|
|
182
|
+
}
|
|
183
|
+
.np-fab-secondary {
|
|
184
|
+
--_container-color: var(--np-color-secondary);
|
|
185
|
+
--_icon-color: var(--np-color-on-secondary);
|
|
186
|
+
}
|
|
187
|
+
.np-fab-tertiary {
|
|
188
|
+
--_container-color: var(--np-color-tertiary);
|
|
189
|
+
--_icon-color: var(--np-color-on-tertiary);
|
|
190
|
+
}
|
|
191
|
+
.np-fab-primary-container {
|
|
192
|
+
--_container-color: var(--np-color-primary-container);
|
|
193
|
+
--_icon-color: var(--np-color-on-primary-container);
|
|
194
|
+
}
|
|
195
|
+
.np-fab-secondary-container {
|
|
196
|
+
--_container-color: var(--np-color-secondary-container);
|
|
197
|
+
--_icon-color: var(--np-color-on-secondary-container);
|
|
198
|
+
}
|
|
199
|
+
.np-fab-tertiary-container {
|
|
200
|
+
--_container-color: var(--np-color-tertiary-container);
|
|
201
|
+
--_icon-color: var(--np-color-on-tertiary-container);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.np-fab-lowered {
|
|
205
|
+
box-shadow: var(--np-fab-elevation, var(--np-elevation-1));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@media (hover: hover) {
|
|
209
|
+
.np-fab:not(:disabled):hover {
|
|
210
|
+
box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
|
|
211
|
+
background-color: color-mix(
|
|
212
|
+
in srgb,
|
|
213
|
+
var(--np-fab-icon-color, var(--_icon-color)) 8%,
|
|
214
|
+
var(--np-fab-container-color, var(--_container-color))
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
.np-fab-lowered:not(:disabled):hover {
|
|
218
|
+
box-shadow: var(--np-fab-elevation, var(--np-elevation-2));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.np-fab:disabled {
|
|
223
|
+
cursor: default;
|
|
224
|
+
box-shadow: none;
|
|
225
|
+
background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
|
|
226
|
+
color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
* The icon is never a hit target, matching `Button`'s. Otherwise swapping it out from under
|
|
231
|
+
* the pointer, which `FabMenu`'s trigger does when it opens, moves the pointer to a different
|
|
232
|
+
* element; the browser hands the button fresh interest on the way and reopens its tooltip.
|
|
233
|
+
*/
|
|
234
|
+
.np-fab-icon {
|
|
235
|
+
display: flex;
|
|
236
|
+
fill: currentColor;
|
|
237
|
+
pointer-events: none;
|
|
238
|
+
}
|
|
239
|
+
</style>
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import CloseIcon from '../icons/CloseIcon.svelte'
|
|
3
|
+
import { arrowKeyNav, rovingTabindex } from '../keyboard-nav.js'
|
|
4
|
+
import { popoverController, syncOpenEffect } from '../popover.svelte.js'
|
|
5
|
+
import { PRESS_DURATION } from '../press.svelte.js'
|
|
6
|
+
import { onMount } from 'svelte'
|
|
7
|
+
import { MediaQuery } from 'svelte/reactivity'
|
|
8
|
+
import Fab from './Fab.svelte'
|
|
9
|
+
import type { FabMenuProps } from './types.js'
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
label = 'Actions',
|
|
13
|
+
icon,
|
|
14
|
+
closeIcon,
|
|
15
|
+
children,
|
|
16
|
+
variant = 'primary-container',
|
|
17
|
+
size = 's',
|
|
18
|
+
placement = 'block-start',
|
|
19
|
+
closeOnSelect = true,
|
|
20
|
+
open = $bindable(false),
|
|
21
|
+
element = $bindable(),
|
|
22
|
+
...attributes
|
|
23
|
+
}: FabMenuProps = $props()
|
|
24
|
+
|
|
25
|
+
const uid = $props.id()
|
|
26
|
+
const menuId = `np-fab-menu-${uid}`
|
|
27
|
+
/*
|
|
28
|
+
* One anchor name per instance. A fixed name in the stylesheet is shared by every FabMenu on
|
|
29
|
+
* the page, and a duplicated anchor name resolves to the last one in tree order, so every menu
|
|
30
|
+
* would open on the last trigger.
|
|
31
|
+
*/
|
|
32
|
+
const anchorName = `--np-fab-menu-${uid}`
|
|
33
|
+
|
|
34
|
+
let menuElement: HTMLDivElement | undefined = $state()
|
|
35
|
+
const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)', false)
|
|
36
|
+
|
|
37
|
+
// Declaring `role="menu"` means owing the keyboard model that goes with it: one tab stop, the
|
|
38
|
+
// arrows moving between actions. The same attachments the toolbar and the bars use.
|
|
39
|
+
const ITEMS = 'button, a[href], [role="menuitem"]'
|
|
40
|
+
const attach = rovingTabindex(ITEMS)
|
|
41
|
+
const arrowHandler = $derived(
|
|
42
|
+
arrowKeyNav(ITEMS, placement.startsWith('inline') ? 'horizontal' : 'vertical'),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const controller = popoverController(() => menuElement)
|
|
46
|
+
|
|
47
|
+
export const show = () => controller.show()
|
|
48
|
+
|
|
49
|
+
export const close = () => {
|
|
50
|
+
clearTimeout(closeTimeout)
|
|
51
|
+
controller.close()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
* An item runs its own press animation on click, the corner morph a Button or a FAB does. The
|
|
56
|
+
* menu is a popover, so closing it means `display: none`, which would cut that off before a
|
|
57
|
+
* frame of it is seen. Holding the close for the length of the press lets the item finish.
|
|
58
|
+
* Matched to Button's own timer, and skipped entirely when there is no animation to wait for.
|
|
59
|
+
*/
|
|
60
|
+
let closeTimeout: ReturnType<typeof setTimeout>
|
|
61
|
+
|
|
62
|
+
const closeAfterPress = () => {
|
|
63
|
+
if (reducedMotion.current) {
|
|
64
|
+
close()
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
clearTimeout(closeTimeout)
|
|
68
|
+
closeTimeout = setTimeout(close, PRESS_DURATION)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onMount(() => {
|
|
72
|
+
return () => {
|
|
73
|
+
clearTimeout(closeTimeout)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
syncOpenEffect(
|
|
78
|
+
() => menuElement,
|
|
79
|
+
() => open,
|
|
80
|
+
show,
|
|
81
|
+
close,
|
|
82
|
+
)
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<!--
|
|
86
|
+
Both icons stay mounted and turn into each other. Swapping one for the other cannot animate:
|
|
87
|
+
the icon arriving has no previous state to move from, so it can only appear.
|
|
88
|
+
-->
|
|
89
|
+
{#snippet triggerIcon()}
|
|
90
|
+
<span class="np-fab-menu-icons" aria-hidden="true">
|
|
91
|
+
<span class="np-fab-menu-icon" class:np-fab-menu-icon-away={open}>
|
|
92
|
+
{#if icon}{@render icon()}{/if}
|
|
93
|
+
</span>
|
|
94
|
+
<span class="np-fab-menu-icon" class:np-fab-menu-icon-away={!open}>
|
|
95
|
+
{#if closeIcon}{@render closeIcon()}{:else}<CloseIcon />{/if}
|
|
96
|
+
</span>
|
|
97
|
+
</span>
|
|
98
|
+
{/snippet}
|
|
99
|
+
|
|
100
|
+
<div
|
|
101
|
+
{...attributes}
|
|
102
|
+
bind:this={element}
|
|
103
|
+
style="anchor-name: {anchorName}; {attributes.style ?? ''}"
|
|
104
|
+
class={['np-fab-menu', `np-fab-menu-${placement}`, attributes.class]}
|
|
105
|
+
>
|
|
106
|
+
<!--
|
|
107
|
+
The tooltip is dropped while the menu is open, and the name given directly instead. A
|
|
108
|
+
tooltip is a `hint` popover, which the browser closes when the menu's `auto` popover opens,
|
|
109
|
+
and the trigger still holds interest at that point, so it would come straight back and sit
|
|
110
|
+
over the open menu. There is nothing to hint at anyway once the actions are on screen.
|
|
111
|
+
-->
|
|
112
|
+
<Fab
|
|
113
|
+
{variant}
|
|
114
|
+
{size}
|
|
115
|
+
{label}
|
|
116
|
+
shape={open ? 'square' : 'round'}
|
|
117
|
+
command="toggle-popover"
|
|
118
|
+
commandfor={menuId}
|
|
119
|
+
aria-haspopup="menu"
|
|
120
|
+
aria-expanded={open}
|
|
121
|
+
class="np-fab-menu-trigger"
|
|
122
|
+
icon={triggerIcon}
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
<div
|
|
126
|
+
bind:this={menuElement}
|
|
127
|
+
id={menuId}
|
|
128
|
+
style="position-anchor: {anchorName}"
|
|
129
|
+
popover="auto"
|
|
130
|
+
role="menu"
|
|
131
|
+
tabindex="-1"
|
|
132
|
+
aria-label={label}
|
|
133
|
+
class="np-fab-menu-list"
|
|
134
|
+
{@attach attach}
|
|
135
|
+
onkeydown={arrowHandler}
|
|
136
|
+
onclick={(event) => {
|
|
137
|
+
// Picking an action closes the menu. `closeOnSelect={false}` keeps it open for a menu
|
|
138
|
+
// whose items toggle something instead of navigating away.
|
|
139
|
+
if (!closeOnSelect) return
|
|
140
|
+
const target = event.target as HTMLElement
|
|
141
|
+
if (target.closest(ITEMS)) closeAfterPress()
|
|
142
|
+
}}
|
|
143
|
+
ontoggle={(event) => {
|
|
144
|
+
open = event.newState === 'open'
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{@render children?.()}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<style>
|
|
152
|
+
.np-fab-menu {
|
|
153
|
+
position: relative;
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* One grid cell holds both icons, so they sit on top of each other rather than side by side. */
|
|
158
|
+
.np-fab-menu-icons {
|
|
159
|
+
display: grid;
|
|
160
|
+
}
|
|
161
|
+
.np-fab-menu-icon {
|
|
162
|
+
grid-area: 1 / 1;
|
|
163
|
+
display: flex;
|
|
164
|
+
}
|
|
165
|
+
.np-fab-menu-icon-away {
|
|
166
|
+
opacity: 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
170
|
+
.np-fab-menu-icon {
|
|
171
|
+
transition:
|
|
172
|
+
opacity var(--np-motion-expressive-fast-effects),
|
|
173
|
+
rotate var(--np-motion-expressive-default-spatial);
|
|
174
|
+
}
|
|
175
|
+
/*
|
|
176
|
+
* Both turn the same way, so the pair reads as one icon rotating rather than two crossing
|
|
177
|
+
* over. The close icon spends its hidden time at -45deg, where an X sits square like the
|
|
178
|
+
* plus it replaces, so opening rotates the plus away and the X into place behind it.
|
|
179
|
+
*/
|
|
180
|
+
.np-fab-menu-icon-away {
|
|
181
|
+
rotate: -45deg;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.np-fab-menu-list {
|
|
186
|
+
position: absolute;
|
|
187
|
+
margin: 0;
|
|
188
|
+
padding: 0;
|
|
189
|
+
border: none;
|
|
190
|
+
background: none;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
align-items: flex-end;
|
|
193
|
+
gap: 0.5rem;
|
|
194
|
+
overflow: visible;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/*
|
|
198
|
+
* `display` only while open. Setting it on the base rule would beat the user agent's
|
|
199
|
+
* `display: none` for a closed popover, because author styles win over user agent styles
|
|
200
|
+
* whatever the specificity, leaving an invisible sheet over the page that swallows clicks.
|
|
201
|
+
*/
|
|
202
|
+
.np-fab-menu-list:popover-open {
|
|
203
|
+
display: flex;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Which side the items fan out to, and which end they stack from. */
|
|
207
|
+
.np-fab-menu-block-start .np-fab-menu-list {
|
|
208
|
+
position-area: block-start;
|
|
209
|
+
margin-block-end: 0.75rem;
|
|
210
|
+
flex-direction: column-reverse;
|
|
211
|
+
}
|
|
212
|
+
.np-fab-menu-block-end .np-fab-menu-list {
|
|
213
|
+
position-area: block-end;
|
|
214
|
+
margin-block-start: 0.75rem;
|
|
215
|
+
}
|
|
216
|
+
.np-fab-menu-inline-start .np-fab-menu-list {
|
|
217
|
+
position-area: inline-start;
|
|
218
|
+
margin-inline-end: 0.75rem;
|
|
219
|
+
flex-direction: row-reverse;
|
|
220
|
+
align-items: center;
|
|
221
|
+
}
|
|
222
|
+
.np-fab-menu-inline-end .np-fab-menu-list {
|
|
223
|
+
position-area: inline-end;
|
|
224
|
+
margin-inline-start: 0.75rem;
|
|
225
|
+
flex-direction: row;
|
|
226
|
+
align-items: center;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
* The items rise into place one after another. Keyframes rather than a transition: a popover
|
|
231
|
+
* goes from `display: none` to shown in one step, and a transition has no start value to run
|
|
232
|
+
* from, so the items would simply appear. An animation runs when the element becomes displayed.
|
|
233
|
+
*
|
|
234
|
+
* The whole stagger fits one window, `--_stagger-window`, however many actions there are. That
|
|
235
|
+
* is what `sibling-count()` buys over a fixed step: eight actions land in the same time two do,
|
|
236
|
+
* each one just following the last a little sooner, rather than the menu taking four times as
|
|
237
|
+
* long to finish arriving. Item one starts at 0ms, so the action nearest the trigger moves
|
|
238
|
+
* first, and the last one starts as the window closes.
|
|
239
|
+
*/
|
|
240
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
241
|
+
.np-fab-menu-list:popover-open > :global(*) {
|
|
242
|
+
--_stagger-window: 210ms;
|
|
243
|
+
--_delay: calc((sibling-index() - 1) / sibling-count() * var(--_stagger-window));
|
|
244
|
+
animation: np-fab-menu-item-in var(--np-motion-expressive-default-spatial) both;
|
|
245
|
+
animation-delay: var(--_delay);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@keyframes np-fab-menu-item-in {
|
|
250
|
+
from {
|
|
251
|
+
opacity: 0;
|
|
252
|
+
translate: 0 0.5rem;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
</style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ButtonElement } from '../button/types.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
export type FabElement = ButtonElement;
|
|
5
|
+
interface BaseFabProps extends HTMLAttributes<FabElement>, Omit<HTMLButtonAttributes, keyof HTMLAttributes<HTMLButtonElement> | 'type'>, Omit<HTMLAnchorAttributes, keyof HTMLAttributes<HTMLAnchorElement> | 'type'> {
|
|
6
|
+
element?: FabElement;
|
|
7
|
+
/**
|
|
8
|
+
* Colour style, named after the tokens it maps to. The three tone styles are the stronger
|
|
9
|
+
* pair, the three container styles the softer one. M3 Expressive no longer recommends a
|
|
10
|
+
* surface-coloured FAB, so there is no `surface`.
|
|
11
|
+
*/
|
|
12
|
+
variant?: 'primary' | 'secondary' | 'tertiary' | 'primary-container' | 'secondary-container' | 'tertiary-container';
|
|
13
|
+
/**
|
|
14
|
+
* 56, 80 and 96 pixels, the same three `Fab` and `ExtendedFab` share. `s` is M3's baseline FAB
|
|
15
|
+
* and the default. The 40 pixel FAB is not here: M3 Expressive no longer recommends it.
|
|
16
|
+
*/
|
|
17
|
+
size?: 's' | 'm' | 'l';
|
|
18
|
+
shape?: 'round' | 'square';
|
|
19
|
+
lowered?: boolean;
|
|
20
|
+
disabled?: boolean | null;
|
|
21
|
+
type?: 'submit' | 'reset' | 'button' | (string & {}) | null;
|
|
22
|
+
}
|
|
23
|
+
export interface FabProps extends BaseFabProps {
|
|
24
|
+
icon?: Snippet;
|
|
25
|
+
/**
|
|
26
|
+
* A FAB shows no text, so its accessible name has to come from somewhere. Sets both
|
|
27
|
+
* `aria-label` and a plain tooltip, the way `Button` treats `title`.
|
|
28
|
+
*/
|
|
29
|
+
label?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ExtendedFabProps extends BaseFabProps {
|
|
32
|
+
icon?: Snippet;
|
|
33
|
+
/** Visible text. An extended FAB is named by it, so it needs no `aria-label`. */
|
|
34
|
+
label?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Collapses to an icon-only FAB, animating the width. Drive it from a scroll or a media query
|
|
37
|
+
* to get M3's collapsing FAB.
|
|
38
|
+
*/
|
|
39
|
+
collapsed?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface FabMenuProps extends HTMLAttributes<HTMLDivElement> {
|
|
42
|
+
/** Names the trigger and the menu. The trigger shows no text. */
|
|
43
|
+
label?: string;
|
|
44
|
+
/** Trigger icon while the menu is closed. */
|
|
45
|
+
icon?: Snippet;
|
|
46
|
+
/** Trigger icon while the menu is open. Defaults to a close icon. */
|
|
47
|
+
closeIcon?: Snippet;
|
|
48
|
+
variant?: FabProps['variant'];
|
|
49
|
+
size?: FabProps['size'];
|
|
50
|
+
/** Which side the items fan out to. */
|
|
51
|
+
placement?: 'block-start' | 'block-end' | 'inline-start' | 'inline-end';
|
|
52
|
+
/** Bindable. Whether the menu is open. */
|
|
53
|
+
open?: boolean;
|
|
54
|
+
/** Closes the menu when an action is picked, which is what M3 does. */
|
|
55
|
+
closeOnSelect?: boolean;
|
|
56
|
+
element?: HTMLDivElement;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
height = 24,
|
|
4
|
+
width = 24,
|
|
5
|
+
}: {
|
|
6
|
+
height?: number
|
|
7
|
+
width?: number
|
|
8
|
+
} = $props()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<svg xmlns="http://www.w3.org/2000/svg" {height} viewBox="0 -960 960 960" {width}
|
|
12
|
+
><path d="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z" /></svg
|
|
13
|
+
>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default ChevronDownIcon;
|
|
2
|
+
type ChevronDownIcon = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const ChevronDownIcon: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
15
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
16
|
+
$$bindings?: Bindings;
|
|
17
|
+
} & Exports;
|
|
18
|
+
(internal: unknown, props: {
|
|
19
|
+
$$events?: Events;
|
|
20
|
+
$$slots?: Slots;
|
|
21
|
+
}): Exports & {
|
|
22
|
+
$set?: any;
|
|
23
|
+
$on?: any;
|
|
24
|
+
};
|
|
25
|
+
z_$$bindings?: Bindings;
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
height = 24,
|
|
4
|
+
width = 24,
|
|
5
|
+
}: {
|
|
6
|
+
height?: number
|
|
7
|
+
width?: number
|
|
8
|
+
} = $props()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<svg xmlns="http://www.w3.org/2000/svg" {height} viewBox="0 -960 960 960" {width}
|
|
12
|
+
><path
|
|
13
|
+
d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"
|
|
14
|
+
/></svg
|
|
15
|
+
>
|
package/dist/icons/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
export { default as ArrowBackIcon } from './ArrowBackIcon.svelte';
|
|
1
2
|
export { default as ArrowDropDownIcon } from './ArrowDropDownIcon.svelte';
|
|
2
3
|
export { default as CalendarToday } from './CalendarToday.svelte';
|
|
3
4
|
export { default as CheckIcon } from './CheckIcon.svelte';
|
|
5
|
+
export { default as ChevronDownIcon } from './ChevronDownIcon.svelte';
|
|
4
6
|
export { default as ChevronLeftIcon } from './ChevronLeftIcon.svelte';
|
|
5
7
|
export { default as ChevronRightIcon } from './ChevronRightIcon.svelte';
|
|
6
8
|
export { default as CloseIcon } from './CloseIcon.svelte';
|
|
7
9
|
export { default as EditCalendarIcon } from './EditCalendarIcon.svelte';
|
|
8
10
|
export { default as Icon } from './Icon.svelte';
|
|
11
|
+
export { default as SearchIcon } from './SearchIcon.svelte';
|
package/dist/icons/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
export { default as ArrowBackIcon } from './ArrowBackIcon.svelte';
|
|
1
2
|
export { default as ArrowDropDownIcon } from './ArrowDropDownIcon.svelte';
|
|
2
3
|
export { default as CalendarToday } from './CalendarToday.svelte';
|
|
3
4
|
export { default as CheckIcon } from './CheckIcon.svelte';
|
|
5
|
+
export { default as ChevronDownIcon } from './ChevronDownIcon.svelte';
|
|
4
6
|
export { default as ChevronLeftIcon } from './ChevronLeftIcon.svelte';
|
|
5
7
|
export { default as ChevronRightIcon } from './ChevronRightIcon.svelte';
|
|
6
8
|
export { default as CloseIcon } from './CloseIcon.svelte';
|
|
7
9
|
export { default as EditCalendarIcon } from './EditCalendarIcon.svelte';
|
|
8
10
|
export { default as Icon } from './Icon.svelte';
|
|
11
|
+
export { default as SearchIcon } from './SearchIcon.svelte';
|