@yewdiel21/motion-kit 0.17.0 → 0.19.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 +240 -199
- package/dist/auto/index.d.ts +19 -3
- package/dist/auto/index.js +35 -3
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-QFBOHPWZ.js → chunk-CQP66AZK.js} +2 -2
- package/dist/{chunk-QP4QJOEN.js → chunk-H4STLMCM.js} +2 -2
- package/dist/{chunk-BZLMCHUD.js → chunk-KDIJX6G4.js} +3 -3
- package/dist/{chunk-DOOMULVV.js → chunk-OLT3CWDJ.js} +4 -2
- package/dist/chunk-OLT3CWDJ.js.map +1 -0
- package/dist/{chunk-VJFPTLAK.js → chunk-PEDYJ2TV.js} +8 -22
- package/dist/chunk-PEDYJ2TV.js.map +1 -0
- package/dist/{chunk-7IQTMF7Y.js → chunk-Y4GIBTRG.js} +5130 -2153
- package/dist/chunk-Y4GIBTRG.js.map +1 -0
- package/dist/css/components/badge-button.css +16 -0
- package/dist/css/components/bento-grid.css +13 -0
- package/dist/css/components/code-block.css +267 -0
- package/dist/css/components/command-search.css +274 -2
- package/dist/css/components/dialog.css +264 -0
- package/dist/css/components/drilldown-menu.css +11 -7
- package/dist/css/components/equalizer-slider.css +26 -0
- package/dist/css/components/handwriting-text.css +15 -0
- package/dist/css/components/heatmap-chart.css +267 -1
- package/dist/css/components/magnetize-button.css +18 -0
- package/dist/css/components/morphing-page-dots.css +13 -0
- package/dist/css/components/not-found.css +22 -0
- package/dist/css/components/notification-stack.css +42 -0
- package/dist/css/components/particle-button.css +14 -0
- package/dist/css/components/radio-group.css +29 -0
- package/dist/css/components/rating-scale.css +23 -0
- package/dist/css/components/scroll-area.css +261 -0
- package/dist/css/components/scroll-preview-dock.css +18 -0
- package/dist/css/components/select.css +300 -4
- package/dist/css/components/signature-pad.css +130 -103
- package/dist/css/components/stepper.css +36 -0
- package/dist/css/components/sticky-header.css +10 -0
- package/dist/css/components/temperature-slider.css +29 -0
- package/dist/css/components/voice-message-bubble.css +62 -45
- package/dist/css/components/waveform-player.css +49 -34
- package/dist/css/components/wheel-picker.css +16 -15
- package/dist/css/tokens.css +4 -0
- package/dist/elements/index.d.ts +217 -4
- package/dist/elements/index.js +614 -9
- package/dist/elements/index.js.map +1 -1
- package/dist/{engine-Bn6hC87X.d.ts → engine-D6XV-WNt.d.ts} +390 -1
- package/dist/gl/index.js +2 -2
- package/dist/index.css +886 -177
- package/dist/index.d.ts +23 -23
- package/dist/index.js +37 -5
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +126 -7
- package/dist/react/index.js +722 -7
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-YS6TZjpa.d.ts → reduced-4croIXl-.d.ts} +1 -1
- package/dist/{store-CZ6vdtIE.d.ts → store-CAHWkurn.d.ts} +1 -1
- package/package.json +110 -110
- package/registry.json +1357 -1160
- package/src/auto/auto-init.ts +341 -308
- package/src/components/badge-button/README.md +28 -0
- package/src/components/badge-button/badge-button.css +15 -0
- package/src/components/badge-button/element.ts +16 -0
- package/src/components/badge-button/engine.ts +85 -0
- package/src/components/badge-button/react.tsx +31 -0
- package/src/components/bento-grid/README.md +26 -0
- package/src/components/bento-grid/bento-grid.css +12 -0
- package/src/components/bento-grid/element.ts +22 -0
- package/src/components/bento-grid/engine.ts +153 -0
- package/src/components/bento-grid/react.tsx +25 -0
- package/src/components/code-block/README.md +1 -1
- package/src/components/code-block/code-block.css +5 -0
- package/src/components/code-block/engine.ts +5 -0
- package/src/components/command-search/README.md +1 -1
- package/src/components/command-search/command-search.css +12 -2
- package/src/components/command-search/engine.ts +6 -0
- package/src/components/dialog/README.md +104 -103
- package/src/components/dialog/dialog.css +2 -0
- package/src/components/dialog/engine.ts +465 -449
- package/src/components/drilldown-menu/drilldown-menu.css +11 -7
- package/src/components/drilldown-menu/engine.ts +44 -4
- package/src/components/equalizer-slider/README.md +43 -0
- package/src/components/equalizer-slider/element.ts +41 -0
- package/src/components/equalizer-slider/engine.ts +32 -0
- package/src/components/equalizer-slider/equalizer-slider.css +26 -0
- package/src/components/equalizer-slider/react.tsx +60 -0
- package/src/components/equalizer-slider/shared.ts +161 -0
- package/src/components/handwriting-text/README.md +29 -0
- package/src/components/handwriting-text/element.ts +14 -0
- package/src/components/handwriting-text/engine.ts +108 -0
- package/src/components/handwriting-text/glyphs.ts +71 -0
- package/src/components/handwriting-text/handwriting-text.css +14 -0
- package/src/components/handwriting-text/react.tsx +23 -0
- package/src/components/heatmap-chart/README.md +48 -48
- package/src/components/heatmap-chart/engine.ts +6 -1
- package/src/components/heatmap-chart/heatmap-chart.css +5 -1
- package/src/components/icons.ts +1 -0
- package/src/components/magnetize-button/README.md +26 -0
- package/src/components/magnetize-button/element.ts +13 -0
- package/src/components/magnetize-button/engine.ts +84 -0
- package/src/components/magnetize-button/magnetize-button.css +17 -0
- package/src/components/magnetize-button/react.tsx +22 -0
- package/src/components/morphing-page-dots/README.md +28 -0
- package/src/components/morphing-page-dots/element.ts +16 -0
- package/src/components/morphing-page-dots/engine.ts +89 -0
- package/src/components/morphing-page-dots/morphing-page-dots.css +12 -0
- package/src/components/morphing-page-dots/react.tsx +32 -0
- package/src/components/not-found/README.md +26 -0
- package/src/components/not-found/element.ts +15 -0
- package/src/components/not-found/engine.ts +57 -0
- package/src/components/not-found/not-found.css +21 -0
- package/src/components/not-found/react.tsx +23 -0
- package/src/components/notification-stack/README.md +24 -0
- package/src/components/notification-stack/element.ts +26 -0
- package/src/components/notification-stack/engine.ts +329 -0
- package/src/components/notification-stack/notification-stack.css +41 -0
- package/src/components/notification-stack/react.tsx +28 -0
- package/src/components/particle-button/README.md +26 -0
- package/src/components/particle-button/element.ts +13 -0
- package/src/components/particle-button/engine.ts +52 -0
- package/src/components/particle-button/particle-button.css +13 -0
- package/src/components/particle-button/react.tsx +22 -0
- package/src/components/radio-group/README.md +43 -0
- package/src/components/radio-group/element.ts +41 -0
- package/src/components/radio-group/engine.ts +11 -0
- package/src/components/radio-group/radio-group.css +28 -0
- package/src/components/radio-group/react.tsx +60 -0
- package/src/components/radio-group/shared.ts +118 -0
- package/src/components/rating-scale/README.md +43 -0
- package/src/components/rating-scale/element.ts +41 -0
- package/src/components/rating-scale/engine.ts +63 -0
- package/src/components/rating-scale/rating-scale.css +22 -0
- package/src/components/rating-scale/react.tsx +60 -0
- package/src/components/scroll-area/README.md +89 -0
- package/src/components/scroll-area/element.ts +35 -0
- package/src/components/scroll-area/engine.ts +528 -0
- package/src/components/scroll-area/react.tsx +67 -0
- package/src/components/scroll-area/scroll-area.css +260 -0
- package/src/components/scroll-preview-dock/README.md +24 -0
- package/src/components/scroll-preview-dock/element.ts +20 -0
- package/src/components/scroll-preview-dock/engine.ts +228 -0
- package/src/components/scroll-preview-dock/react.tsx +26 -0
- package/src/components/scroll-preview-dock/scroll-preview-dock.css +17 -0
- package/src/components/select/README.md +23 -9
- package/src/components/select/engine.ts +16 -4
- package/src/components/select/select.css +38 -4
- package/src/components/signature-pad/engine.ts +550 -437
- package/src/components/signature-pad/signature-pad.css +130 -103
- package/src/components/stepper/README.md +46 -0
- package/src/components/stepper/element.ts +42 -0
- package/src/components/stepper/engine.ts +103 -0
- package/src/components/stepper/react.tsx +60 -0
- package/src/components/stepper/stepper.css +35 -0
- package/src/components/sticky-header/README.md +30 -0
- package/src/components/sticky-header/element.ts +19 -0
- package/src/components/sticky-header/engine.ts +95 -0
- package/src/components/sticky-header/react.tsx +25 -0
- package/src/components/sticky-header/sticky-header.css +9 -0
- package/src/components/temperature-slider/README.md +42 -0
- package/src/components/temperature-slider/element.ts +53 -0
- package/src/components/temperature-slider/engine.ts +44 -0
- package/src/components/temperature-slider/react.tsx +66 -0
- package/src/components/temperature-slider/temperature-slider.css +29 -0
- package/src/components/voice-message-bubble/engine.ts +11 -0
- package/src/components/voice-message-bubble/voice-message-bubble.css +14 -12
- package/src/components/waveform-player/README.md +1 -1
- package/src/components/waveform-player/engine.ts +344 -261
- package/src/components/waveform-player/waveform-player.css +50 -35
- package/src/components/wheel-picker/README.md +3 -1
- package/src/components/wheel-picker/element.ts +12 -5
- package/src/components/wheel-picker/engine.ts +37 -8
- package/src/components/wheel-picker/wheel-picker.css +17 -16
- package/src/core/timing.ts +2 -0
- package/src/css/tokens.css +4 -0
- package/src/elements/index.ts +321 -273
- package/src/index.ts +312 -295
- package/src/motion/morph.ts +5 -2
- package/src/react/index.ts +17 -0
- package/dist/chunk-7IQTMF7Y.js.map +0 -1
- package/dist/chunk-DOOMULVV.js.map +0 -1
- package/dist/chunk-VJFPTLAK.js.map +0 -1
- /package/dist/{chunk-QFBOHPWZ.js.map → chunk-CQP66AZK.js.map} +0 -0
- /package/dist/{chunk-QP4QJOEN.js.map → chunk-H4STLMCM.js.map} +0 -0
- /package/dist/{chunk-BZLMCHUD.js.map → chunk-KDIJX6G4.js.map} +0 -0
|
@@ -1,35 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
--mk-wave-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
[data-mk-wave-
|
|
14
|
-
[data-mk-wave-
|
|
15
|
-
[data-mk-wave-
|
|
16
|
-
[data-mk-wave-
|
|
17
|
-
[data-mk-wave-
|
|
18
|
-
[data-mk-wave-
|
|
19
|
-
[data-mk-wave-
|
|
20
|
-
[data-mk-wave-
|
|
21
|
-
[data-mk-wave-
|
|
22
|
-
[data-mk-wave-
|
|
23
|
-
[data-mk-wave-
|
|
24
|
-
[data-mk-wave-
|
|
25
|
-
[data-mk-wave-
|
|
26
|
-
[data-mk-wave-
|
|
27
|
-
[data-mk-wave-
|
|
28
|
-
[data-mk-wave-
|
|
29
|
-
[data-mk-wave-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
1
|
+
/* Shares the equalizer's vocabulary: tonal track, pill bars, a cut-out head that grows when held. */
|
|
2
|
+
.mk-waveform-player, mk-waveform-player {
|
|
3
|
+
--mk-wave-color: var(--mk-text);
|
|
4
|
+
--mk-wave-track: var(--mk-outline-soft);
|
|
5
|
+
--mk-wave-surface: var(--mk-surface-2);
|
|
6
|
+
--mk-wave-inset: 14px;
|
|
7
|
+
display: block;
|
|
8
|
+
width: 100%;
|
|
9
|
+
min-width: 0;
|
|
10
|
+
color: var(--mk-text);
|
|
11
|
+
font-family: var(--mk-font);
|
|
12
|
+
}
|
|
13
|
+
[data-mk-wave-content] { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: .875rem; }
|
|
14
|
+
[data-mk-wave-toggle] { display: grid; place-items: center; align-self: start; width: 3.5rem; height: 3.5rem; margin-top: .25rem; border: 0; border-radius: 50%; background: var(--mk-text); color: var(--mk-bg); cursor: pointer; transition: border-radius .4s var(--mk-ease-playful); }
|
|
15
|
+
[data-mk-wave-toggle]:hover:enabled { border-radius: 22px; }
|
|
16
|
+
[data-mk-wave-icon] { display: grid; place-items: center; width: 24px; height: 24px; }
|
|
17
|
+
[data-mk-wave-state='playing'] [data-mk-wave-toggle] { border-radius: 20px; }
|
|
18
|
+
[data-mk-wave-toggle] svg { width: 1.4rem; height: 1.4rem; fill: currentColor; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
|
|
19
|
+
[data-mk-wave-body] { display: grid; gap: .5rem; min-width: 0; }
|
|
20
|
+
[data-mk-wave-track] { position: relative; height: 4rem; min-width: 0; border-radius: 24px; background: var(--mk-wave-surface); }
|
|
21
|
+
[data-mk-wave-bars] { position: absolute; inset: 12px var(--mk-wave-inset); display: flex; align-items: center; gap: 3px; pointer-events: none; }
|
|
22
|
+
[data-mk-wave-bar] { display: block; flex: 1 1 0; min-width: 1px; min-height: 4px; border-radius: 64px; background: var(--mk-wave-track); transform-origin: center; transition: background-color .16s; }
|
|
23
|
+
[data-mk-wave-bar][data-mk-played] { background: var(--mk-wave-color); }
|
|
24
|
+
[data-mk-wave-head] { position: absolute; z-index: 2; top: 50%; left: calc(var(--mk-wave-inset) + (100% - 2 * var(--mk-wave-inset)) * var(--mk-wave-progress, 0)); box-sizing: content-box; width: 4px; height: calc(100% - 20px); border: 3px solid var(--mk-wave-surface); border-radius: 64px; background: var(--mk-wave-color); translate: -50% -50%; pointer-events: none; transition: height .35s var(--mk-ease-swatch), border-width .35s var(--mk-ease-swatch); }
|
|
25
|
+
[data-mk-wave-scrubbing] [data-mk-wave-head] { height: calc(100% - 10px); border-width: 5px; }
|
|
26
|
+
@media (hover: hover) { [data-mk-wave-track]:has(input:enabled:hover) [data-mk-wave-head] { height: calc(100% - 14px); } }
|
|
27
|
+
[data-mk-wave-preview] { position: absolute; bottom: calc(100% + 10px); left: calc(var(--mk-wave-inset) + (100% - 2 * var(--mk-wave-inset)) * var(--mk-wave-at, 0)); z-index: 3; padding: .4rem .7rem; border-radius: 99px; background: var(--mk-glass-inverse); color: var(--mk-bg); backdrop-filter: blur(16px); box-shadow: inset 1px 1px 1px color-mix(in srgb, var(--mk-bg) 20%, transparent); font-size: .6875rem; font-variant-numeric: tabular-nums; translate: -50% 0; pointer-events: none; }
|
|
28
|
+
[data-mk-wave-preview][hidden] { display: none; }
|
|
29
|
+
[data-mk-wave-seek] { position: absolute; z-index: 4; inset: 0; box-sizing: border-box; width: 100%; height: 100%; margin: 0; appearance: none; -webkit-appearance: none; background: transparent; opacity: 0; cursor: ew-resize; }
|
|
30
|
+
[data-mk-wave-seek]::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: calc(2 * var(--mk-wave-inset)); height: 4rem; background: transparent; }
|
|
31
|
+
[data-mk-wave-seek]::-moz-range-thumb { width: calc(2 * var(--mk-wave-inset)); height: 4rem; border: 0; background: transparent; }
|
|
32
|
+
[data-mk-wave-track]:has(input:focus-visible) { outline: 2px solid var(--mk-accent); outline-offset: 4px; }
|
|
33
|
+
[data-mk-wave-footer] { display: flex; justify-content: space-between; align-items: center; gap: .75rem; min-height: 2rem; }
|
|
34
|
+
[data-mk-wave-time] { color: var(--mk-muted); font-size: .75rem; font-variant-numeric: tabular-nums; }
|
|
35
|
+
[data-mk-wave-rate] { min-width: 44px; min-height: 36px; padding: .375rem .75rem; border: 0; border-radius: 64px; color: var(--mk-text); background: var(--mk-wave-surface); font: 600 .75rem var(--mk-font); font-variant-numeric: tabular-nums; cursor: pointer; transition: padding .35s var(--mk-ease-swatch), background .15s; }
|
|
36
|
+
[data-mk-wave-rate]:hover:enabled { padding-inline: 1rem; background: color-mix(in srgb, var(--mk-text) 8%, var(--mk-wave-surface)); }
|
|
37
|
+
[data-mk-wave-rate][hidden] { display: none; }
|
|
38
|
+
[data-mk-wave-message] { grid-column: 2; color: var(--mk-muted); font-size: .75rem; line-height: 1.5; }
|
|
39
|
+
[data-mk-wave-message]:empty { display: none; }
|
|
40
|
+
[data-mk-wave-toggle]:focus-visible, [data-mk-wave-rate]:focus-visible { outline: 2px solid var(--mk-accent); outline-offset: 3px; }
|
|
41
|
+
[data-mk-wave-toggle]:disabled, [data-mk-wave-rate]:disabled { opacity: .5; cursor: default; }
|
|
42
|
+
[data-mk-wave-track]:has(input:disabled) { opacity: .55; }
|
|
43
|
+
[data-mk-wave-track]:has(input:disabled) [data-mk-wave-seek] { cursor: default; }
|
|
44
|
+
[data-mk-wave-state='error'] [data-mk-wave-message] { color: var(--mk-error, var(--mk-text)); }
|
|
45
|
+
@media (prefers-reduced-motion: reduce) { [data-mk-wave-bar], [data-mk-wave-toggle], [data-mk-wave-head], [data-mk-wave-rate] { transition: none; } }
|
|
46
|
+
@media (forced-colors: active) {
|
|
47
|
+
[data-mk-wave-toggle], [data-mk-wave-rate] { border: 1px solid ButtonText; }
|
|
48
|
+
[data-mk-wave-bar] { background: GrayText; }
|
|
49
|
+
[data-mk-wave-bar][data-mk-played], [data-mk-wave-head] { background: Highlight; }
|
|
50
|
+
}
|
|
@@ -30,4 +30,6 @@ La instancia expone `setValue(value, notify=false)`, `setItems(items, notify=fal
|
|
|
30
30
|
|
|
31
31
|
Flechas arriba/abajo omiten opciones deshabilitadas; Home/End van a los extremos y PageUp/PageDown saltan varias filas. Escribir busca una etiqueta por prefijo. El listbox tiene un solo foco y `aria-activedescendant`, las opciones exponen selección/estado deshabilitado y los cambios confirmados se anuncian. El reset de formulario recupera el valor inicial o una opción válida si cambió la lista. Sin opciones, el valor enviado es vacío.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Las filas se proyectan sobre un cilindro cuyo radio es la mitad del marco: cerca de los bordes se comprimen, se estrechan y se desvanecen como en la rueda de iOS, y las que pasan de 90° se ocultan. `visibleItems` fija la altura del marco en filas planas; con la curvatura se ven una o dos filas más en los bordes. Las opciones deshabilitadas se muestran atenuadas.
|
|
34
|
+
|
|
35
|
+
El movimiento reducido sustituye el cilindro por una lista plana y elimina los desplazamientos programados suaves. Se usan los tokens de superficie, acento, tipografía y forma del kit; `--mk-wheel-row` y `--mk-wheel-rows` reflejan el tamaño configurado. El componente cancela temporizadores y listeners y restaura el contenedor al destruirse.
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
-
import { createWheelPicker, type WheelPickerInstance, type WheelPickerEntry } from './engine';
|
|
2
|
+
import { createWheelPicker, type WheelPickerInstance, type WheelPickerEntry, type WheelPickerOptions } from './engine';
|
|
3
3
|
|
|
4
4
|
export class MkWheelPickerElement extends MkElement<WheelPickerInstance> {
|
|
5
5
|
static options: Record<string, OptionType> = { items: 'json', value: 'string', name: 'string', label: 'string', disabled: 'boolean', visibleItems: 'number', itemHeight: 'number' };
|
|
6
|
-
|
|
6
|
+
private savedValue: string | undefined;
|
|
7
|
+
private savedItems: WheelPickerEntry[] | undefined;
|
|
8
|
+
protected create(options: Record<string, unknown>) { return createWheelPicker(this, { ...options, ...(this.savedItems ? { items: this.savedItems } : {}), value: this.savedValue ?? options.value } as WheelPickerOptions); }
|
|
7
9
|
attributeChangedCallback(name: string, previous: string | null, next: string | null) {
|
|
8
|
-
if (previous === next
|
|
10
|
+
if (previous === next) return;
|
|
11
|
+
if (name === 'items') this.savedItems = undefined;
|
|
12
|
+
if (!this.instance) { if (name === 'value') this.savedValue = next ?? undefined; return; }
|
|
9
13
|
if (name === 'value' && next !== null) { this.instance.setValue(next); return; }
|
|
10
14
|
if (name === 'items') { this.instance.setItems((this.options.items as WheelPickerEntry[]) ?? []); if (this.options.value !== undefined) this.instance.setValue(String(this.options.value)); return; }
|
|
11
|
-
|
|
15
|
+
this.savedValue = name === 'value' ? undefined : this.instance.value;
|
|
16
|
+
this.instance.destroy();
|
|
17
|
+
this.instance = this.create(this.options);
|
|
12
18
|
}
|
|
19
|
+
disconnectedCallback() { this.savedValue = this.instance?.value ?? this.savedValue; super.disconnectedCallback(); }
|
|
13
20
|
get value() { return this.instance?.value ?? ''; }
|
|
14
21
|
set value(next: string) { this.setAttribute('value', next); }
|
|
15
22
|
setValue(value: string, notify = false) { this.instance?.setValue(value, notify); }
|
|
16
|
-
setItems(items: WheelPickerEntry[], notify = false) { this.instance?.setItems(items, notify); }
|
|
23
|
+
setItems(items: WheelPickerEntry[], notify = false) { this.savedItems = items.map(item => typeof item === 'string' ? item : { ...item }); this.instance?.setItems(items, notify); }
|
|
17
24
|
}
|
|
18
25
|
export const defineWheelPicker = () => defineElement('mk-wheel-picker', MkWheelPickerElement);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createScope } from '../../core/scope';
|
|
2
2
|
import { reduced } from '../../core/reduced';
|
|
3
3
|
import { inert, type Instance } from '../../core/types';
|
|
4
|
+
import { gsap, EASE, registerMotion } from '../../core/gsap';
|
|
4
5
|
|
|
5
6
|
export interface WheelPickerItem { value: string; label: string; disabled?: boolean }
|
|
6
7
|
export type WheelPickerEntry = string | WheelPickerItem;
|
|
@@ -39,6 +40,7 @@ function normalize(items: WheelPickerEntry[]) {
|
|
|
39
40
|
/** Native scrolling and inertia with a centred snap row, roving selection and a real form value. */
|
|
40
41
|
export function createWheelPicker(root: HTMLElement | null, options: WheelPickerOptions = {}): WheelPickerInstance {
|
|
41
42
|
if (!root) return inert({ setValue() {}, setItems() {}, value: '' });
|
|
43
|
+
registerMotion();
|
|
42
44
|
const scope = createScope(), uid = `mk-wheel-${++sequence}`;
|
|
43
45
|
let items = normalize(Array.isArray(options.items) ? options.items : []);
|
|
44
46
|
const rawRows = Number.isFinite(options.visibleItems) ? Math.round(options.visibleItems!) : 5;
|
|
@@ -69,15 +71,31 @@ export function createWheelPicker(root: HTMLElement | null, options: WheelPicker
|
|
|
69
71
|
let typing: ReturnType<typeof setTimeout> | undefined;
|
|
70
72
|
let prefix = '';
|
|
71
73
|
let programmatic: number | null = null;
|
|
74
|
+
let glide: gsap.core.Tween | null = null;
|
|
75
|
+
let feedback: gsap.core.Tween | null = null;
|
|
72
76
|
let dragging: { id: number; y: number; top: number; moved: boolean } | null = null;
|
|
73
77
|
let suppressClick = false;
|
|
74
|
-
scope.add(() => { clearTimeout(timer); clearTimeout(typing); });
|
|
78
|
+
scope.add(() => { clearTimeout(timer); clearTimeout(typing); glide?.kill(); feedback?.kill(); });
|
|
75
79
|
|
|
80
|
+
// The drum's radius is half the frame, so 90° lands on its edge and each row keeps
|
|
81
|
+
// its arc length: rows compress, narrow and recede towards the rims like iOS.
|
|
82
|
+
const radius = height * rows / 2, step = height / radius, camera = radius * 3;
|
|
76
83
|
const paint = () => {
|
|
77
|
-
const offset = viewport.scrollTop / height;
|
|
84
|
+
const offset = viewport.scrollTop / height, flat = reduced();
|
|
78
85
|
nodes.forEach((node, index) => {
|
|
79
|
-
const distance =
|
|
80
|
-
|
|
86
|
+
const distance = index - offset;
|
|
87
|
+
// Project each row onto a cylinder. The ordinary list keeps native scroll,
|
|
88
|
+
// touch inertia and option semantics; Y/Z and orientation form the wheel.
|
|
89
|
+
const angle = distance * step, hidden = Math.abs(angle) >= Math.PI / 2;
|
|
90
|
+
const depth = radius * (Math.cos(angle) - 1);
|
|
91
|
+
// Depth narrows and shrinks the row; its centre stays on the drum's silhouette
|
|
92
|
+
// so the rims meet the frame instead of floating inside it.
|
|
93
|
+
const cameraScale = camera / (camera - depth);
|
|
94
|
+
const shiftY = (radius * Math.sin(angle) - distance * height) / cameraScale;
|
|
95
|
+
node.style.transform = flat || hidden ? '' : `perspective(${camera.toFixed(0)}px) translate3d(0, ${shiftY.toFixed(3)}px, ${depth.toFixed(3)}px) rotateX(${(-angle * 180 / Math.PI).toFixed(3)}deg)`;
|
|
96
|
+
node.style.opacity = String((items[index].disabled ? .32 : 1) * (flat ? 1 : hidden ? 0 : Math.pow(Math.cos(angle), 1.6)));
|
|
97
|
+
node.style.visibility = !flat && hidden ? 'hidden' : '';
|
|
98
|
+
node.style.pointerEvents = !flat && Math.abs(angle) > 1.2 ? 'none' : '';
|
|
81
99
|
node.setAttribute('aria-selected', String(index === current));
|
|
82
100
|
});
|
|
83
101
|
input.value = items[current]?.value ?? '';
|
|
@@ -87,14 +105,24 @@ export function createWheelPicker(root: HTMLElement | null, options: WheelPicker
|
|
|
87
105
|
const announce = (kind: 'input' | 'change') => {
|
|
88
106
|
const value = items[current]?.value ?? '';
|
|
89
107
|
if (kind === 'input') options.onInput?.(value);
|
|
90
|
-
else {
|
|
108
|
+
else {
|
|
109
|
+
options.onChange?.(value); status.textContent = items[current]?.label ?? '';
|
|
110
|
+
feedback?.kill();
|
|
111
|
+
if (!reduced()) feedback = gsap.fromTo(highlight, { scaleX: .94, scaleY: .82 }, { scaleX: 1, scaleY: 1, duration: .5, ease: EASE.click, clearProps: 'transform' });
|
|
112
|
+
}
|
|
91
113
|
input.dispatchEvent(new Event(kind, { bubbles: true }));
|
|
92
114
|
root.dispatchEvent(new CustomEvent(`mk:${kind}`, { bubbles: true, detail: { value, item: current >= 0 ? { ...items[current] } : null } }));
|
|
93
115
|
};
|
|
94
116
|
const align = (animate: boolean) => {
|
|
117
|
+
glide?.kill(); glide = null;
|
|
95
118
|
const top = Math.max(0, current) * height;
|
|
96
|
-
if (animate && !reduced()
|
|
97
|
-
programmatic = top;
|
|
119
|
+
if (animate && !reduced()) {
|
|
120
|
+
programmatic = top;
|
|
121
|
+
const proxy = { top: viewport.scrollTop };
|
|
122
|
+
glide = gsap.to(proxy, { top, duration: .55, ease: EASE.swatch,
|
|
123
|
+
onUpdate: () => { viewport.scrollTop = proxy.top; paint(); },
|
|
124
|
+
onComplete: () => { glide = null; programmatic = null; viewport.scrollTop = top; paint(); },
|
|
125
|
+
});
|
|
98
126
|
} else { programmatic = null; viewport.scrollTop = top; paint(); }
|
|
99
127
|
};
|
|
100
128
|
const select = (index: number, notify: boolean, animate = true) => {
|
|
@@ -111,6 +139,7 @@ export function createWheelPicker(root: HTMLElement | null, options: WheelPicker
|
|
|
111
139
|
};
|
|
112
140
|
const settle = () => {
|
|
113
141
|
if (scope.disposed || dragging) return;
|
|
142
|
+
if (glide?.isActive()) return;
|
|
114
143
|
clearTimeout(timer);
|
|
115
144
|
if (programmatic !== null) {
|
|
116
145
|
// Keep controlled changes from notifying as native scrolling passes intermediate rows.
|
|
@@ -130,7 +159,7 @@ export function createWheelPicker(root: HTMLElement | null, options: WheelPicker
|
|
|
130
159
|
align(false); paint();
|
|
131
160
|
};
|
|
132
161
|
const interrupt = () => {
|
|
133
|
-
|
|
162
|
+
glide?.kill(); glide = null;
|
|
134
163
|
programmatic = null;
|
|
135
164
|
};
|
|
136
165
|
scope.on(viewport, 'pointerdown', event => {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
.mk-wheel-picker, mk-wheel-picker { --mk-wheel-row: 44px; --mk-wheel-rows: 5; display: grid; gap: .75rem; min-width: 0; color: var(--mk-text); font-family: var(--mk-font); }
|
|
2
|
-
[data-mk-wheel-label] { color: var(--mk-muted); font-size: .8125rem; text-align: center; }
|
|
3
|
-
[data-mk-wheel-frame] { position: relative; isolation: isolate; min-width: 0; }
|
|
4
|
-
[data-mk-wheel-highlight] { position: absolute; top: calc(50% - var(--mk-wheel-row) / 2); right: .5rem; left: .5rem; height: var(--mk-wheel-row); border:
|
|
5
|
-
[data-mk-wheel-viewport] { height: calc(var(--mk-wheel-row) * var(--mk-wheel-rows)); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; -webkit-tap-highlight-color: transparent; mask-image: linear-gradient(transparent, #000
|
|
6
|
-
[data-mk-wheel-viewport]::-webkit-scrollbar { display: none; }
|
|
7
|
-
[data-mk-wheel-viewport]:focus { outline: none; }
|
|
8
|
-
[data-mk-wheel-frame]:has([data-mk-wheel-viewport]:focus-visible) { outline: 2px solid var(--mk-accent); outline-offset: 3px; border-radius: 24px; }
|
|
9
|
-
[data-mk-wheel-list] { padding-block: calc(var(--mk-wheel-row) * (var(--mk-wheel-rows) - 1) / 2); }
|
|
10
|
-
[data-mk-wheel-option] { display: grid; place-items: center; height: var(--mk-wheel-row); min-height: 44px; padding-inline: 1rem; color: var(--mk-
|
|
11
|
-
[data-mk-wheel-option][aria-selected='true'] { color: var(--mk-text); font-weight: 650; }
|
|
12
|
-
[data-mk-wheel-option][aria-disabled='true'] {
|
|
13
|
-
[data-mk-wheel-
|
|
14
|
-
[data-mk-
|
|
15
|
-
|
|
16
|
-
@media (
|
|
1
|
+
.mk-wheel-picker, mk-wheel-picker { --mk-wheel-row: 44px; --mk-wheel-rows: 5; display: grid; gap: .75rem; min-width: 0; color: var(--mk-text); font-family: var(--mk-font); }
|
|
2
|
+
[data-mk-wheel-label] { color: var(--mk-muted); font-size: .8125rem; text-align: center; }
|
|
3
|
+
[data-mk-wheel-frame] { position: relative; isolation: isolate; min-width: 0; overflow: hidden; border-radius: var(--mk-radius-xl); background: var(--mk-surface-1); }
|
|
4
|
+
[data-mk-wheel-highlight] { position: absolute; top: calc(50% - var(--mk-wheel-row) / 2); right: .5rem; left: .5rem; height: var(--mk-wheel-row); border-radius: var(--mk-radius-md); background: var(--mk-surface-3); box-shadow: inset 0 1px color-mix(in srgb, var(--mk-text) 5%, transparent), inset 0 -1px color-mix(in srgb, var(--mk-text) 4%, transparent); pointer-events: none; z-index: -1; }
|
|
5
|
+
[data-mk-wheel-viewport] { height: calc(var(--mk-wheel-row) * var(--mk-wheel-rows)); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; -webkit-tap-highlight-color: transparent; mask-image: linear-gradient(transparent, #000 16%, #000 84%, transparent); }
|
|
6
|
+
[data-mk-wheel-viewport]::-webkit-scrollbar { display: none; }
|
|
7
|
+
[data-mk-wheel-viewport]:focus { outline: none; }
|
|
8
|
+
[data-mk-wheel-frame]:has([data-mk-wheel-viewport]:focus-visible) { outline: 2px solid var(--mk-accent); outline-offset: 3px; border-radius: 24px; }
|
|
9
|
+
[data-mk-wheel-list] { padding-block: calc(var(--mk-wheel-row) * (var(--mk-wheel-rows) - 1) / 2); }
|
|
10
|
+
[data-mk-wheel-option] { display: grid; place-items: center; height: var(--mk-wheel-row); min-height: 44px; padding-inline: 1rem; color: var(--mk-text); font-size: 1.35rem; font-variant-numeric: tabular-nums; font-weight: 450; scroll-snap-align: center; cursor: pointer; user-select: none; transform-origin: center; backface-visibility: hidden; transition: color .18s ease; }
|
|
11
|
+
[data-mk-wheel-option][aria-selected='true'] { color: var(--mk-text); font-weight: 650; }
|
|
12
|
+
[data-mk-wheel-option]:not([aria-disabled='true']):hover { color: var(--mk-accent); }
|
|
13
|
+
[data-mk-wheel-option][aria-disabled='true'] { color: var(--mk-muted); cursor: not-allowed; }
|
|
14
|
+
[data-mk-wheel-empty] { display: block; text-align: center; color: var(--mk-muted); font-size: .875rem; }
|
|
15
|
+
[data-mk-disabled] [data-mk-wheel-viewport] { overflow: hidden; opacity: .5; }
|
|
16
|
+
@media (prefers-reduced-motion: reduce) { [data-mk-wheel-option] { transition: none; transform: none !important; } }
|
|
17
|
+
@media (forced-colors: active) { [data-mk-wheel-highlight] { border: 2px solid Highlight; } }
|
package/src/core/timing.ts
CHANGED
|
@@ -200,4 +200,6 @@ export const TIMING = Object.freeze({
|
|
|
200
200
|
repelBento: Object.freeze({ offset: 0.6, stagger: 0.006, strength: 5000, distance: 30, breakpoint: 1024 }),
|
|
201
201
|
rotator: Object.freeze({ interval: 8000, duration: 0.25, y: 5 }),
|
|
202
202
|
dragRuler: Object.freeze({ min: 0, max: 8, step: 0.1, pixelsPerUnit: 60 }),
|
|
203
|
+
/** scroll-area: bars hide after idleMs; edge fades up to `fade` px; a click on the rail glides (s); shortest thumb (px). */
|
|
204
|
+
scrollArea: Object.freeze({ idleMs: 900, fade: 24, glide: 0.6, minThumb: 24 }),
|
|
203
205
|
});
|
package/src/css/tokens.css
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
--mk-ease-nav-move: cubic-bezier(0.38, 0.49, 0, 1.2);
|
|
16
16
|
--mk-ease-swatch: cubic-bezier(0.38, 0.49, 0, 1.5);
|
|
17
17
|
--mk-ease-swatch-light: cubic-bezier(0.11, 1, 0, 1);
|
|
18
|
+
--mk-ease-click: cubic-bezier(0.37, 1.42, 0.37, 1);
|
|
19
|
+
--mk-ease-playful: cubic-bezier(0.38, 0.49, 0, 2);
|
|
20
|
+
--mk-ease-pop: cubic-bezier(0.1, 0.8, 0, 1.1);
|
|
21
|
+
--mk-ease-wipe: cubic-bezier(0.38, 0.49, 0, 1);
|
|
18
22
|
--mk-ease-slot: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
19
23
|
/* Glass, sheet and control curves */
|
|
20
24
|
--mk-ease-glass: cubic-bezier(0.25, 1, 0.5, 1);
|