@yewdiel21/motion-kit 0.14.0 → 0.15.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 (154) hide show
  1. package/README.md +174 -155
  2. package/bin/motion-kit.mjs +3 -3
  3. package/dist/auto/index.d.ts +6 -3
  4. package/dist/auto/index.js +8 -2
  5. package/dist/auto/index.js.map +1 -1
  6. package/dist/{chunk-EY4BJKC4.js → chunk-6IRJYXJU.js} +2 -2
  7. package/dist/{chunk-CU6SXW3E.js → chunk-A6QQCF6V.js} +1309 -310
  8. package/dist/chunk-A6QQCF6V.js.map +1 -0
  9. package/dist/chunk-JXRS3OEA.js +3638 -0
  10. package/dist/chunk-JXRS3OEA.js.map +1 -0
  11. package/dist/css/components/add-to-cart.css +72 -0
  12. package/dist/css/components/bidirectional-slider.css +168 -0
  13. package/dist/css/components/data-table.css +121 -26
  14. package/dist/css/components/filter-builder.css +10 -15
  15. package/dist/css/components/heatmap-chart.css +391 -0
  16. package/dist/css/components/hover-card.css +8 -2
  17. package/dist/css/components/image-preview.css +90 -2
  18. package/dist/css/components/json-viewer.css +33 -1
  19. package/dist/css/components/nav.css +102 -102
  20. package/dist/css/components/orbit-menu.css +130 -0
  21. package/dist/css/components/pagination.css +10 -5
  22. package/dist/css/components/pie-chart.css +390 -0
  23. package/dist/css/components/pill-button.css +135 -0
  24. package/dist/css/components/radar-chart.css +370 -0
  25. package/dist/css/components/radial-chart.css +398 -0
  26. package/dist/css/components/sankey-chart.css +358 -0
  27. package/dist/css/components/scroll-island.css +111 -0
  28. package/dist/css/components/select.css +136 -0
  29. package/dist/css/components/toolbar.css +122 -0
  30. package/dist/css/components/tree-view.css +79 -0
  31. package/dist/css/components/voice-dots.css +70 -0
  32. package/dist/elements/index.d.ts +180 -7
  33. package/dist/elements/index.js +287 -6
  34. package/dist/elements/index.js.map +1 -1
  35. package/dist/{engine-Cn6IhE3z.d.ts → engine-5zRIssTO.d.ts} +11 -1
  36. package/dist/index.css +1832 -168
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.js +33 -3
  39. package/dist/index.js.map +1 -1
  40. package/dist/react/index.d.ts +117 -11
  41. package/dist/react/index.js +314 -13
  42. package/dist/react/index.js.map +1 -1
  43. package/dist/{reduced-BBUusld6.d.ts → reduced-6X_bns60.d.ts} +1 -1
  44. package/dist/{reveal-DEvp6U6T.d.ts → reveal-BfyKKsw4.d.ts} +97 -4
  45. package/dist/store-CA2Qcun_.d.ts +453 -0
  46. package/package.json +9 -16
  47. package/registry.json +1096 -948
  48. package/src/auto/auto-init.ts +6 -0
  49. package/src/components/add-to-cart/README.md +34 -0
  50. package/src/components/add-to-cart/add-to-cart.css +71 -0
  51. package/src/components/add-to-cart/element.ts +20 -0
  52. package/src/components/add-to-cart/engine.ts +206 -0
  53. package/src/components/add-to-cart/react.tsx +29 -0
  54. package/src/components/bidirectional-slider/README.md +46 -0
  55. package/src/components/bidirectional-slider/bidirectional-slider.css +167 -0
  56. package/src/components/bidirectional-slider/element.ts +42 -0
  57. package/src/components/bidirectional-slider/engine.ts +289 -0
  58. package/src/components/bidirectional-slider/react.tsx +45 -0
  59. package/src/components/chart/README.md +10 -0
  60. package/src/components/chart/element.ts +2 -0
  61. package/src/components/chart/engine.ts +50 -23
  62. package/src/components/chart/parts.ts +237 -0
  63. package/src/components/chart/react.tsx +2 -2
  64. package/src/components/chart/use-chart.ts +55 -0
  65. package/src/components/data-table/README.md +3 -3
  66. package/src/components/data-table/data-table.css +121 -26
  67. package/src/components/data-table/engine.ts +81 -29
  68. package/src/components/editable-table/engine.ts +67 -11
  69. package/src/components/filter-builder/engine.ts +33 -2
  70. package/src/components/filter-builder/filter-builder.css +10 -15
  71. package/src/components/heatmap-chart/README.md +48 -0
  72. package/src/components/heatmap-chart/element.ts +21 -0
  73. package/src/components/heatmap-chart/engine.ts +281 -0
  74. package/src/components/heatmap-chart/heatmap-chart.css +93 -0
  75. package/src/components/heatmap-chart/react.tsx +14 -0
  76. package/src/components/hover-card/engine.ts +30 -1
  77. package/src/components/hover-card/hover-card.css +8 -2
  78. package/src/components/image-preview/element.ts +1 -1
  79. package/src/components/image-preview/engine.ts +95 -15
  80. package/src/components/image-preview/image-preview.css +90 -2
  81. package/src/components/image-preview/react.tsx +3 -3
  82. package/src/components/json-viewer/engine.ts +11 -2
  83. package/src/components/json-viewer/json-viewer.css +33 -1
  84. package/src/components/nav/README.md +12 -10
  85. package/src/components/nav/element.ts +5 -5
  86. package/src/components/nav/engine.ts +21 -14
  87. package/src/components/nav/nav.css +102 -102
  88. package/src/components/nav/react.tsx +4 -4
  89. package/src/components/orbit-menu/README.md +46 -0
  90. package/src/components/orbit-menu/element.ts +27 -0
  91. package/src/components/orbit-menu/engine.ts +179 -0
  92. package/src/components/orbit-menu/orbit-menu.css +129 -0
  93. package/src/components/orbit-menu/react.tsx +44 -0
  94. package/src/components/pagination/README.md +1 -1
  95. package/src/components/pagination/engine.ts +50 -4
  96. package/src/components/pagination/pagination.css +10 -5
  97. package/src/components/pie-chart/README.md +48 -0
  98. package/src/components/pie-chart/element.ts +30 -0
  99. package/src/components/pie-chart/engine.ts +283 -0
  100. package/src/components/pie-chart/pie-chart.css +92 -0
  101. package/src/components/pie-chart/react.tsx +14 -0
  102. package/src/components/pill-button/README.md +46 -0
  103. package/src/components/pill-button/element.ts +16 -0
  104. package/src/components/pill-button/engine.ts +77 -0
  105. package/src/components/pill-button/pill-button.css +134 -0
  106. package/src/components/pill-button/react.tsx +32 -0
  107. package/src/components/radar-chart/README.md +48 -0
  108. package/src/components/radar-chart/element.ts +30 -0
  109. package/src/components/radar-chart/engine.ts +327 -0
  110. package/src/components/radar-chart/radar-chart.css +72 -0
  111. package/src/components/radar-chart/react.tsx +14 -0
  112. package/src/components/radial-chart/README.md +48 -0
  113. package/src/components/radial-chart/element.ts +30 -0
  114. package/src/components/radial-chart/engine.ts +290 -0
  115. package/src/components/radial-chart/radial-chart.css +100 -0
  116. package/src/components/radial-chart/react.tsx +14 -0
  117. package/src/components/sankey-chart/README.md +48 -0
  118. package/src/components/sankey-chart/element.ts +31 -0
  119. package/src/components/sankey-chart/engine.ts +401 -0
  120. package/src/components/sankey-chart/react.tsx +14 -0
  121. package/src/components/sankey-chart/sankey-chart.css +60 -0
  122. package/src/components/scroll-island/README.md +34 -0
  123. package/src/components/scroll-island/element.ts +16 -0
  124. package/src/components/scroll-island/engine.ts +252 -0
  125. package/src/components/scroll-island/react.tsx +26 -0
  126. package/src/components/scroll-island/scroll-island.css +110 -0
  127. package/src/components/select/README.md +34 -0
  128. package/src/components/select/element.ts +26 -0
  129. package/src/components/select/engine.ts +267 -0
  130. package/src/components/select/react.tsx +33 -0
  131. package/src/components/select/select.css +135 -0
  132. package/src/components/toolbar/README.md +46 -0
  133. package/src/components/toolbar/element.ts +30 -0
  134. package/src/components/toolbar/engine.ts +230 -0
  135. package/src/components/toolbar/react.tsx +34 -0
  136. package/src/components/toolbar/toolbar.css +121 -0
  137. package/src/components/tree-view/element.ts +1 -1
  138. package/src/components/tree-view/engine.ts +174 -0
  139. package/src/components/tree-view/react.tsx +7 -3
  140. package/src/components/tree-view/tree-view.css +79 -0
  141. package/src/components/voice-dots/README.md +48 -0
  142. package/src/components/voice-dots/element.ts +45 -0
  143. package/src/components/voice-dots/engine.ts +243 -0
  144. package/src/components/voice-dots/react.tsx +52 -0
  145. package/src/components/voice-dots/voice-dots.css +69 -0
  146. package/src/elements/index.ts +39 -0
  147. package/src/index.ts +16 -0
  148. package/src/motion/liquid.ts +14 -4
  149. package/src/react/index.ts +14 -1
  150. package/dist/chunk-6P4P7NU6.js +0 -1401
  151. package/dist/chunk-6P4P7NU6.js.map +0 -1
  152. package/dist/chunk-CU6SXW3E.js.map +0 -1
  153. package/dist/store-DjkTBX7f.d.ts +0 -195
  154. /package/dist/{chunk-EY4BJKC4.js.map → chunk-6IRJYXJU.js.map} +0 -0
