@yewdiel21/motion-kit 0.18.0 → 0.19.1

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 (75) hide show
  1. package/README.md +245 -222
  2. package/bin/motion-kit.mjs +0 -0
  3. package/dist/auto/index.d.ts +4 -3
  4. package/dist/auto/index.js +5 -3
  5. package/dist/auto/index.js.map +1 -1
  6. package/dist/{chunk-QFBOHPWZ.js → chunk-CQP66AZK.js} +2 -2
  7. package/dist/{chunk-QP4QJOEN.js → chunk-H4STLMCM.js} +2 -2
  8. package/dist/{chunk-AWDEKDEJ.js → chunk-LIXSOGS2.js} +8 -22
  9. package/dist/chunk-LIXSOGS2.js.map +1 -0
  10. package/dist/{chunk-Q7IW4FV3.js → chunk-NCGNIPJE.js} +1430 -968
  11. package/dist/chunk-NCGNIPJE.js.map +1 -0
  12. package/dist/{chunk-DOOMULVV.js → chunk-OLT3CWDJ.js} +4 -2
  13. package/dist/chunk-OLT3CWDJ.js.map +1 -0
  14. package/dist/{chunk-EDMXWUW3.js → chunk-PVN7YVKV.js} +3 -3
  15. package/dist/css/components/code-block.css +267 -0
  16. package/dist/css/components/command-search.css +274 -2
  17. package/dist/css/components/dialog.css +264 -0
  18. package/dist/css/components/heatmap-chart.css +267 -1
  19. package/dist/css/components/pill-button.css +7 -5
  20. package/dist/css/components/scroll-area.css +261 -0
  21. package/dist/css/components/scroll-island.css +6 -0
  22. package/dist/css/components/select.css +284 -4
  23. package/dist/elements/index.d.ts +15 -4
  24. package/dist/elements/index.js +36 -6
  25. package/dist/elements/index.js.map +1 -1
  26. package/dist/{engine-t8d7brjn.d.ts → engine-D6XV-WNt.d.ts} +50 -1
  27. package/dist/gl/index.js +2 -2
  28. package/dist/index.css +321 -12
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +7 -5
  31. package/dist/index.js.map +1 -1
  32. package/dist/react/index.d.ts +20 -7
  33. package/dist/react/index.js +56 -7
  34. package/dist/react/index.js.map +1 -1
  35. package/dist/{reduced-dPsZYqtI.d.ts → reduced-4croIXl-.d.ts} +1 -1
  36. package/dist/{store-6hZNKiee.d.ts → store-CAHWkurn.d.ts} +1 -1
  37. package/package.json +117 -110
  38. package/registry.json +1357 -1340
  39. package/src/auto/auto-init.ts +341 -339
  40. package/src/components/code-block/README.md +1 -1
  41. package/src/components/code-block/code-block.css +5 -0
  42. package/src/components/code-block/engine.ts +5 -0
  43. package/src/components/command-search/README.md +1 -1
  44. package/src/components/command-search/command-search.css +12 -2
  45. package/src/components/command-search/engine.ts +6 -0
  46. package/src/components/dialog/README.md +104 -103
  47. package/src/components/dialog/dialog.css +2 -0
  48. package/src/components/dialog/engine.ts +465 -449
  49. package/src/components/heatmap-chart/README.md +48 -48
  50. package/src/components/heatmap-chart/engine.ts +6 -1
  51. package/src/components/heatmap-chart/heatmap-chart.css +5 -1
  52. package/src/components/icons.ts +1 -0
  53. package/src/components/pill-button/engine.ts +1 -1
  54. package/src/components/pill-button/pill-button.css +7 -5
  55. package/src/components/scroll-area/README.md +89 -0
  56. package/src/components/scroll-area/element.ts +35 -0
  57. package/src/components/scroll-area/engine.ts +528 -0
  58. package/src/components/scroll-area/react.tsx +67 -0
  59. package/src/components/scroll-area/scroll-area.css +260 -0
  60. package/src/components/scroll-island/engine.ts +10 -1
  61. package/src/components/scroll-island/scroll-island.css +6 -0
  62. package/src/components/select/README.md +1 -1
  63. package/src/components/select/engine.ts +16 -4
  64. package/src/components/select/select.css +22 -4
  65. package/src/core/timing.ts +2 -0
  66. package/src/elements/index.ts +321 -318
  67. package/src/index.ts +1 -0
  68. package/src/motion/morph.ts +5 -2
  69. package/src/react/index.ts +2 -1
  70. package/dist/chunk-AWDEKDEJ.js.map +0 -1
  71. package/dist/chunk-DOOMULVV.js.map +0 -1
  72. package/dist/chunk-Q7IW4FV3.js.map +0 -1
  73. /package/dist/{chunk-QFBOHPWZ.js.map → chunk-CQP66AZK.js.map} +0 -0
  74. /package/dist/{chunk-QP4QJOEN.js.map → chunk-H4STLMCM.js.map} +0 -0
  75. /package/dist/{chunk-EDMXWUW3.js.map → chunk-PVN7YVKV.js.map} +0 -0
