@yewdiel21/motion-kit 0.17.0 → 0.18.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 +23 -0
- package/dist/auto/index.d.ts +18 -3
- package/dist/auto/index.js +32 -2
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-VJFPTLAK.js → chunk-AWDEKDEJ.js} +2 -2
- package/dist/{chunk-BZLMCHUD.js → chunk-EDMXWUW3.js} +2 -2
- package/dist/{chunk-7IQTMF7Y.js → chunk-Q7IW4FV3.js} +2580 -58
- package/dist/chunk-Q7IW4FV3.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/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/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-preview-dock.css +18 -0
- package/dist/css/components/select.css +16 -0
- 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 +206 -4
- package/dist/elements/index.js +581 -6
- package/dist/elements/index.js.map +1 -1
- package/dist/{engine-Bn6hC87X.d.ts → engine-t8d7brjn.d.ts} +341 -1
- package/dist/index.css +578 -170
- package/dist/index.d.ts +23 -23
- package/dist/index.js +33 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +113 -7
- package/dist/react/index.js +669 -3
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-YS6TZjpa.d.ts → reduced-dPsZYqtI.d.ts} +1 -1
- package/dist/{store-CZ6vdtIE.d.ts → store-6hZNKiee.d.ts} +1 -1
- package/package.json +1 -1
- package/registry.json +180 -0
- package/src/auto/auto-init.ts +31 -0
- 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/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/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-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/select.css +16 -0
- 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/css/tokens.css +4 -0
- package/src/elements/index.ts +45 -0
- package/src/index.ts +311 -295
- package/src/react/index.ts +16 -0
- package/dist/chunk-7IQTMF7Y.js.map +0 -1
- /package/dist/{chunk-VJFPTLAK.js.map → chunk-AWDEKDEJ.js.map} +0 -0
- /package/dist/{chunk-BZLMCHUD.js.map → chunk-EDMXWUW3.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/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);
|
package/src/elements/index.ts
CHANGED
|
@@ -84,6 +84,21 @@ import { MkWaveformPlayerElement } from '../components/waveform-player/element';
|
|
|
84
84
|
import { MkVoiceMessageBubbleElement } from '../components/voice-message-bubble/element';
|
|
85
85
|
import { MkDrilldownMenuElement } from '../components/drilldown-menu/element';
|
|
86
86
|
import { MkWheelPickerElement } from '../components/wheel-picker/element';
|
|
87
|
+
import { MkStepperElement } from '../components/stepper/element';
|
|
88
|
+
import { MkRadioGroupElement } from '../components/radio-group/element';
|
|
89
|
+
import { MkEqualizerSliderElement } from '../components/equalizer-slider/element';
|
|
90
|
+
import { MkTemperatureSliderElement } from '../components/temperature-slider/element';
|
|
91
|
+
import { MkRatingScaleElement } from '../components/rating-scale/element';
|
|
92
|
+
import { MkNotificationStackElement } from '../components/notification-stack/element';
|
|
93
|
+
import { MkBentoGridElement } from '../components/bento-grid/element';
|
|
94
|
+
import { MkScrollPreviewDockElement } from '../components/scroll-preview-dock/element';
|
|
95
|
+
import { MkStickyHeaderElement } from '../components/sticky-header/element';
|
|
96
|
+
import { MkHandwritingTextElement } from '../components/handwriting-text/element';
|
|
97
|
+
import { MkParticleButtonElement } from '../components/particle-button/element';
|
|
98
|
+
import { MkMagnetizeButtonElement } from '../components/magnetize-button/element';
|
|
99
|
+
import { MkBadgeButtonElement } from '../components/badge-button/element';
|
|
100
|
+
import { MkMorphingPageDotsElement } from '../components/morphing-page-dots/element';
|
|
101
|
+
import { MkNotFoundElement } from '../components/not-found/element';
|
|
87
102
|
// new-component:imports (scripts/new-component.mjs inserts above this line)
|
|
88
103
|
|
|
89
104
|
export { MkElement, SafeHTMLElement, defineElement, type OptionType } from './base';
|
|
@@ -173,6 +188,21 @@ export { MkWaveformPlayerElement, defineWaveformPlayer } from '../components/wav
|
|
|
173
188
|
export { MkVoiceMessageBubbleElement, defineVoiceMessageBubble } from '../components/voice-message-bubble/element';
|
|
174
189
|
export { MkDrilldownMenuElement, defineDrilldownMenu } from '../components/drilldown-menu/element';
|
|
175
190
|
export { MkWheelPickerElement, defineWheelPicker } from '../components/wheel-picker/element';
|
|
191
|
+
export { MkStepperElement, defineStepper } from '../components/stepper/element';
|
|
192
|
+
export { MkRadioGroupElement, defineRadioGroup } from '../components/radio-group/element';
|
|
193
|
+
export { MkEqualizerSliderElement, defineEqualizerSlider } from '../components/equalizer-slider/element';
|
|
194
|
+
export { MkTemperatureSliderElement, defineTemperatureSlider } from '../components/temperature-slider/element';
|
|
195
|
+
export { MkRatingScaleElement, defineRatingScale } from '../components/rating-scale/element';
|
|
196
|
+
export { MkNotificationStackElement, defineNotificationStack } from '../components/notification-stack/element';
|
|
197
|
+
export { MkBentoGridElement, defineBentoGrid } from '../components/bento-grid/element';
|
|
198
|
+
export { MkScrollPreviewDockElement, defineScrollPreviewDock } from '../components/scroll-preview-dock/element';
|
|
199
|
+
export { MkStickyHeaderElement, defineStickyHeader } from '../components/sticky-header/element';
|
|
200
|
+
export { MkHandwritingTextElement, defineHandwritingText } from '../components/handwriting-text/element';
|
|
201
|
+
export { MkParticleButtonElement, defineParticleButton } from '../components/particle-button/element';
|
|
202
|
+
export { MkMagnetizeButtonElement, defineMagnetizeButton } from '../components/magnetize-button/element';
|
|
203
|
+
export { MkBadgeButtonElement, defineBadgeButton } from '../components/badge-button/element';
|
|
204
|
+
export { MkMorphingPageDotsElement, defineMorphingPageDots } from '../components/morphing-page-dots/element';
|
|
205
|
+
export { MkNotFoundElement, defineNotFound } from '../components/not-found/element';
|
|
176
206
|
// new-component:exports
|
|
177
207
|
|
|
178
208
|
/** Tag name → element class, for every component. */
|
|
@@ -262,6 +292,21 @@ export const ELEMENTS = {
|
|
|
262
292
|
'mk-voice-message-bubble': MkVoiceMessageBubbleElement,
|
|
263
293
|
'mk-drilldown-menu': MkDrilldownMenuElement,
|
|
264
294
|
'mk-wheel-picker': MkWheelPickerElement,
|
|
295
|
+
'mk-stepper': MkStepperElement,
|
|
296
|
+
'mk-radio-group': MkRadioGroupElement,
|
|
297
|
+
'mk-equalizer-slider': MkEqualizerSliderElement,
|
|
298
|
+
'mk-temperature-slider': MkTemperatureSliderElement,
|
|
299
|
+
'mk-rating-scale': MkRatingScaleElement,
|
|
300
|
+
'mk-notification-stack': MkNotificationStackElement,
|
|
301
|
+
'mk-bento-grid': MkBentoGridElement,
|
|
302
|
+
'mk-scroll-preview-dock': MkScrollPreviewDockElement,
|
|
303
|
+
'mk-sticky-header': MkStickyHeaderElement,
|
|
304
|
+
'mk-handwriting-text': MkHandwritingTextElement,
|
|
305
|
+
'mk-particle-button': MkParticleButtonElement,
|
|
306
|
+
'mk-magnetize-button': MkMagnetizeButtonElement,
|
|
307
|
+
'mk-badge-button': MkBadgeButtonElement,
|
|
308
|
+
'mk-morphing-page-dots': MkMorphingPageDotsElement,
|
|
309
|
+
'mk-not-found': MkNotFoundElement,
|
|
265
310
|
// new-component:entries
|
|
266
311
|
} as const;
|
|
267
312
|
|