@@ -0,0 +1,252 @@
1
+ import { gsap, EASE, registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { createScope } from '../../core/scope';
4
+ import { inert, type Instance } from '../../core/types';
5
+ import { blurIn } from '../../motion/micro';
6
+
7
+ /*
8
+ * Scroll island: the scroller's own bar is hidden and a thin pill takes its
9
+ * place, shown while scrolling (it fades .9 s after). When the mouse comes near
10
+ * the edge, the pill swells into an island, a dark capsule (like iOS's island)
11
+ * with the percentage read and the current section (its last heading above a
12
+ * third of the view), on Melonmind's swing; dragging the island scrolls, a click
13
+ * on the rail glides there. Far from the edge it shrinks back into the bar. It
14
+ * follows the page or any scrolling element; touch keeps the native scrolling.
15
+ * The island is decorative (aria-hidden): keyboard and assistive tech scroll as
16
+ * always.
17
+ */
18
+
19
+ export interface ScrollIslandOptions {
20
+ /** Headings that name the sections (default 'h2, h3'); false: only the percentage. */
21
+ sections?: string | false;
22
+ /** How near the edge, in px, the pointer turns the bar into the island (default 56). */
23
+ reach?: number;
24
+ /** ms the bar stays after scrolling stops (default 900). */
25
+ idle?: number;
26
+ /** Follow the page's scroll (default: when the root is <html> or <body>). */
27
+ page?: boolean;
28
+ /** The current section changed (its heading's text, or null before the first). */
29
+ onSection?: (section: string | null) => void;
30
+ }
31
+
32
+ export interface ScrollIslandInstance extends Instance {
33
+ /** Measure again (after the content changed size). */
34
+ refresh(): void;
35
+ }
36
+
37
+ type State = 'hidden' | 'bar' | 'island';
38
+ /** Space kept from the scroller's edges. */
39
+ const INSET = 6;
40
+ const BAR_WIDTH = 4;
41
+ const ISLAND_HEIGHT = 36;
42
+ const MIN_BAR = 28;
43
+
44
+ export function createScrollIsland(root: HTMLElement | null, options: ScrollIslandOptions = {}): ScrollIslandInstance {
45
+ if (!root) return inert({ refresh() {} });
46
+ registerMotion();
47
+ const scope = createScope();
48
+ const page = options.page ?? (root === document.documentElement || root === document.body);
49
+ const scroller = page ? ((document.scrollingElement as HTMLElement | null) ?? document.documentElement) : root;
50
+ const reach = Number.isFinite(options.reach) ? Math.max(8, options.reach!) : 56;
51
+ const idle = Number.isFinite(options.idle) ? Math.max(0, options.idle!) : 900;
52
+ const marked = page ? document.documentElement : root;
53
+ // The scroller's own bar hides (scroll-island.css).
54
+ scope.attr(marked, 'data-mk-scroll-island-host', '');
55
+
56
+ const island = document.createElement('div');
57
+ island.dataset.mkScrollIsland = '';
58
+ island.dataset.mkState = 'hidden';
59
+ island.setAttribute('aria-hidden', 'true');
60
+ const rail = document.createElement('span');
61
+ rail.dataset.mkIslandRail = '';
62
+ const thumb = document.createElement('div');
63
+ thumb.dataset.mkIslandThumb = '';
64
+ const content = document.createElement('span');
65
+ content.dataset.mkIslandContent = '';
66
+ const percent = document.createElement('span');
67
+ percent.dataset.mkIslandPercent = '';
68
+ const sectionText = document.createElement('span');
69
+ sectionText.dataset.mkIslandSection = '';
70
+ content.append(percent, sectionText);
71
+ thumb.append(content);
72
+ island.append(rail, thumb);
73
+ document.body.append(island);
74
+ scope.add(() => island.remove());
75
+
76
+ const effects = new Set<gsap.core.Animation>();
77
+ const keep = (animation: gsap.core.Animation | null) => {
78
+ if (!animation) return;
79
+ effects.add(animation);
80
+ // Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
81
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
82
+ };
83
+ scope.add(() => {
84
+ for (const effect of effects) effect.kill();
85
+ effects.clear();
86
+ });
87
+
88
+ /** The scroller's box on screen (the window for the page). */
89
+ const frame = () => (page ? { top: 0, left: 0, right: innerWidth, bottom: innerHeight, height: innerHeight } : root.getBoundingClientRect());
90
+ const track = () => {
91
+ const box = frame();
92
+ return { top: box.top + INSET, height: Math.max(0, box.height - INSET * 2), right: box.right, left: box.left, bottom: box.bottom };
93
+ };
94
+ const range = () => Math.max(0, scroller.scrollHeight - scroller.clientHeight);
95
+ const scrollable = () => range() > 1;
96
+ const progress = () => (range() ? Math.min(1, Math.max(0, scroller.scrollTop / range())) : 0);
97
+
98
+ let state: State = 'hidden';
99
+ let section: string | null | undefined;
100
+ let hideTimer: ReturnType<typeof setTimeout> | undefined;
101
+ let dragging: { id: number; offset: number } | null = null;
102
+ scope.add(() => clearTimeout(hideTimer));
103
+
104
+ const headings = () => (options.sections === false ? [] : [...(page ? document.body : root).querySelectorAll<HTMLElement>(options.sections ?? 'h2, h3')]);
105
+ const currentSection = () => {
106
+ const box = frame();
107
+ const line = box.top + scroller.clientHeight * 0.3;
108
+ let found: HTMLElement | null = null;
109
+ for (const heading of headings()) if (heading.getBoundingClientRect().top <= line) found = heading;
110
+ return found?.textContent?.trim() || null;
111
+ };
112
+
113
+ /** Where the thumb goes: the bar's length follows the view; the island is a fixed capsule. */
114
+ const geometry = (as: State) => {
115
+ const { height } = track();
116
+ const size = as === 'island' ? ISLAND_HEIGHT : Math.max(MIN_BAR, Math.min(height, height * (scroller.clientHeight / Math.max(1, scroller.scrollHeight))));
117
+ const top = (height - size) * progress();
118
+ if (as !== 'island') return { top, height: size, width: BAR_WIDTH, borderRadius: BAR_WIDTH / 2 };
119
+ return { top, height: size, width: Math.max(ISLAND_HEIGHT, content.scrollWidth + 28), borderRadius: size / 2 };
120
+ };
121
+
122
+ const place = () => {
123
+ const box = track();
124
+ Object.assign(island.style, { top: `${box.top}px`, height: `${box.height}px`, left: `${box.left}px`, width: `${Math.max(0, box.right - box.left - 4)}px` });
125
+ };
126
+
127
+ const paintText = () => {
128
+ percent.textContent = `${Math.round(progress() * 100)} %`;
129
+ const now = currentSection();
130
+ if (now !== section) {
131
+ const first = section === undefined;
132
+ section = now;
133
+ sectionText.textContent = now ?? '';
134
+ sectionText.hidden = !now;
135
+ if (!first && state === 'island' && !reduced()) keep(blurIn(sectionText, { from: 'soft', duration: 0.3 }));
136
+ options.onSection?.(now);
137
+ root.dispatchEvent(new CustomEvent('mk:section', { bubbles: true, detail: { section: now } }));
138
+ }
139
+ };
140
+
141
+ /** Move to a state; `follow` keeps the thumb on the position without a tween (while scrolling or dragging). */
142
+ const go = (next: State, follow = false) => {
143
+ if (!scrollable()) next = 'hidden';
144
+ const changed = next !== state;
145
+ state = next;
146
+ island.dataset.mkState = next;
147
+ place();
148
+ const shape = geometry(next === 'hidden' ? 'bar' : next);
149
+ if (follow || !changed || reduced()) {
150
+ gsap.killTweensOf(thumb);
151
+ gsap.set(thumb, shape);
152
+ return;
153
+ }
154
+ gsap.killTweensOf(thumb);
155
+ keep(gsap.to(thumb, { ...shape, duration: next === 'island' ? 0.42 : 0.32, ease: next === 'island' ? EASE.swatch : 'power3.out' }));
156
+ };
157
+
158
+ const settleLater = () => {
159
+ clearTimeout(hideTimer);
160
+ hideTimer = setTimeout(() => {
161
+ if (state === 'bar' && !dragging) go('hidden');
162
+ }, idle);
163
+ };
164
+
165
+ const onScroll = () => {
166
+ paintText();
167
+ if (state === 'island' || dragging) go(state, true);
168
+ else {
169
+ go('bar', true);
170
+ settleLater();
171
+ }
172
+ };
173
+ scope.on(page ? window : root, 'scroll', onScroll, { passive: true });
174
+ // A scroller inside the page moves with the page: the overlay follows it.
175
+ if (!page) scope.on(window, 'scroll', () => state !== 'hidden' && place(), { passive: true, capture: true });
176
+ scope.on(window, 'resize', () => go(state, true));
177
+
178
+ const near = (x: number, y: number) => {
179
+ const box = track();
180
+ return y >= box.top - INSET && y <= box.top + box.height + INSET && x <= box.right && box.right - x <= reach;
181
+ };
182
+ scope.on(window, 'pointermove', (event) => {
183
+ const move = event as PointerEvent;
184
+ if (move.pointerType === 'touch') return;
185
+ if (dragging) {
186
+ if (move.pointerId !== dragging.id) return;
187
+ const box = track();
188
+ const shape = geometry('island');
189
+ const ratio = (move.clientY - box.top - dragging.offset) / Math.max(1, box.height - shape.height);
190
+ scroller.scrollTop = Math.min(1, Math.max(0, ratio)) * range();
191
+ onScroll();
192
+ return;
193
+ }
194
+ if (near(move.clientX, move.clientY)) {
195
+ if (state !== 'island' && scrollable()) {
196
+ clearTimeout(hideTimer);
197
+ paintText();
198
+ go('island');
199
+ }
200
+ } else if (state === 'island') {
201
+ const box = track();
202
+ // A little slack, so the island does not flicker at the border of its reach.
203
+ if (box.right - move.clientX > reach + 24 || move.clientY < box.top - 24 || move.clientY > box.top + box.height + 24) {
204
+ go('bar');
205
+ settleLater();
206
+ }
207
+ }
208
+ });
209
+
210
+ // Dragging the island scrolls; a click on the rail glides there.
211
+ scope.on(thumb, 'pointerdown', (event) => {
212
+ const down = event as PointerEvent;
213
+ if (state !== 'island' || down.button !== 0) return;
214
+ down.preventDefault();
215
+ down.stopPropagation();
216
+ const box = thumb.getBoundingClientRect();
217
+ dragging = { id: down.pointerId, offset: down.clientY - box.top || ISLAND_HEIGHT / 2 };
218
+ island.setAttribute('data-mk-dragging', '');
219
+ try {
220
+ thumb.setPointerCapture(down.pointerId);
221
+ } catch {
222
+ // jsdom and synthetic events have no active pointer to capture.
223
+ }
224
+ });
225
+ const release = (event: Event) => {
226
+ if (!dragging || (event as PointerEvent).pointerId !== dragging.id) return;
227
+ dragging = null;
228
+ island.removeAttribute('data-mk-dragging');
229
+ };
230
+ scope.on(window, 'pointerup', release);
231
+ scope.on(window, 'pointercancel', release);
232
+ scope.on(rail, 'pointerdown', (event) => {
233
+ const down = event as PointerEvent;
234
+ if (state !== 'island' || down.button !== 0) return;
235
+ down.preventDefault();
236
+ const box = track();
237
+ const size = geometry('island').height;
238
+ const ratio = (down.clientY - box.top - size / 2) / Math.max(1, box.height - size);
239
+ scroller.scrollTo({ top: Math.round(Math.min(1, Math.max(0, ratio)) * range()), behavior: reduced() ? 'auto' : 'smooth' });
240
+ });
241
+
242
+ paintText();
243
+ go('hidden');
244
+
245
+ return {
246
+ refresh: () => {
247
+ paintText();
248
+ go(state, true);
249
+ },
250
+ destroy: () => scope.dispose(),
251
+ };
252
+ }
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createScrollIsland, type ScrollIslandOptions } from './engine';
5
+
6
+ export interface ScrollIslandProps extends HTMLAttributes<HTMLDivElement>, ScrollIslandOptions {}
7
+
8
+ /**
9
+ * <ScrollIsland style={{ height: 400, overflow: 'auto' }}>…</ScrollIsland> scrolls its children with the island;
10
+ * <ScrollIsland page /> puts it on the page (it renders nothing of its own then).
11
+ */
12
+ export function ScrollIsland({ sections, reach, idle, page, onSection, className, children, ...props }: ScrollIslandProps) {
13
+ const ref = useRef<HTMLDivElement>(null);
14
+ const section = useLatest(onSection);
15
+ useIsomorphicLayoutEffect(() => {
16
+ const created = createScrollIsland(page ? document.documentElement : ref.current, { sections, reach, idle, page, onSection: (name) => section.current?.(name) });
17
+ return () => created.destroy();
18
+ // eslint-disable-next-line react-hooks/exhaustive-deps
19
+ }, [sections, reach, idle, page]);
20
+ if (page) return null;
21
+ return (
22
+ <div {...props} ref={ref} className={['mk-scroll-island', className].filter(Boolean).join(' ')}>
23
+ {children}
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,110 @@
1
+ /* scroll-island — the scroller's bar hides and a thin pill takes its place while scrolling; near the edge it swells
2
+ into a dark capsule (iOS-island style) with the percentage and the current section, which can be dragged. */
3
+ .mk-scroll-island,
4
+ mk-scroll-island {
5
+ display: block;
6
+ }
7
+
8
+ [data-mk-scroll-island-host] {
9
+ scrollbar-width: none;
10
+ }
11
+
12
+ [data-mk-scroll-island-host]::-webkit-scrollbar {
13
+ display: none;
14
+ }
15
+
16
+ [data-mk-scroll-island] {
17
+ position: fixed;
18
+ z-index: 2147483640;
19
+ pointer-events: none;
20
+ }
21
+
22
+ [data-mk-island-rail] {
23
+ position: absolute;
24
+ top: 0;
25
+ right: 0;
26
+ bottom: 0;
27
+ width: 1.5rem;
28
+ cursor: pointer;
29
+ pointer-events: none;
30
+ }
31
+
32
+ [data-mk-island-thumb] {
33
+ position: absolute;
34
+ top: 0;
35
+ right: 0;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ width: 4px;
40
+ overflow: hidden;
41
+ border-radius: 999px;
42
+ background: color-mix(in srgb, var(--mk-text) 42%, transparent);
43
+ color: #fff;
44
+ opacity: 0;
45
+ transition:
46
+ opacity 0.25s ease,
47
+ background-color 0.3s ease,
48
+ box-shadow 0.3s ease;
49
+ }
50
+
51
+ [data-mk-scroll-island][data-mk-state='bar'] [data-mk-island-thumb] {
52
+ opacity: 1;
53
+ }
54
+
55
+ [data-mk-scroll-island][data-mk-state='island'] [data-mk-island-rail],
56
+ [data-mk-scroll-island][data-mk-state='island'] [data-mk-island-thumb] {
57
+ pointer-events: auto;
58
+ }
59
+
60
+ [data-mk-scroll-island][data-mk-state='island'] [data-mk-island-thumb] {
61
+ background: #0c0c0e;
62
+ box-shadow:
63
+ inset 0 0 0 1px rgb(255 255 255 / 0.08),
64
+ 0 12px 30px -8px rgb(0 0 0 / 0.45);
65
+ opacity: 1;
66
+ cursor: grab;
67
+ }
68
+
69
+ [data-mk-scroll-island][data-mk-dragging] [data-mk-island-thumb] {
70
+ cursor: grabbing;
71
+ }
72
+
73
+ [data-mk-island-content] {
74
+ display: inline-flex;
75
+ flex: none;
76
+ align-items: center;
77
+ gap: 0.5rem;
78
+ padding: 0 14px;
79
+ font-family: var(--mk-font);
80
+ font-size: 0.8125rem;
81
+ font-weight: 600;
82
+ font-variant-numeric: tabular-nums;
83
+ white-space: nowrap;
84
+ opacity: 0;
85
+ transition: opacity 0.18s ease;
86
+ }
87
+
88
+ [data-mk-scroll-island][data-mk-state='island'] [data-mk-island-content] {
89
+ opacity: 1;
90
+ transition-delay: 0.1s;
91
+ }
92
+
93
+ [data-mk-island-section] {
94
+ max-width: 12rem;
95
+ overflow: hidden;
96
+ color: rgb(255 255 255 / 0.68);
97
+ font-weight: 500;
98
+ text-overflow: ellipsis;
99
+ }
100
+
101
+ [data-mk-island-section][hidden] {
102
+ display: none;
103
+ }
104
+
105
+ @media (prefers-reduced-motion: reduce) {
106
+ [data-mk-island-thumb],
107
+ [data-mk-island-content] {
108
+ transition: none;
109
+ }
110
+ }
@@ -0,0 +1,34 @@
1
+ # select
2
+
3
+ Qué hace, en una frase.
4
+
5
+ Origen: archivo del kit y parámetros (tiempos, curvas).
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <mk-select></mk-select>
11
+ <script>
12
+ import { defineElements } from '@yewdiel21/motion-kit/elements';
13
+ defineElements(['mk-select']);
14
+ </script>
15
+ ```
16
+
17
+ ## React
18
+
19
+ ```tsx
20
+ <Select />
21
+ ```
22
+
23
+ ## Opciones
24
+
25
+ | Opción | Atributo | Por defecto | Qué hace |
26
+ |---|---|---|---|
27
+
28
+ ## Variables CSS
29
+
30
+ `--mk-select-radius`
31
+
32
+ ## Accesibilidad
33
+
34
+ Roles, foco y teclado. Con movimiento reducido…
@@ -0,0 +1,26 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createSelect, type SelectInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-select class="mk-select" label="Equipo"><select name="equipo">…</select></mk-select>
6
+ * The select inside gives the options and stays in step (forms post it). Emits mk:change ({value}).
7
+ */
8
+ export class MkSelectElement extends MkElement<SelectInstance> {
9
+ static options: Record<string, OptionType> = { value: 'string', name: 'string', label: 'string', placeholder: 'string', disabled: 'boolean' };
10
+
11
+ protected create(options: Record<string, unknown>) {
12
+ return createSelect(this, options);
13
+ }
14
+
15
+ attributeChangedCallback(name: string, previous: string | null, next: string | null) {
16
+ if (previous === next || !this.instance) return;
17
+ if (name === 'value' && next !== null) this.instance.setValue(next, false);
18
+ else super.attributeChangedCallback(name, previous, next);
19
+ }
20
+
21
+ get value() {
22
+ return this.instance?.value ?? '';
23
+ }
24
+ }
25
+
26
+ export const defineSelect = () => defineElement('mk-select', MkSelectElement);