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
package/README.md
CHANGED
|
@@ -54,8 +54,12 @@ Live demos and usage examples for every component are available on [noph.dev](ht
|
|
|
54
54
|
|
|
55
55
|
**Actions**
|
|
56
56
|
[Buttons](https://noph.dev/components/button) ·
|
|
57
|
+
[Button group](https://noph.dev/components/button-group) ·
|
|
58
|
+
[FAB](https://noph.dev/components/fab) ·
|
|
57
59
|
[Icon buttons](https://noph.dev/components/icon-button) ·
|
|
58
60
|
[Segmented buttons](https://noph.dev/components/segmented-button) ·
|
|
61
|
+
[Split button](https://noph.dev/components/split-button) ·
|
|
62
|
+
[Toolbar](https://noph.dev/components/toolbar) ·
|
|
59
63
|
[Ripple](https://noph.dev/components/ripple)
|
|
60
64
|
|
|
61
65
|
**Inputs & selection**
|
|
@@ -65,6 +69,7 @@ Live demos and usage examples for every component are available on [noph.dev](ht
|
|
|
65
69
|
[DatePicker](https://noph.dev/components/date-picker) ·
|
|
66
70
|
[DateTimePicker](https://noph.dev/components/date-time-picker) ·
|
|
67
71
|
[Radio](https://noph.dev/components/radio) ·
|
|
72
|
+
[Search](https://noph.dev/components/search) ·
|
|
68
73
|
[Select](https://noph.dev/components/select) ·
|
|
69
74
|
[Slider](https://noph.dev/components/slider) ·
|
|
70
75
|
[Switch](https://noph.dev/components/switch) ·
|
|
@@ -72,8 +77,10 @@ Live demos and usage examples for every component are available on [noph.dev](ht
|
|
|
72
77
|
|
|
73
78
|
**Navigation**
|
|
74
79
|
[Menus](https://noph.dev/components/menu) ·
|
|
80
|
+
[Navigation Bar](https://noph.dev/components/navigation-bar) ·
|
|
75
81
|
[Navigation Drawer](https://noph.dev/components/navigation-drawer) ·
|
|
76
82
|
[Navigation Rail](https://noph.dev/components/navigation-rail) ·
|
|
83
|
+
[App bar](https://noph.dev/components/app-bar) ·
|
|
77
84
|
[Tabs](https://noph.dev/components/tabs)
|
|
78
85
|
|
|
79
86
|
**Communication**
|
|
@@ -82,9 +89,11 @@ Live demos and usage examples for every component are available on [noph.dev](ht
|
|
|
82
89
|
[Loading indicator](https://noph.dev/components/loading-indicator) ·
|
|
83
90
|
[Progress indicators](https://noph.dev/components/progress) ·
|
|
84
91
|
[Snackbar](https://noph.dev/components/snackbar) ·
|
|
85
|
-
[Tooltips](https://noph.dev/components/tooltip)
|
|
92
|
+
[Tooltips](https://noph.dev/components/tooltip) ·
|
|
93
|
+
[Rich tooltip](https://noph.dev/components/tooltip#rich-tooltip)
|
|
86
94
|
|
|
87
95
|
**Containment**
|
|
88
96
|
[Cards](https://noph.dev/components/card) ·
|
|
89
97
|
[Divider](https://noph.dev/components/divider) ·
|
|
98
|
+
[Sheets](https://noph.dev/components/sheet) ·
|
|
90
99
|
[Lists](https://noph.dev/components/list)
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
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
|
+
export declare const afterExit: (element: Element | undefined, fn: () => void) => () => void;
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
export const afterTwoFrames = (fn) => {
|
|
6
|
+
let inner = 0;
|
|
7
|
+
const outer = requestAnimationFrame(() => {
|
|
8
|
+
inner = requestAnimationFrame(fn);
|
|
9
|
+
});
|
|
10
|
+
return () => {
|
|
11
|
+
cancelAnimationFrame(outer);
|
|
12
|
+
cancelAnimationFrame(inner);
|
|
13
|
+
};
|
|
14
|
+
};
|
|
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
|
+
export const afterExit = (element, fn) => {
|
|
27
|
+
if (!element) {
|
|
28
|
+
fn();
|
|
29
|
+
return () => { };
|
|
30
|
+
}
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
const finish = () => {
|
|
33
|
+
if (!cancelled)
|
|
34
|
+
fn();
|
|
35
|
+
};
|
|
36
|
+
const cancelFrames = afterTwoFrames(() => {
|
|
37
|
+
const animations = element.getAnimations();
|
|
38
|
+
if (animations.length === 0) {
|
|
39
|
+
finish();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
Promise.allSettled(animations.map(({ finished }) => finished)).then(finish);
|
|
43
|
+
});
|
|
44
|
+
return () => {
|
|
45
|
+
cancelled = true;
|
|
46
|
+
cancelFrames();
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AppBarProps } from './types.js'
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
variant = 'small',
|
|
6
|
+
headline,
|
|
7
|
+
subtitle,
|
|
8
|
+
leading,
|
|
9
|
+
search,
|
|
10
|
+
trailing,
|
|
11
|
+
collapsible = false,
|
|
12
|
+
scroller = 'root',
|
|
13
|
+
children,
|
|
14
|
+
element = $bindable(),
|
|
15
|
+
...attributes
|
|
16
|
+
}: AppBarProps = $props()
|
|
17
|
+
|
|
18
|
+
const twoLine = $derived(variant === 'medium' || variant === 'large')
|
|
19
|
+
const isSearch = $derived(variant === 'search')
|
|
20
|
+
// Only a two-line bar has a second row to collapse.
|
|
21
|
+
const collapses = $derived(collapsible && twoLine)
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
{#snippet titles(inline: boolean)}
|
|
25
|
+
<div class={['np-app-bar-titles', inline && 'np-app-bar-titles-inline']}>
|
|
26
|
+
<div class="np-app-bar-headline">{headline}</div>
|
|
27
|
+
{#if subtitle}
|
|
28
|
+
<div class="np-app-bar-subtitle">{subtitle}</div>
|
|
29
|
+
{/if}
|
|
30
|
+
</div>
|
|
31
|
+
{/snippet}
|
|
32
|
+
|
|
33
|
+
<header
|
|
34
|
+
{...attributes}
|
|
35
|
+
bind:this={element}
|
|
36
|
+
class={[
|
|
37
|
+
'np-app-bar',
|
|
38
|
+
`np-app-bar-${variant}`,
|
|
39
|
+
collapses && 'np-app-bar-collapsible',
|
|
40
|
+
collapses && scroller === 'nearest' && 'np-app-bar-scroller-nearest',
|
|
41
|
+
attributes.class,
|
|
42
|
+
]}
|
|
43
|
+
>
|
|
44
|
+
<div class="np-app-bar-row">
|
|
45
|
+
{#if leading}
|
|
46
|
+
<div class="np-app-bar-leading">{@render leading()}</div>
|
|
47
|
+
{/if}
|
|
48
|
+
{#if isSearch}
|
|
49
|
+
<!-- A search app bar carries a field in place of a headline. -->
|
|
50
|
+
<div class="np-app-bar-search-field">{@render search?.()}</div>
|
|
51
|
+
{: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
|
+
<div class="np-app-bar-inline" aria-hidden="true">{@render titles(true)}</div>
|
|
57
|
+
{:else}
|
|
58
|
+
{@render titles(false)}
|
|
59
|
+
{/if}
|
|
60
|
+
{#if trailing}
|
|
61
|
+
<div class="np-app-bar-trailing">{@render trailing()}</div>
|
|
62
|
+
{/if}
|
|
63
|
+
</div>
|
|
64
|
+
{#if twoLine}
|
|
65
|
+
<div class="np-app-bar-second-row">
|
|
66
|
+
{@render titles(false)}
|
|
67
|
+
</div>
|
|
68
|
+
{/if}
|
|
69
|
+
{@render children?.()}
|
|
70
|
+
</header>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
.np-app-bar {
|
|
74
|
+
position: sticky;
|
|
75
|
+
inset-block-start: 0;
|
|
76
|
+
z-index: 8;
|
|
77
|
+
box-sizing: border-box;
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
width: 100%;
|
|
81
|
+
background-color: var(--np-app-bar-container-color, var(--np-color-surface));
|
|
82
|
+
color: var(--np-color-on-surface);
|
|
83
|
+
--np-icon-button-icon-color: var(--np-color-on-surface-variant);
|
|
84
|
+
}
|
|
85
|
+
|
|
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
|
+
.np-app-bar:has(:global(.np-search-expanded)) {
|
|
93
|
+
z-index: 9;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.np-app-bar-row {
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
gap: 0.5rem;
|
|
100
|
+
flex: none;
|
|
101
|
+
/* `min-height` rather than `height`, so a subtitle on a one-row bar grows it. */
|
|
102
|
+
min-height: 4rem;
|
|
103
|
+
padding-inline: 0.25rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.np-app-bar-leading,
|
|
107
|
+
.np-app-bar-trailing {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
flex: none;
|
|
111
|
+
gap: 0.25rem;
|
|
112
|
+
}
|
|
113
|
+
.np-app-bar-trailing {
|
|
114
|
+
margin-inline-start: auto;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.np-app-bar-titles {
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
gap: 0.125rem;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.np-app-bar-subtitle {
|
|
126
|
+
font-size: 0.875rem;
|
|
127
|
+
line-height: 1.25rem;
|
|
128
|
+
font-weight: 500;
|
|
129
|
+
color: var(--np-app-bar-subtitle-color, var(--np-color-on-surface-variant));
|
|
130
|
+
overflow: hidden;
|
|
131
|
+
text-overflow: ellipsis;
|
|
132
|
+
white-space: nowrap;
|
|
133
|
+
}
|
|
134
|
+
/* The collapsed copy sits on one row, so its subtitle stays at the small size. */
|
|
135
|
+
.np-app-bar-titles-inline .np-app-bar-headline {
|
|
136
|
+
font-size: 1.375rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.np-app-bar-headline {
|
|
140
|
+
font-size: var(--_headline-size);
|
|
141
|
+
line-height: 1.2;
|
|
142
|
+
font-weight: 400;
|
|
143
|
+
color: var(--np-app-bar-headline-color, var(--np-color-on-surface));
|
|
144
|
+
overflow: hidden;
|
|
145
|
+
text-overflow: ellipsis;
|
|
146
|
+
white-space: nowrap;
|
|
147
|
+
min-width: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* One-row bars: the headline sits in the action row. */
|
|
151
|
+
.np-app-bar-small,
|
|
152
|
+
.np-app-bar-search {
|
|
153
|
+
--_headline-size: 1.375rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
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
|
+
.np-app-bar-search-field {
|
|
161
|
+
flex: 1;
|
|
162
|
+
min-width: 0;
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
padding-inline: 0.25rem;
|
|
166
|
+
}
|
|
167
|
+
.np-app-bar-small .np-app-bar-titles {
|
|
168
|
+
padding-inline-start: 0.75rem;
|
|
169
|
+
flex: 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Two-row bars: the headline sits below, and the inline copy is revealed on collapse. */
|
|
173
|
+
.np-app-bar-medium {
|
|
174
|
+
--_headline-size: 1.5rem;
|
|
175
|
+
--_second-row-height: 3rem; /* medium: 64 + 48 = 112 */
|
|
176
|
+
}
|
|
177
|
+
.np-app-bar-large {
|
|
178
|
+
--_headline-size: 1.75rem;
|
|
179
|
+
--_second-row-height: 5.5rem; /* large: 64 + 88 = 152 */
|
|
180
|
+
}
|
|
181
|
+
|
|
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
|
+
.np-app-bar-second-row {
|
|
188
|
+
interpolate-size: allow-keywords;
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: flex-end;
|
|
191
|
+
box-sizing: border-box;
|
|
192
|
+
height: auto;
|
|
193
|
+
min-height: var(--_second-row-height);
|
|
194
|
+
padding-inline: 1rem;
|
|
195
|
+
padding-block-end: 1rem;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.np-app-bar-inline {
|
|
200
|
+
font-size: 1.375rem;
|
|
201
|
+
line-height: 1.2;
|
|
202
|
+
font-weight: 400;
|
|
203
|
+
color: var(--np-app-bar-headline-color, var(--np-color-on-surface));
|
|
204
|
+
padding-inline-start: 0.75rem;
|
|
205
|
+
flex: 1;
|
|
206
|
+
min-width: 0;
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
text-overflow: ellipsis;
|
|
209
|
+
white-space: nowrap;
|
|
210
|
+
/* Hidden until the bar collapses. */
|
|
211
|
+
opacity: 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
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
|
+
@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
|
+
:global(html:has(.np-app-bar-collapsible)) {
|
|
228
|
+
overflow-anchor: none;
|
|
229
|
+
}
|
|
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
|
+
:global(:has(.np-app-bar-scroller-nearest)) {
|
|
236
|
+
overflow-anchor: none;
|
|
237
|
+
}
|
|
238
|
+
|
|
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
|
+
.np-app-bar-collapsible .np-app-bar-second-row {
|
|
246
|
+
animation: np-app-bar-collapse linear both;
|
|
247
|
+
animation-timeline: scroll(root block);
|
|
248
|
+
animation-range: 0 var(--_second-row-height);
|
|
249
|
+
}
|
|
250
|
+
.np-app-bar-collapsible .np-app-bar-inline {
|
|
251
|
+
animation: np-app-bar-reveal linear both;
|
|
252
|
+
animation-timeline: scroll(root block);
|
|
253
|
+
animation-range: 0 var(--_second-row-height);
|
|
254
|
+
}
|
|
255
|
+
.np-app-bar-scroller-nearest .np-app-bar-second-row,
|
|
256
|
+
.np-app-bar-scroller-nearest .np-app-bar-inline {
|
|
257
|
+
animation-timeline: scroll(nearest block);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@keyframes np-app-bar-collapse {
|
|
262
|
+
to {
|
|
263
|
+
height: 0;
|
|
264
|
+
min-height: 0;
|
|
265
|
+
padding-block-end: 0;
|
|
266
|
+
opacity: 0;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@keyframes np-app-bar-reveal {
|
|
271
|
+
/* Held hidden for the first half, so the two headlines cross rather than overlap. */
|
|
272
|
+
0%,
|
|
273
|
+
50% {
|
|
274
|
+
opacity: 0;
|
|
275
|
+
}
|
|
276
|
+
to {
|
|
277
|
+
opacity: 1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as AppBar } from './AppBar.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as AppBar } from './AppBar.svelte';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
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
|
+
variant?: 'search' | 'small' | 'medium' | 'large';
|
|
10
|
+
/** Named `headline` rather than `title`, which is the native tooltip attribute. */
|
|
11
|
+
headline?: string;
|
|
12
|
+
/** Secondary line under the headline. */
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
/** Leading action, usually a navigation or back icon button. */
|
|
15
|
+
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
|
+
search?: Snippet;
|
|
21
|
+
/** Trailing actions. */
|
|
22
|
+
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
|
+
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
|
+
scroller?: 'root' | 'nearest';
|
|
34
|
+
element?: HTMLElement;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
value = $bindable(),
|
|
11
11
|
variant = 'outlined',
|
|
12
12
|
element = $bindable(),
|
|
13
|
-
|
|
13
|
+
open = $bindable(false),
|
|
14
14
|
populated,
|
|
15
15
|
clampMenuWidth = false,
|
|
16
16
|
optionsFilter,
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
finalPopulated = populated
|
|
20
20
|
menuElement.hidePopover()
|
|
21
21
|
},
|
|
22
|
+
style,
|
|
22
23
|
onkeydown,
|
|
23
24
|
onclick,
|
|
24
25
|
oninput,
|
|
@@ -36,7 +37,6 @@
|
|
|
36
37
|
let displayOptions = $derived(query === '' && !optionsFilter ? options : options.filter(filterFn))
|
|
37
38
|
let useVirtualList = $derived(displayOptions.length > virtualThreshold)
|
|
38
39
|
let widthProp = $derived(clampMenuWidth || useVirtualList ? 'width' : 'min-width')
|
|
39
|
-
let clientWidth = $state(0)
|
|
40
40
|
let menuElement = $state<HTMLDivElement>()
|
|
41
41
|
let finalPopulated = $derived(populated)
|
|
42
42
|
let activeIndex = $state(NO_INDEX)
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
activeIndex = NO_INDEX
|
|
75
75
|
return
|
|
76
76
|
}
|
|
77
|
-
if (!
|
|
77
|
+
if (!open || activeIndex < 0) return
|
|
78
78
|
const id = `${uid}-opt-${activeIndex}`
|
|
79
79
|
const optEl = document.getElementById(id)
|
|
80
80
|
if (optEl) {
|
|
@@ -135,14 +135,13 @@
|
|
|
135
135
|
autocomplete="off"
|
|
136
136
|
{variant}
|
|
137
137
|
populated={finalPopulated}
|
|
138
|
-
bind:clientWidth
|
|
139
138
|
bind:value
|
|
140
|
-
style="anchor-name:--{uid};"
|
|
139
|
+
style="anchor-name:--{uid};{style ?? ''}"
|
|
141
140
|
role="combobox"
|
|
142
141
|
aria-controls="listbox-{uid}"
|
|
143
|
-
aria-expanded={
|
|
142
|
+
aria-expanded={open}
|
|
144
143
|
aria-autocomplete="list"
|
|
145
|
-
aria-activedescendant={
|
|
144
|
+
aria-activedescendant={open && activeIndex >= 0 ? `${uid}-opt-${activeIndex}` : undefined}
|
|
146
145
|
aria-haspopup="listbox"
|
|
147
146
|
onclick={(event) => {
|
|
148
147
|
finalPopulated = true
|
|
@@ -159,7 +158,7 @@
|
|
|
159
158
|
menuElement?.hidePopover()
|
|
160
159
|
return
|
|
161
160
|
}
|
|
162
|
-
if (event.key === 'Escape' &&
|
|
161
|
+
if (event.key === 'Escape' && open) {
|
|
163
162
|
menuElement?.hidePopover()
|
|
164
163
|
activeIndex = NO_INDEX
|
|
165
164
|
event.preventDefault()
|
|
@@ -189,7 +188,7 @@
|
|
|
189
188
|
event.preventDefault()
|
|
190
189
|
return
|
|
191
190
|
}
|
|
192
|
-
if (event.key === 'Enter' &&
|
|
191
|
+
if (event.key === 'Enter' && open && activeIndex >= 0) {
|
|
193
192
|
const opt = displayOptions[activeIndex]
|
|
194
193
|
if (opt) {
|
|
195
194
|
selectOption(opt)
|
|
@@ -203,7 +202,7 @@
|
|
|
203
202
|
/>
|
|
204
203
|
<Menu
|
|
205
204
|
id="listbox-{uid}"
|
|
206
|
-
style="position-anchor:--{uid};{widthProp}:
|
|
205
|
+
style="position-anchor:--{uid};{widthProp}:anchor-size(width)"
|
|
207
206
|
role="listbox"
|
|
208
207
|
class={[!displayOptions.length && 'np-auto-complete-empty']}
|
|
209
208
|
coverAnchor={false}
|
|
@@ -214,7 +213,7 @@
|
|
|
214
213
|
? 'var(--np-outlined-select-text-field-container-shape)'
|
|
215
214
|
: 'var(--np-filled-select-text-field-container-shape)'}
|
|
216
215
|
anchor={element}
|
|
217
|
-
bind:open
|
|
216
|
+
bind:open
|
|
218
217
|
ontoggle={(e) => {
|
|
219
218
|
if (e.newState === 'closed') {
|
|
220
219
|
activeIndex = NO_INDEX
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AutoCompleteProps } from './types.ts';
|
|
2
|
-
declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value" | "
|
|
2
|
+
declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value" | "open">;
|
|
3
3
|
type AutoComplete = ReturnType<typeof AutoComplete>;
|
|
4
4
|
export default AutoComplete;
|
|
@@ -13,5 +13,5 @@ export interface AutoCompleteProps extends Omit<TextFieldProps, 'clientWidth' |
|
|
|
13
13
|
onoptionselect?: (option: AutoCompleteOption, menuElement: HTMLDivElement) => void;
|
|
14
14
|
clampMenuWidth?: boolean;
|
|
15
15
|
virtualThreshold?: number;
|
|
16
|
-
|
|
16
|
+
open?: boolean;
|
|
17
17
|
}
|
package/dist/badge/Badge.svelte
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { BadgeProps } from './types.js'
|
|
3
3
|
|
|
4
|
-
let {
|
|
4
|
+
let {
|
|
5
|
+
label,
|
|
6
|
+
'aria-label': ariaLabel,
|
|
7
|
+
element = $bindable(),
|
|
8
|
+
...attributes
|
|
9
|
+
}: BadgeProps = $props()
|
|
5
10
|
</script>
|
|
6
11
|
|
|
7
12
|
<div
|
package/dist/badge/types.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import '../internal/focus-ring.css'
|
|
3
|
+
import { pressMorph } from '../press.svelte.js'
|
|
3
4
|
import CircularProgress from '../progress/CircularProgress.svelte'
|
|
4
5
|
import Ripple from '../ripple/Ripple.svelte'
|
|
5
6
|
import Tooltip from '../tooltip/Tooltip.svelte'
|
|
6
|
-
import { onMount } from 'svelte'
|
|
7
7
|
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
8
8
|
import type { ButtonProps } from './types.js'
|
|
9
9
|
|
|
@@ -30,22 +30,12 @@
|
|
|
30
30
|
|
|
31
31
|
const tooltipId = $derived(title && !disabled && !loading ? uid : undefined)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
let pressedTimeout: ReturnType<typeof setTimeout>
|
|
33
|
+
const morph = pressMorph()
|
|
35
34
|
|
|
36
35
|
const handlePress = () => {
|
|
37
36
|
if (disabled || loading) return
|
|
38
|
-
|
|
39
|
-
pressedTimeout = setTimeout(() => {
|
|
40
|
-
pressed = false
|
|
41
|
-
}, 100)
|
|
37
|
+
morph.press()
|
|
42
38
|
}
|
|
43
|
-
|
|
44
|
-
onMount(() => {
|
|
45
|
-
return () => {
|
|
46
|
-
clearTimeout(pressedTimeout)
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
39
|
</script>
|
|
50
40
|
|
|
51
41
|
{#snippet content()}
|
|
@@ -95,7 +85,7 @@
|
|
|
95
85
|
selected && 'selected',
|
|
96
86
|
'enabled',
|
|
97
87
|
variant,
|
|
98
|
-
pressed && 'pressed',
|
|
88
|
+
morph.pressed && 'pressed',
|
|
99
89
|
attributes.class,
|
|
100
90
|
]}
|
|
101
91
|
>
|
|
@@ -128,7 +118,7 @@
|
|
|
128
118
|
selected && 'selected',
|
|
129
119
|
loading && 'np-loading',
|
|
130
120
|
disabled || loading ? `${variant}-disabled disabled` : `${variant} enabled`,
|
|
131
|
-
pressed && 'pressed',
|
|
121
|
+
morph.pressed && 'pressed',
|
|
132
122
|
attributes.class,
|
|
133
123
|
]}
|
|
134
124
|
>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import '../internal/focus-ring.css'
|
|
3
|
+
import { pressMorph } from '../press.svelte.js'
|
|
3
4
|
import CircularProgress from '../progress/CircularProgress.svelte'
|
|
4
5
|
import Ripple from '../ripple/Ripple.svelte'
|
|
5
6
|
import Tooltip from '../tooltip/Tooltip.svelte'
|
|
6
|
-
import { onMount } from 'svelte'
|
|
7
7
|
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
8
8
|
import type { IconButtonProps } from './types.js'
|
|
9
9
|
|
|
@@ -29,22 +29,12 @@
|
|
|
29
29
|
const isLink = $derived(attributes.href != null && !disabled && !loading)
|
|
30
30
|
const tooltipId = $derived(title && !disabled && !loading ? uid : undefined)
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
let pressedTimeout: ReturnType<typeof setTimeout>
|
|
32
|
+
const morph = pressMorph()
|
|
34
33
|
|
|
35
34
|
const handlePress = () => {
|
|
36
35
|
if (disabled || loading) return
|
|
37
|
-
|
|
38
|
-
pressedTimeout = setTimeout(() => {
|
|
39
|
-
pressed = false
|
|
40
|
-
}, 100)
|
|
36
|
+
morph.press()
|
|
41
37
|
}
|
|
42
|
-
|
|
43
|
-
onMount(() => {
|
|
44
|
-
return () => {
|
|
45
|
-
clearTimeout(pressedTimeout)
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
38
|
</script>
|
|
49
39
|
|
|
50
40
|
{#snippet content()}
|
|
@@ -86,7 +76,7 @@
|
|
|
86
76
|
'enabled',
|
|
87
77
|
toggle && 'toggle',
|
|
88
78
|
selected && 'selected',
|
|
89
|
-
pressed && 'pressed',
|
|
79
|
+
morph.pressed && 'pressed',
|
|
90
80
|
attributes.class,
|
|
91
81
|
].filter(Boolean)}
|
|
92
82
|
>
|
|
@@ -119,7 +109,7 @@
|
|
|
119
109
|
disabled || loading ? `${variant}-disabled disabled` : `${variant} enabled`,
|
|
120
110
|
toggle && 'toggle',
|
|
121
111
|
selected && 'selected',
|
|
122
|
-
pressed && 'pressed',
|
|
112
|
+
morph.pressed && 'pressed',
|
|
123
113
|
attributes.class,
|
|
124
114
|
]}
|
|
125
115
|
>
|