@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.
- package/README.md +174 -155
- package/bin/motion-kit.mjs +3 -3
- package/dist/auto/index.d.ts +6 -3
- package/dist/auto/index.js +8 -2
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-EY4BJKC4.js → chunk-6IRJYXJU.js} +2 -2
- package/dist/{chunk-CU6SXW3E.js → chunk-A6QQCF6V.js} +1309 -310
- package/dist/chunk-A6QQCF6V.js.map +1 -0
- package/dist/chunk-JXRS3OEA.js +3638 -0
- package/dist/chunk-JXRS3OEA.js.map +1 -0
- package/dist/css/components/add-to-cart.css +72 -0
- package/dist/css/components/bidirectional-slider.css +168 -0
- package/dist/css/components/data-table.css +121 -26
- package/dist/css/components/filter-builder.css +10 -15
- package/dist/css/components/heatmap-chart.css +391 -0
- package/dist/css/components/hover-card.css +8 -2
- package/dist/css/components/image-preview.css +90 -2
- package/dist/css/components/json-viewer.css +33 -1
- package/dist/css/components/nav.css +102 -102
- package/dist/css/components/orbit-menu.css +130 -0
- package/dist/css/components/pagination.css +10 -5
- package/dist/css/components/pie-chart.css +390 -0
- package/dist/css/components/pill-button.css +135 -0
- package/dist/css/components/radar-chart.css +370 -0
- package/dist/css/components/radial-chart.css +398 -0
- package/dist/css/components/sankey-chart.css +358 -0
- package/dist/css/components/scroll-island.css +111 -0
- package/dist/css/components/select.css +136 -0
- package/dist/css/components/toolbar.css +122 -0
- package/dist/css/components/tree-view.css +79 -0
- package/dist/css/components/voice-dots.css +70 -0
- package/dist/elements/index.d.ts +180 -7
- package/dist/elements/index.js +287 -6
- package/dist/elements/index.js.map +1 -1
- package/dist/{engine-Cn6IhE3z.d.ts → engine-5zRIssTO.d.ts} +11 -1
- package/dist/index.css +1832 -168
- package/dist/index.d.ts +4 -4
- package/dist/index.js +33 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +117 -11
- package/dist/react/index.js +314 -13
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-BBUusld6.d.ts → reduced-6X_bns60.d.ts} +1 -1
- package/dist/{reveal-DEvp6U6T.d.ts → reveal-BfyKKsw4.d.ts} +97 -4
- package/dist/store-CA2Qcun_.d.ts +453 -0
- package/package.json +9 -16
- package/registry.json +1096 -948
- package/src/auto/auto-init.ts +6 -0
- package/src/components/add-to-cart/README.md +34 -0
- package/src/components/add-to-cart/add-to-cart.css +71 -0
- package/src/components/add-to-cart/element.ts +20 -0
- package/src/components/add-to-cart/engine.ts +206 -0
- package/src/components/add-to-cart/react.tsx +29 -0
- package/src/components/bidirectional-slider/README.md +46 -0
- package/src/components/bidirectional-slider/bidirectional-slider.css +167 -0
- package/src/components/bidirectional-slider/element.ts +42 -0
- package/src/components/bidirectional-slider/engine.ts +289 -0
- package/src/components/bidirectional-slider/react.tsx +45 -0
- package/src/components/chart/README.md +10 -0
- package/src/components/chart/element.ts +2 -0
- package/src/components/chart/engine.ts +50 -23
- package/src/components/chart/parts.ts +237 -0
- package/src/components/chart/react.tsx +2 -2
- package/src/components/chart/use-chart.ts +55 -0
- package/src/components/data-table/README.md +3 -3
- package/src/components/data-table/data-table.css +121 -26
- package/src/components/data-table/engine.ts +81 -29
- package/src/components/editable-table/engine.ts +67 -11
- package/src/components/filter-builder/engine.ts +33 -2
- package/src/components/filter-builder/filter-builder.css +10 -15
- package/src/components/heatmap-chart/README.md +48 -0
- package/src/components/heatmap-chart/element.ts +21 -0
- package/src/components/heatmap-chart/engine.ts +281 -0
- package/src/components/heatmap-chart/heatmap-chart.css +93 -0
- package/src/components/heatmap-chart/react.tsx +14 -0
- package/src/components/hover-card/engine.ts +30 -1
- package/src/components/hover-card/hover-card.css +8 -2
- package/src/components/image-preview/element.ts +1 -1
- package/src/components/image-preview/engine.ts +95 -15
- package/src/components/image-preview/image-preview.css +90 -2
- package/src/components/image-preview/react.tsx +3 -3
- package/src/components/json-viewer/engine.ts +11 -2
- package/src/components/json-viewer/json-viewer.css +33 -1
- package/src/components/nav/README.md +12 -10
- package/src/components/nav/element.ts +5 -5
- package/src/components/nav/engine.ts +21 -14
- package/src/components/nav/nav.css +102 -102
- package/src/components/nav/react.tsx +4 -4
- package/src/components/orbit-menu/README.md +46 -0
- package/src/components/orbit-menu/element.ts +27 -0
- package/src/components/orbit-menu/engine.ts +179 -0
- package/src/components/orbit-menu/orbit-menu.css +129 -0
- package/src/components/orbit-menu/react.tsx +44 -0
- package/src/components/pagination/README.md +1 -1
- package/src/components/pagination/engine.ts +50 -4
- package/src/components/pagination/pagination.css +10 -5
- package/src/components/pie-chart/README.md +48 -0
- package/src/components/pie-chart/element.ts +30 -0
- package/src/components/pie-chart/engine.ts +283 -0
- package/src/components/pie-chart/pie-chart.css +92 -0
- package/src/components/pie-chart/react.tsx +14 -0
- package/src/components/pill-button/README.md +46 -0
- package/src/components/pill-button/element.ts +16 -0
- package/src/components/pill-button/engine.ts +77 -0
- package/src/components/pill-button/pill-button.css +134 -0
- package/src/components/pill-button/react.tsx +32 -0
- package/src/components/radar-chart/README.md +48 -0
- package/src/components/radar-chart/element.ts +30 -0
- package/src/components/radar-chart/engine.ts +327 -0
- package/src/components/radar-chart/radar-chart.css +72 -0
- package/src/components/radar-chart/react.tsx +14 -0
- package/src/components/radial-chart/README.md +48 -0
- package/src/components/radial-chart/element.ts +30 -0
- package/src/components/radial-chart/engine.ts +290 -0
- package/src/components/radial-chart/radial-chart.css +100 -0
- package/src/components/radial-chart/react.tsx +14 -0
- package/src/components/sankey-chart/README.md +48 -0
- package/src/components/sankey-chart/element.ts +31 -0
- package/src/components/sankey-chart/engine.ts +401 -0
- package/src/components/sankey-chart/react.tsx +14 -0
- package/src/components/sankey-chart/sankey-chart.css +60 -0
- package/src/components/scroll-island/README.md +34 -0
- package/src/components/scroll-island/element.ts +16 -0
- package/src/components/scroll-island/engine.ts +252 -0
- package/src/components/scroll-island/react.tsx +26 -0
- package/src/components/scroll-island/scroll-island.css +110 -0
- package/src/components/select/README.md +34 -0
- package/src/components/select/element.ts +26 -0
- package/src/components/select/engine.ts +267 -0
- package/src/components/select/react.tsx +33 -0
- package/src/components/select/select.css +135 -0
- package/src/components/toolbar/README.md +46 -0
- package/src/components/toolbar/element.ts +30 -0
- package/src/components/toolbar/engine.ts +230 -0
- package/src/components/toolbar/react.tsx +34 -0
- package/src/components/toolbar/toolbar.css +121 -0
- package/src/components/tree-view/element.ts +1 -1
- package/src/components/tree-view/engine.ts +174 -0
- package/src/components/tree-view/react.tsx +7 -3
- package/src/components/tree-view/tree-view.css +79 -0
- package/src/components/voice-dots/README.md +48 -0
- package/src/components/voice-dots/element.ts +45 -0
- package/src/components/voice-dots/engine.ts +243 -0
- package/src/components/voice-dots/react.tsx +52 -0
- package/src/components/voice-dots/voice-dots.css +69 -0
- package/src/elements/index.ts +39 -0
- package/src/index.ts +16 -0
- package/src/motion/liquid.ts +14 -4
- package/src/react/index.ts +14 -1
- package/dist/chunk-6P4P7NU6.js +0 -1401
- package/dist/chunk-6P4P7NU6.js.map +0 -1
- package/dist/chunk-CU6SXW3E.js.map +0 -1
- package/dist/store-DjkTBX7f.d.ts +0 -195
- /package/dist/{chunk-EY4BJKC4.js.map → chunk-6IRJYXJU.js.map} +0 -0
|
@@ -8,14 +8,19 @@ import { rollDigits } from '../../motion/micro';
|
|
|
8
8
|
* Image preview: thumbnails ([data-mk-image]) open a modal <dialog> where the
|
|
9
9
|
* picture grows out of its thumbnail (0.7 s on the signature curve, the
|
|
10
10
|
* thumbnail's radius easing to 24 px) over a blurred backdrop. Previous/next
|
|
11
|
-
* (buttons, arrow keys, a swipe
|
|
11
|
+
* (buttons, arrow keys, a swipe, the strip of thumbnails) move both pictures at
|
|
12
|
+
* once: the old one leaves as a copy that slides away, shrinking into a blur,
|
|
13
|
+
* while the new one (decoded first) comes in from the side it goes to; a ring
|
|
14
|
+
* glides along the strip to the current thumbnail; the
|
|
12
15
|
* counter rolls. A double click or tap zooms ×2 where you point and a drag pans
|
|
13
16
|
* it. Dragging down, Escape or the close button send the picture back to its
|
|
14
17
|
* thumbnail, and focus returns to it.
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
export interface ImagePreviewOptions {
|
|
18
|
-
labels?: { close?: string; prev?: string; next?: string; dialog?: string };
|
|
21
|
+
labels?: { close?: string; prev?: string; next?: string; dialog?: string; strip?: string; picture?: string };
|
|
22
|
+
/** A strip of thumbnails under the picture (default true; hidden on phones and with one picture). */
|
|
23
|
+
strip?: boolean;
|
|
19
24
|
/** Loop from the last picture to the first (default true). */
|
|
20
25
|
loop?: boolean;
|
|
21
26
|
onChange?: (index: number) => void;
|
|
@@ -52,7 +57,7 @@ export function createImagePreview(root: HTMLElement | null, options: ImagePrevi
|
|
|
52
57
|
registerMotion();
|
|
53
58
|
const scope = createScope();
|
|
54
59
|
const loop = options.loop ?? true;
|
|
55
|
-
const labels = { close: 'Cerrar', prev: 'Imagen anterior', next: 'Imagen siguiente', dialog: 'Vista de imagen', ...options.labels };
|
|
60
|
+
const labels = { close: 'Cerrar', prev: 'Imagen anterior', next: 'Imagen siguiente', dialog: 'Vista de imagen', strip: 'Miniaturas', picture: 'Imagen', ...options.labels };
|
|
56
61
|
|
|
57
62
|
const pictures = (): Picture[] =>
|
|
58
63
|
[...root.querySelectorAll<HTMLElement>('[data-mk-image]')].map((thumb) => {
|
|
@@ -84,7 +89,15 @@ export function createImagePreview(root: HTMLElement | null, options: ImagePrevi
|
|
|
84
89
|
const counter = document.createElement('span');
|
|
85
90
|
counter.dataset.mkLightbox = 'count';
|
|
86
91
|
counter.setAttribute('aria-live', 'polite');
|
|
87
|
-
|
|
92
|
+
const strip = document.createElement('div');
|
|
93
|
+
strip.dataset.mkLightbox = 'strip';
|
|
94
|
+
strip.setAttribute('role', 'group');
|
|
95
|
+
strip.setAttribute('aria-label', labels.strip);
|
|
96
|
+
strip.hidden = true;
|
|
97
|
+
const ring = document.createElement('span');
|
|
98
|
+
ring.dataset.mkLightboxRing = '';
|
|
99
|
+
ring.setAttribute('aria-hidden', 'true');
|
|
100
|
+
dialog.append(backdrop, stage, prev, next, close, counter, strip);
|
|
88
101
|
root.append(dialog);
|
|
89
102
|
scope.add(() => dialog.remove());
|
|
90
103
|
scope.attr(root, 'data-mk-ready', '');
|
|
@@ -128,6 +141,44 @@ export function createImagePreview(root: HTMLElement | null, options: ImagePrevi
|
|
|
128
141
|
prev.hidden = next.hidden = list.length < 2;
|
|
129
142
|
};
|
|
130
143
|
|
|
144
|
+
let ringShown = false;
|
|
145
|
+
const buildStrip = () => {
|
|
146
|
+
const show = options.strip !== false && list.length > 1;
|
|
147
|
+
strip.hidden = !show;
|
|
148
|
+
dialog.toggleAttribute('data-mk-strip', show);
|
|
149
|
+
ringShown = false;
|
|
150
|
+
if (!show) return strip.replaceChildren();
|
|
151
|
+
strip.replaceChildren(
|
|
152
|
+
ring,
|
|
153
|
+
...list.map((picture, i) => {
|
|
154
|
+
const thumb = document.createElement('button');
|
|
155
|
+
thumb.type = 'button';
|
|
156
|
+
thumb.dataset.mkLightboxThumb = String(i);
|
|
157
|
+
thumb.setAttribute('aria-label', `${labels.picture} ${i + 1} de ${list.length}`);
|
|
158
|
+
const small = thumbImg(picture);
|
|
159
|
+
const img = document.createElement('img');
|
|
160
|
+
img.src = (small instanceof HTMLImageElement ? small.getAttribute('src') || small.currentSrc : '') || picture.src;
|
|
161
|
+
img.alt = '';
|
|
162
|
+
img.draggable = false;
|
|
163
|
+
thumb.append(img);
|
|
164
|
+
return thumb;
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
const paintStrip = () => {
|
|
169
|
+
if (strip.hidden) return;
|
|
170
|
+
const thumbs = [...strip.querySelectorAll<HTMLElement>('[data-mk-lightbox-thumb]')];
|
|
171
|
+
thumbs.forEach((thumb, i) => (i === index ? thumb.setAttribute('aria-current', 'true') : thumb.removeAttribute('aria-current')));
|
|
172
|
+
const active = thumbs[index];
|
|
173
|
+
if (!active) return;
|
|
174
|
+
const box = { x: active.offsetLeft, y: active.offsetTop, width: active.offsetWidth, height: active.offsetHeight };
|
|
175
|
+
if (!ringShown || reduced()) gsap.set(ring, box);
|
|
176
|
+
else keep(gsap.to(ring, { ...box, duration: 0.35, ease: EASE.swatch }));
|
|
177
|
+
ringShown = true;
|
|
178
|
+
// A long strip keeps the current thumbnail in its middle (the page behind never scrolls).
|
|
179
|
+
if (typeof strip.scrollTo === 'function') strip.scrollTo({ left: active.offsetLeft + active.offsetWidth / 2 - strip.clientWidth / 2, behavior: reduced() ? 'auto' : 'smooth' });
|
|
180
|
+
};
|
|
181
|
+
|
|
131
182
|
/** Transform that puts the big image over the thumbnail. */
|
|
132
183
|
const fromThumb = (picture: Picture) => {
|
|
133
184
|
const small = thumbImg(picture)!.getBoundingClientRect();
|
|
@@ -158,16 +209,18 @@ export function createImagePreview(root: HTMLElement | null, options: ImagePrevi
|
|
|
158
209
|
isOpen = true;
|
|
159
210
|
index = target;
|
|
160
211
|
counter.textContent = '';
|
|
212
|
+
buildStrip();
|
|
161
213
|
write(target);
|
|
162
214
|
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
163
215
|
else dialog.setAttribute('open', '');
|
|
216
|
+
paintStrip();
|
|
164
217
|
close.focus({ preventScroll: true });
|
|
165
218
|
const picture = list[target]!;
|
|
166
219
|
options.onOpenChange?.(true);
|
|
167
220
|
root.dispatchEvent(new CustomEvent('mk:open', { bubbles: true, detail: { index } }));
|
|
168
221
|
if (reduced()) return;
|
|
169
222
|
keep(gsap.fromTo(backdrop, { opacity: 0 }, { opacity: 1, duration: 0.4, ease: 'power2.out', clearProps: 'opacity' }));
|
|
170
|
-
keep(gsap.fromTo([prev, next, close, counter, caption], { opacity: 0 }, { opacity: 1, duration: 0.3, delay: 0.3, clearProps: 'opacity' }));
|
|
223
|
+
keep(gsap.fromTo([prev, next, close, counter, caption, strip], { opacity: 0 }, { opacity: 1, duration: 0.3, delay: 0.3, clearProps: 'opacity' }));
|
|
171
224
|
const grow = () => {
|
|
172
225
|
const start = fromThumb(picture);
|
|
173
226
|
if (!start) return void keep(gsap.fromTo(image, { opacity: 0, scale: 0.92 }, { opacity: 1, scale: 1, duration: 0.35, ease: EASE.pop, clearProps: 'opacity,scale,transform' }));
|
|
@@ -201,11 +254,11 @@ export function createImagePreview(root: HTMLElement | null, options: ImagePrevi
|
|
|
201
254
|
gsap.set(image, { scale: 1, x: 0, y: 0 });
|
|
202
255
|
const end = fromThumb(picture);
|
|
203
256
|
keep(gsap.to(backdrop, { opacity: 0, duration: 0.35, ease: 'power2.in' }));
|
|
204
|
-
keep(gsap.to([prev, next, close, counter, caption], { opacity: 0, duration: 0.15 }));
|
|
257
|
+
keep(gsap.to([prev, next, close, counter, caption, strip], { opacity: 0, duration: 0.15 }));
|
|
205
258
|
if (!end) return void keep(gsap.to(image, { opacity: 0, scale: 0.92, duration: 0.25, ease: 'power2.in', onComplete: finish }));
|
|
206
259
|
thumbImg(picture)!.style.visibility = 'hidden';
|
|
207
260
|
keep(gsap.to(image, { ...end, duration: 0.5, ease: EASE.signature, onComplete: () => {
|
|
208
|
-
gsap.set([prev, next, close, counter, caption], { clearProps: 'opacity' });
|
|
261
|
+
gsap.set([prev, next, close, counter, caption, strip], { clearProps: 'opacity' });
|
|
209
262
|
finish();
|
|
210
263
|
} }));
|
|
211
264
|
};
|
|
@@ -222,16 +275,43 @@ export function createImagePreview(root: HTMLElement | null, options: ImagePrevi
|
|
|
222
275
|
index = target;
|
|
223
276
|
options.onChange?.(index);
|
|
224
277
|
root!.dispatchEvent(new CustomEvent('mk:change', { bubbles: true, detail: { index, previous: before } }));
|
|
225
|
-
if (reduced())
|
|
278
|
+
if (reduced()) {
|
|
279
|
+
write(target);
|
|
280
|
+
paintStrip();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
226
283
|
gsap.killTweensOf(image);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
);
|
|
284
|
+
ghost?.remove();
|
|
285
|
+
// The picture that leaves is a copy over the old one (where a swipe left it), so the new one can come in at once.
|
|
286
|
+
const leaving = image.cloneNode() as HTMLImageElement;
|
|
287
|
+
leaving.dataset.mkLightbox = 'ghost';
|
|
288
|
+
leaving.alt = '';
|
|
289
|
+
leaving.setAttribute('aria-hidden', 'true');
|
|
290
|
+
Object.assign(leaving.style, { position: 'absolute', left: `${image.offsetLeft}px`, top: `${image.offsetTop}px`, width: `${image.offsetWidth}px`, height: `${image.offsetHeight}px`, margin: '0' });
|
|
291
|
+
stage.append(leaving);
|
|
292
|
+
ghost = leaving;
|
|
293
|
+
write(target);
|
|
294
|
+
paintStrip();
|
|
295
|
+
gsap.set(image, { opacity: 0, x: 0, y: 0 });
|
|
296
|
+
keep(gsap.to(leaving, { xPercent: -28 * direction, scale: 0.86, opacity: 0, filter: 'blur(10px)', duration: 0.45, ease: EASE.signature, onComplete: () => {
|
|
297
|
+
leaving.remove();
|
|
298
|
+
if (ghost === leaving) ghost = null;
|
|
299
|
+
} }));
|
|
300
|
+
const enter = () => {
|
|
301
|
+
if (!isOpen || index !== target) return;
|
|
302
|
+
keep(gsap.fromTo(image, { xPercent: 24 * direction, scale: 1.04, opacity: 0, filter: 'blur(10px)' }, { xPercent: 0, scale: 1, opacity: 1, filter: 'blur(0px)', duration: 0.5, ease: EASE.signature, clearProps: 'transform,opacity,filter' }));
|
|
303
|
+
};
|
|
304
|
+
// The new picture enters decoded (never blank), waiting at most 250 ms for it.
|
|
305
|
+
const decoded = typeof image.decode === 'function' ? image.decode().catch(() => undefined) : Promise.resolve();
|
|
306
|
+
if (image.complete && image.naturalWidth) enter();
|
|
307
|
+
else void Promise.race([decoded, new Promise((done) => setTimeout(done, 250))]).then(enter);
|
|
234
308
|
}
|
|
309
|
+
let ghost: HTMLImageElement | null = null;
|
|
310
|
+
scope.add(() => ghost?.remove());
|
|
311
|
+
scope.on(strip, 'click', (event) => {
|
|
312
|
+
const thumb = (event.target as Element | null)?.closest<HTMLElement>('[data-mk-lightbox-thumb]');
|
|
313
|
+
if (thumb) go(Number(thumb.dataset.mkLightboxThumb));
|
|
314
|
+
});
|
|
235
315
|
|
|
236
316
|
scope.on(root, 'click', (event) => {
|
|
237
317
|
const thumb = (event.target as Element | null)?.closest<HTMLElement>('[data-mk-image]');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* image-preview — thumbnails in Melonmind cards (16 px radius) and a full-screen dialog: a blurred dark backdrop, the
|
|
2
|
-
picture at 16 px radius, round glass buttons
|
|
2
|
+
picture at 16 px radius, round glass buttons, a counter pill and a glass strip of 8 px thumbnails with a ring that
|
|
3
|
+
glides to the current one. */
|
|
3
4
|
.mk-image-preview,
|
|
4
5
|
mk-image-preview {
|
|
5
6
|
display: grid;
|
|
@@ -88,6 +89,83 @@ mk-image-preview {
|
|
|
88
89
|
user-select: none;
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
/* The picture that leaves while the next one comes in (the engine places it over the old one). */
|
|
93
|
+
[data-mk-lightbox='ghost'] {
|
|
94
|
+
border-radius: 16px;
|
|
95
|
+
object-fit: contain;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
[data-mk-lightbox-dialog][data-mk-strip] [data-mk-lightbox='image'] {
|
|
100
|
+
max-height: min(78dvh, calc(100dvh - 11rem));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
[data-mk-lightbox='strip'] {
|
|
104
|
+
position: absolute;
|
|
105
|
+
bottom: 1rem;
|
|
106
|
+
left: 50%;
|
|
107
|
+
display: flex;
|
|
108
|
+
gap: 0.375rem;
|
|
109
|
+
max-width: calc(100vw - 2rem);
|
|
110
|
+
padding: 0.375rem;
|
|
111
|
+
overflow-x: auto;
|
|
112
|
+
border-radius: 14px;
|
|
113
|
+
background: rgb(255 255 255 / 0.08);
|
|
114
|
+
box-shadow:
|
|
115
|
+
inset 1px 1px 1px rgb(255 255 255 / 0.12),
|
|
116
|
+
inset -1px -1px 1px rgb(255 255 255 / 0.05);
|
|
117
|
+
backdrop-filter: blur(16px);
|
|
118
|
+
scrollbar-width: none;
|
|
119
|
+
translate: -50% 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
[data-mk-lightbox='strip'][hidden] {
|
|
123
|
+
display: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
[data-mk-lightbox-thumb] {
|
|
127
|
+
position: relative;
|
|
128
|
+
z-index: 1;
|
|
129
|
+
flex: none;
|
|
130
|
+
width: 2.75rem;
|
|
131
|
+
height: 2.75rem;
|
|
132
|
+
padding: 0;
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
border: 0;
|
|
135
|
+
border-radius: 8px;
|
|
136
|
+
background: rgb(255 255 255 / 0.06);
|
|
137
|
+
opacity: 0.5;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
transition: opacity 0.2s ease;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
[data-mk-lightbox-thumb]:hover,
|
|
143
|
+
[data-mk-lightbox-thumb][aria-current='true'] {
|
|
144
|
+
opacity: 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
[data-mk-lightbox-thumb]:focus-visible {
|
|
148
|
+
outline: 2px solid #fff;
|
|
149
|
+
outline-offset: 2px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
[data-mk-lightbox-thumb] img {
|
|
153
|
+
display: block;
|
|
154
|
+
width: 100%;
|
|
155
|
+
height: 100%;
|
|
156
|
+
object-fit: cover;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
[data-mk-lightbox-ring] {
|
|
160
|
+
position: absolute;
|
|
161
|
+
top: 0;
|
|
162
|
+
left: 0;
|
|
163
|
+
z-index: 2;
|
|
164
|
+
border-radius: 9px;
|
|
165
|
+
box-shadow: 0 0 0 2px #fff;
|
|
166
|
+
pointer-events: none;
|
|
167
|
+
}
|
|
168
|
+
|
|
91
169
|
[data-mk-lightbox-dialog][data-mk-zoomed] [data-mk-lightbox='image'] {
|
|
92
170
|
cursor: move;
|
|
93
171
|
}
|
|
@@ -177,6 +255,15 @@ mk-image-preview {
|
|
|
177
255
|
}
|
|
178
256
|
|
|
179
257
|
@media (max-width: 680px) {
|
|
258
|
+
/* Phones swipe; the arrows take the bottom. */
|
|
259
|
+
[data-mk-lightbox='strip'] {
|
|
260
|
+
display: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
[data-mk-lightbox-dialog][data-mk-strip] [data-mk-lightbox='image'] {
|
|
264
|
+
max-height: 78dvh;
|
|
265
|
+
}
|
|
266
|
+
|
|
180
267
|
[data-mk-lightbox='prev'],
|
|
181
268
|
[data-mk-lightbox='next'] {
|
|
182
269
|
top: auto;
|
|
@@ -187,7 +274,8 @@ mk-image-preview {
|
|
|
187
274
|
|
|
188
275
|
@media (prefers-reduced-motion: reduce) {
|
|
189
276
|
:is(.mk-image-preview, mk-image-preview) [data-mk-image],
|
|
190
|
-
[data-mk-lightbox-dialog] button[data-mk-lightbox]
|
|
277
|
+
[data-mk-lightbox-dialog] button[data-mk-lightbox],
|
|
278
|
+
[data-mk-lightbox-thumb] {
|
|
191
279
|
transition: none;
|
|
192
280
|
scale: none !important;
|
|
193
281
|
}
|
|
@@ -17,20 +17,20 @@ export interface ImagePreviewProps extends Omit<HTMLAttributes<HTMLDivElement>,
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** <ImagePreview images={[{ src, thumb, alt, caption }]} />: a grid of thumbnails that open a viewer. */
|
|
20
|
-
export function ImagePreview({ images, labels, loop, onChange, onOpenChange, instanceRef, className, ...props }: ImagePreviewProps) {
|
|
20
|
+
export function ImagePreview({ images, labels, loop, strip, onChange, onOpenChange, instanceRef, className, ...props }: ImagePreviewProps) {
|
|
21
21
|
const ref = useRef<HTMLDivElement>(null);
|
|
22
22
|
const change = useLatest(onChange);
|
|
23
23
|
const openChange = useLatest(onOpenChange);
|
|
24
24
|
const key = JSON.stringify(labels ?? {});
|
|
25
25
|
useIsomorphicLayoutEffect(() => {
|
|
26
|
-
const created = createImagePreview(ref.current, { labels, loop, onChange: (i) => change.current?.(i), onOpenChange: (open) => openChange.current?.(open) });
|
|
26
|
+
const created = createImagePreview(ref.current, { labels, loop, strip, onChange: (i) => change.current?.(i), onOpenChange: (open) => openChange.current?.(open) });
|
|
27
27
|
if (instanceRef) instanceRef.current = created;
|
|
28
28
|
return () => {
|
|
29
29
|
created.destroy();
|
|
30
30
|
if (instanceRef) instanceRef.current = null;
|
|
31
31
|
};
|
|
32
32
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
-
}, [key, loop]);
|
|
33
|
+
}, [key, loop, strip]);
|
|
34
34
|
return (
|
|
35
35
|
<div {...props} ref={ref} className={['mk-image-preview', className].filter(Boolean).join(' ')}>
|
|
36
36
|
{images.map((image, i) => (
|
|
@@ -3,7 +3,7 @@ import { reduced } from '../../core/reduced';
|
|
|
3
3
|
import { createScope } from '../../core/scope';
|
|
4
4
|
import { inert, type Instance } from '../../core/types';
|
|
5
5
|
import { walk, type TreeNode } from '../../core/tree';
|
|
6
|
-
import { press } from '../../motion/micro';
|
|
6
|
+
import { blurIn, press } from '../../motion/micro';
|
|
7
7
|
import { markText } from '../../core/mark';
|
|
8
8
|
import { createTreeView } from '../tree-view/engine';
|
|
9
9
|
|
|
@@ -16,7 +16,10 @@ import { createTreeView } from '../tree-view/engine';
|
|
|
16
16
|
* pop; Ctrl/⌘+C copies the focused value. The search marks keys and values and
|
|
17
17
|
* opens what holds them. setData() keeps what was open and flashes the values
|
|
18
18
|
* that changed (and the keys that are new) in the accent for a moment. The
|
|
19
|
-
* selected row's path ($.user.roles[0]) shows below, ready to copy
|
|
19
|
+
* selected row's path ($.user.roles[0]) shows below, ready to copy: it rises
|
|
20
|
+
* out of a blur the first time and slides in on each new choice. On hover the
|
|
21
|
+
* copy button slides in and the value sits on a faint pill of its colour; an
|
|
22
|
+
* open branch lets its summary ("4 claves") step back.
|
|
20
23
|
*/
|
|
21
24
|
|
|
22
25
|
export type JsonType = 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null';
|
|
@@ -336,8 +339,14 @@ export function createJsonViewer(root: HTMLElement | null, options: JsonViewerOp
|
|
|
336
339
|
},
|
|
337
340
|
onSelect: (node) => {
|
|
338
341
|
const meta = node.data as JsonMeta;
|
|
342
|
+
const appearing = foot.hidden;
|
|
343
|
+
const moved = pathText.textContent !== meta.path;
|
|
339
344
|
foot.hidden = false;
|
|
340
345
|
pathText.textContent = meta.path;
|
|
346
|
+
if (!reduced()) {
|
|
347
|
+
if (appearing) keep(blurIn(foot, { from: 'rise', duration: 0.32 }));
|
|
348
|
+
else if (moved) keep(blurIn(pathText, { from: 'soft', duration: 0.28 }));
|
|
349
|
+
}
|
|
341
350
|
options.onSelect?.(meta.path, meta.value);
|
|
342
351
|
root.dispatchEvent(new CustomEvent('mk:select', { bubbles: true, detail: { path: meta.path, value: meta.value } }));
|
|
343
352
|
},
|
|
@@ -79,7 +79,8 @@ mk-json-viewer {
|
|
|
79
79
|
transition:
|
|
80
80
|
background-color 0.15s,
|
|
81
81
|
color 0.15s,
|
|
82
|
-
opacity 0.15s
|
|
82
|
+
opacity 0.15s,
|
|
83
|
+
translate 0.2s ease;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
[data-mk-jv-tool]:hover,
|
|
@@ -189,6 +190,27 @@ mk-json-viewer {
|
|
|
189
190
|
color: var(--mk-muted);
|
|
190
191
|
font-size: 0.75rem;
|
|
191
192
|
text-overflow: ellipsis;
|
|
193
|
+
transition:
|
|
194
|
+
opacity 0.25s ease,
|
|
195
|
+
translate 0.3s cubic-bezier(0.38, 0.49, 0, 1.5);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* An open branch shows its content: the count steps back. */
|
|
199
|
+
[role='treeitem'][aria-expanded='true'] > [data-mk-tree-row] [data-mk-jv-summary] {
|
|
200
|
+
opacity: 0.45;
|
|
201
|
+
translate: -3px 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Hovered values sit on a faint pill of their own colour. */
|
|
205
|
+
[data-mk-jv-value] {
|
|
206
|
+
margin: 0 -0.2rem;
|
|
207
|
+
padding: 0 0.2rem;
|
|
208
|
+
border-radius: 4px;
|
|
209
|
+
transition: background-color 0.2s ease;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
[data-mk-tree-row]:hover [data-mk-jv-value] {
|
|
213
|
+
background: color-mix(in srgb, currentColor 12%, transparent);
|
|
192
214
|
}
|
|
193
215
|
|
|
194
216
|
[data-mk-jv-copy] {
|
|
@@ -197,11 +219,14 @@ mk-json-viewer {
|
|
|
197
219
|
margin-left: auto;
|
|
198
220
|
border-radius: 6px;
|
|
199
221
|
opacity: 0;
|
|
222
|
+
translate: 4px 0;
|
|
200
223
|
}
|
|
201
224
|
|
|
225
|
+
/* The copy button slides in on hover (or focus). */
|
|
202
226
|
[data-mk-tree-row]:hover [data-mk-jv-copy],
|
|
203
227
|
[role='treeitem']:focus-visible > [data-mk-tree-row] [data-mk-jv-copy] {
|
|
204
228
|
opacity: 1;
|
|
229
|
+
translate: 0 0;
|
|
205
230
|
}
|
|
206
231
|
|
|
207
232
|
@media (hover: none) {
|
|
@@ -268,4 +293,11 @@ mk-json-viewer {
|
|
|
268
293
|
[data-mk-jv-added] {
|
|
269
294
|
animation: none;
|
|
270
295
|
}
|
|
296
|
+
|
|
297
|
+
[data-mk-jv-copy],
|
|
298
|
+
[data-mk-jv-summary],
|
|
299
|
+
[data-mk-jv-value] {
|
|
300
|
+
transition: none;
|
|
301
|
+
translate: none;
|
|
302
|
+
}
|
|
271
303
|
}
|
|
@@ -4,13 +4,15 @@ Navegación con siete estilos de escritorio y una sola barra de cristal en el m
|
|
|
4
4
|
|
|
5
5
|
| Estilo | Cómo se ve |
|
|
6
6
|
|---|---|
|
|
7
|
-
| `
|
|
8
|
-
| `
|
|
7
|
+
| `sidebar` | Barra lateral con borde; la página actual en una píldora rellena |
|
|
8
|
+
| `pills` | Píldoras de texto centradas que se ensanchan al pasar y al estar activas (padding con resorte, 0.3 s `cubic(.38,.49,0,1.5)`) |
|
|
9
9
|
| `dock` | Barra flotante de cristal centrada abajo; el botón activo se ensancha a 80 px |
|
|
10
|
-
| `
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
10
|
+
| `card` | La vista es una tarjeta sobre la cáscara tintada; la página actual es un chip blanco |
|
|
11
|
+
| `rail` | Riel de botones redondos de 56 px; el activo se cuadra (radio 0.3 s `cubic(.38,.49,0,2)`) |
|
|
12
|
+
| `wash` | Barra lateral suave con un lavado primario en la página actual |
|
|
13
|
+
| `magnify` | Palabras grandes; la que tiene el ratón y sus vecinas crecen como un dock (1.2 / 1.09 / 1.03) |
|
|
14
|
+
|
|
15
|
+
Los nombres anteriores a la 0.15 (`clasica`, `bonita`, `guapa`, `tasks`, `melon`, `tunnel`) siguen funcionando: el motor los convierte en `sidebar`, `pills`, `card`, `rail`, `wash` y `magnify` (`NAV_STYLE_ALIASES`, `toNavStyle()`); en el marcado escrito usa ya la clase nueva para que se vea bien antes del JS.
|
|
14
16
|
|
|
15
17
|
`accent` pinta la página actual en primary-container. Al cambiar de página el icono se rellena (`mk-nav-icon`, 125 ms, `FILL 0 → 1`, para fuentes de iconos como Material Symbols). Con `reveal`, al arrancar la cáscara se descubre desde el primer botón (`revealFrom`).
|
|
16
18
|
|
|
@@ -22,7 +24,7 @@ Navegación con siete estilos de escritorio y una sola barra de cristal en el m
|
|
|
22
24
|
<header class="mk-shell__header"><h1 class="mk-shell__title">Inicio</h1></header>
|
|
23
25
|
…
|
|
24
26
|
</main>
|
|
25
|
-
<mk-nav class="mk-nav mk-nav--
|
|
27
|
+
<mk-nav class="mk-nav mk-nav--pills" nav-style="pills" storage-key="mi-app-nav">
|
|
26
28
|
<div class="mk-nav__buttons">
|
|
27
29
|
<a class="mk-nav__button" href="/" aria-current="page"><svg class="mk-nav__icon">…</svg><span class="mk-nav__name">Inicio</span></a>
|
|
28
30
|
<a class="mk-nav__button" href="/movimientos"><svg class="mk-nav__icon">…</svg><span class="mk-nav__name">Movimientos</span></a>
|
|
@@ -42,7 +44,7 @@ Navegación con siete estilos de escritorio y una sola barra de cristal en el m
|
|
|
42
44
|
|
|
43
45
|
Escribe la clase `mk-nav--<estilo>` en el marcado para que se vea bien antes de que cargue el JS. Sin custom element: `createNav(document.querySelector('.mk-nav'), opciones)` sobre un `<nav>`.
|
|
44
46
|
|
|
45
|
-
Partes: `.mk-nav__button--first` (botón de arriba: cuenta, espacio de trabajo), `.mk-nav__buttons`, `.mk-nav__actions` (`.mk-nav__button--action`), `.mk-nav__options` + `.mk-nav__label` (bloque extra que solo muestran
|
|
47
|
+
Partes: `.mk-nav__button--first` (botón de arriba: cuenta, espacio de trabajo), `.mk-nav__buttons`, `.mk-nav__actions` (`.mk-nav__button--action`), `.mk-nav__options` + `.mk-nav__label` (bloque extra que solo muestran sidebar, card, wash y magnify), `.mk-nav__icon`, `.mk-nav__name`, `data-mk-hide-mobile` (se esconde en el móvil). Vista: `.mk-shell__view`, `.mk-shell__header`, `.mk-shell__title`, `.mk-shell__content` (cada estilo ajusta su cabecera).
|
|
46
48
|
|
|
47
49
|
## React
|
|
48
50
|
|
|
@@ -52,7 +54,7 @@ const [page, setPage] = useState('home');
|
|
|
52
54
|
<div className="mk-shell" style={{ height: '100dvh' }}>
|
|
53
55
|
<main className="mk-shell__view">…</main>
|
|
54
56
|
<Nav
|
|
55
|
-
navStyle="
|
|
57
|
+
navStyle="rail"
|
|
56
58
|
items={[
|
|
57
59
|
{ value: 'home', label: 'Inicio', icon: <HomeIcon /> },
|
|
58
60
|
{ value: 'movements', label: 'Movimientos', icon: <RowsIcon /> },
|
|
@@ -76,7 +78,7 @@ const [page, setPage] = useState('home');
|
|
|
76
78
|
|
|
77
79
|
## Variables CSS
|
|
78
80
|
|
|
79
|
-
`--mk-nav-icon-size` (24px; 16px en
|
|
81
|
+
`--mk-nav-icon-size` (24px; 16px en card y wash con acento). Colores de los roles Material (`surface-container-*`, `primary-container`, `tertiary-container`…).
|
|
80
82
|
|
|
81
83
|
## Accesibilidad
|
|
82
84
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
-
import { createNav, type NavInstance, type NavStyle } from './engine';
|
|
2
|
+
import { createNav, type NavInstance, type NavStyle, type NavStyleAlias } from './engine';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* <mk-nav class="mk-nav mk-nav--
|
|
5
|
+
* <mk-nav class="mk-nav mk-nav--pills" nav-style="pills" [accent] [reveal] [storage-key="mi-app-nav"]>
|
|
6
6
|
* <div class="mk-nav__buttons"><a class="mk-nav__button" href="/">…</a></div>
|
|
7
7
|
* </mk-nav>
|
|
8
8
|
* Emits `mk:navigate` (detail: index, value, item, originalEvent) on click.
|
|
@@ -12,7 +12,7 @@ export class MkNavElement extends MkElement<NavInstance> {
|
|
|
12
12
|
|
|
13
13
|
protected create(options: Record<string, unknown>) {
|
|
14
14
|
return createNav(this, {
|
|
15
|
-
style: options.navStyle as NavStyle | undefined,
|
|
15
|
+
style: options.navStyle as NavStyle | NavStyleAlias | undefined,
|
|
16
16
|
accent: options.accent as boolean | undefined,
|
|
17
17
|
reveal: Boolean(options.reveal),
|
|
18
18
|
storageKey: options.storageKey as string | undefined,
|
|
@@ -22,7 +22,7 @@ export class MkNavElement extends MkElement<NavInstance> {
|
|
|
22
22
|
// Style and accent change in place: rebuilding would replay the reveal.
|
|
23
23
|
attributeChangedCallback(name: string, previous: string | null, next: string | null) {
|
|
24
24
|
if (previous === next || !this.instance) return;
|
|
25
|
-
if (name === 'nav-style' && next) this.instance.setStyle(next as NavStyle);
|
|
25
|
+
if (name === 'nav-style' && next) this.instance.setStyle(next as NavStyle | NavStyleAlias);
|
|
26
26
|
else if (name === 'accent') this.instance.setAccent(next !== null && next !== 'false');
|
|
27
27
|
else super.attributeChangedCallback(name, previous, next);
|
|
28
28
|
}
|
|
@@ -31,7 +31,7 @@ export class MkNavElement extends MkElement<NavInstance> {
|
|
|
31
31
|
this.instance?.select(target);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
setStyle(style: NavStyle) {
|
|
34
|
+
setStyle(style: NavStyle | NavStyleAlias) {
|
|
35
35
|
this.instance?.setStyle(style);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -4,20 +4,26 @@ import { inert, warnOnce, type Instance } from '../../core/types';
|
|
|
4
4
|
import { revealFrom, type RevealHandle } from '../../motion/reveal';
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
|
-
* navigation: seven desktop styles
|
|
8
|
-
*
|
|
7
|
+
* navigation: seven desktop styles named after how they look (sidebar, pills,
|
|
8
|
+
* dock, card, rail, wash, magnify) and one glass bar on phones, all in CSS (nav.css). The
|
|
9
9
|
* engine marks the current page (aria-current="page"), switches styles (and can
|
|
10
10
|
* remember the choice), and on start can reveal the
|
|
11
11
|
* app shell from the first button.
|
|
12
12
|
* Parts: .mk-nav__buttons .mk-nav__button (data-mk-value or href), .mk-nav__button--first.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
export const NAV_STYLES = ['
|
|
15
|
+
export const NAV_STYLES = ['sidebar', 'pills', 'dock', 'card', 'rail', 'wash', 'magnify'] as const;
|
|
16
16
|
export type NavStyle = (typeof NAV_STYLES)[number];
|
|
17
|
+
/** The names before 0.15, still accepted: each turns into the style it always was. */
|
|
18
|
+
export const NAV_STYLE_ALIASES = { clasica: 'sidebar', bonita: 'pills', guapa: 'card', tasks: 'rail', melon: 'wash', tunnel: 'magnify' } as const satisfies Record<string, NavStyle>;
|
|
19
|
+
export type NavStyleAlias = keyof typeof NAV_STYLE_ALIASES;
|
|
20
|
+
/** A style name (new or old) as one of NAV_STYLES, or undefined. */
|
|
21
|
+
export const toNavStyle = (value: unknown): NavStyle | undefined =>
|
|
22
|
+
NAV_STYLES.includes(value as NavStyle) ? (value as NavStyle) : (NAV_STYLE_ALIASES as Record<string, NavStyle>)[value as string];
|
|
17
23
|
|
|
18
24
|
export interface NavOptions {
|
|
19
|
-
/** Default: the saved one (storageKey), else the mk-nav--<style> class in the markup, else '
|
|
20
|
-
style?: NavStyle;
|
|
25
|
+
/** Default: the saved one (storageKey), else the mk-nav--<style> class in the markup, else 'pills'. Old names (clasica…) work too. */
|
|
26
|
+
style?: NavStyle | NavStyleAlias;
|
|
21
27
|
/** The accent variant: the current page in primary-container. */
|
|
22
28
|
accent?: boolean;
|
|
23
29
|
/** Reveal the shell from the first button when the nav starts (off by default). */
|
|
@@ -31,7 +37,7 @@ export interface NavOptions {
|
|
|
31
37
|
export interface NavInstance extends Instance {
|
|
32
38
|
/** Mark a page as current, by index or value (silent: no event). */
|
|
33
39
|
select(target: number | string): void;
|
|
34
|
-
setStyle(style: NavStyle): void;
|
|
40
|
+
setStyle(style: NavStyle | NavStyleAlias): void;
|
|
35
41
|
setAccent(on: boolean): void;
|
|
36
42
|
readonly index: number;
|
|
37
43
|
readonly style: NavStyle;
|
|
@@ -47,7 +53,6 @@ export interface NavigateDetail {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
const ITEM = '.mk-nav__buttons .mk-nav__button';
|
|
50
|
-
const isStyle = (value: unknown): value is NavStyle => NAV_STYLES.includes(value as NavStyle);
|
|
51
56
|
const valueOf = (item: HTMLElement) => item.dataset.mkValue ?? item.getAttribute('href') ?? undefined;
|
|
52
57
|
|
|
53
58
|
const read = (key: string) => {
|
|
@@ -77,17 +82,18 @@ export function revealShell(nav: HTMLElement): RevealHandle | null {
|
|
|
77
82
|
export function createNav(root: HTMLElement | null, options: NavOptions = {}): NavInstance {
|
|
78
83
|
if (!root) {
|
|
79
84
|
warnOnce('nav: missing root');
|
|
80
|
-
return inert({ select() {}, setStyle() {}, setAccent() {}, index: -1, style: '
|
|
85
|
+
return inert({ select() {}, setStyle() {}, setAccent() {}, index: -1, style: 'pills', items: [] });
|
|
81
86
|
}
|
|
82
87
|
const scope = createScope();
|
|
83
88
|
const items = () => [...root.querySelectorAll<HTMLElement>(ITEM)];
|
|
84
|
-
const
|
|
89
|
+
const known = [...NAV_STYLES, ...Object.keys(NAV_STYLE_ALIASES)];
|
|
90
|
+
const fromClass = toNavStyle(known.find((name) => root.classList.contains(`mk-nav--${name}`)));
|
|
85
91
|
const saved = options.storageKey ? read(options.storageKey) : null;
|
|
86
|
-
let style: NavStyle = options.style ?? (
|
|
92
|
+
let style: NavStyle = toNavStyle(options.style) ?? toNavStyle(saved) ?? fromClass ?? 'pills';
|
|
87
93
|
|
|
88
94
|
const paint = (accent: boolean) => {
|
|
89
95
|
const classes = new Set(root.classList);
|
|
90
|
-
for (const name of
|
|
96
|
+
for (const name of known) classes.delete(`mk-nav--${name}`);
|
|
91
97
|
classes.add('mk-nav');
|
|
92
98
|
classes.add(`mk-nav--${style}`);
|
|
93
99
|
if (accent) classes.add('mk-nav--accent');
|
|
@@ -133,13 +139,14 @@ export function createNav(root: HTMLElement | null, options: NavOptions = {}): N
|
|
|
133
139
|
else mark(item);
|
|
134
140
|
},
|
|
135
141
|
setStyle(next) {
|
|
136
|
-
|
|
142
|
+
const resolved = toNavStyle(next);
|
|
143
|
+
if (!resolved) {
|
|
137
144
|
warnOnce(`nav: unknown style "${String(next)}" (use ${NAV_STYLES.join(', ')})`);
|
|
138
145
|
return;
|
|
139
146
|
}
|
|
140
|
-
style =
|
|
147
|
+
style = resolved;
|
|
141
148
|
paint(accent);
|
|
142
|
-
if (options.storageKey) write(options.storageKey,
|
|
149
|
+
if (options.storageKey) write(options.storageKey, resolved);
|
|
143
150
|
},
|
|
144
151
|
setAccent(on) {
|
|
145
152
|
accent = on;
|