@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.
Files changed (85) hide show
  1. package/README.md +25 -0
  2. package/dist/auto/index.d.ts +8 -3
  3. package/dist/auto/index.js +13 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-A6QQCF6V.js → chunk-7IQTMF7Y.js} +1542 -54
  6. package/dist/chunk-7IQTMF7Y.js.map +1 -0
  7. package/dist/{chunk-6IRJYXJU.js → chunk-BZLMCHUD.js} +2 -2
  8. package/dist/{chunk-JXRS3OEA.js → chunk-VJFPTLAK.js} +29 -7
  9. package/dist/chunk-VJFPTLAK.js.map +1 -0
  10. package/dist/css/components/chart.css +7 -0
  11. package/dist/css/components/drilldown-menu.css +130 -0
  12. package/dist/css/components/heatmap-chart.css +7 -0
  13. package/dist/css/components/pie-chart.css +7 -0
  14. package/dist/css/components/radar-chart.css +7 -0
  15. package/dist/css/components/radial-chart.css +16 -2
  16. package/dist/css/components/sankey-chart.css +7 -0
  17. package/dist/css/components/scroll-island.css +3 -0
  18. package/dist/css/components/signature-pad.css +104 -0
  19. package/dist/css/components/voice-message-bubble.css +50 -0
  20. package/dist/css/components/waveform-player.css +36 -0
  21. package/dist/css/components/wheel-picker.css +17 -0
  22. package/dist/elements/index.d.ts +90 -4
  23. package/dist/elements/index.js +290 -8
  24. package/dist/elements/index.js.map +1 -1
  25. package/dist/{reveal-BfyKKsw4.d.ts → engine-Bn6hC87X.d.ts} +206 -1
  26. package/dist/index.css +324 -2
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.js +13 -3
  29. package/dist/index.js.map +1 -1
  30. package/dist/react/index.d.ts +48 -10
  31. package/dist/react/index.js +240 -10
  32. package/dist/react/index.js.map +1 -1
  33. package/dist/{reduced-6X_bns60.d.ts → reduced-YS6TZjpa.d.ts} +1 -1
  34. package/dist/{store-CA2Qcun_.d.ts → store-CZ6vdtIE.d.ts} +5 -1
  35. package/package.json +110 -110
  36. package/registry.json +64 -0
  37. package/src/auto/auto-init.ts +308 -293
  38. package/src/components/add-to-cart/README.md +49 -34
  39. package/src/components/add-to-cart/react.tsx +4 -0
  40. package/src/components/chart/README.md +49 -34
  41. package/src/components/chart/chart.css +7 -0
  42. package/src/components/chart/element.ts +2 -1
  43. package/src/components/chart/engine.ts +105 -13
  44. package/src/components/chart/react.tsx +2 -2
  45. package/src/components/drilldown-menu/README.md +163 -0
  46. package/src/components/drilldown-menu/drilldown-menu.css +129 -0
  47. package/src/components/drilldown-menu/element.ts +59 -0
  48. package/src/components/drilldown-menu/engine.ts +338 -0
  49. package/src/components/drilldown-menu/react.tsx +76 -0
  50. package/src/components/radial-chart/README.md +10 -0
  51. package/src/components/radial-chart/element.ts +2 -2
  52. package/src/components/radial-chart/engine.ts +33 -6
  53. package/src/components/radial-chart/radial-chart.css +9 -2
  54. package/src/components/radial-chart/react.tsx +2 -2
  55. package/src/components/scroll-island/README.md +44 -34
  56. package/src/components/scroll-island/element.ts +1 -1
  57. package/src/components/scroll-island/engine.ts +25 -3
  58. package/src/components/scroll-island/react.tsx +3 -3
  59. package/src/components/scroll-island/scroll-island.css +3 -0
  60. package/src/components/signature-pad/README.md +97 -0
  61. package/src/components/signature-pad/element.ts +57 -0
  62. package/src/components/signature-pad/engine.ts +437 -0
  63. package/src/components/signature-pad/react.tsx +56 -0
  64. package/src/components/signature-pad/signature-pad.css +103 -0
  65. package/src/components/voice-message-bubble/README.md +31 -0
  66. package/src/components/voice-message-bubble/element.ts +20 -0
  67. package/src/components/voice-message-bubble/engine.ts +68 -0
  68. package/src/components/voice-message-bubble/react.tsx +27 -0
  69. package/src/components/voice-message-bubble/voice-message-bubble.css +12 -0
  70. package/src/components/waveform-player/README.md +30 -0
  71. package/src/components/waveform-player/element.ts +21 -0
  72. package/src/components/waveform-player/engine.ts +261 -0
  73. package/src/components/waveform-player/react.tsx +27 -0
  74. package/src/components/waveform-player/waveform-player.css +35 -0
  75. package/src/components/wheel-picker/README.md +33 -0
  76. package/src/components/wheel-picker/element.ts +18 -0
  77. package/src/components/wheel-picker/engine.ts +210 -0
  78. package/src/components/wheel-picker/react.tsx +32 -0
  79. package/src/components/wheel-picker/wheel-picker.css +16 -0
  80. package/src/elements/index.ts +273 -258
  81. package/src/index.ts +295 -290
  82. package/src/react/index.ts +176 -171
  83. package/dist/chunk-A6QQCF6V.js.map +0 -1
  84. package/dist/chunk-JXRS3OEA.js.map +0 -1
  85. /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; } }