noph-ui 0.45.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.
- package/dist/animation.d.ts +0 -15
- package/dist/animation.js +0 -15
- package/dist/app-bar/AppBar.svelte +2 -52
- package/dist/app-bar/types.d.ts +0 -22
- package/dist/button/SplitButton.svelte +0 -17
- package/dist/button/types.d.ts +0 -6
- package/dist/checkbox/Checkbox.svelte +0 -2
- package/dist/chip/FilterChip.svelte +0 -2
- package/dist/chip/types.d.ts +0 -8
- package/dist/date-picker/DatePickerDialog.svelte +0 -10
- package/dist/date-picker/DateRangePicker.svelte +0 -5
- package/dist/date-picker/DockedDatePicker.svelte +0 -11
- package/dist/date-picker/DockedDateTimePicker.svelte +0 -8
- package/dist/date-picker/exitVisibility.svelte.d.ts +0 -5
- package/dist/date-picker/exitVisibility.svelte.js +0 -7
- package/dist/date-picker/optionList.d.ts +0 -12
- package/dist/date-picker/optionList.js +0 -12
- package/dist/dialog/Dialog.svelte +0 -22
- package/dist/dialog/types.d.ts +0 -1
- package/dist/fab/ExtendedFab.svelte +0 -26
- package/dist/fab/Fab.svelte +0 -28
- package/dist/fab/FabMenu.svelte +0 -48
- package/dist/fab/types.d.ts +0 -24
- package/dist/loading-indicator/shapes.js +0 -7
- package/dist/navigation-bar/NavigationBar.svelte +0 -7
- package/dist/navigation-bar/NavigationBarItem.svelte +0 -22
- package/dist/navigation-bar/types.d.ts +0 -10
- package/dist/navigation-drawer/NavigationDrawer.svelte +0 -21
- package/dist/navigation-drawer/types.d.ts +0 -3
- package/dist/popover.svelte.d.ts +0 -9
- package/dist/popover.svelte.js +0 -9
- package/dist/press.svelte.d.ts +0 -13
- package/dist/press.svelte.js +0 -13
- package/dist/search/Search.svelte +0 -81
- package/dist/search/types.d.ts +0 -20
- package/dist/sheet/Sheet.svelte +0 -25
- package/dist/sheet/types.d.ts +0 -8
- package/dist/slider/Slider.svelte +0 -36
- package/dist/snackbar/Snackbar.svelte +3 -17
- package/dist/tabs/Tab.svelte +0 -4
- package/dist/tabs/Tabs.svelte +2 -2
- package/dist/text-field/TextField.svelte +0 -2
- package/dist/toolbar/Toolbar.svelte +0 -12
- package/dist/toolbar/types.d.ts +0 -7
- package/dist/tooltip/RichTooltip.svelte +0 -7
- package/package.json +1 -1
package/dist/animation.d.ts
CHANGED
|
@@ -1,17 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runs `fn` after two frames, so a class or attribute change written just before it has been through
|
|
3
|
-
* one style recalculation. Returns a canceller.
|
|
4
|
-
*/
|
|
5
1
|
export declare const afterTwoFrames: (fn: () => void) => () => void;
|
|
6
|
-
/**
|
|
7
|
-
* Runs `fn` once `element` has finished the transitions it is playing, so content gated on `open`
|
|
8
|
-
* can stay mounted for as long as its container is still on screen. A container that animates its
|
|
9
|
-
* exit is still painted after `open` has flipped to `false`, and content dropped on that flip empties
|
|
10
|
-
* the container a frame before it starts to leave.
|
|
11
|
-
*
|
|
12
|
-
* The wait starts two frames in because a transition does not exist until the browser has
|
|
13
|
-
* recalculated styles, so `getAnimations()` reads empty straight after the change that starts it.
|
|
14
|
-
* An element with no exit animation therefore finishes on the second frame. Returns a canceller for
|
|
15
|
-
* the container reopening mid exit.
|
|
16
|
-
*/
|
|
17
2
|
export declare const afterExit: (element: Element | undefined, fn: () => void) => () => void;
|
package/dist/animation.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runs `fn` after two frames, so a class or attribute change written just before it has been through
|
|
3
|
-
* one style recalculation. Returns a canceller.
|
|
4
|
-
*/
|
|
5
1
|
export const afterTwoFrames = (fn) => {
|
|
6
2
|
let inner = 0;
|
|
7
3
|
const outer = requestAnimationFrame(() => {
|
|
@@ -12,17 +8,6 @@ export const afterTwoFrames = (fn) => {
|
|
|
12
8
|
cancelAnimationFrame(inner);
|
|
13
9
|
};
|
|
14
10
|
};
|
|
15
|
-
/**
|
|
16
|
-
* Runs `fn` once `element` has finished the transitions it is playing, so content gated on `open`
|
|
17
|
-
* can stay mounted for as long as its container is still on screen. A container that animates its
|
|
18
|
-
* exit is still painted after `open` has flipped to `false`, and content dropped on that flip empties
|
|
19
|
-
* the container a frame before it starts to leave.
|
|
20
|
-
*
|
|
21
|
-
* The wait starts two frames in because a transition does not exist until the browser has
|
|
22
|
-
* recalculated styles, so `getAnimations()` reads empty straight after the change that starts it.
|
|
23
|
-
* An element with no exit animation therefore finishes on the second frame. Returns a canceller for
|
|
24
|
-
* the container reopening mid exit.
|
|
25
|
-
*/
|
|
26
11
|
export const afterExit = (element, fn) => {
|
|
27
12
|
if (!element) {
|
|
28
13
|
fn();
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
const twoLine = $derived(variant === 'medium' || variant === 'large')
|
|
19
19
|
const isSearch = $derived(variant === 'search')
|
|
20
|
-
// Only a two-line bar has a second row to collapse.
|
|
21
20
|
const collapses = $derived(collapsible && twoLine)
|
|
22
21
|
</script>
|
|
23
22
|
|
|
@@ -46,13 +45,8 @@
|
|
|
46
45
|
<div class="np-app-bar-leading">{@render leading()}</div>
|
|
47
46
|
{/if}
|
|
48
47
|
{#if isSearch}
|
|
49
|
-
<!-- A search app bar carries a field in place of a headline. -->
|
|
50
48
|
<div class="np-app-bar-search-field">{@render search?.()}</div>
|
|
51
49
|
{:else if twoLine}
|
|
52
|
-
<!--
|
|
53
|
-
The collapsed copy of the headline and subtitle. Hidden from assistive technology so they
|
|
54
|
-
are announced once; the real ones below stay in the accessibility tree even at height zero.
|
|
55
|
-
-->
|
|
56
50
|
<div class="np-app-bar-inline" aria-hidden="true">{@render titles(true)}</div>
|
|
57
51
|
{:else}
|
|
58
52
|
{@render titles(false)}
|
|
@@ -83,12 +77,6 @@
|
|
|
83
77
|
--np-icon-button-icon-color: var(--np-color-on-surface-variant);
|
|
84
78
|
}
|
|
85
79
|
|
|
86
|
-
/*
|
|
87
|
-
* An expanded search floats past the bar's own bounds, into the space of whatever comes
|
|
88
|
-
* after it in the document. Sibling bars share the same z-index, so without this the one
|
|
89
|
-
* holding the expanded search would lose to a later sibling and the view would open behind
|
|
90
|
-
* it rather than over it.
|
|
91
|
-
*/
|
|
92
80
|
.np-app-bar:has(:global(.np-search-expanded)) {
|
|
93
81
|
z-index: 9;
|
|
94
82
|
}
|
|
@@ -98,7 +86,6 @@
|
|
|
98
86
|
align-items: center;
|
|
99
87
|
gap: 0.5rem;
|
|
100
88
|
flex: none;
|
|
101
|
-
/* `min-height` rather than `height`, so a subtitle on a one-row bar grows it. */
|
|
102
89
|
min-height: 4rem;
|
|
103
90
|
padding-inline: 0.25rem;
|
|
104
91
|
}
|
|
@@ -131,7 +118,6 @@
|
|
|
131
118
|
text-overflow: ellipsis;
|
|
132
119
|
white-space: nowrap;
|
|
133
120
|
}
|
|
134
|
-
/* The collapsed copy sits on one row, so its subtitle stays at the small size. */
|
|
135
121
|
.np-app-bar-titles-inline .np-app-bar-headline {
|
|
136
122
|
font-size: 1.375rem;
|
|
137
123
|
}
|
|
@@ -147,16 +133,11 @@
|
|
|
147
133
|
min-width: 0;
|
|
148
134
|
}
|
|
149
135
|
|
|
150
|
-
/* One-row bars: the headline sits in the action row. */
|
|
151
136
|
.np-app-bar-small,
|
|
152
137
|
.np-app-bar-search {
|
|
153
138
|
--_headline-size: 1.375rem;
|
|
154
139
|
}
|
|
155
140
|
|
|
156
|
-
/*
|
|
157
|
-
* The field takes the row, so a search bar has no headline to size. Named `-search-field`
|
|
158
|
-
* rather than `-search`, which is already the variant class on the header.
|
|
159
|
-
*/
|
|
160
141
|
.np-app-bar-search-field {
|
|
161
142
|
flex: 1;
|
|
162
143
|
min-width: 0;
|
|
@@ -169,21 +150,15 @@
|
|
|
169
150
|
flex: 1;
|
|
170
151
|
}
|
|
171
152
|
|
|
172
|
-
/* Two-row bars: the headline sits below, and the inline copy is revealed on collapse. */
|
|
173
153
|
.np-app-bar-medium {
|
|
174
154
|
--_headline-size: 1.5rem;
|
|
175
|
-
--_second-row-height: 3rem;
|
|
155
|
+
--_second-row-height: 3rem;
|
|
176
156
|
}
|
|
177
157
|
.np-app-bar-large {
|
|
178
158
|
--_headline-size: 1.75rem;
|
|
179
|
-
--_second-row-height: 5.5rem;
|
|
159
|
+
--_second-row-height: 5.5rem;
|
|
180
160
|
}
|
|
181
161
|
|
|
182
|
-
/*
|
|
183
|
-
* Sized by its content with a floor, not a fixed height, so a headline plus a subtitle is not
|
|
184
|
-
* clipped. `interpolate-size` lets the collapse animate that intrinsic height down to zero,
|
|
185
|
-
* the same way the extended FAB animates its width.
|
|
186
|
-
*/
|
|
187
162
|
.np-app-bar-second-row {
|
|
188
163
|
interpolate-size: allow-keywords;
|
|
189
164
|
display: flex;
|
|
@@ -207,41 +182,17 @@
|
|
|
207
182
|
overflow: hidden;
|
|
208
183
|
text-overflow: ellipsis;
|
|
209
184
|
white-space: nowrap;
|
|
210
|
-
/* Hidden until the bar collapses. */
|
|
211
185
|
opacity: 0;
|
|
212
186
|
}
|
|
213
187
|
|
|
214
|
-
/*
|
|
215
|
-
* The collapse. `animation-timeline: scroll()` drives these keyframes from the scroll position
|
|
216
|
-
* itself, so there is no scroll listener, no measurement and no state. `animation-range` ends
|
|
217
|
-
* the collapse once the page has scrolled past the second row's height.
|
|
218
|
-
*/
|
|
219
188
|
@supports (animation-timeline: scroll()) {
|
|
220
|
-
/*
|
|
221
|
-
* Scroll anchoring has to be off on whichever element scrolls, or the collapse cannot
|
|
222
|
-
* happen at all: the bar shrinking above the viewport is exactly the content shift that
|
|
223
|
-
* anchoring exists to compensate for, so the browser pushes the scroll position back and
|
|
224
|
-
* the two fight to a standstill. `:has()` reaches the scroller so a consumer does not have
|
|
225
|
-
* to know this, and only ever matches when a collapsible bar is actually present.
|
|
226
|
-
*/
|
|
227
189
|
:global(html:has(.np-app-bar-collapsible)) {
|
|
228
190
|
overflow-anchor: none;
|
|
229
191
|
}
|
|
230
|
-
/*
|
|
231
|
-
* `scroll(nearest block)` resolves to the nearest scrollable ancestor at any depth, and CSS
|
|
232
|
-
* cannot single that one out, so every ancestor of the bar is covered. Turning anchoring off
|
|
233
|
-
* on an ancestor that does not scroll costs nothing.
|
|
234
|
-
*/
|
|
235
192
|
:global(:has(.np-app-bar-scroller-nearest)) {
|
|
236
193
|
overflow-anchor: none;
|
|
237
194
|
}
|
|
238
195
|
|
|
239
|
-
/*
|
|
240
|
-
* Deliberately not gated on `prefers-reduced-motion`. The collapse is a layout state tied
|
|
241
|
-
* one to one to the scroll position, the way `position: sticky` is, not motion that plays on
|
|
242
|
-
* its own. Turning it off would leave the bar permanently eating a third of a small screen
|
|
243
|
-
* rather than removing an animation.
|
|
244
|
-
*/
|
|
245
196
|
.np-app-bar-collapsible .np-app-bar-second-row {
|
|
246
197
|
animation: np-app-bar-collapse linear both;
|
|
247
198
|
animation-timeline: scroll(root block);
|
|
@@ -268,7 +219,6 @@
|
|
|
268
219
|
}
|
|
269
220
|
|
|
270
221
|
@keyframes np-app-bar-reveal {
|
|
271
|
-
/* Held hidden for the first half, so the two headlines cross rather than overlap. */
|
|
272
222
|
0%,
|
|
273
223
|
50% {
|
|
274
224
|
opacity: 0;
|
package/dist/app-bar/types.d.ts
CHANGED
|
@@ -1,35 +1,13 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
export interface AppBarProps extends HTMLAttributes<HTMLElement> {
|
|
4
|
-
/**
|
|
5
|
-
* M3 Expressive's four: `'search'` carries a search field instead of a headline and is the
|
|
6
|
-
* emphasised, global entry point to search. `'small'` is one row. `'medium'` and `'large'` are
|
|
7
|
-
* the flexible variants, putting the headline on a second line below the action row.
|
|
8
|
-
*/
|
|
9
4
|
variant?: 'search' | 'small' | 'medium' | 'large';
|
|
10
|
-
/** Named `headline` rather than `title`, which is the native tooltip attribute. */
|
|
11
5
|
headline?: string;
|
|
12
|
-
/** Secondary line under the headline. */
|
|
13
6
|
subtitle?: string;
|
|
14
|
-
/** Leading action, usually a navigation or back icon button. */
|
|
15
7
|
leading?: Snippet;
|
|
16
|
-
/**
|
|
17
|
-
* The search field for `variant="search"`. Put a `Search` here. Replaces the headline, which a
|
|
18
|
-
* search app bar does not show.
|
|
19
|
-
*/
|
|
20
8
|
search?: Snippet;
|
|
21
|
-
/** Trailing actions. */
|
|
22
9
|
trailing?: Snippet;
|
|
23
|
-
/**
|
|
24
|
-
* Collapses a `medium` or `large` bar down to one row as the page scrolls. Driven by
|
|
25
|
-
* `animation-timeline: scroll()`, so there is no scroll listener. Where that is unsupported the
|
|
26
|
-
* bar simply stays expanded.
|
|
27
|
-
*/
|
|
28
10
|
collapsible?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* The element the collapse is tied to. `'root'` (the default) follows the page scroller; pass a
|
|
31
|
-
* different scroll container's axis with `'nearest'` when the bar sits inside one.
|
|
32
|
-
*/
|
|
33
11
|
scroller?: 'root' | 'nearest';
|
|
34
12
|
element?: HTMLElement;
|
|
35
13
|
}
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
const uid = $props.id()
|
|
23
23
|
const menuId = `np-split-button-menu-${uid}`
|
|
24
24
|
|
|
25
|
-
// The trailing half is the menu's invoker, so it is also the popover's implicit anchor.
|
|
26
25
|
let trigger: HTMLElement | undefined = $state()
|
|
27
26
|
</script>
|
|
28
27
|
|
|
@@ -37,10 +36,6 @@
|
|
|
37
36
|
class={['np-split-button', attributes.class]}
|
|
38
37
|
>
|
|
39
38
|
<Button {variant} {size} {disabled} {onclick} start={icon}>{label}</Button>
|
|
40
|
-
<!--
|
|
41
|
-
A `Button` rather than an `IconButton`, because an icon button has no `elevated` variant and
|
|
42
|
-
a split button's two halves have to match.
|
|
43
|
-
-->
|
|
44
39
|
<Button
|
|
45
40
|
{variant}
|
|
46
41
|
{size}
|
|
@@ -60,29 +55,17 @@
|
|
|
60
55
|
</ButtonGroup>
|
|
61
56
|
|
|
62
57
|
<style>
|
|
63
|
-
/* The caret turns over while the menu is open, the way the select arrow does. */
|
|
64
58
|
:global(.np-split-button-trigger svg) {
|
|
65
59
|
transition: rotate var(--np-motion-expressive-fast-effects);
|
|
66
60
|
}
|
|
67
61
|
:global(.np-split-button-trigger[aria-expanded='true'] svg) {
|
|
68
62
|
rotate: 180deg;
|
|
69
63
|
}
|
|
70
|
-
/*
|
|
71
|
-
The trailing half only holds a caret, so it does not need a full button's width. It is
|
|
72
|
-
square rather than merely narrow, because every size sets its round corner to half its
|
|
73
|
-
height, and only on a square does that corner come out as a true circle.
|
|
74
|
-
*/
|
|
75
64
|
:global(.np-split-button > .np-split-button-trigger) {
|
|
76
65
|
--np-button-padding: 0;
|
|
77
66
|
aspect-ratio: 1;
|
|
78
67
|
justify-content: center;
|
|
79
68
|
}
|
|
80
|
-
/*
|
|
81
|
-
While the menu is open the trailing half reads as selected: its inner corner rounds out to
|
|
82
|
-
the outer one, so the half becomes a circle, and it keeps a state layer up. Overriding the
|
|
83
|
-
group's inner corner rather than `border-radius` leaves ButtonGroup's own corner rules in
|
|
84
|
-
charge of which corner is which.
|
|
85
|
-
*/
|
|
86
69
|
:global(.np-split-button > .np-split-button-trigger[aria-expanded='true']) {
|
|
87
70
|
--np-button-group-inner-corner: var(--_round-radius, var(--np-shape-corner-full));
|
|
88
71
|
}
|
package/dist/button/types.d.ts
CHANGED
|
@@ -29,20 +29,14 @@ export interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
29
29
|
element?: HTMLElement;
|
|
30
30
|
}
|
|
31
31
|
export interface SplitButtonProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onclick'> {
|
|
32
|
-
/** Text of the leading action. */
|
|
33
32
|
label?: string;
|
|
34
|
-
/** Leading icon on the action half. */
|
|
35
33
|
icon?: Snippet;
|
|
36
|
-
/** Menu items for the trailing half. Rendered inside a `Menu`. */
|
|
37
34
|
menu?: Snippet<[string]>;
|
|
38
35
|
variant?: Exclude<ButtonProps['variant'], 'text'>;
|
|
39
36
|
size?: BaseButtonProps['size'];
|
|
40
37
|
disabled?: boolean | null;
|
|
41
|
-
/** Bindable. Whether the menu is open. */
|
|
42
38
|
open?: boolean;
|
|
43
|
-
/** Accessible name for the trailing half, which shows only a caret. */
|
|
44
39
|
menuLabel?: string;
|
|
45
|
-
/** The action half's click handler. The trailing half opens the menu instead. */
|
|
46
40
|
onclick?: MouseEventHandler<ButtonElement>;
|
|
47
41
|
element?: HTMLElement;
|
|
48
42
|
}
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
let groupValue = $derived(
|
|
22
22
|
typeof value === 'string' || typeof value === 'number' ? value : undefined,
|
|
23
23
|
)
|
|
24
|
-
// A grouped checkbox takes its checked state from the group, so it cannot also `bind:checked`.
|
|
25
|
-
// One controlled input covers both, with the change handler writing back whichever is in play.
|
|
26
24
|
let isChecked = $derived(
|
|
27
25
|
Array.isArray(group) && groupValue !== undefined ? group.includes(groupValue) : checked,
|
|
28
26
|
)
|
|
@@ -29,8 +29,6 @@
|
|
|
29
29
|
|
|
30
30
|
let chipLabel: HTMLLabelElement | undefined = $state()
|
|
31
31
|
|
|
32
|
-
// A grouped chip takes its checked state from the group, so it cannot also `bind:checked`. One
|
|
33
|
-
// controlled input covers both, with the change handler writing back whichever is in play.
|
|
34
32
|
let isChecked = $derived(
|
|
35
33
|
Array.isArray(group) && value !== undefined ? group.includes(value) : selected,
|
|
36
34
|
)
|
package/dist/chip/types.d.ts
CHANGED
|
@@ -4,10 +4,6 @@ import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes, MouseE
|
|
|
4
4
|
export interface ChipSetProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
element?: HTMLDivElement;
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Declared from the element attributes rather than `ButtonProps`, which leaked `size`, `shape`,
|
|
9
|
-
* `toggle`, `loading` and `selected` onto a chip. A chip has one size and one shape.
|
|
10
|
-
*/
|
|
11
7
|
export interface AssistChipProps extends HTMLAttributes<ButtonElement>, Omit<HTMLButtonAttributes, keyof HTMLAttributes<HTMLButtonElement> | 'type'>, Omit<HTMLAnchorAttributes, keyof HTMLAttributes<HTMLAnchorElement> | 'type'> {
|
|
12
8
|
variant?: 'outlined' | 'elevated';
|
|
13
9
|
disabled?: boolean | null;
|
|
@@ -16,10 +12,6 @@ export interface AssistChipProps extends HTMLAttributes<ButtonElement>, Omit<HTM
|
|
|
16
12
|
element?: HTMLElement;
|
|
17
13
|
type?: 'submit' | 'reset' | 'button' | (string & {}) | null;
|
|
18
14
|
}
|
|
19
|
-
/**
|
|
20
|
-
* A suggestion chip carries a generated suggestion rather than an action, so it is text-first and
|
|
21
|
-
* takes no leading icon. Everything else matches an assist chip.
|
|
22
|
-
*/
|
|
23
15
|
export type SuggestionChipProps = Omit<AssistChipProps, 'icon'>;
|
|
24
16
|
export interface FilterChipProps extends HTMLAttributes<HTMLDivElement> {
|
|
25
17
|
selected?: boolean;
|
|
@@ -130,11 +130,6 @@
|
|
|
130
130
|
cancelYearEnter = afterTwoFrames(() => (yearEnter = false))
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
/*
|
|
134
|
-
* The same pair every overlay in the library exports. A trigger with `command="show-modal"` and
|
|
135
|
-
* `commandfor` needs neither, so these are for the times there is no trigger to point at the
|
|
136
|
-
* dialog. `open` follows either way, written back from the dialog's own toggle event.
|
|
137
|
-
*/
|
|
138
133
|
export const show = () => {
|
|
139
134
|
if (element && !element.open) element.showModal()
|
|
140
135
|
}
|
|
@@ -227,8 +222,6 @@
|
|
|
227
222
|
} else {
|
|
228
223
|
if (!confirming) oncancel?.()
|
|
229
224
|
confirming = false
|
|
230
|
-
// Handing the month back before the dialog has gone would step the calendar to another
|
|
231
|
-
// month in front of the user, so that waits for the fade as well.
|
|
232
225
|
exit.scheduleExit(element, () => (displayMonth = monthBeforeOpen))
|
|
233
226
|
}
|
|
234
227
|
}}
|
|
@@ -246,9 +239,6 @@
|
|
|
246
239
|
type="button"
|
|
247
240
|
aria-label={mode === 'calendar' ? inputModeLabel : calendarModeLabel}
|
|
248
241
|
onclick={() => {
|
|
249
|
-
// The calendar moves `pending` on without touching the field, so the text has to
|
|
250
|
-
// catch up before it is shown again. Otherwise the field contradicts the headline
|
|
251
|
-
// and OK confirms a date the user never saw there.
|
|
252
242
|
if (mode === 'calendar') syncText()
|
|
253
243
|
setMode(mode === 'calendar' ? 'input' : 'calendar')
|
|
254
244
|
showYearPicker(false)
|
|
@@ -116,11 +116,6 @@
|
|
|
116
116
|
if (target && scroller) scroller.scrollTop = target.offsetTop
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
/*
|
|
120
|
-
* The same pair every overlay in the library exports. A trigger with `command="show-modal"` and
|
|
121
|
-
* `commandfor` needs neither, so these are for the times there is no trigger to point at the
|
|
122
|
-
* dialog. `open` follows either way, written back from the dialog's own toggle event.
|
|
123
|
-
*/
|
|
124
119
|
export const show = () => {
|
|
125
120
|
if (element && !element.open) element.showModal()
|
|
126
121
|
}
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
let {
|
|
36
36
|
value = $bindable(),
|
|
37
37
|
defaultValue,
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- absorbed on purpose
|
|
39
38
|
type,
|
|
40
39
|
'aria-invalid': ariaInvalid,
|
|
41
40
|
style,
|
|
@@ -215,11 +214,6 @@
|
|
|
215
214
|
focusCalendar()
|
|
216
215
|
}
|
|
217
216
|
|
|
218
|
-
/*
|
|
219
|
-
* The same pair every overlay in the library exports, for the times there is no trigger to point
|
|
220
|
-
* at the picker. `close()` leaves focus where it is; only a close the user asked for, through the
|
|
221
|
-
* field or the cancel button, hands focus back to the input.
|
|
222
|
-
*/
|
|
223
217
|
export const show = () => openPicker()
|
|
224
218
|
|
|
225
219
|
export const close = () => {
|
|
@@ -269,7 +263,6 @@
|
|
|
269
263
|
const parsed = usableDate(next)
|
|
270
264
|
commit(parsed ? toISODate(parsed) : undefined)
|
|
271
265
|
if (parsed) setMonth(parsed)
|
|
272
|
-
// Recorded after the commit, so that it is remembered against the value it just produced.
|
|
273
266
|
typed = { source: current, text: next }
|
|
274
267
|
invalidText = undefined
|
|
275
268
|
}
|
|
@@ -281,8 +274,6 @@
|
|
|
281
274
|
}
|
|
282
275
|
const parsed = usableDate(text)
|
|
283
276
|
invalidText = parsed ? undefined : text
|
|
284
|
-
// Leaving the field tidies a loose entry: dropping what was typed falls back to the
|
|
285
|
-
// canonical formatting of the value it produced.
|
|
286
277
|
if (parsed) typed = undefined
|
|
287
278
|
}
|
|
288
279
|
|
|
@@ -299,8 +290,6 @@
|
|
|
299
290
|
}
|
|
300
291
|
|
|
301
292
|
const chooseYear = (year: number) => {
|
|
302
|
-
// A year is offered as soon as any of its days is in range, so keeping the month the list was
|
|
303
|
-
// opened from can land on a month where nothing at all is selectable.
|
|
304
293
|
setMonth(new Date(year, clampMonthInYear(year, monthDate.getMonth(), minDate, maxDate), 1))
|
|
305
294
|
leaveList()
|
|
306
295
|
}
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
let {
|
|
50
50
|
value = $bindable(),
|
|
51
51
|
defaultValue,
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- absorbed on purpose
|
|
53
52
|
type,
|
|
54
53
|
'aria-invalid': ariaInvalid,
|
|
55
54
|
style,
|
|
@@ -119,8 +118,6 @@
|
|
|
119
118
|
let week = $derived(firstDayOfWeek ?? getFirstDayOfWeek(locale))
|
|
120
119
|
let minDate = $derived(parseISODateTime(min))
|
|
121
120
|
let maxDate = $derived(parseISODateTime(max, MINUTES_IN_DAY - 1))
|
|
122
|
-
// `as('datetime-local')` types its value as `string | number` and carries the last submission in
|
|
123
|
-
// `defaultValue`, so both are funnelled into one ISO string the rest of the component reads.
|
|
124
121
|
let current = $derived<string | undefined>(
|
|
125
122
|
typeof value === 'string' ? value : typeof defaultValue === 'string' ? defaultValue : undefined,
|
|
126
123
|
)
|
|
@@ -298,11 +295,6 @@
|
|
|
298
295
|
focusCalendar()
|
|
299
296
|
}
|
|
300
297
|
|
|
301
|
-
/*
|
|
302
|
-
* The same pair every overlay in the library exports, for the times there is no trigger to point
|
|
303
|
-
* at the picker. `close()` leaves focus where it is; only a close the user asked for, through the
|
|
304
|
-
* field or the cancel button, hands focus back to the input.
|
|
305
|
-
*/
|
|
306
298
|
export const show = () => openPicker()
|
|
307
299
|
|
|
308
300
|
export const close = () => {
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `visible` for a dialog that fades itself out, so it stays on screen after `open` has flipped to
|
|
3
|
-
* `false`. Content tied to `open` would leave the fading dialog empty a frame before it starts to
|
|
4
|
-
* leave, so this holds `visible` true until the exit animation has actually finished.
|
|
5
|
-
*/
|
|
6
1
|
export declare const exitVisibility: () => {
|
|
7
2
|
readonly visible: boolean;
|
|
8
3
|
show: () => void;
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { afterExit } from '../animation.js';
|
|
2
|
-
/**
|
|
3
|
-
* `visible` for a dialog that fades itself out, so it stays on screen after `open` has flipped to
|
|
4
|
-
* `false`. Content tied to `open` would leave the fading dialog empty a frame before it starts to
|
|
5
|
-
* leave, so this holds `visible` true until the exit animation has actually finished.
|
|
6
|
-
*/
|
|
7
2
|
export const exitVisibility = () => {
|
|
8
3
|
let visible = $state(false);
|
|
9
4
|
let cancelExit;
|
|
10
|
-
// A dialog torn down mid fade has nothing left to reveal, and running a stale `onDone` against a
|
|
11
|
-
// parent that has already dropped its state is worse than not running it at all.
|
|
12
5
|
$effect(() => () => cancelExit?.());
|
|
13
6
|
return {
|
|
14
7
|
get visible() {
|
|
@@ -1,16 +1,4 @@
|
|
|
1
1
|
import type { Attachment } from 'svelte/attachments';
|
|
2
2
|
import type { TimeOption } from './types.ts';
|
|
3
|
-
/**
|
|
4
|
-
* The option that carries the list's single tab stop: the selected one, or else the offered option
|
|
5
|
-
* nearest to the value, so arriving with the keyboard lands next to where the value already is.
|
|
6
|
-
*/
|
|
7
3
|
export declare const tabStop: (options: TimeOption[], value: number | undefined) => number | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* Scrolls the selected option into view inside its own list, rather than through `scrollIntoView`,
|
|
10
|
-
* which takes every scrollable ancestor with it and would move the page behind the picker.
|
|
11
|
-
*
|
|
12
|
-
* `centre` puts the option in the middle of the list every time it changes. Otherwise the list is
|
|
13
|
-
* centred once, on the option it opens with, and afterwards the selection is only pulled to the
|
|
14
|
-
* nearest edge when it has moved out of view, so stepping through options does not jump the list.
|
|
15
|
-
*/
|
|
16
4
|
export declare const revealSelected: (centre?: boolean) => Attachment<HTMLElement>;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The option that carries the list's single tab stop: the selected one, or else the offered option
|
|
3
|
-
* nearest to the value, so arriving with the keyboard lands next to where the value already is.
|
|
4
|
-
*/
|
|
5
1
|
export const tabStop = (options, value) => {
|
|
6
2
|
if (options.some((option) => option.value === value))
|
|
7
3
|
return value;
|
|
@@ -13,14 +9,6 @@ export const tabStop = (options, value) => {
|
|
|
13
9
|
return pool[0].value;
|
|
14
10
|
return pool.reduce((nearest, option) => Math.abs(option.value - value) < Math.abs(nearest.value - value) ? option : nearest).value;
|
|
15
11
|
};
|
|
16
|
-
/**
|
|
17
|
-
* Scrolls the selected option into view inside its own list, rather than through `scrollIntoView`,
|
|
18
|
-
* which takes every scrollable ancestor with it and would move the page behind the picker.
|
|
19
|
-
*
|
|
20
|
-
* `centre` puts the option in the middle of the list every time it changes. Otherwise the list is
|
|
21
|
-
* centred once, on the option it opens with, and afterwards the selection is only pulled to the
|
|
22
|
-
* nearest edge when it has moved out of view, so stepping through options does not jump the list.
|
|
23
|
-
*/
|
|
24
12
|
export const revealSelected = (centre = false) => {
|
|
25
13
|
let centred = false;
|
|
26
14
|
return (element) => {
|
|
@@ -20,10 +20,6 @@
|
|
|
20
20
|
|
|
21
21
|
const uid = $props.id()
|
|
22
22
|
|
|
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
23
|
export const show = () => {
|
|
28
24
|
if (element && !element.open) element.showModal()
|
|
29
25
|
}
|
|
@@ -40,13 +36,6 @@
|
|
|
40
36
|
)
|
|
41
37
|
</script>
|
|
42
38
|
|
|
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
39
|
<dialog
|
|
51
40
|
bind:this={element}
|
|
52
41
|
{...attributes}
|
|
@@ -57,11 +46,6 @@
|
|
|
57
46
|
class={['np-dialog-container', !quick && 'np-animate', attributes.class]}
|
|
58
47
|
ontoggle={(event) => {
|
|
59
48
|
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
49
|
if (event.newState === 'open') element?.focus()
|
|
66
50
|
ontoggle?.(event)
|
|
67
51
|
}}
|
|
@@ -108,7 +92,6 @@
|
|
|
108
92
|
.np-dialog-container {
|
|
109
93
|
background: transparent;
|
|
110
94
|
border: none;
|
|
111
|
-
/* Focus lands here on open only to stay off the actions, not as something to show. */
|
|
112
95
|
outline: none;
|
|
113
96
|
margin: auto;
|
|
114
97
|
padding: var(--np-dialog-inset, 2rem 1rem);
|
|
@@ -120,14 +103,9 @@
|
|
|
120
103
|
overflow: visible;
|
|
121
104
|
color: var(--np-color-on-surface);
|
|
122
105
|
}
|
|
123
|
-
/* A closed dialog is `display: none` from the user agent, which the enter transition needs. */
|
|
124
106
|
.np-dialog-container:not([open]) {
|
|
125
107
|
display: none;
|
|
126
108
|
}
|
|
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
109
|
.np-animate:not([open]) {
|
|
132
110
|
pointer-events: none;
|
|
133
111
|
}
|
package/dist/dialog/types.d.ts
CHANGED
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
const uid = $props.id()
|
|
23
23
|
|
|
24
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
25
|
const tooltipId = $derived(collapsed && label && !disabled ? uid : undefined)
|
|
27
26
|
|
|
28
27
|
const morph = pressMorph()
|
|
@@ -97,10 +96,6 @@
|
|
|
97
96
|
{/if}
|
|
98
97
|
|
|
99
98
|
<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
99
|
.np-extended-fab {
|
|
105
100
|
interpolate-size: allow-keywords;
|
|
106
101
|
position: relative;
|
|
@@ -128,27 +123,12 @@
|
|
|
128
123
|
padding-inline: var(--_padding);
|
|
129
124
|
}
|
|
130
125
|
|
|
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
126
|
.round {
|
|
140
127
|
border-radius: var(--np-fab-shape, calc(var(--_height) / 2));
|
|
141
128
|
}
|
|
142
129
|
.square {
|
|
143
130
|
border-radius: var(--np-fab-shape, var(--_square-radius));
|
|
144
131
|
}
|
|
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
132
|
@media (prefers-reduced-motion: no-preference) {
|
|
153
133
|
.round:not(:disabled):is(:active, .pressed) {
|
|
154
134
|
border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
|
|
@@ -158,7 +138,6 @@
|
|
|
158
138
|
}
|
|
159
139
|
}
|
|
160
140
|
|
|
161
|
-
/* The same three sizes as `Fab`, which is what M3 Expressive aligned the two on. */
|
|
162
141
|
.s {
|
|
163
142
|
--_height: 3.5rem;
|
|
164
143
|
--_square-radius: 1rem;
|
|
@@ -184,7 +163,6 @@
|
|
|
184
163
|
--np-icon-size: 2.25rem;
|
|
185
164
|
}
|
|
186
165
|
|
|
187
|
-
/* Collapsed is the icon-only FAB: square box, no text, no padding around the label. */
|
|
188
166
|
.np-fab-collapsed {
|
|
189
167
|
width: var(--_height);
|
|
190
168
|
padding-inline: 0;
|
|
@@ -219,10 +197,6 @@
|
|
|
219
197
|
}
|
|
220
198
|
}
|
|
221
199
|
|
|
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
200
|
.np-fab-primary {
|
|
227
201
|
--_container-color: var(--np-color-primary);
|
|
228
202
|
--_icon-color: var(--np-color-on-primary);
|