@yewdiel21/motion-kit 0.14.0 → 0.16.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 +178 -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-NFZIX3IA.js} +2 -2
- package/dist/chunk-RBEKR5UZ.js +3660 -0
- package/dist/chunk-RBEKR5UZ.js.map +1 -0
- package/dist/{chunk-CU6SXW3E.js → chunk-SDQ776LG.js} +1596 -485
- package/dist/chunk-SDQ776LG.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/chart.css +7 -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 +398 -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 +397 -0
- package/dist/css/components/pill-button.css +135 -0
- package/dist/css/components/radar-chart.css +377 -0
- package/dist/css/components/radial-chart.css +412 -0
- package/dist/css/components/sankey-chart.css +365 -0
- package/dist/css/components/scroll-island.css +114 -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 +293 -7
- package/dist/elements/index.js.map +1 -1
- package/dist/{engine-Cn6IhE3z.d.ts → engine-5zRIssTO.d.ts} +11 -1
- package/dist/index.css +1849 -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 +320 -13
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-BBUusld6.d.ts → reduced-DQwVXQYb.d.ts} +1 -1
- package/dist/{reveal-DEvp6U6T.d.ts → reveal-DRpNO4KP.d.ts} +109 -4
- package/dist/store-Da3zSh8Z.d.ts +457 -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 +49 -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 +33 -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 +49 -24
- package/src/components/chart/chart.css +7 -0
- package/src/components/chart/element.ts +4 -1
- package/src/components/chart/engine.ts +153 -34
- 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 +58 -0
- package/src/components/radial-chart/element.ts +30 -0
- package/src/components/radial-chart/engine.ts +317 -0
- package/src/components/radial-chart/radial-chart.css +107 -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 +44 -0
- package/src/components/scroll-island/element.ts +16 -0
- package/src/components/scroll-island/engine.ts +274 -0
- package/src/components/scroll-island/react.tsx +26 -0
- package/src/components/scroll-island/scroll-island.css +113 -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-NFZIX3IA.js.map} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requestFrame } from '../../core/frame';
|
|
2
|
+
import { reduced } from '../../core/reduced';
|
|
2
3
|
import { createScope } from '../../core/scope';
|
|
3
4
|
import { inert, type Instance } from '../../core/types';
|
|
4
5
|
|
|
@@ -20,12 +21,16 @@ let sequence = 0;
|
|
|
20
21
|
/** Distance kept from the viewport edges. */
|
|
21
22
|
const EDGE = 12;
|
|
22
23
|
const GAP = 12;
|
|
24
|
+
/** Longest fold back into the trigger (the CSS one is .22 s); then it hides anyway. */
|
|
25
|
+
const FOLD_MS = 400;
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* Nonmodal preview: pointer, keyboard focus and tap all reveal the same panel.
|
|
26
29
|
* The panel stays inside the viewport: it flips to the other side when its own
|
|
27
30
|
* has no room and shifts sideways (--mk-hover-shift) away from the edges, again
|
|
28
31
|
* on scroll and resize. Opened from the keyboard it appears without the entrance.
|
|
32
|
+
* It grows out of the trigger's centre (--mk-hover-origin-x) and, when the
|
|
33
|
+
* pointer leaves, folds back into it (data-mk-closing, .22 s) before hiding.
|
|
29
34
|
*/
|
|
30
35
|
export function createHoverCard(root: HTMLElement | null, options: HoverCardOptions = {}): HoverCardInstance {
|
|
31
36
|
const trigger = root?.querySelector<HTMLElement>('[data-mk-hover-trigger]');
|
|
@@ -68,6 +73,9 @@ export function createHoverCard(root: HTMLElement | null, options: HoverCardOpti
|
|
|
68
73
|
if (left + box.width > view.width - EDGE) shift = view.width - EDGE - box.width - left;
|
|
69
74
|
if (left + shift < EDGE) shift = EDGE - left;
|
|
70
75
|
if (shift) root.style.setProperty('--mk-hover-shift', `${Math.round(shift)}px`);
|
|
76
|
+
// The panel grows from (and folds back into) the trigger's centre.
|
|
77
|
+
const target = trigger.getBoundingClientRect();
|
|
78
|
+
root.style.setProperty('--mk-hover-origin-x', `${Math.round(target.left + target.width / 2 - (left + shift))}px`);
|
|
71
79
|
};
|
|
72
80
|
let pending: (() => void) | null = null;
|
|
73
81
|
const schedulePlace = () => {
|
|
@@ -80,13 +88,34 @@ export function createHoverCard(root: HTMLElement | null, options: HoverCardOpti
|
|
|
80
88
|
scope.add(() => pending?.());
|
|
81
89
|
|
|
82
90
|
const cancel = () => { clearTimeout(timer); timer = undefined; };
|
|
91
|
+
// The fold back into the trigger: the panel stays until its animation ends (or FOLD_MS passes).
|
|
92
|
+
let folding: ReturnType<typeof setTimeout> | undefined;
|
|
93
|
+
const settleFold = () => {
|
|
94
|
+
clearTimeout(folding);
|
|
95
|
+
folding = undefined;
|
|
96
|
+
panel.removeEventListener('animationend', settleFold);
|
|
97
|
+
if (scope.disposed) return;
|
|
98
|
+
scope.attr(root, 'data-mk-closing', null);
|
|
99
|
+
if (!opened) scope.attr(panel, 'hidden', '');
|
|
100
|
+
};
|
|
101
|
+
scope.add(() => {
|
|
102
|
+
clearTimeout(folding);
|
|
103
|
+
panel.removeEventListener('animationend', settleFold);
|
|
104
|
+
});
|
|
83
105
|
const set = (value: boolean) => {
|
|
84
106
|
cancel();
|
|
85
107
|
if (scope.disposed) return;
|
|
86
108
|
const changed = value !== opened;
|
|
87
109
|
opened = value;
|
|
88
110
|
scope.attr(trigger, 'aria-expanded', String(value));
|
|
89
|
-
|
|
111
|
+
if (value) {
|
|
112
|
+
if (folding !== undefined) settleFold();
|
|
113
|
+
scope.attr(panel, 'hidden', null);
|
|
114
|
+
} else if (changed && !reduced() && root.dataset.mkInput !== 'keyboard' && !panel.hidden) {
|
|
115
|
+
scope.attr(root, 'data-mk-closing', '');
|
|
116
|
+
panel.addEventListener('animationend', settleFold);
|
|
117
|
+
folding = setTimeout(settleFold, FOLD_MS);
|
|
118
|
+
} else if (folding === undefined) scope.attr(panel, 'hidden', '');
|
|
90
119
|
scope.attr(root, 'data-mk-open', value ? '' : null);
|
|
91
120
|
if (value && changed) {
|
|
92
121
|
scope.attr(root, 'data-mk-input', pointerInput ? 'pointer' : 'keyboard');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
.mk-hover-card, mk-hover-card { position: relative; display: inline-flex; }
|
|
2
2
|
.mk-hover-card [data-mk-hover-panel], mk-hover-card [data-mk-hover-panel] {
|
|
3
|
-
|
|
3
|
+
/* It grows from, and folds back into, the trigger's centre (the engine measures it). */
|
|
4
|
+
transform-origin: var(--mk-hover-origin-x, 50%) 100%;
|
|
4
5
|
position: absolute; z-index: 10; bottom: calc(100% + .75rem); left: var(--mk-hover-shift, 0);
|
|
5
6
|
width: min(19rem, calc(100vw - 1.5rem)); padding: 1.25rem; border-radius: var(--mk-radius-lg);
|
|
6
7
|
color: var(--mk-on-glass-inverse); background: var(--mk-glass-inverse); border: 0; border-radius: var(--mk-radius-lg, 24px);
|
|
@@ -10,10 +11,15 @@
|
|
|
10
11
|
}
|
|
11
12
|
/* Bridge over the gap so the pointer can travel into the panel. */
|
|
12
13
|
.mk-hover-card [data-mk-hover-panel]::after, mk-hover-card [data-mk-hover-panel]::after { content: ''; position: absolute; inset: -1rem 0; z-index: -1; }
|
|
13
|
-
:is(.mk-hover-card, mk-hover-card)[data-mk-side=bottom] [data-mk-hover-panel] { top: calc(100% + .75rem); bottom: auto; transform-origin: 50% 0; animation-name: mk-hover-card-down; }
|
|
14
|
+
:is(.mk-hover-card, mk-hover-card)[data-mk-side=bottom] [data-mk-hover-panel] { top: calc(100% + .75rem); bottom: auto; transform-origin: var(--mk-hover-origin-x, 50%) 0; animation-name: mk-hover-card-down; }
|
|
15
|
+
/* Leaving: it shrinks back into the trigger out of a blur, then the engine hides it. */
|
|
16
|
+
:is(.mk-hover-card, mk-hover-card)[data-mk-closing] [data-mk-hover-panel] { pointer-events: none; animation: mk-hover-card-out .22s cubic-bezier(.4,0,1,1) both; }
|
|
17
|
+
:is(.mk-hover-card, mk-hover-card)[data-mk-closing][data-mk-side=bottom] [data-mk-hover-panel] { animation-name: mk-hover-card-out-up; }
|
|
14
18
|
/* Opened from the keyboard: no entrance. */
|
|
15
19
|
:is(.mk-hover-card, mk-hover-card)[data-mk-input=keyboard] [data-mk-hover-panel] { animation: none; }
|
|
16
20
|
.mk-hover-card [hidden], mk-hover-card [hidden] { display: none !important; }
|
|
17
21
|
@keyframes mk-hover-card-in { from { opacity: 0; filter: blur(32px); transform: translateY(8px) scale(.7); } to { opacity: 1; filter: none; transform: none; } }
|
|
22
|
+
@keyframes mk-hover-card-out { to { opacity: 0; filter: blur(12px); transform: translateY(10px) scale(.3); } }
|
|
23
|
+
@keyframes mk-hover-card-out-up { to { opacity: 0; filter: blur(12px); transform: translateY(-10px) scale(.3); } }
|
|
18
24
|
@keyframes mk-hover-card-down { from { opacity: 0; filter: blur(32px); transform: translateY(-8px) scale(.7); } to { opacity: 1; filter: none; transform: none; } }
|
|
19
25
|
@media (prefers-reduced-motion: reduce) { .mk-hover-card [data-mk-hover-panel], mk-hover-card [data-mk-hover-panel] { animation: none; } }
|
|
@@ -8,7 +8,7 @@ import { createImagePreview, type ImagePreviewInstance } from './engine';
|
|
|
8
8
|
* Emits mk:open, mk:change ({index, previous}) and mk:close.
|
|
9
9
|
*/
|
|
10
10
|
export class MkImagePreviewElement extends MkElement<ImagePreviewInstance> {
|
|
11
|
-
static options: Record<string, OptionType> = { loop: 'boolean', labels: 'json' };
|
|
11
|
+
static options: Record<string, OptionType> = { loop: 'boolean', strip: 'boolean', labels: 'json' };
|
|
12
12
|
|
|
13
13
|
protected create(options: Record<string, unknown>) {
|
|
14
14
|
return createImagePreview(this, options);
|
|
@@ -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
|
}
|