@@ -0,0 +1,260 @@
1
+ /* scroll-area — the scroller keeps native scrolling with its system bar hidden; the kit's bars ride on top: a 6 px pill
2
+ (text at 32 %) that shows while hovered or scrolling, swells to 10 px over a tonal rail on the swing curve and turns the
3
+ accent while dragged. Edges with more content fade out; an optional tonal pill takes the view back to the top. */
4
+ @property --mk-scroll-thickness {
5
+ syntax: '<length>';
6
+ inherits: true;
7
+ initial-value: 6px;
8
+ }
9
+
10
+ .mk-scroll-area,
11
+ mk-scroll-area {
12
+ --mk-scroll-thumb: color-mix(in srgb, var(--mk-text) 32%, transparent);
13
+ --mk-scroll-thumb-hover: color-mix(in srgb, var(--mk-text) 48%, transparent);
14
+ --mk-scroll-rail: color-mix(in srgb, var(--mk-text) 6%, transparent);
15
+ display: flex;
16
+ flex-direction: column;
17
+ min-width: 0;
18
+ min-height: 0;
19
+ overflow: hidden;
20
+ }
21
+
22
+ /* Its own viewport fills the area (a height or a max-height on the area both work). */
23
+ :is(.mk-scroll-area, mk-scroll-area) > [data-mk-scroll-viewport] {
24
+ flex: 1 1 auto;
25
+ min-width: 0;
26
+ min-height: 0;
27
+ overflow: auto;
28
+ overscroll-behavior: contain;
29
+ border-radius: inherit;
30
+ outline: none;
31
+ }
32
+
33
+ :is(.mk-scroll-area, mk-scroll-area)[data-mk-scroll-axis='y'] > [data-mk-scroll-viewport] {
34
+ overflow-x: hidden;
35
+ }
36
+
37
+ :is(.mk-scroll-area, mk-scroll-area)[data-mk-scroll-axis='x'] > [data-mk-scroll-viewport] {
38
+ overflow-y: hidden;
39
+ }
40
+
41
+ [data-mk-scroll-anchor] {
42
+ position: relative;
43
+ }
44
+
45
+ /* The kit's bars replace the system's wherever the scroller's own styles asked for a thin one. */
46
+ [data-mk-scroll-viewport] {
47
+ scrollbar-width: none !important;
48
+ }
49
+
50
+ [data-mk-scroll-viewport]::-webkit-scrollbar {
51
+ display: none !important;
52
+ }
53
+
54
+ [data-mk-scroll-area]:has(> [data-mk-scroll-viewport]:focus-visible) {
55
+ outline: 2px solid var(--mk-accent);
56
+ outline-offset: 2px;
57
+ }
58
+
59
+ /* Edge fades: as deep as what is hidden past each edge, up to 24 px (written by the engine). */
60
+ [data-mk-fade] > [data-mk-scroll-viewport] {
61
+ --mk-scroll-fade-top: 0px;
62
+ --mk-scroll-fade-right: 0px;
63
+ --mk-scroll-fade-bottom: 0px;
64
+ --mk-scroll-fade-left: 0px;
65
+ -webkit-mask-image:
66
+ linear-gradient(to bottom, transparent, #000 var(--mk-scroll-fade-top), #000 calc(100% - var(--mk-scroll-fade-bottom)), transparent),
67
+ linear-gradient(to right, transparent, #000 var(--mk-scroll-fade-left), #000 calc(100% - var(--mk-scroll-fade-right)), transparent);
68
+ -webkit-mask-composite: source-in;
69
+ mask-image:
70
+ linear-gradient(to bottom, transparent, #000 var(--mk-scroll-fade-top), #000 calc(100% - var(--mk-scroll-fade-bottom)), transparent),
71
+ linear-gradient(to right, transparent, #000 var(--mk-scroll-fade-left), #000 calc(100% - var(--mk-scroll-fade-right)), transparent);
72
+ mask-composite: intersect;
73
+ }
74
+
75
+ /* Rails: a 14 px grip along the edge; its tonal rail and the pill inside are drawn smaller. */
76
+ [data-mk-scroll-rail] {
77
+ --mk-scroll-thickness: 6px;
78
+ position: absolute;
79
+ z-index: 2;
80
+ opacity: 0;
81
+ touch-action: none;
82
+ -webkit-user-select: none;
83
+ user-select: none;
84
+ transition:
85
+ opacity 0.2s ease,
86
+ --mk-scroll-thickness 0.35s var(--mk-ease-swatch);
87
+ }
88
+
89
+ [data-mk-scroll-rail][hidden] {
90
+ display: none;
91
+ }
92
+
93
+ /* The rails run along the viewport's box inside the root (--mk-scroll-view-*, written by the engine);
94
+ --mk-scroll-inset keeps their ends clear of rounded corners. */
95
+ [data-mk-scroll-rail][data-mk-axis='y'] {
96
+ top: calc(var(--mk-scroll-view-top, 0px) + var(--mk-scroll-inset, 6px));
97
+ right: var(--mk-scroll-view-right, 0px);
98
+ bottom: calc(var(--mk-scroll-view-bottom, 0px) + var(--mk-scroll-inset, 6px));
99
+ width: 14px;
100
+ }
101
+
102
+ [data-mk-scroll-rail][data-mk-axis='x'] {
103
+ right: calc(var(--mk-scroll-view-right, 0px) + var(--mk-scroll-inset, 6px));
104
+ bottom: var(--mk-scroll-view-bottom, 0px);
105
+ left: calc(var(--mk-scroll-view-left, 0px) + var(--mk-scroll-inset, 6px));
106
+ height: 14px;
107
+ }
108
+
109
+ /* Both bars: each stops short of the other, no corner square. */
110
+ [data-mk-overflow-x] > [data-mk-scroll-rail][data-mk-axis='y'] {
111
+ bottom: calc(var(--mk-scroll-view-bottom, 0px) + 14px);
112
+ }
113
+
114
+ [data-mk-overflow-y] > [data-mk-scroll-rail][data-mk-axis='x'] {
115
+ right: calc(var(--mk-scroll-view-right, 0px) + 14px);
116
+ }
117
+
118
+ [data-mk-active] > [data-mk-scroll-rail],
119
+ [data-mk-scroll-rail]:hover,
120
+ [data-mk-scroll-rail][data-mk-dragging] {
121
+ opacity: 1;
122
+ }
123
+
124
+ [data-mk-scroll-rail]:hover,
125
+ [data-mk-scroll-rail][data-mk-dragging] {
126
+ --mk-scroll-thickness: 10px;
127
+ }
128
+
129
+ [data-mk-scroll-rail]::before {
130
+ content: '';
131
+ position: absolute;
132
+ border-radius: 999px;
133
+ background: var(--mk-scroll-rail, color-mix(in srgb, var(--mk-text) 6%, transparent));
134
+ opacity: 0;
135
+ transition: opacity 0.2s ease;
136
+ }
137
+
138
+ [data-mk-scroll-rail][data-mk-axis='y']::before {
139
+ top: 0;
140
+ right: 2px;
141
+ bottom: 0;
142
+ width: 10px;
143
+ }
144
+
145
+ [data-mk-scroll-rail][data-mk-axis='x']::before {
146
+ right: 0;
147
+ bottom: 2px;
148
+ left: 0;
149
+ height: 10px;
150
+ }
151
+
152
+ [data-mk-scroll-rail]:hover::before,
153
+ [data-mk-scroll-rail][data-mk-dragging]::before {
154
+ opacity: 1;
155
+ }
156
+
157
+ [data-mk-scroll-thumb] {
158
+ position: absolute;
159
+ top: 0;
160
+ left: 0;
161
+ cursor: grab;
162
+ will-change: transform;
163
+ }
164
+
165
+ [data-mk-scroll-rail][data-mk-axis='y'] [data-mk-scroll-thumb] {
166
+ width: 100%;
167
+ }
168
+
169
+ [data-mk-scroll-rail][data-mk-axis='x'] [data-mk-scroll-thumb] {
170
+ height: 100%;
171
+ }
172
+
173
+ [data-mk-scroll-rail][data-mk-dragging] [data-mk-scroll-thumb] {
174
+ cursor: grabbing;
175
+ }
176
+
177
+ /* The pill stays centred on the 10 px rail while it swells. */
178
+ [data-mk-scroll-pill] {
179
+ position: absolute;
180
+ border-radius: 999px;
181
+ background: var(--mk-scroll-thumb, color-mix(in srgb, var(--mk-text) 32%, transparent));
182
+ transition: background-color 0.2s ease;
183
+ }
184
+
185
+ [data-mk-scroll-rail][data-mk-axis='y'] [data-mk-scroll-pill] {
186
+ top: 0;
187
+ right: calc(2px + (10px - var(--mk-scroll-thickness)) / 2);
188
+ bottom: 0;
189
+ width: var(--mk-scroll-thickness);
190
+ }
191
+
192
+ [data-mk-scroll-rail][data-mk-axis='x'] [data-mk-scroll-pill] {
193
+ right: 0;
194
+ bottom: calc(2px + (10px - var(--mk-scroll-thickness)) / 2);
195
+ left: 0;
196
+ height: var(--mk-scroll-thickness);
197
+ }
198
+
199
+ [data-mk-scroll-rail]:hover [data-mk-scroll-pill] {
200
+ background: var(--mk-scroll-thumb-hover, color-mix(in srgb, var(--mk-text) 48%, transparent));
201
+ }
202
+
203
+ [data-mk-scroll-rail][data-mk-dragging] [data-mk-scroll-pill] {
204
+ background: var(--mk-scroll-thumb-active, var(--mk-accent));
205
+ }
206
+
207
+ [data-mk-dragging] [data-mk-scroll-viewport] {
208
+ -webkit-user-select: none;
209
+ user-select: none;
210
+ }
211
+
212
+ /* Back to top: a tonal pill in the corner, clear of the bars. */
213
+ [data-mk-scroll-top] {
214
+ position: absolute;
215
+ z-index: 3;
216
+ right: calc(var(--mk-scroll-view-right, 0px) + 20px);
217
+ bottom: calc(var(--mk-scroll-view-bottom, 0px) + 16px);
218
+ display: inline-grid;
219
+ place-items: center;
220
+ width: 40px;
221
+ height: 40px;
222
+ padding: 0;
223
+ border: 0;
224
+ border-radius: 999px;
225
+ background: var(--mk-surface-4);
226
+ box-shadow: 0 8px 24px -8px color-mix(in srgb, var(--mk-shadow-color, #000) 45%, transparent);
227
+ color: var(--mk-text);
228
+ cursor: pointer;
229
+ transition:
230
+ background-color 0.15s,
231
+ box-shadow 0.2s;
232
+ }
233
+
234
+ [data-mk-overflow-x] > [data-mk-scroll-top] {
235
+ bottom: calc(var(--mk-scroll-view-bottom, 0px) + 22px);
236
+ }
237
+
238
+ [data-mk-scroll-top][hidden] {
239
+ display: none;
240
+ }
241
+
242
+ [data-mk-scroll-top]:hover {
243
+ background: color-mix(in srgb, var(--mk-surface-4) 80%, var(--mk-text) 10%);
244
+ }
245
+
246
+ [data-mk-scroll-top]:focus-visible {
247
+ outline: none;
248
+ box-shadow:
249
+ 0 0 0 3px color-mix(in srgb, var(--mk-accent) 45%, transparent),
250
+ 0 8px 24px -8px color-mix(in srgb, var(--mk-shadow-color, #000) 45%, transparent);
251
+ }
252
+
253
+ @media (prefers-reduced-motion: reduce) {
254
+ [data-mk-scroll-rail],
255
+ [data-mk-scroll-rail]::before,
256
+ [data-mk-scroll-pill],
257
+ [data-mk-scroll-top] {
258
+ transition: none;
259
+ }
260
+ }
@@ -129,7 +129,12 @@ export function createScrollIsland(root: HTMLElement | null, options: ScrollIsla
129
129
  const size = as === 'island' ? ISLAND_HEIGHT : Math.max(MIN_BAR, Math.min(height, height * (scroller.clientHeight / Math.max(1, scroller.scrollHeight))));
130
130
  const top = (height - size) * progress();
131
131
  if (as !== 'island') return { top, height: size, width: BAR_WIDTH, borderRadius: BAR_WIDTH / 2 };
132
- return { top, height: size, width: Math.min(Math.max(ISLAND_HEIGHT, track().right - track().left - INSET * 2), Math.max(ISLAND_HEIGHT, content.scrollWidth + 28)), borderRadius: size / 2 };
132
+ // Measure at the content's natural width, independent of the collapsed bar
133
+ // or an interrupted expansion. Padding is already included in offsetWidth.
134
+ content.style.maxWidth = 'none';
135
+ const width = content.offsetWidth;
136
+ content.style.removeProperty('max-width');
137
+ return { top, height: size, width: Math.min(Math.max(ISLAND_HEIGHT, track().right - track().left - INSET * 2), Math.max(ISLAND_HEIGHT, width)), borderRadius: size / 2 };
133
138
  };
134
139
 
135
140
  const place = () => {
@@ -189,6 +194,10 @@ export function createScrollIsland(root: HTMLElement | null, options: ScrollIsla
189
194
  scope.on(window, 'resize', () => go(state, true));
190
195
 
191
196
  const near = (x: number, y: number) => {
197
+ if (state === 'island') {
198
+ const capsule = thumb.getBoundingClientRect();
199
+ if (x >= capsule.left && x <= capsule.right && y >= capsule.top && y <= capsule.bottom) return true;
200
+ }
192
201
  const box = track();
193
202
  return y >= box.top - INSET && y <= box.top + box.height + INSET && x <= box.right && box.right - x <= reach;
194
203
  };
@@ -73,6 +73,8 @@ mk-scroll-island {
73
73
  [data-mk-island-content] {
74
74
  display: inline-flex;
75
75
  flex: none;
76
+ box-sizing: border-box;
77
+ width: max-content;
76
78
  max-width: 100%;
77
79
  min-width: 0;
78
80
  align-items: center;
@@ -87,6 +89,10 @@ mk-scroll-island {
87
89
  transition: opacity 0.18s ease;
88
90
  }
89
91
 
92
+ [data-mk-island-percent] {
93
+ flex: none;
94
+ }
95
+
90
96
  [data-mk-scroll-island][data-mk-state='island'] [data-mk-island-content] {
91
97
  opacity: 1;
92
98
  transition-delay: 0.1s;
@@ -1,6 +1,6 @@
1
1
  # select
2
2
 
3
- Una elección de una lista que se abre como el `menu` del kit (crece desde su botón y las opciones entran escalonadas) en lugar de la lista del sistema. Mejora un `<select>` escrito a mano, o crea uno desde `options`, y lo mantiene sincronizado y oculto: los formularios lo envían y sus escuchas de `change` siguen funcionando. El valor elegido entra con un blur breve y el chevron gira.
3
+ Una elección de una lista que se abre como el `menu` del kit (crece desde su botón y las opciones entran escalonadas) en lugar de la lista del sistema. Mejora un `<select>` escrito a mano, o crea uno desde `options`, y lo mantiene sincronizado y oculto: los formularios lo envían y sus escuchas de `change` siguen funcionando. El valor elegido entra con un blur breve y el chevron gira. Una lista larga se desplaza con la barra de [`scroll-area`](../scroll-area/README.md), no con la del sistema; respeta las esquinas redondeadas del menú y funde los bordes.
4
4
 
5
5
  ## HTML / Astro
6
6
 
@@ -4,6 +4,7 @@ import { createScope } from '../../core/scope';
4
4
  import { inert, type Instance } from '../../core/types';
5
5
  import { press } from '../../motion/micro';
6
6
  import { createMenu } from '../menu/engine';
7
+ import { createScrollArea } from '../scroll-area/engine';
7
8
 
8
9
  /*
9
10
  * Select: one choice from a list, opened as the kit's menu (it grows out of its
@@ -12,8 +13,9 @@ import { createMenu } from '../menu/engine';
12
13
  * step: forms post it and its `change` listeners keep working) or builds one from
13
14
  * `options`. The chosen label slides in out of a blur, the chevron turns, typing
14
15
  * a letter on the closed button picks the next option that starts with it, and
15
- * ↓ ↑ Enter Space open the menu. Changes made to the select's options (by the app)
16
- * show up on their own.
16
+ * ↓ ↑ Enter Space open the menu. A long list scrolls under the kit's scroll-area
17
+ * bar (no system bar on the menu's rounded edge) with its edges fading out.
18
+ * Changes made to the select's options (by the app) show up on their own.
17
19
  */
18
20
 
19
21
  export interface SelectOption {
@@ -121,6 +123,10 @@ export function createSelect(root: HTMLElement | null, options: SelectOptions =
121
123
  dialog.className = 'mk-dialog mk-menu mk-select-menu';
122
124
  dialog.dataset.mkSelectMenu = '';
123
125
  dialog.setAttribute('aria-label', options.label ?? 'Opciones');
126
+ // The items scroll inside a list, so the bar rides over the menu instead of cutting its rounded edge.
127
+ const list = document.createElement('div');
128
+ list.dataset.mkSelectList = '';
129
+ dialog.append(list);
124
130
  root.append(dialog);
125
131
  made.push(dialog);
126
132
  scope.attr(root, 'data-mk-ready', '');
@@ -161,7 +167,7 @@ export function createSelect(root: HTMLElement | null, options: SelectOptions =
161
167
  nodes.push(heading, ...[...child.children].filter((node): node is HTMLOptionElement => node instanceof HTMLOptionElement).map(item));
162
168
  } else if (child instanceof HTMLOptionElement) nodes.push(item(child));
163
169
  }
164
- dialog.replaceChildren(...nodes);
170
+ list.replaceChildren(...nodes);
165
171
  };
166
172
 
167
173
  const placeholderText = () => options.placeholder ?? choices().find((option) => option.value === '')?.text ?? 'Elige…';
@@ -201,13 +207,19 @@ export function createSelect(root: HTMLElement | null, options: SelectOptions =
201
207
  root.dispatchEvent(new CustomEvent('mk:change', { bubbles: true, detail: { value } }));
202
208
  };
203
209
 
210
+ // The menu's children must be its items: the bar stays out of assistive tech (the list scrolls with the keys).
211
+ const area = createScrollArea(dialog, { viewport: list, axis: 'y', focusable: false, decorative: true });
204
212
  const menu = createMenu(dialog, {
205
213
  triggers: false,
206
- onOpen: markItems,
214
+ onOpen: () => {
215
+ markItems();
216
+ area.refresh();
217
+ },
207
218
  onClose: () => trigger.setAttribute('aria-expanded', 'false'),
208
219
  onSelect: (value) => choose(value, true),
209
220
  });
210
221
  scope.add(() => menu.destroy());
222
+ scope.add(() => area.destroy());
211
223
  // The menu's own selection stays inside: listeners on the select hear its change.
212
224
  scope.on(dialog, 'mk:select', (event) => event.stopPropagation());
213
225
 
@@ -72,20 +72,38 @@ mk-select {
72
72
  transform: rotate(180deg);
73
73
  }
74
74
 
75
- /* The menu: like the panels of the data table (surface-1, a hairline, 8 px items). */
75
+ /* The menu: like the panels of the data table (surface-1, a hairline, 8 px items). The items scroll in a list with the
76
+ kit's scroll-area bar, kept clear of the rounded corners; the system bar never shows. */
76
77
  .mk-dialog.mk-select-menu {
77
78
  --mk-dialog-bg: var(--mk-surface-1);
78
79
  --mk-dialog-radius: 16px;
79
80
  --mk-dialog-shadow: 0 0 0 1px var(--mk-outline-soft), 0 16px 48px -12px color-mix(in srgb, var(--mk-shadow-color, #000) 35%, transparent);
80
- gap: 0.125rem;
81
+ --mk-scroll-inset: 10px;
81
82
  min-width: max(10rem, var(--mk-select-width, 0px));
82
83
  max-height: min(20rem, 60dvh);
83
- padding: 0.375rem;
84
- overflow-y: auto;
84
+ padding: 0;
85
+ overflow: hidden;
85
86
  -webkit-backdrop-filter: none;
86
87
  backdrop-filter: none;
87
88
  }
88
89
 
90
+ .mk-dialog.mk-select-menu [data-mk-select-list] {
91
+ display: flex;
92
+ flex: 1 1 auto;
93
+ flex-direction: column;
94
+ gap: 0.125rem;
95
+ min-height: 0;
96
+ padding: 0.375rem;
97
+ overflow-x: hidden;
98
+ overflow-y: auto;
99
+ overscroll-behavior: contain;
100
+ }
101
+
102
+ /* A long list leaves the bar its own lane. */
103
+ .mk-dialog.mk-select-menu[data-mk-overflow-y] [data-mk-select-list] {
104
+ padding-inline-end: 0.875rem;
105
+ }
106
+
89
107
  .mk-dialog.mk-select-menu [data-mk-item] {
90
108
  flex: none;
91
109
  gap: 0.625rem;
@@ -200,4 +200,6 @@ export const TIMING = Object.freeze({
200
200
  repelBento: Object.freeze({ offset: 0.6, stagger: 0.006, strength: 5000, distance: 30, breakpoint: 1024 }),
201
201
  rotator: Object.freeze({ interval: 8000, duration: 0.25, y: 5 }),
202
202
  dragRuler: Object.freeze({ min: 0, max: 8, step: 0.1, pixelsPerUnit: 60 }),
203
+ /** scroll-area: bars hide after idleMs; edge fades up to `fade` px; a click on the rail glides (s); shortest thumb (px). */
204
+ scrollArea: Object.freeze({ idleMs: 900, fade: 24, glide: 0.6, minThumb: 24 }),
203
205
  });