@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.
Files changed (146) hide show
  1. package/README.md +23 -0
  2. package/dist/auto/index.d.ts +18 -3
  3. package/dist/auto/index.js +32 -2
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-VJFPTLAK.js → chunk-AWDEKDEJ.js} +2 -2
  6. package/dist/{chunk-BZLMCHUD.js → chunk-EDMXWUW3.js} +2 -2
  7. package/dist/{chunk-7IQTMF7Y.js → chunk-Q7IW4FV3.js} +2580 -58
  8. package/dist/chunk-Q7IW4FV3.js.map +1 -0
  9. package/dist/css/components/badge-button.css +16 -0
  10. package/dist/css/components/bento-grid.css +13 -0
  11. package/dist/css/components/drilldown-menu.css +11 -7
  12. package/dist/css/components/equalizer-slider.css +26 -0
  13. package/dist/css/components/handwriting-text.css +15 -0
  14. package/dist/css/components/magnetize-button.css +18 -0
  15. package/dist/css/components/morphing-page-dots.css +13 -0
  16. package/dist/css/components/not-found.css +22 -0
  17. package/dist/css/components/notification-stack.css +42 -0
  18. package/dist/css/components/particle-button.css +14 -0
  19. package/dist/css/components/radio-group.css +29 -0
  20. package/dist/css/components/rating-scale.css +23 -0
  21. package/dist/css/components/scroll-preview-dock.css +18 -0
  22. package/dist/css/components/select.css +16 -0
  23. package/dist/css/components/signature-pad.css +130 -103
  24. package/dist/css/components/stepper.css +36 -0
  25. package/dist/css/components/sticky-header.css +10 -0
  26. package/dist/css/components/temperature-slider.css +29 -0
  27. package/dist/css/components/voice-message-bubble.css +62 -45
  28. package/dist/css/components/waveform-player.css +49 -34
  29. package/dist/css/components/wheel-picker.css +16 -15
  30. package/dist/css/tokens.css +4 -0
  31. package/dist/elements/index.d.ts +206 -4
  32. package/dist/elements/index.js +581 -6
  33. package/dist/elements/index.js.map +1 -1
  34. package/dist/{engine-Bn6hC87X.d.ts → engine-t8d7brjn.d.ts} +341 -1
  35. package/dist/index.css +578 -170
  36. package/dist/index.d.ts +23 -23
  37. package/dist/index.js +33 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/react/index.d.ts +113 -7
  40. package/dist/react/index.js +669 -3
  41. package/dist/react/index.js.map +1 -1
  42. package/dist/{reduced-YS6TZjpa.d.ts → reduced-dPsZYqtI.d.ts} +1 -1
  43. package/dist/{store-CZ6vdtIE.d.ts → store-6hZNKiee.d.ts} +1 -1
  44. package/package.json +1 -1
  45. package/registry.json +180 -0
  46. package/src/auto/auto-init.ts +31 -0
  47. package/src/components/badge-button/README.md +28 -0
  48. package/src/components/badge-button/badge-button.css +15 -0
  49. package/src/components/badge-button/element.ts +16 -0
  50. package/src/components/badge-button/engine.ts +85 -0
  51. package/src/components/badge-button/react.tsx +31 -0
  52. package/src/components/bento-grid/README.md +26 -0
  53. package/src/components/bento-grid/bento-grid.css +12 -0
  54. package/src/components/bento-grid/element.ts +22 -0
  55. package/src/components/bento-grid/engine.ts +153 -0
  56. package/src/components/bento-grid/react.tsx +25 -0
  57. package/src/components/drilldown-menu/drilldown-menu.css +11 -7
  58. package/src/components/drilldown-menu/engine.ts +44 -4
  59. package/src/components/equalizer-slider/README.md +43 -0
  60. package/src/components/equalizer-slider/element.ts +41 -0
  61. package/src/components/equalizer-slider/engine.ts +32 -0
  62. package/src/components/equalizer-slider/equalizer-slider.css +26 -0
  63. package/src/components/equalizer-slider/react.tsx +60 -0
  64. package/src/components/equalizer-slider/shared.ts +161 -0
  65. package/src/components/handwriting-text/README.md +29 -0
  66. package/src/components/handwriting-text/element.ts +14 -0
  67. package/src/components/handwriting-text/engine.ts +108 -0
  68. package/src/components/handwriting-text/glyphs.ts +71 -0
  69. package/src/components/handwriting-text/handwriting-text.css +14 -0
  70. package/src/components/handwriting-text/react.tsx +23 -0
  71. package/src/components/magnetize-button/README.md +26 -0
  72. package/src/components/magnetize-button/element.ts +13 -0
  73. package/src/components/magnetize-button/engine.ts +84 -0
  74. package/src/components/magnetize-button/magnetize-button.css +17 -0
  75. package/src/components/magnetize-button/react.tsx +22 -0
  76. package/src/components/morphing-page-dots/README.md +28 -0
  77. package/src/components/morphing-page-dots/element.ts +16 -0
  78. package/src/components/morphing-page-dots/engine.ts +89 -0
  79. package/src/components/morphing-page-dots/morphing-page-dots.css +12 -0
  80. package/src/components/morphing-page-dots/react.tsx +32 -0
  81. package/src/components/not-found/README.md +26 -0
  82. package/src/components/not-found/element.ts +15 -0
  83. package/src/components/not-found/engine.ts +57 -0
  84. package/src/components/not-found/not-found.css +21 -0
  85. package/src/components/not-found/react.tsx +23 -0
  86. package/src/components/notification-stack/README.md +24 -0
  87. package/src/components/notification-stack/element.ts +26 -0
  88. package/src/components/notification-stack/engine.ts +329 -0
  89. package/src/components/notification-stack/notification-stack.css +41 -0
  90. package/src/components/notification-stack/react.tsx +28 -0
  91. package/src/components/particle-button/README.md +26 -0
  92. package/src/components/particle-button/element.ts +13 -0
  93. package/src/components/particle-button/engine.ts +52 -0
  94. package/src/components/particle-button/particle-button.css +13 -0
  95. package/src/components/particle-button/react.tsx +22 -0
  96. package/src/components/radio-group/README.md +43 -0
  97. package/src/components/radio-group/element.ts +41 -0
  98. package/src/components/radio-group/engine.ts +11 -0
  99. package/src/components/radio-group/radio-group.css +28 -0
  100. package/src/components/radio-group/react.tsx +60 -0
  101. package/src/components/radio-group/shared.ts +118 -0
  102. package/src/components/rating-scale/README.md +43 -0
  103. package/src/components/rating-scale/element.ts +41 -0
  104. package/src/components/rating-scale/engine.ts +63 -0
  105. package/src/components/rating-scale/rating-scale.css +22 -0
  106. package/src/components/rating-scale/react.tsx +60 -0
  107. package/src/components/scroll-preview-dock/README.md +24 -0
  108. package/src/components/scroll-preview-dock/element.ts +20 -0
  109. package/src/components/scroll-preview-dock/engine.ts +228 -0
  110. package/src/components/scroll-preview-dock/react.tsx +26 -0
  111. package/src/components/scroll-preview-dock/scroll-preview-dock.css +17 -0
  112. package/src/components/select/README.md +23 -9
  113. package/src/components/select/select.css +16 -0
  114. package/src/components/signature-pad/engine.ts +550 -437
  115. package/src/components/signature-pad/signature-pad.css +130 -103
  116. package/src/components/stepper/README.md +46 -0
  117. package/src/components/stepper/element.ts +42 -0
  118. package/src/components/stepper/engine.ts +103 -0
  119. package/src/components/stepper/react.tsx +60 -0
  120. package/src/components/stepper/stepper.css +35 -0
  121. package/src/components/sticky-header/README.md +30 -0
  122. package/src/components/sticky-header/element.ts +19 -0
  123. package/src/components/sticky-header/engine.ts +95 -0
  124. package/src/components/sticky-header/react.tsx +25 -0
  125. package/src/components/sticky-header/sticky-header.css +9 -0
  126. package/src/components/temperature-slider/README.md +42 -0
  127. package/src/components/temperature-slider/element.ts +53 -0
  128. package/src/components/temperature-slider/engine.ts +44 -0
  129. package/src/components/temperature-slider/react.tsx +66 -0
  130. package/src/components/temperature-slider/temperature-slider.css +29 -0
  131. package/src/components/voice-message-bubble/engine.ts +11 -0
  132. package/src/components/voice-message-bubble/voice-message-bubble.css +14 -12
  133. package/src/components/waveform-player/README.md +1 -1
  134. package/src/components/waveform-player/engine.ts +344 -261
  135. package/src/components/waveform-player/waveform-player.css +50 -35
  136. package/src/components/wheel-picker/README.md +3 -1
  137. package/src/components/wheel-picker/element.ts +12 -5
  138. package/src/components/wheel-picker/engine.ts +37 -8
  139. package/src/components/wheel-picker/wheel-picker.css +17 -16
  140. package/src/css/tokens.css +4 -0
  141. package/src/elements/index.ts +45 -0
  142. package/src/index.ts +311 -295
  143. package/src/react/index.ts +16 -0
  144. package/dist/chunk-7IQTMF7Y.js.map +0 -1
  145. /package/dist/{chunk-VJFPTLAK.js.map → chunk-AWDEKDEJ.js.map} +0 -0
  146. /package/dist/{chunk-BZLMCHUD.js.map → chunk-EDMXWUW3.js.map} +0 -0
