@yewdiel21/motion-kit 0.15.0 → 0.17.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 +25 -0
- package/dist/auto/index.d.ts +8 -3
- package/dist/auto/index.js +13 -3
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-A6QQCF6V.js → chunk-7IQTMF7Y.js} +1542 -54
- package/dist/chunk-7IQTMF7Y.js.map +1 -0
- package/dist/{chunk-6IRJYXJU.js → chunk-BZLMCHUD.js} +2 -2
- package/dist/{chunk-JXRS3OEA.js → chunk-VJFPTLAK.js} +29 -7
- package/dist/chunk-VJFPTLAK.js.map +1 -0
- package/dist/css/components/chart.css +7 -0
- package/dist/css/components/drilldown-menu.css +130 -0
- package/dist/css/components/heatmap-chart.css +7 -0
- package/dist/css/components/pie-chart.css +7 -0
- package/dist/css/components/radar-chart.css +7 -0
- package/dist/css/components/radial-chart.css +16 -2
- package/dist/css/components/sankey-chart.css +7 -0
- package/dist/css/components/scroll-island.css +3 -0
- package/dist/css/components/signature-pad.css +104 -0
- package/dist/css/components/voice-message-bubble.css +50 -0
- package/dist/css/components/waveform-player.css +36 -0
- package/dist/css/components/wheel-picker.css +17 -0
- package/dist/elements/index.d.ts +90 -4
- package/dist/elements/index.js +290 -8
- package/dist/elements/index.js.map +1 -1
- package/dist/{reveal-BfyKKsw4.d.ts → engine-Bn6hC87X.d.ts} +206 -1
- package/dist/index.css +324 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +48 -10
- package/dist/react/index.js +240 -10
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-6X_bns60.d.ts → reduced-YS6TZjpa.d.ts} +1 -1
- package/dist/{store-CA2Qcun_.d.ts → store-CZ6vdtIE.d.ts} +5 -1
- package/package.json +110 -110
- package/registry.json +64 -0
- package/src/auto/auto-init.ts +308 -293
- package/src/components/add-to-cart/README.md +49 -34
- package/src/components/add-to-cart/react.tsx +4 -0
- package/src/components/chart/README.md +49 -34
- package/src/components/chart/chart.css +7 -0
- package/src/components/chart/element.ts +2 -1
- package/src/components/chart/engine.ts +105 -13
- package/src/components/chart/react.tsx +2 -2
- package/src/components/drilldown-menu/README.md +163 -0
- package/src/components/drilldown-menu/drilldown-menu.css +129 -0
- package/src/components/drilldown-menu/element.ts +59 -0
- package/src/components/drilldown-menu/engine.ts +338 -0
- package/src/components/drilldown-menu/react.tsx +76 -0
- package/src/components/radial-chart/README.md +10 -0
- package/src/components/radial-chart/element.ts +2 -2
- package/src/components/radial-chart/engine.ts +33 -6
- package/src/components/radial-chart/radial-chart.css +9 -2
- package/src/components/radial-chart/react.tsx +2 -2
- package/src/components/scroll-island/README.md +44 -34
- package/src/components/scroll-island/element.ts +1 -1
- package/src/components/scroll-island/engine.ts +25 -3
- package/src/components/scroll-island/react.tsx +3 -3
- package/src/components/scroll-island/scroll-island.css +3 -0
- package/src/components/signature-pad/README.md +97 -0
- package/src/components/signature-pad/element.ts +57 -0
- package/src/components/signature-pad/engine.ts +437 -0
- package/src/components/signature-pad/react.tsx +56 -0
- package/src/components/signature-pad/signature-pad.css +103 -0
- package/src/components/voice-message-bubble/README.md +31 -0
- package/src/components/voice-message-bubble/element.ts +20 -0
- package/src/components/voice-message-bubble/engine.ts +68 -0
- package/src/components/voice-message-bubble/react.tsx +27 -0
- package/src/components/voice-message-bubble/voice-message-bubble.css +12 -0
- package/src/components/waveform-player/README.md +30 -0
- package/src/components/waveform-player/element.ts +21 -0
- package/src/components/waveform-player/engine.ts +261 -0
- package/src/components/waveform-player/react.tsx +27 -0
- package/src/components/waveform-player/waveform-player.css +35 -0
- package/src/components/wheel-picker/README.md +33 -0
- package/src/components/wheel-picker/element.ts +18 -0
- package/src/components/wheel-picker/engine.ts +210 -0
- package/src/components/wheel-picker/react.tsx +32 -0
- package/src/components/wheel-picker/wheel-picker.css +16 -0
- package/src/elements/index.ts +273 -258
- package/src/index.ts +295 -290
- package/src/react/index.ts +176 -171
- package/dist/chunk-A6QQCF6V.js.map +0 -1
- package/dist/chunk-JXRS3OEA.js.map +0 -1
- /package/dist/{chunk-6IRJYXJU.js.map → chunk-BZLMCHUD.js.map} +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createScope } from '../../core/scope';
|
|
2
|
+
import { reduced } from '../../core/reduced';
|
|
3
|
+
import { inert, type Instance } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
export interface WheelPickerItem { value: string; label: string; disabled?: boolean }
|
|
6
|
+
export type WheelPickerEntry = string | WheelPickerItem;
|
|
7
|
+
export interface WheelPickerOptions {
|
|
8
|
+
items?: WheelPickerEntry[];
|
|
9
|
+
value?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Odd number of visible rows, 3…9 (default 5). */
|
|
14
|
+
visibleItems?: number;
|
|
15
|
+
/** Height per row in px, at least 44 (default 44). */
|
|
16
|
+
itemHeight?: number;
|
|
17
|
+
onInput?: (value: string) => void;
|
|
18
|
+
onChange?: (value: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface WheelPickerInstance extends Instance {
|
|
21
|
+
setValue(value: string, notify?: boolean): void;
|
|
22
|
+
setItems(items: WheelPickerEntry[], notify?: boolean): void;
|
|
23
|
+
readonly value: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let sequence = 0;
|
|
27
|
+
function normalize(items: WheelPickerEntry[]) {
|
|
28
|
+
const seen = new Set<string>();
|
|
29
|
+
return items.flatMap(item => {
|
|
30
|
+
const row = typeof item === 'string' ? { value: item, label: item } : item;
|
|
31
|
+
if (!row || typeof row !== 'object' || row.value == null) return [];
|
|
32
|
+
const value = String(row.value);
|
|
33
|
+
if (seen.has(value)) return [];
|
|
34
|
+
seen.add(value);
|
|
35
|
+
return [{ value, label: String(row.label ?? value), disabled: Boolean(row.disabled) }];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Native scrolling and inertia with a centred snap row, roving selection and a real form value. */
|
|
40
|
+
export function createWheelPicker(root: HTMLElement | null, options: WheelPickerOptions = {}): WheelPickerInstance {
|
|
41
|
+
if (!root) return inert({ setValue() {}, setItems() {}, value: '' });
|
|
42
|
+
const scope = createScope(), uid = `mk-wheel-${++sequence}`;
|
|
43
|
+
let items = normalize(Array.isArray(options.items) ? options.items : []);
|
|
44
|
+
const rawRows = Number.isFinite(options.visibleItems) ? Math.round(options.visibleItems!) : 5;
|
|
45
|
+
const rows = Math.min(9, Math.max(3, rawRows % 2 ? rawRows : rawRows + 1));
|
|
46
|
+
const height = Number.isFinite(options.itemHeight) ? Math.max(44, Math.min(96, options.itemHeight!)) : 44;
|
|
47
|
+
const disabled = Boolean(options.disabled);
|
|
48
|
+
const originalStyle = root.getAttribute('style');
|
|
49
|
+
root.style.setProperty('--mk-wheel-row', `${height}px`);
|
|
50
|
+
root.style.setProperty('--mk-wheel-rows', String(rows));
|
|
51
|
+
scope.add(() => { if (originalStyle === null) root.removeAttribute('style'); else root.setAttribute('style', originalStyle); });
|
|
52
|
+
scope.attr(root, 'data-mk-ready', ''); scope.attr(root, 'data-mk-disabled', disabled ? '' : null);
|
|
53
|
+
const label = document.createElement('span'); label.id = `${uid}-label`; label.dataset.mkWheelLabel = ''; label.textContent = options.label ?? root.getAttribute('aria-label') ?? 'Seleccionar';
|
|
54
|
+
const frame = document.createElement('div'); frame.dataset.mkWheelFrame = '';
|
|
55
|
+
const highlight = document.createElement('span'); highlight.dataset.mkWheelHighlight = ''; highlight.setAttribute('aria-hidden', 'true');
|
|
56
|
+
const viewport = document.createElement('div'); viewport.dataset.mkWheelViewport = ''; viewport.setAttribute('role', 'listbox'); viewport.setAttribute('aria-labelledby', label.id); viewport.setAttribute('aria-orientation', 'vertical'); viewport.setAttribute('aria-disabled', String(disabled)); viewport.tabIndex = disabled ? -1 : 0;
|
|
57
|
+
const list = document.createElement('div'); list.dataset.mkWheelList = ''; viewport.append(list); frame.append(highlight, viewport);
|
|
58
|
+
const input = document.createElement('input'); input.type = 'hidden'; if (options.name) input.name = options.name; input.disabled = disabled;
|
|
59
|
+
const status = document.createElement('span'); status.className = 'mk-sr-only'; status.setAttribute('role', 'status');
|
|
60
|
+
root.append(label, frame, input, status);
|
|
61
|
+
scope.add(() => { label.remove(); frame.remove(); input.remove(); status.remove(); });
|
|
62
|
+
const enabled = () => items.flatMap((item, index) => item.disabled ? [] : [index]);
|
|
63
|
+
const find = (value: string | undefined) => items.findIndex(item => value !== undefined && item.value === String(value) && !item.disabled);
|
|
64
|
+
let current = find(options.value); if (current < 0) current = enabled()[0] ?? -1;
|
|
65
|
+
const initial = items[current]?.value ?? '';
|
|
66
|
+
let committed = initial;
|
|
67
|
+
let nodes: HTMLDivElement[] = [];
|
|
68
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
69
|
+
let typing: ReturnType<typeof setTimeout> | undefined;
|
|
70
|
+
let prefix = '';
|
|
71
|
+
let programmatic: number | null = null;
|
|
72
|
+
let dragging: { id: number; y: number; top: number; moved: boolean } | null = null;
|
|
73
|
+
let suppressClick = false;
|
|
74
|
+
scope.add(() => { clearTimeout(timer); clearTimeout(typing); });
|
|
75
|
+
|
|
76
|
+
const paint = () => {
|
|
77
|
+
const offset = viewport.scrollTop / height;
|
|
78
|
+
nodes.forEach((node, index) => {
|
|
79
|
+
const distance = Math.max(-3, Math.min(3, index - offset));
|
|
80
|
+
node.style.transform = reduced() ? '' : `perspective(600px) rotateX(${-distance * 14}deg) scale(${1 - Math.abs(distance) * 0.055})`;
|
|
81
|
+
node.setAttribute('aria-selected', String(index === current));
|
|
82
|
+
});
|
|
83
|
+
input.value = items[current]?.value ?? '';
|
|
84
|
+
if (current >= 0) viewport.setAttribute('aria-activedescendant', `${uid}-${current}`);
|
|
85
|
+
else viewport.removeAttribute('aria-activedescendant');
|
|
86
|
+
};
|
|
87
|
+
const announce = (kind: 'input' | 'change') => {
|
|
88
|
+
const value = items[current]?.value ?? '';
|
|
89
|
+
if (kind === 'input') options.onInput?.(value);
|
|
90
|
+
else { options.onChange?.(value); status.textContent = items[current]?.label ?? ''; }
|
|
91
|
+
input.dispatchEvent(new Event(kind, { bubbles: true }));
|
|
92
|
+
root.dispatchEvent(new CustomEvent(`mk:${kind}`, { bubbles: true, detail: { value, item: current >= 0 ? { ...items[current] } : null } }));
|
|
93
|
+
};
|
|
94
|
+
const align = (animate: boolean) => {
|
|
95
|
+
const top = Math.max(0, current) * height;
|
|
96
|
+
if (animate && !reduced() && typeof viewport.scrollTo === 'function') {
|
|
97
|
+
programmatic = top; viewport.scrollTo({ top, behavior: 'smooth' });
|
|
98
|
+
} else { programmatic = null; viewport.scrollTop = top; paint(); }
|
|
99
|
+
};
|
|
100
|
+
const select = (index: number, notify: boolean, animate = true) => {
|
|
101
|
+
if (scope.disposed || index < 0 || index >= items.length || items[index].disabled) return;
|
|
102
|
+
const previous = items[current]?.value ?? '';
|
|
103
|
+
current = index; paint(); align(animate);
|
|
104
|
+
const value = items[current].value;
|
|
105
|
+
committed = value;
|
|
106
|
+
if (notify && value !== previous) { announce('input'); announce('change'); }
|
|
107
|
+
};
|
|
108
|
+
const nearest = () => {
|
|
109
|
+
const at = viewport.scrollTop / height;
|
|
110
|
+
return enabled().reduce((best, index) => best < 0 || Math.abs(index - at) < Math.abs(best - at) ? index : best, -1);
|
|
111
|
+
};
|
|
112
|
+
const settle = () => {
|
|
113
|
+
if (scope.disposed || dragging) return;
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
if (programmatic !== null) {
|
|
116
|
+
// Keep controlled changes from notifying as native scrolling passes intermediate rows.
|
|
117
|
+
programmatic = null; viewport.scrollTop = Math.max(0, current) * height; paint(); return;
|
|
118
|
+
}
|
|
119
|
+
const index = nearest(); if (index < 0) return;
|
|
120
|
+
current = index; align(false); paint();
|
|
121
|
+
const value = items[current].value;
|
|
122
|
+
if (value !== committed) { committed = value; announce('change'); }
|
|
123
|
+
};
|
|
124
|
+
const build = () => {
|
|
125
|
+
list.replaceChildren();
|
|
126
|
+
nodes = items.map((item, index) => {
|
|
127
|
+
const node = document.createElement('div'); node.id = `${uid}-${index}`; node.dataset.mkWheelOption = String(index); node.setAttribute('role', 'option'); node.setAttribute('aria-disabled', String(item.disabled)); node.textContent = item.label; list.append(node); return node;
|
|
128
|
+
});
|
|
129
|
+
if (!items.length) { const empty = document.createElement('span'); empty.dataset.mkWheelEmpty = ''; empty.textContent = 'Sin opciones'; list.append(empty); }
|
|
130
|
+
align(false); paint();
|
|
131
|
+
};
|
|
132
|
+
const interrupt = () => {
|
|
133
|
+
if (programmatic !== null && typeof viewport.scrollTo === 'function') viewport.scrollTo({ top: viewport.scrollTop, behavior: 'instant' });
|
|
134
|
+
programmatic = null;
|
|
135
|
+
};
|
|
136
|
+
scope.on(viewport, 'pointerdown', event => {
|
|
137
|
+
interrupt();
|
|
138
|
+
const pointer = event as PointerEvent;
|
|
139
|
+
if (disabled || pointer.pointerType === 'touch' || pointer.button !== 0) return;
|
|
140
|
+
dragging = { id: pointer.pointerId, y: pointer.clientY, top: viewport.scrollTop, moved: false };
|
|
141
|
+
viewport.focus({ preventScroll: true });
|
|
142
|
+
});
|
|
143
|
+
scope.on(window, 'pointermove', event => {
|
|
144
|
+
const pointer = event as PointerEvent;
|
|
145
|
+
if (!dragging || pointer.pointerId !== dragging.id) return;
|
|
146
|
+
const delta = pointer.clientY - dragging.y;
|
|
147
|
+
if (Math.abs(delta) > 4 && !dragging.moved) {
|
|
148
|
+
dragging.moved = true; viewport.style.scrollSnapType = 'none';
|
|
149
|
+
try { viewport.setPointerCapture(pointer.pointerId); } catch { /* Synthetic events have no pointer capture. */ }
|
|
150
|
+
}
|
|
151
|
+
if (!dragging.moved) return;
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
viewport.scrollTop = Math.max(0, Math.min(Math.max(0, (items.length - 1) * height), dragging.top - delta));
|
|
154
|
+
});
|
|
155
|
+
const release = (event: Event) => {
|
|
156
|
+
if (!dragging || dragging.id !== (event as PointerEvent).pointerId) return;
|
|
157
|
+
suppressClick = dragging.moved; dragging = null; viewport.style.removeProperty('scroll-snap-type');
|
|
158
|
+
if (suppressClick) { settle(); clearTimeout(timer); timer = setTimeout(() => { suppressClick = false; }, 0); }
|
|
159
|
+
};
|
|
160
|
+
scope.on(window, 'pointerup', release); scope.on(window, 'pointercancel', release); scope.on(viewport, 'lostpointercapture', release);
|
|
161
|
+
scope.on(viewport, 'wheel', interrupt, { passive: true });
|
|
162
|
+
scope.on(viewport, 'scroll', () => {
|
|
163
|
+
if (disabled) return;
|
|
164
|
+
if (programmatic === null) {
|
|
165
|
+
const index = nearest();
|
|
166
|
+
if (index >= 0 && index !== current) { current = index; paint(); announce('input'); }
|
|
167
|
+
}
|
|
168
|
+
paint(); clearTimeout(timer); timer = setTimeout(settle, 100);
|
|
169
|
+
}, { passive: true });
|
|
170
|
+
scope.on(viewport, 'scrollend', settle);
|
|
171
|
+
scope.on(viewport, 'click', event => {
|
|
172
|
+
if (disabled) return;
|
|
173
|
+
if (suppressClick) { event.preventDefault(); suppressClick = false; return; }
|
|
174
|
+
const row = (event.target as Element).closest<HTMLElement>('[data-mk-wheel-option]');
|
|
175
|
+
if (!row || !list.contains(row)) return;
|
|
176
|
+
select(Number(row.dataset.mkWheelOption), true); viewport.focus({ preventScroll: true });
|
|
177
|
+
});
|
|
178
|
+
scope.on(viewport, 'keydown', event => {
|
|
179
|
+
if (disabled) return;
|
|
180
|
+
const key = (event as KeyboardEvent).key, choices = enabled(), at = choices.indexOf(current);
|
|
181
|
+
if (key === 'Enter' || key === ' ') { event.preventDefault(); return; }
|
|
182
|
+
const move = key === 'ArrowDown' ? 1 : key === 'ArrowUp' ? -1 : key === 'PageDown' ? rows - 1 : key === 'PageUp' ? 1 - rows : 0;
|
|
183
|
+
let next = key === 'Home' ? choices[0] : key === 'End' ? choices.at(-1) : move ? choices[Math.min(choices.length - 1, Math.max(0, at + move))] : undefined;
|
|
184
|
+
if (next === undefined && key.length === 1 && !(event as KeyboardEvent).ctrlKey && !(event as KeyboardEvent).metaKey) {
|
|
185
|
+
prefix += key.toLocaleLowerCase(); clearTimeout(typing); typing = setTimeout(() => { prefix = ''; }, 600);
|
|
186
|
+
next = choices.find(index => items[index].label.toLocaleLowerCase().startsWith(prefix));
|
|
187
|
+
}
|
|
188
|
+
if (next === undefined) return;
|
|
189
|
+
event.preventDefault(); interrupt(); select(next, true);
|
|
190
|
+
});
|
|
191
|
+
const form = root.closest('form');
|
|
192
|
+
if (form) scope.on(form, 'reset', event => queueMicrotask(() => {
|
|
193
|
+
if (scope.disposed || event.defaultPrevented) return;
|
|
194
|
+
const index = find(initial); select(index >= 0 ? index : enabled()[0] ?? -1, true, false); paint();
|
|
195
|
+
}));
|
|
196
|
+
build(); input.defaultValue = initial;
|
|
197
|
+
return {
|
|
198
|
+
setValue(value, notify = false) { const next = find(value); if (next !== current) select(next, notify); },
|
|
199
|
+
setItems(next, notify = false) {
|
|
200
|
+
if (scope.disposed) return;
|
|
201
|
+
clearTimeout(timer); programmatic = null;
|
|
202
|
+
const before = items[current]?.value ?? ''; items = normalize(Array.isArray(next) ? next : []);
|
|
203
|
+
current = find(before); if (current < 0) current = enabled()[0] ?? -1;
|
|
204
|
+
build(); committed = items[current]?.value ?? '';
|
|
205
|
+
if (notify && committed !== before) { announce('input'); announce('change'); }
|
|
206
|
+
},
|
|
207
|
+
get value() { return items[current]?.value ?? ''; },
|
|
208
|
+
destroy: () => scope.dispose(),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useReducer, useRef, type HTMLAttributes } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
|
|
4
|
+
import { createWheelPicker, type WheelPickerInstance, type WheelPickerOptions } from './engine';
|
|
5
|
+
|
|
6
|
+
export interface WheelPickerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'onInput' | 'defaultValue' | 'children' | 'dangerouslySetInnerHTML'>, WheelPickerOptions {
|
|
7
|
+
defaultValue?: string;
|
|
8
|
+
instanceRef?: { current: WheelPickerInstance | null };
|
|
9
|
+
}
|
|
10
|
+
export function WheelPicker({ items, value, defaultValue, name, label, disabled, visibleItems, itemHeight, onInput, onChange, instanceRef, className, ...props }: WheelPickerProps) {
|
|
11
|
+
const ref = useRef<HTMLDivElement>(null), instance = useRef<WheelPickerInstance | null>(null);
|
|
12
|
+
const changed = useLatest(onChange), input = useLatest(onInput), latestItems = useLatest(items);
|
|
13
|
+
const controlled = useLatest(value);
|
|
14
|
+
const [, refresh] = useReducer((revision: number) => revision + 1, 0);
|
|
15
|
+
const last = useRef(value ?? defaultValue);
|
|
16
|
+
useIsomorphicLayoutEffect(() => {
|
|
17
|
+
const created = createWheelPicker(ref.current, { items: latestItems.current, value: value ?? last.current, name, label, disabled, visibleItems, itemHeight,
|
|
18
|
+
onInput: next => { last.current = next; input.current?.(next); }, onChange: next => { last.current = next; changed.current?.(next); if (controlled.current !== undefined) refresh(); } });
|
|
19
|
+
instance.current = created;
|
|
20
|
+
return () => { last.current = created.value; created.destroy(); instance.current = null; };
|
|
21
|
+
}, [name, label, disabled, visibleItems, itemHeight]);
|
|
22
|
+
const key = JSON.stringify(items ?? []);
|
|
23
|
+
useIsomorphicLayoutEffect(() => { instance.current?.setItems(items ?? []); }, [key]);
|
|
24
|
+
useIsomorphicLayoutEffect(() => { if (value !== undefined) instance.current?.setValue(value); });
|
|
25
|
+
useIsomorphicLayoutEffect(() => {
|
|
26
|
+
if (instanceRef) instanceRef.current = instance.current;
|
|
27
|
+
return () => { if (instanceRef) instanceRef.current = null; };
|
|
28
|
+
}, [instanceRef, name, label, disabled, visibleItems, itemHeight]);
|
|
29
|
+
const domProps: HTMLAttributes<HTMLDivElement> = { ...props };
|
|
30
|
+
delete domProps.children; delete domProps.dangerouslySetInnerHTML;
|
|
31
|
+
return <div {...domProps} ref={ref} className={['mk-wheel-picker', className].filter(Boolean).join(' ')} />;
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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: 1px solid var(--mk-outline-soft); border-radius: var(--mk-radius-full); background: var(--mk-surface-3); 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 35%, #000 65%, 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-muted); font-size: 1.1rem; font-variant-numeric: tabular-nums; font-weight: 450; scroll-snap-align: center; cursor: pointer; user-select: none; transform-origin: center; transition: color .18s ease; }
|
|
11
|
+
[data-mk-wheel-option][aria-selected='true'] { color: var(--mk-text); font-weight: 650; }
|
|
12
|
+
[data-mk-wheel-option][aria-disabled='true'] { opacity: .35; cursor: not-allowed; text-decoration: line-through; }
|
|
13
|
+
[data-mk-wheel-empty] { display: block; text-align: center; color: var(--mk-muted); font-size: .875rem; }
|
|
14
|
+
[data-mk-disabled] [data-mk-wheel-viewport] { overflow: hidden; opacity: .5; }
|
|
15
|
+
@media (prefers-reduced-motion: reduce) { [data-mk-wheel-option] { transition: none; transform: none !important; } }
|
|
16
|
+
@media (forced-colors: active) { [data-mk-wheel-highlight] { border: 2px solid Highlight; } }
|