noph-ui 0.52.0 → 0.53.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 +1 -13
- package/dist/animation.js +18 -13
- package/dist/button/Button.svelte +2 -14
- package/dist/button/IconButton.svelte +5 -18
- package/dist/button/SegmentedButton.svelte +8 -11
- package/dist/card/Card.svelte +3 -19
- package/dist/card/types.d.ts +12 -2
- package/dist/carousel/Carousel.svelte +77 -14
- package/dist/carousel/CarouselItem.svelte +26 -14
- package/dist/carousel/carouselMorph.js +1 -1
- package/dist/carousel/carouselStrategy.js +25 -27
- package/dist/checkbox/Checkbox.svelte +1 -8
- package/dist/chip/FilterChip.svelte +1 -13
- package/dist/date-picker/Calendar.svelte +31 -30
- package/dist/date-picker/Calendar.svelte.d.ts +2 -0
- package/dist/date-picker/CalendarHeader.svelte +84 -62
- package/dist/date-picker/CalendarHeader.svelte.d.ts +3 -0
- package/dist/date-picker/DatePickerDialog.svelte +134 -75
- package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -3
- package/dist/date-picker/DateRangePicker.svelte +226 -78
- package/dist/date-picker/DateRangePicker.svelte.d.ts +3 -3
- package/dist/date-picker/DockedDatePicker.svelte +76 -464
- package/dist/date-picker/DockedDatePicker.svelte.d.ts +2 -2
- package/dist/date-picker/DockedDateTimePicker.svelte +128 -525
- package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +2 -2
- package/dist/date-picker/DockedPickerField.svelte +221 -0
- package/dist/date-picker/DockedPickerField.svelte.d.ts +9 -0
- package/dist/date-picker/MonthPanel.svelte +291 -0
- package/dist/date-picker/MonthPanel.svelte.d.ts +12 -0
- package/dist/date-picker/SelectionList.svelte +7 -6
- package/dist/date-picker/SelectionList.svelte.d.ts +1 -0
- package/dist/date-picker/TimeColumn.svelte +2 -6
- package/dist/date-picker/TimeColumn.svelte.d.ts +1 -0
- package/dist/date-picker/YearGrid.svelte +2 -11
- package/dist/date-picker/YearGrid.svelte.d.ts +1 -0
- package/dist/date-picker/dateUtils.d.ts +4 -0
- package/dist/date-picker/dateUtils.js +15 -88
- package/dist/date-picker/exitVisibility.svelte.js +13 -6
- package/dist/date-picker/formatters.d.ts +16 -0
- package/dist/date-picker/formatters.js +48 -15
- package/dist/date-picker/internalTypes.d.ts +59 -1
- package/dist/date-picker/shortcuts.d.ts +11 -0
- package/dist/date-picker/shortcuts.js +40 -0
- package/dist/date-picker/timeUtils.d.ts +8 -3
- package/dist/date-picker/timeUtils.js +33 -51
- package/dist/date-picker/types.d.ts +22 -0
- package/dist/icons/Icon.svelte +1 -4
- package/dist/internal/exit.css +0 -14
- package/dist/internal/field.css +337 -0
- package/dist/internal/focus-ring.css +23 -8
- package/dist/internal/pop-in.css +19 -0
- package/dist/internal/visually-hidden.css +11 -0
- package/dist/list/Item.svelte +32 -18
- package/dist/menu/Menu.svelte +6 -25
- package/dist/menu/Menu.svelte.d.ts +1 -0
- package/dist/navigation-bar/NavigationBarItem.svelte +6 -30
- package/dist/navigation-drawer/NavigationDrawerItem.svelte +4 -11
- package/dist/navigation-rail/NavigationRail.svelte +2 -3
- package/dist/navigation-rail/NavigationRailItem.svelte +2 -13
- package/dist/press.svelte.d.ts +0 -1
- package/dist/press.svelte.js +0 -11
- package/dist/progress/CircularProgress.svelte +26 -81
- package/dist/progress/CircularProgress.svelte.d.ts +1 -0
- package/dist/progress/LinearProgress.svelte +19 -151
- package/dist/progress/LinearProgress.svelte.d.ts +1 -0
- package/dist/progress/four-color.css +34 -0
- package/dist/radio/Radio.svelte +6 -9
- package/dist/ripple/Ripple.svelte +8 -16
- package/dist/search/Search.svelte +2 -9
- package/dist/search/Search.svelte.d.ts +1 -0
- package/dist/select/Check.svelte +8 -31
- package/dist/select/Check.svelte.d.ts +1 -1
- package/dist/select/NativeSelect.svelte +97 -366
- package/dist/select/NativeSelect.svelte.d.ts +1 -0
- package/dist/select/Option.svelte +1 -9
- package/dist/select/Select.svelte +139 -746
- package/dist/select/Select.svelte.d.ts +1 -0
- package/dist/select/VirtualList.svelte +0 -1
- package/dist/sheet/Sheet.svelte +11 -22
- package/dist/slider/Slider.svelte +13 -37
- package/dist/slider/Slider.svelte.d.ts +1 -0
- package/dist/switch/Switch.svelte +2 -8
- package/dist/tabs/Tab.svelte +2 -4
- package/dist/text-field/TextField.svelte +81 -609
- package/dist/text-field/TextField.svelte.d.ts +1 -0
- package/dist/time-picker/ClockDial.svelte +8 -25
- package/dist/time-picker/ClockDial.svelte.d.ts +2 -0
- package/dist/time-picker/DockedTimePicker.svelte +102 -244
- package/dist/time-picker/DockedTimePicker.svelte.d.ts +2 -2
- package/dist/time-picker/PeriodSelector.svelte +8 -7
- package/dist/time-picker/PeriodSelector.svelte.d.ts +1 -0
- package/dist/time-picker/TimeInputs.svelte +13 -2
- package/dist/time-picker/TimePickerDialog.svelte +32 -26
- package/dist/time-picker/TimePickerDialog.svelte.d.ts +2 -2
- package/dist/time-picker/TimePickerPanel.svelte +26 -4
- package/dist/time-picker/TimeSelectors.svelte +4 -9
- package/dist/time-picker/TimeSelectors.svelte.d.ts +1 -0
- package/dist/time-picker/internalTypes.d.ts +2 -0
- package/dist/time-picker/timePickerState.svelte.js +3 -9
- package/dist/tooltip/RichTooltip.svelte +2 -23
- package/dist/tooltip/RichTooltip.svelte.d.ts +1 -0
- package/dist/tooltip/Tooltip.svelte +2 -24
- package/dist/tooltip/Tooltip.svelte.d.ts +1 -0
- package/package.json +12 -12
package/dist/animation.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { Attachment } from 'svelte/attachments';
|
|
2
2
|
export declare const afterTwoFrames: (fn: () => void) => () => void;
|
|
3
|
-
/**
|
|
4
|
-
* Calls `fn` once the element's animations have finished, two frames from now so that transitions
|
|
5
|
-
* just started are counted. `subtree` includes those of its descendants. Endless animations, such
|
|
6
|
-
* as a spinner's, are not waited for.
|
|
7
|
-
*/
|
|
8
3
|
export declare const afterExit: (element: Element | undefined, fn: () => void, { subtree }?: {
|
|
9
4
|
subtree?: boolean;
|
|
10
5
|
}) => () => void;
|
|
@@ -12,16 +7,9 @@ export declare const parseMotion: (token: string) => {
|
|
|
12
7
|
duration: number;
|
|
13
8
|
easing: string;
|
|
14
9
|
};
|
|
15
|
-
/** Reads a motion token such as `--np-motion-standard-fast-spatial` off an element's styles. */
|
|
16
10
|
export declare const motionOf: (element: Element, token: string) => {
|
|
17
11
|
duration: number;
|
|
18
12
|
easing: string;
|
|
19
13
|
};
|
|
20
|
-
/**
|
|
21
|
-
* Keeps a dialog or popover on screen while it animates out, in browsers that drop it from the top
|
|
22
|
-
* layer the moment it closes because they cannot transition `overlay`, such as Safari. From the
|
|
23
|
-
* start of closing until its exit animations end it carries `data-np-exiting`, and the rules in
|
|
24
|
-
* `internal/exit.css` keep it shown meanwhile, over the page but not blocking it. `beforetoggle`
|
|
25
|
-
* fires before every way of closing, from Escape and light dismiss to `close()`.
|
|
26
|
-
*/
|
|
27
14
|
export declare const exitAnimation: Attachment<HTMLElement>;
|
|
15
|
+
export declare const morphSize: (element: HTMLElement | undefined, apply: () => void, token: string) => void;
|
package/dist/animation.js
CHANGED
|
@@ -8,11 +8,6 @@ export const afterTwoFrames = (fn) => {
|
|
|
8
8
|
cancelAnimationFrame(inner);
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
|
-
/**
|
|
12
|
-
* Calls `fn` once the element's animations have finished, two frames from now so that transitions
|
|
13
|
-
* just started are counted. `subtree` includes those of its descendants. Endless animations, such
|
|
14
|
-
* as a spinner's, are not waited for.
|
|
15
|
-
*/
|
|
16
11
|
export const afterExit = (element, fn, { subtree = false } = {}) => {
|
|
17
12
|
if (!element) {
|
|
18
13
|
fn();
|
|
@@ -47,15 +42,7 @@ export const parseMotion = (token) => {
|
|
|
47
42
|
easing: easing || 'linear',
|
|
48
43
|
};
|
|
49
44
|
};
|
|
50
|
-
/** Reads a motion token such as `--np-motion-standard-fast-spatial` off an element's styles. */
|
|
51
45
|
export const motionOf = (element, token) => parseMotion(getComputedStyle(element).getPropertyValue(token));
|
|
52
|
-
/**
|
|
53
|
-
* Keeps a dialog or popover on screen while it animates out, in browsers that drop it from the top
|
|
54
|
-
* layer the moment it closes because they cannot transition `overlay`, such as Safari. From the
|
|
55
|
-
* start of closing until its exit animations end it carries `data-np-exiting`, and the rules in
|
|
56
|
-
* `internal/exit.css` keep it shown meanwhile, over the page but not blocking it. `beforetoggle`
|
|
57
|
-
* fires before every way of closing, from Escape and light dismiss to `close()`.
|
|
58
|
-
*/
|
|
59
46
|
export const exitAnimation = (element) => {
|
|
60
47
|
if (CSS.supports('overlay: auto'))
|
|
61
48
|
return;
|
|
@@ -79,3 +66,21 @@ export const exitAnimation = (element) => {
|
|
|
79
66
|
done();
|
|
80
67
|
};
|
|
81
68
|
};
|
|
69
|
+
export const morphSize = (element, apply, token) => {
|
|
70
|
+
if (!element || matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
71
|
+
apply();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const from = element.getBoundingClientRect();
|
|
75
|
+
element.getAnimations().forEach((animation) => animation.cancel());
|
|
76
|
+
apply();
|
|
77
|
+
const to = element.getBoundingClientRect();
|
|
78
|
+
const frame = (rect) => ({
|
|
79
|
+
...(from.width !== to.width && { width: `${rect.width}px` }),
|
|
80
|
+
...(from.height !== to.height && { height: `${rect.height}px` }),
|
|
81
|
+
});
|
|
82
|
+
if (from.width === to.width && from.height === to.height)
|
|
83
|
+
return;
|
|
84
|
+
element.style.overflow = 'hidden';
|
|
85
|
+
element.animate([frame(from), frame(to)], motionOf(element, token)).finished.then(() => element.style.removeProperty('overflow'), () => { });
|
|
86
|
+
};
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
bind:this={element}
|
|
100
100
|
class={[
|
|
101
101
|
'np-button',
|
|
102
|
+
'np-focus-ring',
|
|
102
103
|
size,
|
|
103
104
|
shapeClass,
|
|
104
105
|
isToggle && 'toggle',
|
|
@@ -134,6 +135,7 @@
|
|
|
134
135
|
}}
|
|
135
136
|
class={[
|
|
136
137
|
'np-button',
|
|
138
|
+
'np-focus-ring',
|
|
137
139
|
size,
|
|
138
140
|
shapeClass,
|
|
139
141
|
isToggle && 'toggle',
|
|
@@ -294,15 +296,6 @@
|
|
|
294
296
|
.outlined-disabled {
|
|
295
297
|
--_outlined-border-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
|
|
296
298
|
}
|
|
297
|
-
.enabled:focus-visible {
|
|
298
|
-
outline-style: solid;
|
|
299
|
-
outline-color: var(--np-color-secondary);
|
|
300
|
-
outline-width: 3px;
|
|
301
|
-
outline-offset: 2px;
|
|
302
|
-
}
|
|
303
|
-
.enabled:focus-visible :global(.np-ripple-surface)::before {
|
|
304
|
-
opacity: var(--np-ripple-focus-opacity, 0.1);
|
|
305
|
-
}
|
|
306
299
|
/* Extra small and small buttons keep a 48px tall target around them. */
|
|
307
300
|
.np-touch {
|
|
308
301
|
position: absolute;
|
|
@@ -311,11 +304,6 @@
|
|
|
311
304
|
translate: 0 -50%;
|
|
312
305
|
height: max(3rem, 100%);
|
|
313
306
|
}
|
|
314
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
315
|
-
.enabled:focus-visible {
|
|
316
|
-
animation: focusAnimation var(--np-motion-expressive-slow-effects) forwards;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
307
|
.text {
|
|
320
308
|
color: var(--np-text-button-label-text-color, var(--np-color-primary));
|
|
321
309
|
}
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
bind:this={element}
|
|
90
90
|
class={[
|
|
91
91
|
'np-icon-button',
|
|
92
|
+
'np-focus-ring',
|
|
92
93
|
size,
|
|
93
94
|
width,
|
|
94
95
|
variant,
|
|
@@ -125,6 +126,7 @@
|
|
|
125
126
|
}}
|
|
126
127
|
class={[
|
|
127
128
|
'np-icon-button',
|
|
129
|
+
'np-focus-ring',
|
|
128
130
|
size,
|
|
129
131
|
width,
|
|
130
132
|
shapeClass,
|
|
@@ -294,20 +296,6 @@
|
|
|
294
296
|
outline-width: 1px;
|
|
295
297
|
outline-offset: -1px;
|
|
296
298
|
}
|
|
297
|
-
.enabled:focus-visible {
|
|
298
|
-
outline-style: solid;
|
|
299
|
-
outline-color: var(--np-color-secondary);
|
|
300
|
-
outline-width: 3px;
|
|
301
|
-
outline-offset: 2px;
|
|
302
|
-
}
|
|
303
|
-
.enabled:focus-visible :global(.np-ripple-surface)::before {
|
|
304
|
-
opacity: var(--np-ripple-focus-opacity, 0.1);
|
|
305
|
-
}
|
|
306
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
307
|
-
.enabled:focus-visible {
|
|
308
|
-
animation: focusAnimation var(--np-motion-expressive-slow-effects) forwards;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
299
|
.text {
|
|
312
300
|
color: var(--np-icon-button-icon-color, var(--np-color-on-surface-variant));
|
|
313
301
|
}
|
|
@@ -363,8 +351,7 @@
|
|
|
363
351
|
color: var(--np-color-on-surface-variant);
|
|
364
352
|
}
|
|
365
353
|
|
|
366
|
-
.outlined:not(.selected)::after
|
|
367
|
-
.outlined-disabled::after {
|
|
354
|
+
.outlined:not(.selected)::after {
|
|
368
355
|
content: '';
|
|
369
356
|
position: absolute;
|
|
370
357
|
inset: 0;
|
|
@@ -379,8 +366,8 @@
|
|
|
379
366
|
}
|
|
380
367
|
.np-touch {
|
|
381
368
|
position: absolute;
|
|
382
|
-
height: max(
|
|
383
|
-
width: max(
|
|
369
|
+
height: max(3rem, 100%);
|
|
370
|
+
width: max(3rem, 100%);
|
|
384
371
|
}
|
|
385
372
|
|
|
386
373
|
.np-icon-button-circular-progress {
|
|
@@ -45,7 +45,14 @@
|
|
|
45
45
|
{#each options as option, i (i)}
|
|
46
46
|
{let optionLabel = $derived(option.label ?? `${name}-${i}`)}
|
|
47
47
|
{let optionValue = $derived(option.value ?? optionLabel)}
|
|
48
|
-
<label
|
|
48
|
+
<label
|
|
49
|
+
class={[
|
|
50
|
+
'np-segmented-button',
|
|
51
|
+
'np-focus-ring-within',
|
|
52
|
+
'np-focus-ring-inset',
|
|
53
|
+
option.icon && 'width-icon',
|
|
54
|
+
]}
|
|
55
|
+
>
|
|
49
56
|
<div class="check-icon-wrapper">
|
|
50
57
|
<div class="check-icon">
|
|
51
58
|
<CheckIcon />
|
|
@@ -257,13 +264,6 @@
|
|
|
257
264
|
opacity: var(--np-ripple-focus-opacity, 0.1);
|
|
258
265
|
}
|
|
259
266
|
|
|
260
|
-
.np-segmented-button:has(input:focus-visible) {
|
|
261
|
-
outline-style: solid;
|
|
262
|
-
outline-color: var(--np-color-secondary);
|
|
263
|
-
outline-width: 3px;
|
|
264
|
-
outline-offset: -3px;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
267
|
@media (prefers-reduced-motion: no-preference) {
|
|
268
268
|
.np-segmented-button {
|
|
269
269
|
transition: all var(--np-motion-expressive-fast-effects);
|
|
@@ -282,8 +282,5 @@
|
|
|
282
282
|
width var(--np-motion-expressive-fast-effects),
|
|
283
283
|
opacity var(--np-motion-expressive-fast-effects);
|
|
284
284
|
}
|
|
285
|
-
.np-segmented-button:has(input:focus-visible) {
|
|
286
|
-
animation: focusAnimationInset var(--np-motion-expressive-slow-effects) forwards;
|
|
287
|
-
}
|
|
288
285
|
}
|
|
289
286
|
</style>
|
package/dist/card/Card.svelte
CHANGED
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
aria-disabled={disabled}
|
|
67
67
|
class={[
|
|
68
68
|
'np-card-container',
|
|
69
|
+
'np-focus-ring',
|
|
69
70
|
`np-card-${variant}`,
|
|
70
71
|
disabled && 'np-card-disabled',
|
|
71
72
|
attributes.class,
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
{disabled}
|
|
83
84
|
class={[
|
|
84
85
|
'np-card-container',
|
|
86
|
+
'np-focus-ring',
|
|
85
87
|
`np-card-${variant}`,
|
|
86
88
|
disabled && 'np-card-disabled',
|
|
87
89
|
attributes.class,
|
|
@@ -98,6 +100,7 @@
|
|
|
98
100
|
role="link"
|
|
99
101
|
class={[
|
|
100
102
|
'np-card-container',
|
|
103
|
+
'np-focus-ring',
|
|
101
104
|
`np-card-${variant}`,
|
|
102
105
|
disabled && 'np-card-disabled',
|
|
103
106
|
attributes.class,
|
|
@@ -111,7 +114,6 @@
|
|
|
111
114
|
.np-card-elevated {
|
|
112
115
|
border-width: 0;
|
|
113
116
|
box-shadow: var(--np-elevation-1);
|
|
114
|
-
color: var(--np-color-on-surface);
|
|
115
117
|
--background-color: var(
|
|
116
118
|
--np-elevated-card-container-color,
|
|
117
119
|
var(--np-color-surface-container-low)
|
|
@@ -123,7 +125,6 @@
|
|
|
123
125
|
}
|
|
124
126
|
.np-card-filled {
|
|
125
127
|
border-width: 0;
|
|
126
|
-
color: var(--np-color-on-surface);
|
|
127
128
|
--background-color: var(
|
|
128
129
|
--np-filled-card-container-color,
|
|
129
130
|
var(--np-color-surface-container-highest)
|
|
@@ -134,7 +135,6 @@
|
|
|
134
135
|
--background-color: var(--np-color-surface-variant);
|
|
135
136
|
}
|
|
136
137
|
.np-card-outlined {
|
|
137
|
-
color: var(--np-color-on-surface);
|
|
138
138
|
border: 1px solid var(--np-color-outline-variant);
|
|
139
139
|
--background-color: var(--np-outlined-card-container-color, var(--np-color-surface));
|
|
140
140
|
--border-radius: var(--np-outlined-card-container-shape, var(--np-shape-corner-medium));
|
|
@@ -159,21 +159,6 @@
|
|
|
159
159
|
cursor: pointer;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
.np-card-container:focus-visible :global(.np-ripple-surface)::before {
|
|
163
|
-
opacity: var(--np-ripple-focus-opacity, 0.1);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.np-card-container:focus-visible {
|
|
167
|
-
outline-style: solid;
|
|
168
|
-
outline-color: var(--np-color-secondary);
|
|
169
|
-
outline-width: 3px;
|
|
170
|
-
outline-offset: 2px;
|
|
171
|
-
}
|
|
172
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
173
|
-
.np-card-container:focus-visible {
|
|
174
|
-
animation: focusAnimation var(--np-motion-expressive-slow-effects) forwards;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
162
|
.np-card-disabled {
|
|
178
163
|
opacity: 0.38;
|
|
179
164
|
cursor: unset;
|
|
@@ -199,7 +184,6 @@
|
|
|
199
184
|
flex: 1 0 auto;
|
|
200
185
|
min-width: calc(100% - 32px);
|
|
201
186
|
margin: 1rem;
|
|
202
|
-
grid-gap: 0.5rem;
|
|
203
187
|
gap: 0.5rem;
|
|
204
188
|
}
|
|
205
189
|
|
package/dist/card/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { ElementKindProps } from '../internal/elementTag.js';
|
|
3
3
|
export type CardElement = HTMLDivElement | HTMLButtonElement | HTMLAnchorElement;
|
|
4
|
-
|
|
4
|
+
interface CardBaseProps extends ElementKindProps<CardElement> {
|
|
5
5
|
variant?: 'elevated' | 'filled' | 'outlined';
|
|
6
6
|
image?: string | null;
|
|
7
7
|
/** Describes `image` for assistive technology. Leave it out for a decorative image. */
|
|
@@ -10,5 +10,15 @@ export interface CardProps extends ElementKindProps<CardElement> {
|
|
|
10
10
|
headline?: string | null;
|
|
11
11
|
subhead?: string | null;
|
|
12
12
|
supportingText?: string | null;
|
|
13
|
-
action?: Snippet<[]>;
|
|
14
13
|
}
|
|
14
|
+
export type CardProps = CardBaseProps & ({
|
|
15
|
+
action?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
action?: Snippet<[]>;
|
|
18
|
+
type?: 'text' | null;
|
|
19
|
+
href?: never;
|
|
20
|
+
onclick?: never;
|
|
21
|
+
command?: never;
|
|
22
|
+
popovertarget?: never;
|
|
23
|
+
});
|
|
24
|
+
export {};
|
|
@@ -62,13 +62,47 @@
|
|
|
62
62
|
: undefined,
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
const ITEMS = '.np-carousel-item:is(button, a[href]):not(.np-carousel-item-disabled)'
|
|
66
|
+
const TABBABLE =
|
|
67
|
+
'a[href], button, input:not([type="hidden"]), select, textarea, summary, [tabindex], [contenteditable]'
|
|
68
|
+
|
|
69
|
+
let arrowHandler = $derived(arrowKeyNav(ITEMS, axis, { wrap: false }))
|
|
70
|
+
|
|
71
|
+
const tabbable = (node: HTMLElement) =>
|
|
72
|
+
node.tabIndex >= 0 &&
|
|
73
|
+
!node.matches(':disabled') &&
|
|
74
|
+
!node.closest('[inert]') &&
|
|
75
|
+
node.checkVisibility({ visibilityProperty: true })
|
|
76
|
+
|
|
77
|
+
const leave = (event: KeyboardEvent & { currentTarget: EventTarget & HTMLDivElement }) => {
|
|
78
|
+
const { key, target, currentTarget: carousel } = event
|
|
79
|
+
if (vertical || (key !== 'ArrowUp' && key !== 'ArrowDown')) return
|
|
80
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return
|
|
81
|
+
if (!(target instanceof HTMLElement) || !target.matches(ITEMS)) return
|
|
82
|
+
|
|
83
|
+
// A modal dialog makes the rest of the page inert without an [inert] attribute.
|
|
84
|
+
const modal = carousel.closest('dialog')
|
|
85
|
+
const scope = modal?.matches(':modal') ? modal : document
|
|
86
|
+
const nodes = [...scope.querySelectorAll<HTMLElement>(TABBABLE)]
|
|
87
|
+
const after = (node: HTMLElement) =>
|
|
88
|
+
!!(carousel.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING)
|
|
89
|
+
const candidates =
|
|
90
|
+
key === 'ArrowDown' ? nodes.filter(after) : nodes.filter((node) => !after(node)).reverse()
|
|
91
|
+
for (const node of candidates) {
|
|
92
|
+
if (carousel.contains(node) || node.contains(carousel) || !tabbable(node)) continue
|
|
93
|
+
node.focus()
|
|
94
|
+
if (document.activeElement !== node) continue
|
|
95
|
+
event.preventDefault()
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
}
|
|
66
99
|
|
|
67
100
|
const handleKeydown = (
|
|
68
101
|
event: KeyboardEvent & { currentTarget: EventTarget & HTMLDivElement },
|
|
69
102
|
) => {
|
|
70
103
|
userKeydown?.(event)
|
|
71
104
|
if (!event.defaultPrevented) arrowHandler(event)
|
|
105
|
+
if (!event.defaultPrevented) leave(event)
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
let places = $derived(new Map(items.map((item, index) => [item, index + 1])))
|
|
@@ -81,7 +115,10 @@
|
|
|
81
115
|
const notch =
|
|
82
116
|
place > 0 ? (snapPoints?.children[place - 1] as HTMLElement | undefined) : undefined
|
|
83
117
|
if (notch) notch.scrollIntoView({ block: 'start', inline: 'start', behavior })
|
|
84
|
-
else
|
|
118
|
+
else {
|
|
119
|
+
const inline = alignment === 'center' && !vertical ? 'center' : 'nearest'
|
|
120
|
+
item.scrollIntoView({ block: 'nearest', inline, behavior })
|
|
121
|
+
}
|
|
85
122
|
}
|
|
86
123
|
|
|
87
124
|
const carouselContext: CarouselContext = {
|
|
@@ -113,6 +150,7 @@
|
|
|
113
150
|
`np-carousel-${variant}`,
|
|
114
151
|
`np-carousel-${axis}`,
|
|
115
152
|
snapping && 'np-carousel-snap',
|
|
153
|
+
alignment === 'center' && !morphing && 'np-carousel-center',
|
|
116
154
|
cropping && 'np-carousel-cropping',
|
|
117
155
|
attributes.class,
|
|
118
156
|
]}
|
|
@@ -302,29 +340,20 @@
|
|
|
302
340
|
|
|
303
341
|
.np-carousel-cropping.np-carousel-horizontal .np-carousel-track :global(.np-carousel-item) {
|
|
304
342
|
animation-timeline: view(inline), view(inline);
|
|
305
|
-
clip-path: inset(
|
|
306
|
-
0 var(--_crop-end) 0 var(--_crop-start) round
|
|
307
|
-
var(--np-carousel-item-container-shape, var(--np-shape-corner-extra-large))
|
|
308
|
-
);
|
|
343
|
+
clip-path: inset(0 var(--_crop-end) 0 var(--_crop-start) round var(--_carousel-item-corner));
|
|
309
344
|
--np-carousel-parallax: var(--_shift) 0;
|
|
310
345
|
}
|
|
311
346
|
|
|
312
347
|
.np-carousel-cropping.np-carousel-horizontal
|
|
313
348
|
.np-carousel-track:dir(rtl)
|
|
314
349
|
:global(.np-carousel-item) {
|
|
315
|
-
clip-path: inset(
|
|
316
|
-
0 var(--_crop-start) 0 var(--_crop-end) round
|
|
317
|
-
var(--np-carousel-item-container-shape, var(--np-shape-corner-extra-large))
|
|
318
|
-
);
|
|
350
|
+
clip-path: inset(0 var(--_crop-start) 0 var(--_crop-end) round var(--_carousel-item-corner));
|
|
319
351
|
--_shift: calc(var(--_visible, 0.3333) * (var(--_in) - var(--_out)) * 100%);
|
|
320
352
|
}
|
|
321
353
|
|
|
322
354
|
.np-carousel-cropping.np-carousel-vertical .np-carousel-track :global(.np-carousel-item) {
|
|
323
355
|
animation-timeline: view(block), view(block);
|
|
324
|
-
clip-path: inset(
|
|
325
|
-
var(--_crop-start) 0 var(--_crop-end) 0 round
|
|
326
|
-
var(--np-carousel-item-container-shape, var(--np-shape-corner-extra-large))
|
|
327
|
-
);
|
|
356
|
+
clip-path: inset(var(--_crop-start) 0 var(--_crop-end) 0 round var(--_carousel-item-corner));
|
|
328
357
|
--np-carousel-parallax: 0 var(--_shift);
|
|
329
358
|
}
|
|
330
359
|
|
|
@@ -332,6 +361,20 @@
|
|
|
332
361
|
inset-inline-start: var(--_crop-start);
|
|
333
362
|
inset-inline-end: var(--_crop-end);
|
|
334
363
|
}
|
|
364
|
+
|
|
365
|
+
.np-carousel-cropping.np-carousel-horizontal
|
|
366
|
+
.np-carousel-track
|
|
367
|
+
:global(.np-carousel-item::after) {
|
|
368
|
+
inset-inline-start: var(--_crop-start);
|
|
369
|
+
inset-inline-end: var(--_crop-end);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.np-carousel-cropping.np-carousel-vertical
|
|
373
|
+
.np-carousel-track
|
|
374
|
+
:global(.np-carousel-item::after) {
|
|
375
|
+
inset-block-start: var(--_crop-start);
|
|
376
|
+
inset-block-end: var(--_crop-end);
|
|
377
|
+
}
|
|
335
378
|
}
|
|
336
379
|
|
|
337
380
|
@supports (animation-timeline: scroll(nearest inline)) {
|
|
@@ -346,6 +389,14 @@
|
|
|
346
389
|
:global(.np-carousel-morphing-block) .np-carousel-track :global(.np-carousel-item) {
|
|
347
390
|
animation-timeline: scroll(nearest block);
|
|
348
391
|
}
|
|
392
|
+
|
|
393
|
+
:global(.np-carousel-morphing-inline) .np-carousel-track :global(.np-carousel-item::after) {
|
|
394
|
+
inset-inline: var(--np-carousel-clip);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
:global(.np-carousel-morphing-block) .np-carousel-track :global(.np-carousel-item::after) {
|
|
398
|
+
inset-block: var(--np-carousel-clip);
|
|
399
|
+
}
|
|
349
400
|
}
|
|
350
401
|
|
|
351
402
|
.np-carousel-snap-points > span {
|
|
@@ -374,6 +425,18 @@
|
|
|
374
425
|
scroll-snap-align: start;
|
|
375
426
|
}
|
|
376
427
|
|
|
428
|
+
.np-carousel-snap.np-carousel-center .np-carousel-track :global(.np-carousel-item) {
|
|
429
|
+
scroll-snap-align: center;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.np-carousel-center.np-carousel-horizontal .np-carousel-scroller {
|
|
433
|
+
scroll-padding-inline: var(--_pad);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.np-carousel-center.np-carousel-vertical .np-carousel-scroller {
|
|
437
|
+
scroll-padding-block: var(--_pad);
|
|
438
|
+
}
|
|
439
|
+
|
|
377
440
|
:global(.np-carousel-morphing) .np-carousel-track :global(.np-carousel-item) {
|
|
378
441
|
scroll-snap-align: none;
|
|
379
442
|
}
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
disabled = false,
|
|
17
17
|
children,
|
|
18
18
|
element = $bindable(),
|
|
19
|
-
onpointerdown,
|
|
20
19
|
style,
|
|
21
20
|
...attributes
|
|
22
21
|
}: CarouselItemProps = $props()
|
|
@@ -39,20 +38,20 @@
|
|
|
39
38
|
.join(';'),
|
|
40
39
|
)
|
|
41
40
|
|
|
41
|
+
let text = $derived(attributes['aria-label'] ?? label ?? imageAlt)
|
|
42
42
|
let accessibleName = $derived(
|
|
43
|
-
|
|
44
|
-
(
|
|
45
|
-
|
|
46
|
-
: undefined),
|
|
43
|
+
tag !== 'div' && text && position > 0
|
|
44
|
+
? carouselContext.itemLabel(text, position, total)
|
|
45
|
+
: (attributes['aria-label'] ?? undefined),
|
|
47
46
|
)
|
|
48
47
|
|
|
49
48
|
let labelHidden = $derived(accessibleName !== undefined)
|
|
50
49
|
|
|
51
|
-
const
|
|
52
|
-
event:
|
|
50
|
+
const handleClick = (
|
|
51
|
+
event: MouseEvent & { currentTarget: EventTarget & CarouselItemElement },
|
|
53
52
|
) => {
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (!disabled) press.press()
|
|
54
|
+
attributes.onclick?.(event)
|
|
56
55
|
}
|
|
57
56
|
</script>
|
|
58
57
|
|
|
@@ -100,7 +99,7 @@
|
|
|
100
99
|
{disabled}
|
|
101
100
|
aria-disabled={disabled}
|
|
102
101
|
aria-label={accessibleName}
|
|
103
|
-
|
|
102
|
+
onclick={handleClick}
|
|
104
103
|
style={itemStyle}
|
|
105
104
|
class={[
|
|
106
105
|
'np-carousel-item',
|
|
@@ -120,7 +119,7 @@
|
|
|
120
119
|
aria-disabled={disabled}
|
|
121
120
|
aria-label={accessibleName}
|
|
122
121
|
tabindex={disabled ? -1 : undefined}
|
|
123
|
-
|
|
122
|
+
onclick={handleClick}
|
|
124
123
|
style={itemStyle}
|
|
125
124
|
class={[
|
|
126
125
|
'np-carousel-item',
|
|
@@ -135,6 +134,12 @@
|
|
|
135
134
|
{/if}
|
|
136
135
|
|
|
137
136
|
<style>
|
|
137
|
+
@property --_carousel-item-corner {
|
|
138
|
+
syntax: '<length-percentage>';
|
|
139
|
+
inherits: false;
|
|
140
|
+
initial-value: 0px;
|
|
141
|
+
}
|
|
142
|
+
|
|
138
143
|
.np-carousel-item {
|
|
139
144
|
position: relative;
|
|
140
145
|
box-sizing: border-box;
|
|
@@ -154,7 +159,11 @@
|
|
|
154
159
|
inline-size: var(--np-carousel-item-width, 12.5rem);
|
|
155
160
|
block-size: var(--np-carousel-item-height, auto);
|
|
156
161
|
align-self: stretch;
|
|
157
|
-
|
|
162
|
+
--_carousel-item-corner: var(
|
|
163
|
+
--np-carousel-item-container-shape,
|
|
164
|
+
var(--np-shape-corner-extra-large)
|
|
165
|
+
);
|
|
166
|
+
border-radius: var(--_carousel-item-corner);
|
|
158
167
|
box-shadow: var(--np-carousel-item-elevation, none);
|
|
159
168
|
background-color: var(--np-carousel-item-container-color, transparent);
|
|
160
169
|
--np-ripple-hover-color: var(--np-carousel-item-state-layer-color, var(--np-color-on-surface));
|
|
@@ -271,11 +280,14 @@
|
|
|
271
280
|
|
|
272
281
|
@media (prefers-reduced-motion: no-preference) {
|
|
273
282
|
.np-carousel-item {
|
|
274
|
-
transition:
|
|
283
|
+
transition: --_carousel-item-corner var(--np-motion-expressive-fast-effects);
|
|
275
284
|
}
|
|
276
285
|
|
|
277
286
|
.np-carousel-item:is(button, a):is(:active, .np-carousel-item-pressed) {
|
|
278
|
-
|
|
287
|
+
--_carousel-item-corner: var(
|
|
288
|
+
--np-carousel-item-pressed-container-shape,
|
|
289
|
+
var(--np-shape-corner-large)
|
|
290
|
+
);
|
|
279
291
|
}
|
|
280
292
|
}
|
|
281
293
|
|
|
@@ -57,7 +57,7 @@ export const carouselMorph = (options) => (scroller) => {
|
|
|
57
57
|
sheet.replaceSync('');
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
const shape = 'var(--
|
|
60
|
+
const shape = 'var(--_carousel-item-corner)';
|
|
61
61
|
const offset = (along) => vertical ? `inset-block-start:${px(along)}` : `inset-inline-start:${px(along)}`;
|
|
62
62
|
const mask = (clip) => vertical ? `inset(${px(clip)} 0 round ${shape})` : `inset(0 ${px(clip)} round ${shape})`;
|
|
63
63
|
const at = (percent) => `${Math.round(percent * 1000) / 1000}%`;
|
|
@@ -103,7 +103,7 @@ const build = (input, sizes, counts) => {
|
|
|
103
103
|
keylines.push({
|
|
104
104
|
kind,
|
|
105
105
|
size,
|
|
106
|
-
loc: stride / 2 +
|
|
106
|
+
loc: stride / 2 + index * stride,
|
|
107
107
|
locOffset: offset + size / 2,
|
|
108
108
|
});
|
|
109
109
|
offset += size;
|
|
@@ -120,7 +120,7 @@ const build = (input, sizes, counts) => {
|
|
|
120
120
|
loc: keylines[keylines.length - 1].loc + stride,
|
|
121
121
|
locOffset: availableSpace + spacing / 2,
|
|
122
122
|
});
|
|
123
|
-
const scrollMax = Math.max(0, (itemCount - order.length
|
|
123
|
+
const scrollMax = Math.max(0, (itemCount - order.length) * stride);
|
|
124
124
|
return {
|
|
125
125
|
smallCount: counts.small,
|
|
126
126
|
mediumCount: counts.medium,
|
|
@@ -221,38 +221,39 @@ const withOrder = (arrangement, order) => {
|
|
|
221
221
|
keylines: [keylines[0], ...shifted, keylines[keylines.length - 1]],
|
|
222
222
|
};
|
|
223
223
|
};
|
|
224
|
+
const movableSlots = (arrangement, towards) => {
|
|
225
|
+
const order = arrangement.keylines.slice(1, -1);
|
|
226
|
+
const first = order.findIndex((slot) => slot.kind === 'large');
|
|
227
|
+
if (first < 0)
|
|
228
|
+
return 0;
|
|
229
|
+
if (towards === 'start')
|
|
230
|
+
return first;
|
|
231
|
+
return order.length - 1 - order.findLastIndex((slot) => slot.kind === 'large');
|
|
232
|
+
};
|
|
224
233
|
export const shiftSteps = (arrangement, towards) => {
|
|
225
234
|
const states = [arrangement];
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const order = arrangement.keylines.slice(1, -1);
|
|
229
|
-
const movable = order.filter((slot) => slot.kind !== 'large').length;
|
|
230
|
-
if (movable === 0 || !order.some((slot) => slot.kind === 'large'))
|
|
231
|
-
return states;
|
|
232
|
-
const working = [...order];
|
|
235
|
+
const movable = movableSlots(arrangement, towards);
|
|
236
|
+
const working = arrangement.keylines.slice(1, -1);
|
|
233
237
|
for (let step = 0; step < movable; step += 1) {
|
|
234
238
|
if (towards === 'end') {
|
|
235
239
|
const [moved] = working.splice(working.length - 1, 1);
|
|
236
|
-
working.
|
|
240
|
+
const first = working.findIndex((slot) => slot.kind === 'large');
|
|
241
|
+
const larger = working.findIndex((slot, index) => index < first && slot.size > moved.size);
|
|
242
|
+
working.splice(larger < 0 ? first : larger, 0, moved);
|
|
237
243
|
}
|
|
238
244
|
else {
|
|
239
245
|
const [moved] = working.splice(0, 1);
|
|
240
|
-
working.
|
|
246
|
+
const last = working.findLastIndex((slot) => slot.kind === 'large');
|
|
247
|
+
const smaller = working.findIndex((slot, index) => index > last && slot.size < moved.size);
|
|
248
|
+
working.splice(smaller < 0 ? working.length : smaller, 0, moved);
|
|
241
249
|
}
|
|
242
250
|
states.push(withOrder(arrangement, working));
|
|
243
251
|
}
|
|
244
252
|
return states;
|
|
245
253
|
};
|
|
246
|
-
const shiftDistance = (arrangement) => {
|
|
247
|
-
const moving = arrangement.keylines
|
|
248
|
-
.slice(1, -1)
|
|
249
|
-
.filter((keyline) => keyline.kind !== 'large').length;
|
|
250
|
-
return moving * arrangement.stride;
|
|
251
|
-
};
|
|
252
254
|
export const morphTrack = (arrangement) => {
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const endShift = shift;
|
|
255
|
+
const startShift = movableSlots(arrangement, 'start') * arrangement.stride;
|
|
256
|
+
const endShift = movableSlots(arrangement, 'end') * arrangement.stride;
|
|
256
257
|
const scrollMax = arrangement.scrollMax + startShift + endShift;
|
|
257
258
|
return {
|
|
258
259
|
arrangement,
|
|
@@ -290,14 +291,11 @@ export const placeItemOnTrack = (track, index, scroll) => {
|
|
|
290
291
|
return { x: base.x + held, clip: base.clip };
|
|
291
292
|
};
|
|
292
293
|
export const notches = (track) => {
|
|
293
|
-
const { arrangement,
|
|
294
|
-
const
|
|
295
|
-
const step = uniform ? Math.max(1, arrangement.stride) : arrangement.stride;
|
|
296
|
-
const last = uniform ? scrollMax : arrangement.scrollMax;
|
|
297
|
-
const base = uniform ? 0 : startShift;
|
|
294
|
+
const { arrangement, scrollMax } = track;
|
|
295
|
+
const step = Math.max(1, arrangement.stride);
|
|
298
296
|
const out = [0];
|
|
299
|
-
for (let at = step; at <=
|
|
300
|
-
out.push(
|
|
297
|
+
for (let at = step; at <= scrollMax + 0.001; at += step)
|
|
298
|
+
out.push(at);
|
|
301
299
|
if (scrollMax > out[out.length - 1] + 0.001)
|
|
302
300
|
out.push(scrollMax);
|
|
303
301
|
return out;
|