@@ -1,35 +1,50 @@
1
- .mk-waveform-player, mk-waveform-player {
2
- --mk-wave-color: var(--mk-accent);
3
- --mk-wave-track: color-mix(in srgb, var(--mk-text) 38%, transparent);
4
- display: block;
5
- width: 100%;
6
- min-width: 0;
7
- color: var(--mk-text);
8
- font-family: var(--mk-font);
9
- }
10
- [data-mk-wave-content] { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: .875rem; }
11
- [data-mk-wave-toggle] { display: grid; place-items: center; width: 3rem; height: 3rem; border: 0; border-radius: 50%; background: var(--mk-wave-color); color: var(--mk-on-accent, var(--mk-bg)); cursor: pointer; }
12
- [data-mk-wave-toggle] svg { width: 1.4rem; height: 1.4rem; fill: currentColor; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
13
- [data-mk-wave-body] { min-width: 0; }
14
- [data-mk-wave-track] { position: relative; height: 3rem; border-radius: .5rem; }
15
- [data-mk-wave-bars] { display: flex; align-items: center; gap: 3px; height: 100%; pointer-events: none; overflow: hidden; }
16
- [data-mk-wave-bar] { display: block; flex: 1 1 0; min-width: 1px; border-radius: 999px; background: var(--mk-wave-track); transition: background-color .16s ease; }
17
- [data-mk-wave-bar][data-mk-played] { background: var(--mk-wave-color); }
18
- [data-mk-wave-seek] { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
19
- [data-mk-wave-track]:has(input:focus-visible) { outline: 2px solid var(--mk-wave-color); outline-offset: 4px; }
20
- [data-mk-wave-footer] { display: flex; justify-content: space-between; align-items: center; gap: .75rem; min-height: 2rem; }
21
- [data-mk-wave-time] { color: var(--mk-muted); font-size: .75rem; font-variant-numeric: tabular-nums; }
22
- [data-mk-wave-rate] { min-width: 44px; min-height: 44px; padding: .25rem .5rem; border: 0; border-radius: 99px; color: var(--mk-text); background: var(--mk-surface-2); font: 600 .75rem var(--mk-font); cursor: pointer; }
23
- [data-mk-wave-rate][hidden] { display: none; }
24
- [data-mk-wave-message] { grid-column: 2; color: var(--mk-muted); font-size: .75rem; line-height: 1.5; }
25
- [data-mk-wave-message]:empty { display: none; }
26
- [data-mk-wave-toggle]:focus-visible, [data-mk-wave-rate]:focus-visible { outline: 2px solid var(--mk-wave-color); outline-offset: 3px; }
27
- [data-mk-wave-toggle]:disabled, [data-mk-wave-rate]:disabled { opacity: .5; cursor: default; }
28
- [data-mk-wave-track]:has(input:disabled) { opacity: .55; }
29
- [data-mk-wave-state='error'] [data-mk-wave-message] { color: var(--mk-error, var(--mk-text)); }
30
- @media (prefers-reduced-motion: reduce) { [data-mk-wave-bar] { transition: none; } }
31
- @media (forced-colors: active) {
32
- [data-mk-wave-toggle], [data-mk-wave-rate] { border: 1px solid ButtonText; }
33
- [data-mk-wave-bar] { background: GrayText; }
34
- [data-mk-wave-bar][data-mk-played] { background: Highlight; }
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
- El movimiento reducido elimina la inclinación y 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.
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
- protected create(options: Record<string, unknown>) { return createWheelPicker(this, options); }
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 || !this.instance) return;
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
- super.attributeChangedCallback(name, previous, next);
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 = 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})`;
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 { options.onChange?.(value); status.textContent = items[current]?.label ?? ''; }
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() && typeof viewport.scrollTo === 'function') {
97
- programmatic = top; viewport.scrollTo({ top, behavior: 'smooth' });
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
- if (programmatic !== null && typeof viewport.scrollTo === 'function') viewport.scrollTo({ top: viewport.scrollTop, behavior: 'instant' });
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: 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; } }
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; } }
@@ -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);
@@ -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