noph-ui 0.35.0 → 0.36.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/README.md +13 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/loading-indicator/LoadingIndicator.svelte +135 -0
- package/dist/loading-indicator/LoadingIndicator.svelte.d.ts +4 -0
- package/dist/loading-indicator/index.d.ts +1 -0
- package/dist/loading-indicator/index.js +1 -0
- package/dist/loading-indicator/shapes.d.ts +6 -0
- package/dist/loading-indicator/shapes.js +49 -0
- package/dist/loading-indicator/types.d.ts +7 -0
- package/dist/loading-indicator/types.js +1 -0
- package/dist/navigation-drawer/NavigationDrawerItem.svelte +1 -1
- package/dist/progress/LinearProgress.svelte +26 -6
- package/dist/select/Option.svelte +0 -1
- package/dist/slider/Slider.svelte +86 -18
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,30 +52,30 @@ body {
|
|
|
52
52
|
|
|
53
53
|
## Roadmap
|
|
54
54
|
|
|
55
|
-
Beta
|
|
55
|
+
Beta
|
|
56
56
|
|
|
57
|
+
- Auto complete
|
|
58
|
+
- Badges
|
|
57
59
|
- Buttons
|
|
58
60
|
- Cards
|
|
59
61
|
- Checkbox
|
|
62
|
+
- Chips
|
|
63
|
+
- Dialogs
|
|
60
64
|
- Divider
|
|
61
65
|
- Icon buttons
|
|
62
|
-
- Lists
|
|
66
|
+
- Lists
|
|
67
|
+
- Loading indicator
|
|
68
|
+
- Menus
|
|
69
|
+
- Navigation Drawer
|
|
70
|
+
- Navigation Rail
|
|
63
71
|
- Progress indicators
|
|
64
72
|
- Radio
|
|
65
73
|
- Ripple
|
|
66
74
|
- Segmented buttons
|
|
67
75
|
- Select
|
|
76
|
+
- Slider
|
|
68
77
|
- Snackbar
|
|
69
78
|
- Switch
|
|
70
|
-
- Text fields
|
|
71
79
|
- Tabs
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- Auto complete
|
|
76
|
-
- Chips (Docs missing)
|
|
77
|
-
- Dialogs (Fullscreen + Docs missing)
|
|
78
|
-
- Menus (Positioning missing + Docs missing)
|
|
79
|
-
- Navigation Drawer (Docs missing)
|
|
80
|
-
- Navigation Rail (Badge is missing + Docs missing)
|
|
81
|
-
- Tooltips (Positioning missing)
|
|
80
|
+
- Text fields
|
|
81
|
+
- Tooltips
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './chip/index.js';
|
|
|
7
7
|
export * from './dialog/index.js';
|
|
8
8
|
export * from './divider/index.js';
|
|
9
9
|
export * from './list/index.js';
|
|
10
|
+
export * from './loading-indicator/index.js';
|
|
10
11
|
export * from './menu/index.js';
|
|
11
12
|
export * from './navigation-drawer/index.js';
|
|
12
13
|
export * from './navigation-rail/index.js';
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './chip/index.js';
|
|
|
7
7
|
export * from './dialog/index.js';
|
|
8
8
|
export * from './divider/index.js';
|
|
9
9
|
export * from './list/index.js';
|
|
10
|
+
export * from './loading-indicator/index.js';
|
|
10
11
|
export * from './menu/index.js';
|
|
11
12
|
export * from './navigation-drawer/index.js';
|
|
12
13
|
export * from './navigation-rail/index.js';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
DETERMINATE_SCALE,
|
|
4
|
+
DETERMINATE_SHAPES,
|
|
5
|
+
INDETERMINATE_SCALE,
|
|
6
|
+
INDETERMINATE_SHAPES,
|
|
7
|
+
VIEWBOX,
|
|
8
|
+
outlinePath,
|
|
9
|
+
} from './shapes.ts'
|
|
10
|
+
import type { LoadingIndicatorProps } from './types.ts'
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
value = 0,
|
|
14
|
+
max = 1,
|
|
15
|
+
indeterminate = false,
|
|
16
|
+
contained = false,
|
|
17
|
+
...attributes
|
|
18
|
+
}: LoadingIndicatorProps = $props()
|
|
19
|
+
|
|
20
|
+
const MORPH_DURATION = 650
|
|
21
|
+
const ROTATION_DURATION = 4666
|
|
22
|
+
const QUARTER_TURN = 90
|
|
23
|
+
const CENTER = VIEWBOX / 2
|
|
24
|
+
const DAMPING_RATIO = 0.6
|
|
25
|
+
const NATURAL_FREQUENCY = Math.sqrt(200)
|
|
26
|
+
const DAMPED_FREQUENCY = NATURAL_FREQUENCY * Math.sqrt(1 - DAMPING_RATIO * DAMPING_RATIO)
|
|
27
|
+
const springValue = (seconds: number) =>
|
|
28
|
+
1 -
|
|
29
|
+
Math.exp(-DAMPING_RATIO * NATURAL_FREQUENCY * seconds) *
|
|
30
|
+
(Math.cos(DAMPED_FREQUENCY * seconds) +
|
|
31
|
+
((DAMPING_RATIO * NATURAL_FREQUENCY) / DAMPED_FREQUENCY) *
|
|
32
|
+
Math.sin(DAMPED_FREQUENCY * seconds))
|
|
33
|
+
|
|
34
|
+
const clamp = (n: number) => (n > 0 ? Math.min(n, 1) : 0)
|
|
35
|
+
const rotate = (degrees: number) => `rotate(${(degrees % 360).toFixed(2)} ${CENTER} ${CENTER})`
|
|
36
|
+
|
|
37
|
+
const initialPath = outlinePath(
|
|
38
|
+
INDETERMINATE_SHAPES[0],
|
|
39
|
+
INDETERMINATE_SHAPES[1],
|
|
40
|
+
0,
|
|
41
|
+
INDETERMINATE_SCALE,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
let progress = $derived(clamp(value / max))
|
|
45
|
+
let determinatePath = $derived(
|
|
46
|
+
outlinePath(DETERMINATE_SHAPES[0], DETERMINATE_SHAPES[1], progress, DETERMINATE_SCALE),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
let indicator = $state<SVGPathElement>()
|
|
50
|
+
|
|
51
|
+
$effect(() => {
|
|
52
|
+
if (!indicator) return
|
|
53
|
+
const path = indicator
|
|
54
|
+
const start = performance.now()
|
|
55
|
+
let raf = 0
|
|
56
|
+
const frame = (now: number) => {
|
|
57
|
+
raf = requestAnimationFrame(frame)
|
|
58
|
+
const elapsed = Math.max(0, now - start)
|
|
59
|
+
const index = Math.floor(elapsed / MORPH_DURATION)
|
|
60
|
+
const morph = springValue((elapsed % MORPH_DURATION) / 1000)
|
|
61
|
+
path.setAttribute(
|
|
62
|
+
'd',
|
|
63
|
+
outlinePath(
|
|
64
|
+
INDETERMINATE_SHAPES[index % INDETERMINATE_SHAPES.length],
|
|
65
|
+
INDETERMINATE_SHAPES[(index + 1) % INDETERMINATE_SHAPES.length],
|
|
66
|
+
clamp(morph),
|
|
67
|
+
INDETERMINATE_SCALE,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
path.setAttribute(
|
|
71
|
+
'transform',
|
|
72
|
+
rotate((elapsed / ROTATION_DURATION) * 360 + (index + morph) * QUARTER_TURN),
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
raf = requestAnimationFrame(frame)
|
|
76
|
+
return () => cancelAnimationFrame(raf)
|
|
77
|
+
})
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<div
|
|
81
|
+
{...attributes}
|
|
82
|
+
class={['np-loading-indicator', contained && 'contained']}
|
|
83
|
+
role="progressbar"
|
|
84
|
+
aria-valuemin="0"
|
|
85
|
+
aria-valuemax={max}
|
|
86
|
+
aria-valuenow={indeterminate ? undefined : value}
|
|
87
|
+
>
|
|
88
|
+
<svg viewBox="0 0 {VIEWBOX} {VIEWBOX}" aria-hidden="true">
|
|
89
|
+
{#if indeterminate}
|
|
90
|
+
<path bind:this={indicator} class="indicator" d={initialPath}></path>
|
|
91
|
+
{:else}
|
|
92
|
+
<path class="indicator" d={determinatePath} transform={rotate(-progress * 180)}></path>
|
|
93
|
+
{/if}
|
|
94
|
+
</svg>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<style>
|
|
98
|
+
.np-loading-indicator {
|
|
99
|
+
all: unset;
|
|
100
|
+
--_color: var(--np-color-primary);
|
|
101
|
+
display: inline-flex;
|
|
102
|
+
vertical-align: middle;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
inline-size: var(--np-loading-indicator-size, 3rem);
|
|
106
|
+
block-size: var(--np-loading-indicator-size, 3rem);
|
|
107
|
+
border-radius: var(--np-shape-corner-full);
|
|
108
|
+
contain: strict;
|
|
109
|
+
content-visibility: auto;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.contained {
|
|
113
|
+
--_color: var(--np-color-on-primary-container);
|
|
114
|
+
background-color: var(
|
|
115
|
+
--np-loading-indicator-container-color,
|
|
116
|
+
var(--np-color-primary-container)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
svg {
|
|
121
|
+
display: block;
|
|
122
|
+
inline-size: 100%;
|
|
123
|
+
block-size: 100%;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.indicator {
|
|
127
|
+
fill: var(--np-loading-indicator-color, var(--_color));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@media (forced-colors: active) {
|
|
131
|
+
.indicator {
|
|
132
|
+
fill: CanvasText;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LoadingIndicator } from './LoadingIndicator.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LoadingIndicator } from './LoadingIndicator.svelte';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const INDETERMINATE_SHAPES: number[][];
|
|
2
|
+
export declare const DETERMINATE_SHAPES: number[][];
|
|
3
|
+
export declare const INDETERMINATE_SCALE = 0.6853;
|
|
4
|
+
export declare const DETERMINATE_SCALE = 0.7917;
|
|
5
|
+
export declare const VIEWBOX = 48;
|
|
6
|
+
export declare const outlinePath: (from: number[], to: number[], progress: number, scale: number) => string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const SAMPLE_COUNT = 128;
|
|
2
|
+
const decode = (encoded) => {
|
|
3
|
+
const radii = [];
|
|
4
|
+
for (let i = 0; i < SAMPLE_COUNT; i++) {
|
|
5
|
+
radii.push(parseInt(encoded.slice(i * 3, i * 3 + 3), 36) / 10000);
|
|
6
|
+
}
|
|
7
|
+
return radii;
|
|
8
|
+
};
|
|
9
|
+
export const INDETERMINATE_SHAPES = [
|
|
10
|
+
// MaterialShapes.SoftBurst
|
|
11
|
+
'2zl30r3533bb3ik3pq3se3s33oh3gs39u33v30a2zn31b3693co3k53qk3sk3ro3n53fb38k32t2zz2zr32037h3e23ls3r83sm3r43lj3dv37c31y2zr30032w38p3fi3ne3rq3sj3qf3jw3ch3653192zm30b33y39z3h03oo3s53sd3pk3ib3b534z30q2zl30r3533bb3ik3pq3se3s33oh3gs39u33v30a2zn31b3693co3k53qk3sk3ro3n53fb38k32t2zz2zr32037h3e23ls3r83sm3r43lj3dv37c31y2zr30032w38p3fi3ne3rq3sj3qf3jw3ch3653192zm30b33y39z3h03oo3s53sd3pk3ib3b534z30q',
|
|
12
|
+
// MaterialShapes.Cookie9Sided
|
|
13
|
+
'3ne3kp3hr3g53fr3gl3iq3m33ot3qq3rw3sa3rz3qx3p33mg3jc3hi3gz3hn3jn3my3pp3rn3st3t73sv3rs3pw3n73jz3i23hg3i23jz3n73pw3rs3sv3t73st3rn3pp3my3jn3hn3gz3hi3jc3mg3p33qx3rz3sa3rw3qq3ot3m33iq3gl3fr3g53hr3kp3ne3pb3qg3qw3qm3pm3nv3lc3i13fk3eg3ei3fr3ie3lc3nh3ov3pk3pk3ou3ne3l83i93fb3dr3df3e83gb3jh3lz3nr3os3p53os3nr3lz3jh3gb3e83df3dr3fb3i93l83ne3ou3pk3pk3ov3nh3lc3ie3fr3ei3eg3fk3i13lc3nv3pm3qm3qw3qg3pb',
|
|
14
|
+
// MaterialShapes.Pentagon
|
|
15
|
+
'3ng3lh3jw3im3ho3h23gq3gq3h03hl3ih3jp3l93n63ph3s73vc3y23zz41241e40z3zr3xr3uz3se3q73od3mx3lt3l13kk3kf3kk3l13lt3mx3od3q73se3uz3xr3zr40z41e4123zz3y23vc3s73ph3n63l93jp3ih3hl3h03gq3gq3h23ho3im3jw3lh3ng3pp3rf3se3sp3sb3r93pg3mx3jm3gc3dh3b138z37935u34r33x33e33533533f33y34s35w37b3913b43df3f93gk3hb3hl3hb3gk3f93df3b439137b35w34s33y33f33533533e33x34r35u37938z3b13dh3gc3jm3mx3pg3r93sb3sp3se3rf3pp',
|
|
16
|
+
// MaterialShapes.Pill
|
|
17
|
+
'3t53rz3qr3pj3o93mz3lo3kd3j13hp3gd3f13dq3cp3by3bj3be3bj3by3cp3dq3f13gd3hp3j13kd3lo3mz3o93pj3qr3rz3t53ua3vd3wc3wu3xm3yg3z83zx40k41341j41x42842f42k42l42k42f42841x41j41340k3zx3z83yg3xm3wu3wc3vd3ua3t53rz3qr3pj3o93mz3lo3kd3j13hp3gd3f13dq3cp3by3bj3be3bj3by3cp3dq3f13gd3hp3j13kd3lo3mz3o93pj3qr3rz3t53ua3vd3wc3wu3xm3yg3z83zx40k41341j41x42842f42k42l42k42f42841x41j41340k3zx3z83yg3xm3wu3wc3vd3ua',
|
|
18
|
+
// MaterialShapes.Sunny
|
|
19
|
+
'3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa3t63sa3pe3l73hi3ea3bj3973893973bj3ea3hi3l73pe3sa',
|
|
20
|
+
// MaterialShapes.Cookie4Sided
|
|
21
|
+
'2yr2z530a32b35g39l3ea3iv3mz3qk3tm3w53y73zr40v41j41r41i40u3zr3y63w43tk3qi3mw3it3e739j35f32a3092z42yr2z530a32b35g39l3ea3iv3mz3qk3tm3w53y73zr40v41j41r41i40u3zr3y63w43tk3qi3mw3it3e739j35f32a3092z42yr2z530a32b35g39l3ea3iv3mz3qk3tm3w53y73zr40v41j41r41i40u3zr3y63w43tk3qi3mw3it3e739j35f32a3092z42yr2z530a32b35g39l3ea3iv3mz3qk3tm3w53y73zr40v41j41r41i40u3zr3y63w43tk3qi3mw3it3e739j35f32a3092z4',
|
|
22
|
+
// MaterialShapes.Oval
|
|
23
|
+
'3ec3bw39m37i35k33s32630q2zg2yb2xc2wj2vv2vd2uz2ur2up2ur2uz2vd2vv2wj2xc2yb2zg30q32633s35k37i39m3bw3ec3gx3jn3mi3ph3sj3vn3yr41t44r47j4a14c84e14fd4g64gg4g64fd4e14c84a147j44r41t3yr3vn3sj3ph3mi3jn3gx3ec3bw39m37i35k33s32630q2zg2yb2xc2wj2vv2vd2uz2ur2up2ur2uz2vd2vv2wj2xc2yb2zg30q32633s35k37i39m3bw3ec3gx3jn3mi3ph3sj3vn3yr41t44r47j4a14c84e14fd4g64gg4g64fd4e14c84a147j44r41t3yr3vn3sj3ph3mi3jn3gx',
|
|
24
|
+
].map(decode);
|
|
25
|
+
export const DETERMINATE_SHAPES = [
|
|
26
|
+
// MaterialShapes.Circle
|
|
27
|
+
'3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so3so',
|
|
28
|
+
// MaterialShapes.SoftBurst
|
|
29
|
+
'2zl30r3533bb3ik3pq3se3s33oh3gs39u33v30a2zn31b3693co3k53qk3sk3ro3n53fb38k32t2zz2zr32037h3e23ls3r83sm3r43lj3dv37c31y2zr30032w38p3fi3ne3rq3sj3qf3jw3ch3653192zm30b33y39z3h03oo3s53sd3pk3ib3b534z30q2zl30r3533bb3ik3pq3se3s33oh3gs39u33v30a2zn31b3693co3k53qk3sk3ro3n53fb38k32t2zz2zr32037h3e23ls3r83sm3r43lj3dv37c31y2zr30032w38p3fi3ne3rq3sj3qf3jw3ch3653192zm30b33y39z3h03oo3s53sd3pk3ib3b534z30q',
|
|
30
|
+
].map(decode);
|
|
31
|
+
export const INDETERMINATE_SCALE = 0.6853;
|
|
32
|
+
export const DETERMINATE_SCALE = 0.7917;
|
|
33
|
+
export const VIEWBOX = 48;
|
|
34
|
+
const CENTER = VIEWBOX / 2;
|
|
35
|
+
const COS = Array.from({ length: SAMPLE_COUNT }, (_, i) => Math.cos((2 * Math.PI * i) / SAMPLE_COUNT));
|
|
36
|
+
const SIN = Array.from({ length: SAMPLE_COUNT }, (_, i) => Math.sin((2 * Math.PI * i) / SAMPLE_COUNT));
|
|
37
|
+
export const outlinePath = (from, to, progress, scale) => {
|
|
38
|
+
const size = VIEWBOX * scale;
|
|
39
|
+
let d = '';
|
|
40
|
+
for (let i = 0; i < SAMPLE_COUNT; i++) {
|
|
41
|
+
const radius = (from[i] + (to[i] - from[i]) * progress) * size;
|
|
42
|
+
d +=
|
|
43
|
+
(i === 0 ? 'M' : 'L') +
|
|
44
|
+
(CENTER + radius * COS[i]).toFixed(2) +
|
|
45
|
+
',' +
|
|
46
|
+
(CENTER + radius * SIN[i]).toFixed(2);
|
|
47
|
+
}
|
|
48
|
+
return d + 'Z';
|
|
49
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
transition-property: transform, opacity;
|
|
87
87
|
transition-timing-function: linear;
|
|
88
88
|
background-color: var(--np-color-secondary-container);
|
|
89
|
-
border-radius: var(--np-shape-corner-full);
|
|
89
|
+
border-radius: var(--np-navigation-drawer-item-container-shape, var(--np-shape-corner-full));
|
|
90
90
|
z-index: -1;
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -22,9 +22,8 @@
|
|
|
22
22
|
let wave = $derived(wavy && !reducedMotion.current)
|
|
23
23
|
|
|
24
24
|
let wavelength = $derived(indeterminate ? INDETERMINATE_WAVELENGTH : DETERMINATE_WAVELENGTH)
|
|
25
|
-
|
|
26
|
-
!wave ? 0 : indeterminate || (
|
|
27
|
-
)
|
|
25
|
+
const amplitudeFor = (fraction: number) =>
|
|
26
|
+
!wave ? 0 : indeterminate || (fraction > 0.1 && fraction < 0.95) ? 1 : 0
|
|
28
27
|
|
|
29
28
|
const cubicBezier = (x1: number, y1: number, x2: number, y2: number) => {
|
|
30
29
|
const A = (a: number, b: number) => 1 - 3 * b + 3 * a
|
|
@@ -112,6 +111,10 @@
|
|
|
112
111
|
const len = wavelength
|
|
113
112
|
const width = containerWidth
|
|
114
113
|
const host = wavePathA?.closest('.progress') as HTMLElement | null
|
|
114
|
+
// Live declaration: --np-lp-percent is a registered custom property that carries
|
|
115
|
+
// the same transition as the track, so reading it each frame gives the wave the
|
|
116
|
+
// eased position instead of jumping straight to the new value.
|
|
117
|
+
const hostStyles = host && getComputedStyle(host)
|
|
115
118
|
let raf = 0
|
|
116
119
|
let last = 0
|
|
117
120
|
const frame = (now: number) => {
|
|
@@ -119,7 +122,12 @@
|
|
|
119
122
|
const dt = last ? Math.min(64, now - last) : 16
|
|
120
123
|
last = now
|
|
121
124
|
const phase = ((now / 1000) % 1) * len
|
|
122
|
-
|
|
125
|
+
const eased =
|
|
126
|
+
hostStyles && !indeterminate
|
|
127
|
+
? parseFloat(hostStyles.getPropertyValue('--np-lp-percent'))
|
|
128
|
+
: percent
|
|
129
|
+
const shown = Number.isFinite(eased) ? eased : percent
|
|
130
|
+
renderedAmplitude += (amplitudeFor(shown / 100) - renderedAmplitude) * Math.min(1, dt / 500)
|
|
123
131
|
const wave = buildWave(width, len, renderedAmplitude, phase)
|
|
124
132
|
if (indeterminate) {
|
|
125
133
|
const t = now % CYCLE
|
|
@@ -131,7 +139,7 @@
|
|
|
131
139
|
trim(wavePathA, wave, t1 * width, h1 * width)
|
|
132
140
|
trim(wavePathB, wave, t2 * width, h2 * width)
|
|
133
141
|
} else {
|
|
134
|
-
trim(wavePathA, wave, 0, (
|
|
142
|
+
trim(wavePathA, wave, 0, (shown / 100) * width)
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
raf = requestAnimationFrame(frame)
|
|
@@ -148,7 +156,7 @@
|
|
|
148
156
|
let gapOn = $derived(percent > 0 ? 1 : 0)
|
|
149
157
|
|
|
150
158
|
let trackStyles = $derived(
|
|
151
|
-
`--_percent:${percent}%;--_buffer-percent:${bufferPercent}%;--_gap-on:${gapOn}`,
|
|
159
|
+
`--_percent:${percent}%;--np-lp-percent:${percent}%;--_buffer-percent:${bufferPercent}%;--_gap-on:${gapOn}`,
|
|
152
160
|
)
|
|
153
161
|
|
|
154
162
|
let hideDots = $derived(indeterminate || !hasBuffer || bufferValue >= max || value >= max)
|
|
@@ -328,6 +336,18 @@
|
|
|
328
336
|
inherits: true;
|
|
329
337
|
initial-value: 0%;
|
|
330
338
|
}
|
|
339
|
+
@property --np-lp-percent {
|
|
340
|
+
syntax: '<percentage>';
|
|
341
|
+
inherits: true;
|
|
342
|
+
initial-value: 0%;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* The wave is trimmed in script, so it cannot transition the way the track does.
|
|
346
|
+
Transitioning the value it is trimmed from on the same token keeps the two in
|
|
347
|
+
lockstep: same start, same duration, same curve. */
|
|
348
|
+
.progress.wavy:not(.indeterminate) {
|
|
349
|
+
transition: --np-lp-percent var(--np-motion-expressive-default-effects);
|
|
350
|
+
}
|
|
331
351
|
|
|
332
352
|
.progress.indeterminate {
|
|
333
353
|
animation:
|
|
@@ -99,8 +99,32 @@
|
|
|
99
99
|
const isActive = (t: number) => t >= activeFrom && t <= activeTo
|
|
100
100
|
|
|
101
101
|
let dragging = $state<'start' | 'end' | null>(null)
|
|
102
|
+
let tracking = $state(false)
|
|
102
103
|
let trackElement = $state<HTMLDivElement>()
|
|
104
|
+
let activeLaneElement = $state<HTMLDivElement>()
|
|
105
|
+
let iconElement = $state<HTMLSpanElement>()
|
|
106
|
+
let iconFits = $state(true)
|
|
103
107
|
let pointerFocused = $state(false)
|
|
108
|
+
let pointerOrigin: { x: number; y: number } | null = null
|
|
109
|
+
|
|
110
|
+
const DRAG_THRESHOLD = 3
|
|
111
|
+
|
|
112
|
+
$effect(() => {
|
|
113
|
+
if (!icon || !activeLaneElement) return
|
|
114
|
+
const lane = activeLaneElement
|
|
115
|
+
const inlineOf = (r: DOMRect) => (orientation === 'vertical' ? r.height : r.width)
|
|
116
|
+
const measure = () => {
|
|
117
|
+
if (!iconElement) return
|
|
118
|
+
const padding = parseFloat(getComputedStyle(iconElement).insetInlineStart) || 0
|
|
119
|
+
iconFits =
|
|
120
|
+
inlineOf(lane.getBoundingClientRect()) >=
|
|
121
|
+
inlineOf(iconElement.getBoundingClientRect()) + 2 * padding
|
|
122
|
+
}
|
|
123
|
+
const observer = new ResizeObserver(measure)
|
|
124
|
+
observer.observe(lane)
|
|
125
|
+
measure()
|
|
126
|
+
return () => observer.disconnect()
|
|
127
|
+
})
|
|
104
128
|
|
|
105
129
|
const fractionFromEvent = (e: PointerEvent) => {
|
|
106
130
|
if (!trackElement) return 0
|
|
@@ -131,9 +155,23 @@
|
|
|
131
155
|
}
|
|
132
156
|
}
|
|
133
157
|
|
|
158
|
+
const onpointermove = (e: PointerEvent) => {
|
|
159
|
+
if (!dragging) return
|
|
160
|
+
if (!tracking) {
|
|
161
|
+
const d = pointerOrigin
|
|
162
|
+
? Math.hypot(e.clientX - pointerOrigin.x, e.clientY - pointerOrigin.y)
|
|
163
|
+
: Infinity
|
|
164
|
+
if (d <= DRAG_THRESHOLD) return
|
|
165
|
+
tracking = true
|
|
166
|
+
}
|
|
167
|
+
moveTo(e)
|
|
168
|
+
}
|
|
169
|
+
|
|
134
170
|
const onpointerdown = (e: PointerEvent) => {
|
|
135
171
|
if (disabled || e.button !== 0) return
|
|
136
172
|
const v = min + clamp(fractionFromEvent(e), 0, 1) * span
|
|
173
|
+
pointerOrigin = { x: e.clientX, y: e.clientY }
|
|
174
|
+
tracking = false
|
|
137
175
|
dragging = !range
|
|
138
176
|
? 'start'
|
|
139
177
|
: lo === hi
|
|
@@ -154,6 +192,8 @@
|
|
|
154
192
|
if (!dragging) return
|
|
155
193
|
const input = dragging === 'start' ? inputElement : endInputElement
|
|
156
194
|
dragging = null
|
|
195
|
+
tracking = false
|
|
196
|
+
pointerOrigin = null
|
|
157
197
|
element?.releasePointerCapture(e.pointerId)
|
|
158
198
|
input?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
159
199
|
}
|
|
@@ -173,12 +213,13 @@
|
|
|
173
213
|
labeled && 'np-labeled',
|
|
174
214
|
disabled && 'np-disabled',
|
|
175
215
|
dragging && 'np-dragging',
|
|
216
|
+
tracking && 'np-tracking',
|
|
176
217
|
pointerFocused && 'np-pointer-focused',
|
|
177
218
|
attributes.class,
|
|
178
219
|
]}
|
|
179
220
|
role="presentation"
|
|
180
221
|
{onpointerdown}
|
|
181
|
-
onpointermove
|
|
222
|
+
{onpointermove}
|
|
182
223
|
onpointerup={endDrag}
|
|
183
224
|
onpointercancel={endDrag}
|
|
184
225
|
onkeydown={() => (pointerFocused = false)}
|
|
@@ -190,12 +231,15 @@
|
|
|
190
231
|
<span class="np-slider-stop np-slider-stop-start"></span>
|
|
191
232
|
</div>
|
|
192
233
|
{/if}
|
|
193
|
-
<div class="np-slider-lane np-slider-active">
|
|
194
|
-
{#if icon}
|
|
195
|
-
<span class="np-slider-icon">{@render icon()}</span>
|
|
234
|
+
<div class="np-slider-lane np-slider-active" bind:this={activeLaneElement}>
|
|
235
|
+
{#if icon && iconFits}
|
|
236
|
+
<span class="np-slider-icon" bind:this={iconElement}>{@render icon()}</span>
|
|
196
237
|
{/if}
|
|
197
238
|
</div>
|
|
198
239
|
<div class="np-slider-lane np-slider-inactive-end">
|
|
240
|
+
{#if icon && !iconFits}
|
|
241
|
+
<span class="np-slider-icon np-on-inactive" bind:this={iconElement}>{@render icon()}</span>
|
|
242
|
+
{/if}
|
|
199
243
|
<span class="np-slider-stop np-slider-stop-end"></span>
|
|
200
244
|
</div>
|
|
201
245
|
|
|
@@ -266,6 +310,8 @@
|
|
|
266
310
|
--_inside: var(--np-slider-track-inside-shape, 0.125rem);
|
|
267
311
|
--_stop: var(--np-slider-stop-indicator-size, 0.25rem);
|
|
268
312
|
--_gap: calc(var(--_hw) / 2 + 0.375rem);
|
|
313
|
+
--_ring: 3px;
|
|
314
|
+
--_ring-gap: 0.4rem;
|
|
269
315
|
|
|
270
316
|
position: relative;
|
|
271
317
|
display: block;
|
|
@@ -320,8 +366,7 @@
|
|
|
320
366
|
inset-inline-end var(--np-motion-expressive-fast-effects);
|
|
321
367
|
}
|
|
322
368
|
|
|
323
|
-
.np-
|
|
324
|
-
.np-dragging .np-slider-handle {
|
|
369
|
+
.np-tracking .np-slider-lane {
|
|
325
370
|
transition: none;
|
|
326
371
|
}
|
|
327
372
|
|
|
@@ -372,6 +417,10 @@
|
|
|
372
417
|
writing-mode: horizontal-tb;
|
|
373
418
|
}
|
|
374
419
|
|
|
420
|
+
.np-slider-icon.np-on-inactive {
|
|
421
|
+
color: var(--np-slider-icon-inactive-color, var(--np-color-on-secondary-container));
|
|
422
|
+
}
|
|
423
|
+
|
|
375
424
|
.np-slider-icon :global(svg) {
|
|
376
425
|
inline-size: var(--_icon-size);
|
|
377
426
|
block-size: var(--_icon-size);
|
|
@@ -387,6 +436,7 @@
|
|
|
387
436
|
border-radius: var(--np-shape-corner-full);
|
|
388
437
|
background: var(--np-slider-inactive-stop-color, var(--np-color-on-secondary-container));
|
|
389
438
|
pointer-events: none;
|
|
439
|
+
transition: background-color var(--np-motion-expressive-fast-effects);
|
|
390
440
|
}
|
|
391
441
|
|
|
392
442
|
.np-slider-stop-end {
|
|
@@ -408,15 +458,21 @@
|
|
|
408
458
|
}
|
|
409
459
|
|
|
410
460
|
.np-slider-handle {
|
|
461
|
+
/* The current handle box drives both size and offset, so a handle that
|
|
462
|
+
narrows or shortens stays centred on the value it points at. */
|
|
463
|
+
--_cw: var(--_hw);
|
|
464
|
+
--_ch: var(--_handle-height);
|
|
465
|
+
|
|
411
466
|
position: absolute;
|
|
412
|
-
inset-inline-start: calc(var(--_p) - var(--
|
|
413
|
-
inset-block-start: calc((var(--_track-height) - var(--
|
|
414
|
-
inline-size: var(--
|
|
415
|
-
block-size: var(--
|
|
467
|
+
inset-inline-start: calc(var(--_p) - var(--_cw) / 2);
|
|
468
|
+
inset-block-start: calc((var(--_track-height) - var(--_ch)) / 2);
|
|
469
|
+
inline-size: var(--_cw);
|
|
470
|
+
block-size: var(--_ch);
|
|
416
471
|
border-radius: var(--np-slider-handle-shape, var(--np-shape-corner-full));
|
|
417
472
|
background: var(--np-slider-handle-color, var(--np-color-primary));
|
|
418
473
|
transition:
|
|
419
474
|
inset-inline-start var(--np-motion-expressive-fast-effects),
|
|
475
|
+
inset-block-start var(--np-motion-expressive-fast-effects),
|
|
420
476
|
inline-size var(--np-motion-expressive-fast-effects),
|
|
421
477
|
block-size var(--np-motion-expressive-fast-effects);
|
|
422
478
|
}
|
|
@@ -429,18 +485,25 @@
|
|
|
429
485
|
--_p: var(--_p2);
|
|
430
486
|
}
|
|
431
487
|
|
|
488
|
+
/* Focus narrows the handle the same way a press does and keeps its full height;
|
|
489
|
+
the ring stands clear of it instead of hugging it. */
|
|
432
490
|
.np-slider:not(.np-disabled, .np-pointer-focused):has(.np-slider-input-start:focus-visible)
|
|
433
491
|
.np-slider-handle-start,
|
|
434
492
|
.np-slider:not(.np-disabled, .np-pointer-focused):has(.np-slider-input-end:focus-visible)
|
|
435
493
|
.np-slider-handle-end {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
outline:
|
|
439
|
-
outline-offset: 0.125rem;
|
|
494
|
+
--_cw: var(--np-slider-handle-width-focus, 0.125rem);
|
|
495
|
+
outline: var(--_ring) solid var(--np-color-secondary);
|
|
496
|
+
outline-offset: var(--_ring-gap);
|
|
440
497
|
}
|
|
441
498
|
|
|
442
499
|
.np-dragging .np-slider-handle {
|
|
443
|
-
|
|
500
|
+
--_cw: var(--np-slider-handle-width-focus, 0.125rem);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.np-tracking .np-slider-handle {
|
|
504
|
+
transition:
|
|
505
|
+
inline-size var(--np-motion-expressive-fast-effects),
|
|
506
|
+
block-size var(--np-motion-expressive-fast-effects);
|
|
444
507
|
}
|
|
445
508
|
|
|
446
509
|
.np-slider-label-anchor {
|
|
@@ -530,10 +593,15 @@
|
|
|
530
593
|
color: var(--np-color-surface);
|
|
531
594
|
}
|
|
532
595
|
|
|
596
|
+
.np-slider.np-disabled .np-slider-icon.np-on-inactive {
|
|
597
|
+
color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
|
|
598
|
+
}
|
|
599
|
+
|
|
533
600
|
@media (prefers-reduced-motion: reduce) {
|
|
534
|
-
.np-slider-lane,
|
|
535
|
-
.np-slider-handle,
|
|
536
|
-
.np-slider-
|
|
601
|
+
.np-slider .np-slider-lane,
|
|
602
|
+
.np-slider .np-slider-handle,
|
|
603
|
+
.np-slider .np-slider-tick,
|
|
604
|
+
.np-slider .np-slider-label {
|
|
537
605
|
transition: none;
|
|
538
606
|
}
|
|
539
607
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './chip/types.ts';
|
|
|
6
6
|
export * from './dialog/types.ts';
|
|
7
7
|
export * from './divider/types.ts';
|
|
8
8
|
export * from './list/types.ts';
|
|
9
|
+
export * from './loading-indicator/types.ts';
|
|
9
10
|
export * from './menu/types.ts';
|
|
10
11
|
export * from './navigation-drawer/types.ts';
|
|
11
12
|
export * from './navigation-rail/types.ts';
|
package/dist/types.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from './chip/types.ts';
|
|
|
6
6
|
export * from './dialog/types.ts';
|
|
7
7
|
export * from './divider/types.ts';
|
|
8
8
|
export * from './list/types.ts';
|
|
9
|
+
export * from './loading-indicator/types.ts';
|
|
9
10
|
export * from './menu/types.ts';
|
|
10
11
|
export * from './navigation-drawer/types.ts';
|
|
11
12
|
export * from './navigation-rail/types.ts';
|