@yewdiel21/motion-kit 0.10.0 → 0.14.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 +155 -93
- package/dist/auto/index.d.ts +53 -3
- package/dist/auto/index.js +101 -3
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-IKQ5FODN.js → chunk-6P4P7NU6.js} +2 -218
- package/dist/chunk-6P4P7NU6.js.map +1 -0
- package/dist/chunk-CU6SXW3E.js +20334 -0
- package/dist/chunk-CU6SXW3E.js.map +1 -0
- package/dist/{chunk-XAKC2QY7.js → chunk-EY4BJKC4.js} +3 -28
- package/dist/chunk-EY4BJKC4.js.map +1 -0
- package/dist/chunk-QP4QJOEN.js +72 -0
- package/dist/chunk-QP4QJOEN.js.map +1 -0
- package/dist/css/components/activity-feed.css +240 -0
- package/dist/css/components/attachment.css +266 -0
- package/dist/css/components/breadcrumb.css +110 -0
- package/dist/css/components/chart-tooltip.css +168 -0
- package/dist/css/components/chart.css +296 -0
- package/dist/css/components/code-block.css +264 -0
- package/dist/css/components/color-area.css +82 -0
- package/dist/css/components/color-field.css +67 -0
- package/dist/css/components/color-picker.css +131 -0
- package/dist/css/components/color-slider.css +113 -0
- package/dist/css/components/command-search.css +252 -0
- package/dist/css/components/context-menu.css +23 -0
- package/dist/css/components/credit-card.css +288 -0
- package/dist/css/components/data-table.css +591 -0
- package/dist/css/components/date-range-picker.css +168 -0
- package/dist/css/components/date-time-picker.css +178 -0
- package/dist/css/components/drawer.css +58 -0
- package/dist/css/components/editable-table.css +201 -0
- package/dist/css/components/empty-state.css +61 -0
- package/dist/css/components/file-explorer.css +633 -0
- package/dist/css/components/filter-builder.css +232 -0
- package/dist/css/components/hold-confirm.css +87 -0
- package/dist/css/components/hover-card.css +13 -7
- package/dist/css/components/image-preview.css +195 -0
- package/dist/css/components/json-viewer.css +272 -0
- package/dist/css/components/like-button.css +78 -0
- package/dist/css/components/link-preview.css +275 -0
- package/dist/css/components/loading-button.css +77 -0
- package/dist/css/components/multi-select.css +267 -0
- package/dist/css/components/notification-bell.css +127 -0
- package/dist/css/components/password-strength.css +183 -0
- package/dist/css/components/popover.css +25 -0
- package/dist/css/components/progress.css +140 -0
- package/dist/css/components/questionnaire.css +217 -0
- package/dist/css/components/range-calendar.css +186 -0
- package/dist/css/components/range-slider.css +192 -0
- package/dist/css/components/resizable-panel.css +243 -0
- package/dist/css/components/rich-text-editor.css +284 -0
- package/dist/css/components/scratch-card.css +75 -0
- package/dist/css/components/skeleton.css +108 -0
- package/dist/css/components/split-view.css +19 -0
- package/dist/css/components/star-rating.css +80 -0
- package/dist/css/components/stat-card.css +164 -0
- package/dist/css/components/swipe-actions.css +105 -0
- package/dist/css/components/tag-input.css +244 -0
- package/dist/css/components/time-field.css +88 -0
- package/dist/css/components/timeline.css +219 -0
- package/dist/css/components/tooltip.css +79 -0
- package/dist/css/components/tree-view.css +210 -0
- package/dist/css/components/virtual-list.css +195 -0
- package/dist/css/tokens.css +185 -181
- package/dist/elements/index.d.ts +674 -5
- package/dist/elements/index.js +1361 -8
- package/dist/elements/index.js.map +1 -1
- package/dist/{engine-GDGwQnJE.d.ts → engine-Cn6IhE3z.d.ts} +1 -1
- package/dist/engine-QECQnGz1.d.ts +33 -0
- package/dist/index.css +9104 -189
- package/dist/index.d.ts +89 -5
- package/dist/index.js +243 -9
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +579 -23
- package/dist/react/index.js +2042 -7
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-BmBzKH94.d.ts → reduced-BBUusld6.d.ts} +9 -2
- package/dist/reveal-DEvp6U6T.d.ts +2381 -0
- package/dist/{store-DYb3tTsZ.d.ts → store-DjkTBX7f.d.ts} +2 -2
- package/package.json +1 -1
- package/registry.json +406 -1
- package/src/auto/auto-init.ts +98 -0
- package/src/components/activity-feed/README.md +56 -0
- package/src/components/activity-feed/activity-feed.css +239 -0
- package/src/components/activity-feed/element.ts +36 -0
- package/src/components/activity-feed/engine.ts +351 -0
- package/src/components/activity-feed/react.tsx +32 -0
- package/src/components/attachment/README.md +27 -0
- package/src/components/attachment/attachment.css +265 -0
- package/src/components/attachment/element.ts +50 -0
- package/src/components/attachment/engine.ts +469 -0
- package/src/components/attachment/react.tsx +65 -0
- package/src/components/breadcrumb/README.md +38 -0
- package/src/components/breadcrumb/breadcrumb.css +109 -0
- package/src/components/breadcrumb/element.ts +27 -0
- package/src/components/breadcrumb/engine.ts +249 -0
- package/src/components/breadcrumb/react.tsx +30 -0
- package/src/components/chart/README.md +24 -0
- package/src/components/chart/chart.css +295 -0
- package/src/components/chart/element.ts +41 -0
- package/src/components/chart/engine.ts +811 -0
- package/src/components/chart/react.tsx +50 -0
- package/src/components/chart-tooltip/README.md +53 -0
- package/src/components/chart-tooltip/chart-tooltip.css +167 -0
- package/src/components/chart-tooltip/element.ts +37 -0
- package/src/components/chart-tooltip/engine.ts +442 -0
- package/src/components/chart-tooltip/react.tsx +47 -0
- package/src/components/code-block/README.md +19 -0
- package/src/components/code-block/code-block.css +263 -0
- package/src/components/code-block/element.ts +34 -0
- package/src/components/code-block/engine.ts +254 -0
- package/src/components/code-block/highlight.ts +244 -0
- package/src/components/code-block/react.tsx +94 -0
- package/src/components/color-area/README.md +50 -0
- package/src/components/color-area/color-area.css +81 -0
- package/src/components/color-area/element.ts +26 -0
- package/src/components/color-area/engine.ts +246 -0
- package/src/components/color-area/react.tsx +42 -0
- package/src/components/color-field/README.md +43 -0
- package/src/components/color-field/color-field.css +66 -0
- package/src/components/color-field/element.ts +26 -0
- package/src/components/color-field/engine.ts +174 -0
- package/src/components/color-field/react.tsx +42 -0
- package/src/components/color-picker/README.md +45 -0
- package/src/components/color-picker/color-picker.css +130 -0
- package/src/components/color-picker/element.ts +45 -0
- package/src/components/color-picker/engine.ts +314 -0
- package/src/components/color-picker/react.tsx +47 -0
- package/src/components/color-slider/README.md +55 -0
- package/src/components/color-slider/color-slider.css +112 -0
- package/src/components/color-slider/element.ts +26 -0
- package/src/components/color-slider/engine.ts +234 -0
- package/src/components/color-slider/react.tsx +45 -0
- package/src/components/command-search/README.md +15 -0
- package/src/components/command-search/command-search.css +251 -0
- package/src/components/command-search/element.ts +36 -0
- package/src/components/command-search/engine.ts +356 -0
- package/src/components/command-search/match.ts +53 -0
- package/src/components/command-search/react.tsx +66 -0
- package/src/components/context-menu/README.md +31 -0
- package/src/components/context-menu/context-menu.css +22 -0
- package/src/components/context-menu/element.ts +40 -0
- package/src/components/context-menu/engine.ts +153 -0
- package/src/components/context-menu/react.tsx +57 -0
- package/src/components/credit-card/README.md +20 -0
- package/src/components/credit-card/card.ts +77 -0
- package/src/components/credit-card/credit-card.css +287 -0
- package/src/components/credit-card/element.ts +33 -0
- package/src/components/credit-card/engine.ts +410 -0
- package/src/components/credit-card/react.tsx +36 -0
- package/src/components/data-table/README.md +83 -0
- package/src/components/data-table/data-table.css +590 -0
- package/src/components/data-table/element.ts +54 -0
- package/src/components/data-table/engine.ts +1059 -0
- package/src/components/data-table/react.tsx +94 -0
- package/src/components/date-range-picker/README.md +51 -0
- package/src/components/date-range-picker/date-range-picker.css +167 -0
- package/src/components/date-range-picker/element.ts +52 -0
- package/src/components/date-range-picker/engine.ts +352 -0
- package/src/components/date-range-picker/react.tsx +57 -0
- package/src/components/date-time-picker/README.md +48 -0
- package/src/components/date-time-picker/date-time-picker.css +177 -0
- package/src/components/date-time-picker/element.ts +47 -0
- package/src/components/date-time-picker/engine.ts +350 -0
- package/src/components/date-time-picker/react.tsx +54 -0
- package/src/components/dialog/README.md +103 -102
- package/src/components/dialog/element.ts +123 -118
- package/src/components/dialog/engine.ts +449 -432
- package/src/components/drawer/README.md +28 -0
- package/src/components/drawer/drawer.css +57 -0
- package/src/components/drawer/element.ts +21 -0
- package/src/components/drawer/engine.ts +56 -0
- package/src/components/drawer/react.tsx +85 -0
- package/src/components/editable-table/README.md +67 -0
- package/src/components/editable-table/editable-table.css +200 -0
- package/src/components/editable-table/element.ts +45 -0
- package/src/components/editable-table/engine.ts +775 -0
- package/src/components/editable-table/react.tsx +79 -0
- package/src/components/empty-state/README.md +41 -0
- package/src/components/empty-state/element.ts +23 -0
- package/src/components/empty-state/empty-state.css +60 -0
- package/src/components/empty-state/engine.ts +232 -0
- package/src/components/empty-state/react.tsx +34 -0
- package/src/components/file-explorer/README.md +67 -0
- package/src/components/file-explorer/element.ts +39 -0
- package/src/components/file-explorer/engine.ts +1307 -0
- package/src/components/file-explorer/file-explorer.css +632 -0
- package/src/components/file-explorer/react.tsx +77 -0
- package/src/components/filter-builder/README.md +47 -0
- package/src/components/filter-builder/element.ts +34 -0
- package/src/components/filter-builder/engine.ts +365 -0
- package/src/components/filter-builder/filter-builder.css +231 -0
- package/src/components/filter-builder/react.tsx +36 -0
- package/src/components/hold-confirm/README.md +13 -0
- package/src/components/hold-confirm/element.ts +24 -0
- package/src/components/hold-confirm/engine.ts +216 -0
- package/src/components/hold-confirm/hold-confirm.css +86 -0
- package/src/components/hold-confirm/react.tsx +28 -0
- package/src/components/hover-card/README.md +6 -2
- package/src/components/hover-card/engine.ts +68 -6
- package/src/components/hover-card/hover-card.css +13 -7
- package/src/components/image-preview/README.md +16 -0
- package/src/components/image-preview/element.ts +26 -0
- package/src/components/image-preview/engine.ts +325 -0
- package/src/components/image-preview/image-preview.css +194 -0
- package/src/components/image-preview/react.tsx +43 -0
- package/src/components/json-viewer/README.md +56 -0
- package/src/components/json-viewer/element.ts +37 -0
- package/src/components/json-viewer/engine.ts +440 -0
- package/src/components/json-viewer/json-viewer.css +271 -0
- package/src/components/json-viewer/react.tsx +56 -0
- package/src/components/like-button/README.md +13 -0
- package/src/components/like-button/element.ts +47 -0
- package/src/components/like-button/engine.ts +145 -0
- package/src/components/like-button/like-button.css +77 -0
- package/src/components/like-button/react.tsx +39 -0
- package/src/components/link-preview/README.md +19 -0
- package/src/components/link-preview/element.ts +29 -0
- package/src/components/link-preview/engine.ts +192 -0
- package/src/components/link-preview/link-preview.css +274 -0
- package/src/components/link-preview/react.tsx +101 -0
- package/src/components/loading-button/README.md +45 -0
- package/src/components/loading-button/element.ts +34 -0
- package/src/components/loading-button/engine.ts +201 -0
- package/src/components/loading-button/loading-button.css +76 -0
- package/src/components/loading-button/react.tsx +43 -0
- package/src/components/multi-select/README.md +48 -0
- package/src/components/multi-select/element.ts +28 -0
- package/src/components/multi-select/engine.ts +294 -0
- package/src/components/multi-select/multi-select.css +266 -0
- package/src/components/multi-select/react.tsx +39 -0
- package/src/components/notification-bell/README.md +14 -0
- package/src/components/notification-bell/element.ts +48 -0
- package/src/components/notification-bell/engine.ts +202 -0
- package/src/components/notification-bell/notification-bell.css +126 -0
- package/src/components/notification-bell/react.tsx +57 -0
- package/src/components/password-strength/README.md +17 -0
- package/src/components/password-strength/element.ts +22 -0
- package/src/components/password-strength/engine.ts +212 -0
- package/src/components/password-strength/password-strength.css +182 -0
- package/src/components/password-strength/react.tsx +36 -0
- package/src/components/password-strength/score.ts +62 -0
- package/src/components/popover/README.md +44 -0
- package/src/components/popover/element.ts +31 -0
- package/src/components/popover/engine.ts +114 -0
- package/src/components/popover/popover.css +24 -0
- package/src/components/popover/react.tsx +50 -0
- package/src/components/progress/README.md +17 -0
- package/src/components/progress/element.ts +30 -0
- package/src/components/progress/engine.ts +173 -0
- package/src/components/progress/progress.css +139 -0
- package/src/components/progress/react.tsx +35 -0
- package/src/components/questionnaire/README.md +32 -0
- package/src/components/questionnaire/element.ts +118 -0
- package/src/components/questionnaire/engine.ts +255 -0
- package/src/components/questionnaire/questionnaire.css +216 -0
- package/src/components/questionnaire/react.tsx +140 -0
- package/src/components/range-calendar/README.md +58 -0
- package/src/components/range-calendar/element.ts +42 -0
- package/src/components/range-calendar/engine.ts +390 -0
- package/src/components/range-calendar/range-calendar.css +185 -0
- package/src/components/range-calendar/react.tsx +56 -0
- package/src/components/range-slider/README.md +23 -0
- package/src/components/range-slider/element.ts +53 -0
- package/src/components/range-slider/engine.ts +316 -0
- package/src/components/range-slider/range-slider.css +191 -0
- package/src/components/range-slider/react.tsx +66 -0
- package/src/components/resizable-panel/README.md +61 -0
- package/src/components/resizable-panel/element.ts +36 -0
- package/src/components/resizable-panel/engine.ts +551 -0
- package/src/components/resizable-panel/react.tsx +74 -0
- package/src/components/resizable-panel/resizable-panel.css +242 -0
- package/src/components/rich-text-editor/README.md +57 -0
- package/src/components/rich-text-editor/element.ts +34 -0
- package/src/components/rich-text-editor/engine.ts +673 -0
- package/src/components/rich-text-editor/react.tsx +60 -0
- package/src/components/rich-text-editor/rich-text-editor.css +283 -0
- package/src/components/scratch-card/README.md +15 -0
- package/src/components/scratch-card/element.ts +30 -0
- package/src/components/scratch-card/engine.ts +257 -0
- package/src/components/scratch-card/react.tsx +40 -0
- package/src/components/scratch-card/scratch-card.css +74 -0
- package/src/components/skeleton/README.md +15 -0
- package/src/components/skeleton/element.ts +32 -0
- package/src/components/skeleton/engine.ts +104 -0
- package/src/components/skeleton/react.tsx +41 -0
- package/src/components/skeleton/skeleton.css +107 -0
- package/src/components/split-view/README.md +50 -0
- package/src/components/split-view/element.ts +34 -0
- package/src/components/split-view/engine.ts +114 -0
- package/src/components/split-view/react.tsx +63 -0
- package/src/components/split-view/split-view.css +18 -0
- package/src/components/star-rating/README.md +14 -0
- package/src/components/star-rating/element.ts +26 -0
- package/src/components/star-rating/engine.ts +217 -0
- package/src/components/star-rating/react.tsx +33 -0
- package/src/components/star-rating/star-rating.css +79 -0
- package/src/components/stat-card/README.md +42 -0
- package/src/components/stat-card/element.ts +40 -0
- package/src/components/stat-card/engine.ts +283 -0
- package/src/components/stat-card/react.tsx +36 -0
- package/src/components/stat-card/stat-card.css +163 -0
- package/src/components/swipe-actions/README.md +19 -0
- package/src/components/swipe-actions/element.ts +26 -0
- package/src/components/swipe-actions/engine.ts +260 -0
- package/src/components/swipe-actions/react.tsx +80 -0
- package/src/components/swipe-actions/swipe-actions.css +104 -0
- package/src/components/tag-input/README.md +42 -0
- package/src/components/tag-input/element.ts +39 -0
- package/src/components/tag-input/engine.ts +313 -0
- package/src/components/tag-input/react.tsx +54 -0
- package/src/components/tag-input/tag-input.css +243 -0
- package/src/components/time-field/README.md +54 -0
- package/src/components/time-field/element.ts +38 -0
- package/src/components/time-field/engine.ts +110 -0
- package/src/components/time-field/react.tsx +45 -0
- package/src/components/time-field/time-field.css +87 -0
- package/src/components/timeline/README.md +57 -0
- package/src/components/timeline/element.ts +31 -0
- package/src/components/timeline/engine.ts +300 -0
- package/src/components/timeline/react.tsx +59 -0
- package/src/components/timeline/timeline.css +218 -0
- package/src/components/tooltip/README.md +46 -0
- package/src/components/tooltip/element.ts +22 -0
- package/src/components/tooltip/engine.ts +288 -0
- package/src/components/tooltip/react.tsx +30 -0
- package/src/components/tooltip/tooltip.css +78 -0
- package/src/components/tree-view/README.md +61 -0
- package/src/components/tree-view/element.ts +32 -0
- package/src/components/tree-view/engine.ts +569 -0
- package/src/components/tree-view/react.tsx +78 -0
- package/src/components/tree-view/tree-view.css +209 -0
- package/src/components/virtual-list/README.md +81 -0
- package/src/components/virtual-list/element.ts +42 -0
- package/src/components/virtual-list/engine.ts +678 -0
- package/src/components/virtual-list/react.tsx +101 -0
- package/src/components/virtual-list/virtual-list.css +194 -0
- package/src/core/chips.ts +196 -0
- package/src/core/color.ts +304 -0
- package/src/core/date-range.ts +67 -0
- package/src/core/dates.ts +11 -1
- package/src/core/files.ts +107 -0
- package/src/core/filter.ts +229 -0
- package/src/core/gsap.ts +80 -65
- package/src/core/listbox.ts +332 -0
- package/src/core/mark.ts +29 -0
- package/src/core/position.ts +89 -0
- package/src/core/relative-time.ts +77 -0
- package/src/core/segment-field.ts +373 -0
- package/src/core/segments.ts +153 -0
- package/src/core/tree.ts +116 -0
- package/src/core/virtual.ts +79 -0
- package/src/css/tokens.css +186 -182
- package/src/elements/index.ts +147 -0
- package/src/index.ts +72 -0
- package/src/motion/micro.ts +248 -0
- package/src/motion/sheet.ts +88 -65
- package/src/motion/surface.ts +9 -0
- package/src/react/index.ts +57 -0
- package/dist/chunk-IKQ5FODN.js.map +0 -1
- package/dist/chunk-J6OCFNGU.js +0 -177
- package/dist/chunk-J6OCFNGU.js.map +0 -1
- package/dist/chunk-TFNRIZHU.js +0 -3977
- package/dist/chunk-TFNRIZHU.js.map +0 -1
- package/dist/chunk-XAKC2QY7.js.map +0 -1
- package/dist/reveal-jsX-uwUz.d.ts +0 -625
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import { createBreadcrumb, type BreadcrumbInstance, type BreadcrumbItem } from './engine';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <mk-breadcrumb class="mk-breadcrumb" items='[{"label":"Inicio","href":"/"},{"label":"Proyectos","href":"/p"},{"label":"Melon"}]'>
|
|
6
|
+
* </mk-breadcrumb> — or authored <a>/<span> children. Emits mk:navigate ({item, index}, cancelable).
|
|
7
|
+
*/
|
|
8
|
+
export class MkBreadcrumbElement extends MkElement<BreadcrumbInstance> {
|
|
9
|
+
static options: Record<string, OptionType> = { items: 'json', label: 'string', maxItems: 'number' };
|
|
10
|
+
|
|
11
|
+
protected create(options: Record<string, unknown>) {
|
|
12
|
+
return createBreadcrumb(this, options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
attributeChangedCallback(name: string, previous: string | null, next: string | null) {
|
|
16
|
+
if (previous === next || !this.instance) return;
|
|
17
|
+
if (name === 'items') {
|
|
18
|
+
try {
|
|
19
|
+
this.instance.setItems(JSON.parse(next ?? '[]') as BreadcrumbItem[]);
|
|
20
|
+
} catch {
|
|
21
|
+
// Malformed JSON: keep the current path.
|
|
22
|
+
}
|
|
23
|
+
} else super.attributeChangedCallback(name, previous, next);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const defineBreadcrumb = () => defineElement('mk-breadcrumb', MkBreadcrumbElement);
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { gsap, EASE, Flip, registerMotion } from '../../core/gsap';
|
|
2
|
+
import { reduced } from '../../core/reduced';
|
|
3
|
+
import { createScope } from '../../core/scope';
|
|
4
|
+
import { TIMING } from '../../core/timing';
|
|
5
|
+
import { inert, type Instance } from '../../core/types';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Breadcrumb: the path to the current page as an ordered list in a
|
|
9
|
+
* <nav aria-label>; the last item is the page itself (aria-current="page").
|
|
10
|
+
* When the items do not fit (or pass maxItems) the middle ones fold into a
|
|
11
|
+
* "…" button; pressing it shows them in place, flowing in from the left
|
|
12
|
+
* (blur 16 → 0, 40 ms apart) while the rest glides to make room. setItems()
|
|
13
|
+
* keeps the shared start of the path: the items that stay glide to their new
|
|
14
|
+
* place (layout reflow), new ones grow in, and the old current page turns into
|
|
15
|
+
* a link. Links underline from the left on hover; activating one emits a
|
|
16
|
+
* cancelable mk:navigate (onNavigate) before the browser follows it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface BreadcrumbItem {
|
|
20
|
+
label: string;
|
|
21
|
+
href?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface BreadcrumbOptions {
|
|
25
|
+
/** The path; without it, the authored <ol> or <a>/<span> children are read. */
|
|
26
|
+
items?: BreadcrumbItem[];
|
|
27
|
+
/** Accessible name of the navigation (default 'Ruta de navegación'). */
|
|
28
|
+
label?: string;
|
|
29
|
+
/** 'auto' (default) folds the middle when it does not fit; a number keeps at most that many items. */
|
|
30
|
+
maxItems?: number | 'auto';
|
|
31
|
+
/** Called before following a link; event.preventDefault() keeps the browser where it is (SPA routing). */
|
|
32
|
+
onNavigate?: (item: BreadcrumbItem, index: number, event: Event) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BreadcrumbInstance extends Instance {
|
|
36
|
+
setItems(items: BreadcrumbItem[]): void;
|
|
37
|
+
readonly items: BreadcrumbItem[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const readAuthored = (root: HTMLElement): BreadcrumbItem[] =>
|
|
41
|
+
[...root.querySelectorAll<HTMLElement>('li, :scope > a, :scope > span')]
|
|
42
|
+
.map((element) => {
|
|
43
|
+
const link = element.matches('a') ? (element as HTMLAnchorElement) : element.querySelector<HTMLAnchorElement>('a');
|
|
44
|
+
return { label: (link ?? element).textContent?.trim() ?? '', href: link?.getAttribute('href') ?? undefined };
|
|
45
|
+
})
|
|
46
|
+
.filter((item) => item.label);
|
|
47
|
+
|
|
48
|
+
/** A crumb is the same one at the same place with the same label (a current page that turns into a link stays). */
|
|
49
|
+
const key = (item: BreadcrumbItem, index: number) => `${index}:${item.label}`;
|
|
50
|
+
|
|
51
|
+
export function createBreadcrumb(root: HTMLElement | null, options: BreadcrumbOptions = {}): BreadcrumbInstance {
|
|
52
|
+
if (!root) return inert({ setItems() {}, items: [] });
|
|
53
|
+
registerMotion();
|
|
54
|
+
const scope = createScope();
|
|
55
|
+
let items = (options.items ?? readAuthored(root)).map((item) => ({ ...item }));
|
|
56
|
+
const maxItems = typeof options.maxItems === 'number' && options.maxItems >= 2 ? Math.floor(options.maxItems) : 'auto';
|
|
57
|
+
const effects = new Set<gsap.core.Animation>();
|
|
58
|
+
const keep = (animation: gsap.core.Animation | null) => {
|
|
59
|
+
if (!animation) return;
|
|
60
|
+
effects.add(animation);
|
|
61
|
+
// Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
|
|
62
|
+
for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Authored markup stays in the DOM but out of sight; the engine draws its own list.
|
|
66
|
+
const authored = [...root.childNodes];
|
|
67
|
+
const holder = document.createElement('div');
|
|
68
|
+
holder.hidden = true;
|
|
69
|
+
holder.append(...authored);
|
|
70
|
+
const nav = document.createElement('nav');
|
|
71
|
+
nav.dataset.mkBreadcrumbNav = '';
|
|
72
|
+
nav.setAttribute('aria-label', options.label ?? 'Ruta de navegación');
|
|
73
|
+
const list = document.createElement('ol');
|
|
74
|
+
list.dataset.mkBreadcrumbList = '';
|
|
75
|
+
nav.append(list);
|
|
76
|
+
root.append(nav);
|
|
77
|
+
scope.add(() => {
|
|
78
|
+
for (const effect of effects) effect.kill();
|
|
79
|
+
effects.clear();
|
|
80
|
+
nav.remove();
|
|
81
|
+
root.append(...holder.childNodes);
|
|
82
|
+
});
|
|
83
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The items that stay glide to their new place (layout reflow spring); the new ones come in from the side of
|
|
87
|
+
* travel out of a blur (from the left when the folded middle opens, from the right when the path grows).
|
|
88
|
+
*/
|
|
89
|
+
const reflow = (change: () => void, from: 'left' | 'right') => {
|
|
90
|
+
const crumbs = () => [...list.querySelectorAll<HTMLElement>('[data-mk-crumb]')];
|
|
91
|
+
if (reduced()) return change();
|
|
92
|
+
const state = Flip.getState(crumbs());
|
|
93
|
+
change();
|
|
94
|
+
keep(
|
|
95
|
+
Flip.from(state, {
|
|
96
|
+
targets: crumbs(),
|
|
97
|
+
duration: TIMING.layoutTravel,
|
|
98
|
+
ease: EASE.layoutReflow,
|
|
99
|
+
onEnter: (entering) =>
|
|
100
|
+
gsap.fromTo(
|
|
101
|
+
entering,
|
|
102
|
+
{ opacity: 0, x: from === 'left' ? -12 : 12, filter: 'blur(8px)' },
|
|
103
|
+
{ opacity: 1, x: 0, filter: 'blur(0px)', duration: 0.35, stagger: 0.04, ease: 'power2.out', clearProps: 'opacity,transform,filter' },
|
|
104
|
+
),
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Fold the middle when collapsed: which indexes are hidden. */
|
|
110
|
+
let expanded = false;
|
|
111
|
+
let hidden: number[] = [];
|
|
112
|
+
|
|
113
|
+
const crumb = (item: BreadcrumbItem, index: number) => {
|
|
114
|
+
const li = document.createElement('li');
|
|
115
|
+
li.dataset.mkCrumb = '';
|
|
116
|
+
li.dataset.flipId = key(item, index);
|
|
117
|
+
const last = index === items.length - 1;
|
|
118
|
+
if (last) {
|
|
119
|
+
const span = document.createElement('span');
|
|
120
|
+
span.dataset.mkCrumbCurrent = '';
|
|
121
|
+
span.setAttribute('aria-current', 'page');
|
|
122
|
+
span.textContent = item.label;
|
|
123
|
+
li.append(span);
|
|
124
|
+
} else if (item.href !== undefined) {
|
|
125
|
+
const link = document.createElement('a');
|
|
126
|
+
link.dataset.mkCrumbLink = '';
|
|
127
|
+
link.href = item.href;
|
|
128
|
+
link.dataset.mkIndex = String(index);
|
|
129
|
+
link.textContent = item.label;
|
|
130
|
+
li.append(link);
|
|
131
|
+
} else {
|
|
132
|
+
const span = document.createElement('span');
|
|
133
|
+
span.dataset.mkCrumbText = '';
|
|
134
|
+
span.textContent = item.label;
|
|
135
|
+
li.append(span);
|
|
136
|
+
}
|
|
137
|
+
return li;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const more = () => {
|
|
141
|
+
const li = document.createElement('li');
|
|
142
|
+
li.dataset.mkCrumb = '';
|
|
143
|
+
li.dataset.flipId = 'more';
|
|
144
|
+
const button = document.createElement('button');
|
|
145
|
+
button.type = 'button';
|
|
146
|
+
button.dataset.mkCrumbMore = '';
|
|
147
|
+
button.setAttribute('aria-expanded', 'false');
|
|
148
|
+
button.setAttribute('aria-label', hidden.length === 1 ? 'Mostrar 1 nivel oculto' : `Mostrar ${hidden.length} niveles ocultos`);
|
|
149
|
+
button.textContent = '…';
|
|
150
|
+
li.append(button);
|
|
151
|
+
return li;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const render = () => {
|
|
155
|
+
const nodes: HTMLElement[] = [];
|
|
156
|
+
items.forEach((item, index) => {
|
|
157
|
+
if (hidden.includes(index)) {
|
|
158
|
+
if (index === hidden[0]) nodes.push(more());
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
nodes.push(crumb(item, index));
|
|
162
|
+
});
|
|
163
|
+
list.replaceChildren(...nodes);
|
|
164
|
+
nav.toggleAttribute('data-mk-expanded', expanded);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/** The middle indexes to fold, keeping the first item and the last `keepEnd`. */
|
|
168
|
+
const fold = (keepEnd: number) => {
|
|
169
|
+
const end = Math.max(1, items.length - keepEnd);
|
|
170
|
+
return Array.from({ length: Math.max(0, end - 1) }, (_, i) => i + 1);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const fits = () => list.scrollWidth <= list.clientWidth + 1;
|
|
174
|
+
|
|
175
|
+
const layout = () => {
|
|
176
|
+
if (expanded || items.length <= 2) {
|
|
177
|
+
hidden = [];
|
|
178
|
+
render();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (maxItems !== 'auto') {
|
|
182
|
+
hidden = items.length > maxItems ? fold(maxItems - 1) : [];
|
|
183
|
+
render();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Fold from the middle until the row fits with the current page whole (jsdom has no layout: it always fits);
|
|
187
|
+
// only when even the shortest fold is too long does the current page ellipsize.
|
|
188
|
+
nav.setAttribute('data-mk-measuring', '');
|
|
189
|
+
hidden = [];
|
|
190
|
+
render();
|
|
191
|
+
for (let keepEnd = items.length - 2; keepEnd >= 1 && !fits(); keepEnd--) {
|
|
192
|
+
hidden = fold(keepEnd);
|
|
193
|
+
render();
|
|
194
|
+
}
|
|
195
|
+
nav.removeAttribute('data-mk-measuring');
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
scope.on(list, 'click', (event) => {
|
|
199
|
+
const target = event.target as Element | null;
|
|
200
|
+
if (target?.closest('[data-mk-crumb-more]')) {
|
|
201
|
+
const shown = hidden.map((index) => key(items[index]!, index));
|
|
202
|
+
reflow(() => {
|
|
203
|
+
expanded = true;
|
|
204
|
+
hidden = [];
|
|
205
|
+
render();
|
|
206
|
+
}, 'left');
|
|
207
|
+
// The button is gone: the focus moves to the first level it revealed.
|
|
208
|
+
const first = [...list.querySelectorAll<HTMLElement>('[data-mk-crumb]')].find((li) => shown.includes(li.dataset.flipId ?? ''));
|
|
209
|
+
first?.querySelector<HTMLElement>('a')?.focus();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const link = target?.closest<HTMLAnchorElement>('a[data-mk-crumb-link]');
|
|
213
|
+
if (!link) return;
|
|
214
|
+
const index = Number(link.dataset.mkIndex);
|
|
215
|
+
const item = items[index]!;
|
|
216
|
+
options.onNavigate?.(item, index, event);
|
|
217
|
+
const navigate = new CustomEvent('mk:navigate', { bubbles: true, cancelable: true, detail: { item, index } });
|
|
218
|
+
if (!root.dispatchEvent(navigate)) event.preventDefault();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (typeof ResizeObserver === 'function') {
|
|
222
|
+
let width = 0;
|
|
223
|
+
scope.observe(
|
|
224
|
+
new ResizeObserver(() => {
|
|
225
|
+
const next = nav.clientWidth;
|
|
226
|
+
if (Math.abs(next - width) < 1) return;
|
|
227
|
+
width = next;
|
|
228
|
+
if (!expanded) layout();
|
|
229
|
+
}),
|
|
230
|
+
).observe(nav);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
layout();
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
setItems(next) {
|
|
237
|
+
if (scope.disposed) return;
|
|
238
|
+
reflow(() => {
|
|
239
|
+
items = next.map((item) => ({ ...item }));
|
|
240
|
+
expanded = false;
|
|
241
|
+
layout();
|
|
242
|
+
}, 'right');
|
|
243
|
+
},
|
|
244
|
+
get items() {
|
|
245
|
+
return items.map((item) => ({ ...item }));
|
|
246
|
+
},
|
|
247
|
+
destroy: () => scope.dispose(),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, type HTMLAttributes } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
|
|
4
|
+
import { createBreadcrumb, type BreadcrumbInstance, type BreadcrumbItem, type BreadcrumbOptions } from './engine';
|
|
5
|
+
|
|
6
|
+
export interface BreadcrumbProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, Omit<BreadcrumbOptions, 'items'> {
|
|
7
|
+
items: BreadcrumbItem[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** <Breadcrumb items={path} onNavigate={(item, i, event) => { event.preventDefault(); go(item.href) }} />. */
|
|
11
|
+
export function Breadcrumb({ items, label, maxItems, onNavigate, className, ...props }: BreadcrumbProps) {
|
|
12
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
13
|
+
const instance = useRef<BreadcrumbInstance | null>(null);
|
|
14
|
+
const navigate = useLatest(onNavigate);
|
|
15
|
+
const key = JSON.stringify(items);
|
|
16
|
+
useIsomorphicLayoutEffect(() => {
|
|
17
|
+
const created = createBreadcrumb(ref.current, { items, label, maxItems, onNavigate: (item, index, event) => navigate.current?.(item, index, event) });
|
|
18
|
+
instance.current = created;
|
|
19
|
+
return () => {
|
|
20
|
+
created.destroy();
|
|
21
|
+
instance.current = null;
|
|
22
|
+
};
|
|
23
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
24
|
+
}, [label, maxItems]);
|
|
25
|
+
useIsomorphicLayoutEffect(() => {
|
|
26
|
+
if (instance.current && JSON.stringify(instance.current.items) !== key) instance.current.setItems(items);
|
|
27
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
28
|
+
}, [key]);
|
|
29
|
+
return <div {...props} ref={ref} className={['mk-breadcrumb', className].filter(Boolean).join(' ')} />;
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# chart
|
|
2
|
+
|
|
3
|
+
Gráfica SVG reutilizable de línea, área y barras (agrupadas o apiladas) sin motor canvas ni dependencias. Sigue las gráficas de métricas de Melon Mind: base en cero, línea de 2 px, área que se desvanece de .25 a 0, barras de hasta 24 px con extremo redondeado de 4 px y huecos de 2 px, rejilla fina y tooltip con superficie invertida.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<mk-chart class="mk-chart" type="bar" stacked label="Sesiones por semana"
|
|
7
|
+
labels="S1,S2,S3,S4"
|
|
8
|
+
series='[{"name":"Ana","values":[4,6,5,7]},{"name":"Luis","values":[2,3,4,1]}]'></mk-chart>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const chart = createChart(root, { type: 'area', labels: days, series: [{ name: 'Ingresos', values }], formatValue: money, label: 'Ingresos de octubre' });
|
|
13
|
+
chart.update({ series: [{ name: 'Ingresos', values: next }] });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
React: `<Chart type="line" labels={days} series={series} formatValue={money} label="Ingresos" onSelect={setDay} />`.
|
|
17
|
+
|
|
18
|
+
Datos: `labels` + `series: ChartSeries[]` (`{name, values: (number | null)[], color?, dash?}`; `dash` es un patrón SVG para líneas, p. ej. `'5 5'` para un ritmo o `'1 6'` para una proyección) o `points: ChartPoint[]` (`{label, value}`) para una serie. `null` es un hueco sin dato: la línea se corta y un punto aislado se muestra con marcador. Opciones: `type='line'|'area'|'bar'`, `stacked` (barras), `formatValue(n)` (ejes, tooltip y tabla), `label` (nombre accesible y título de la tabla), `height=240`, `emptyLabel`, `legend` (por defecto con dos o más series), `axis` (valores y líneas guía en el eje y; por defecto sólo la línea base), `endLabel` (líneas: el último valor de la primera serie en una píldora al final; por defecto `true`), `tooltip` (por defecto `true`; con `false` la gráfica deja la guía y los marcadores y la tarjeta la pone `chart-tooltip`), `onSelect(index | null)`. API: `update(options)` (combina y redibuja), `select(index | null)`, `selected`, `destroy()`. Evento `mk:select` con `{index, label, values, x, y, series}`: `x`/`y` son el punto señalado en píxeles del área de la gráfica y `series` trae `{name, color, value, previous}` por serie.
|
|
19
|
+
|
|
20
|
+
Puntero y toque eligen el punto más cercano (el toque mantiene la selección; el ratón la quita al salir). Con foco en la gráfica, flechas/Inicio/Fin recorren los valores y Escape los oculta; un estado educado anuncia el valor elegido. La gráfica tiene un resumen como nombre accesible y una tabla para lectores de pantalla con todos los valores (y el total en barras apiladas). Sin datos muestra `emptyLabel`.
|
|
21
|
+
|
|
22
|
+
Movimiento: el primer dibujo traza la línea de izquierda a derecha (barras: crecen desde la base) y cada `update` desliza los valores nuevos desde los anteriores; el tooltip aparece con un pop de cristal y se desliza entre puntos. Con movimiento reducido todo cambia sin transición.
|
|
23
|
+
|
|
24
|
+
Colores: una sola serie es monocroma (`--mk-chart-mono`, el color del texto, al estilo de los indicadores de Melon Mind); con varias, cada serie usa su `color` o la ranura `--mk-chart-N` en orden fijo (paleta comprobada para daltonismo, con pasos propios en oscuro); a partir de la novena serie todas comparten `--mk-chart-other`: agrúpalas en «Otros». Los textos usan los colores de texto del tema; el color identifica la marca. Se redimensiona con su contenedor (ResizeObserver) y `destroy()` quita todo lo creado. Variables: `--mk-chart-mono`, `--mk-chart-1…8`, `--mk-chart-other`, `--mk-chart-surface` (anillo de marcadores), `--mk-chart-grid`.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/* chart — Melon Mind's Indicadores: a monochrome line on the surface, the baseline only, the last value in a pill,
|
|
2
|
+
a dotted guide, the inverse glass tooltip and outlined legend pills.
|
|
3
|
+
Several series use --mk-chart-1…8 (a colour-blind-checked order, light and dark steps). */
|
|
4
|
+
.mk-chart,
|
|
5
|
+
mk-chart {
|
|
6
|
+
--mk-chart-mono: var(--mk-text);
|
|
7
|
+
--mk-chart-1: #2a78d6;
|
|
8
|
+
--mk-chart-2: #eb6834;
|
|
9
|
+
--mk-chart-3: #1baf7a;
|
|
10
|
+
--mk-chart-4: #eda100;
|
|
11
|
+
--mk-chart-5: #e87ba4;
|
|
12
|
+
--mk-chart-6: #008300;
|
|
13
|
+
--mk-chart-7: #6250d6;
|
|
14
|
+
--mk-chart-8: #e34948;
|
|
15
|
+
--mk-chart-other: var(--mk-muted);
|
|
16
|
+
--mk-chart-surface: var(--mk-surface-1);
|
|
17
|
+
--mk-chart-grid: color-mix(in srgb, var(--mk-text) 14%, transparent);
|
|
18
|
+
position: relative;
|
|
19
|
+
display: grid;
|
|
20
|
+
gap: 0.75rem;
|
|
21
|
+
min-width: 0;
|
|
22
|
+
color: var(--mk-text);
|
|
23
|
+
font-family: var(--mk-font);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@media (prefers-color-scheme: dark) {
|
|
27
|
+
:where(:root:not([data-mk-mode='light'])) :is(.mk-chart, mk-chart) {
|
|
28
|
+
--mk-chart-1: #3987e5;
|
|
29
|
+
--mk-chart-2: #d95926;
|
|
30
|
+
--mk-chart-3: #199e70;
|
|
31
|
+
--mk-chart-4: #c98500;
|
|
32
|
+
--mk-chart-5: #d55181;
|
|
33
|
+
--mk-chart-6: #008300;
|
|
34
|
+
--mk-chart-7: #9085e9;
|
|
35
|
+
--mk-chart-8: #e66767;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:where(:root[data-mk-mode='dark']) :is(.mk-chart, mk-chart) {
|
|
40
|
+
--mk-chart-1: #3987e5;
|
|
41
|
+
--mk-chart-2: #d95926;
|
|
42
|
+
--mk-chart-3: #199e70;
|
|
43
|
+
--mk-chart-4: #c98500;
|
|
44
|
+
--mk-chart-5: #d55181;
|
|
45
|
+
--mk-chart-6: #008300;
|
|
46
|
+
--mk-chart-7: #9085e9;
|
|
47
|
+
--mk-chart-8: #e66767;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-plot] {
|
|
51
|
+
position: relative;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-plot][hidden] {
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-svg] {
|
|
60
|
+
display: block;
|
|
61
|
+
width: 100%;
|
|
62
|
+
height: auto;
|
|
63
|
+
overflow: visible;
|
|
64
|
+
touch-action: pan-y;
|
|
65
|
+
border-radius: 12px;
|
|
66
|
+
cursor: crosshair;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-svg]:focus {
|
|
70
|
+
outline: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-svg]:focus-visible {
|
|
74
|
+
outline: 2px solid var(--mk-accent);
|
|
75
|
+
outline-offset: 4px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:is(.mk-chart, mk-chart) [data-mk-part='grid'] line {
|
|
79
|
+
stroke: var(--mk-chart-grid);
|
|
80
|
+
stroke-width: 1;
|
|
81
|
+
shape-rendering: crispEdges;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
:is(.mk-chart, mk-chart) [data-mk-part='grid'] line[data-mk-zero] {
|
|
85
|
+
stroke: color-mix(in srgb, var(--mk-text) 22%, transparent);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
:is(.mk-chart, mk-chart) text {
|
|
89
|
+
fill: var(--mk-muted);
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
font-variant-numeric: tabular-nums;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:is(.mk-chart, mk-chart) [data-mk-part='total'] {
|
|
95
|
+
fill: var(--mk-text);
|
|
96
|
+
font-size: 10px;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
:is(.mk-chart, mk-chart) [data-mk-part='line'] {
|
|
101
|
+
fill: none;
|
|
102
|
+
stroke-linecap: round;
|
|
103
|
+
stroke-linejoin: round;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
:is(.mk-chart, mk-chart) :is([data-mk-part='dot'], [data-mk-part='marker'], [data-mk-part='end']) {
|
|
107
|
+
stroke: var(--mk-chart-surface);
|
|
108
|
+
stroke-width: 2.5;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* The selection guide: Melonmind's dotted day line. */
|
|
112
|
+
:is(.mk-chart, mk-chart) [data-mk-part='guide'] {
|
|
113
|
+
stroke: color-mix(in srgb, var(--mk-text) 45%, transparent);
|
|
114
|
+
stroke-width: 1;
|
|
115
|
+
stroke-dasharray: 2 4;
|
|
116
|
+
shape-rendering: crispEdges;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
:is(.mk-chart, mk-chart) [data-mk-part='band'] {
|
|
120
|
+
fill: color-mix(in srgb, var(--mk-text) 6%, transparent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:is(.mk-chart, mk-chart) [data-mk-part='series'] path[data-mk-index] {
|
|
124
|
+
transition: opacity 0.2s ease;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
:is(.mk-chart, mk-chart) [data-mk-part='series'] path[data-mk-dim] {
|
|
128
|
+
opacity: 0.35;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* The last value. */
|
|
132
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-end] {
|
|
133
|
+
position: absolute;
|
|
134
|
+
top: 0;
|
|
135
|
+
left: 0;
|
|
136
|
+
padding: 0.15rem 0.55rem;
|
|
137
|
+
border-radius: var(--mk-radius-full);
|
|
138
|
+
background: var(--mk-surface-3);
|
|
139
|
+
color: var(--mk-text);
|
|
140
|
+
font-size: 0.75rem;
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
font-variant-numeric: tabular-nums;
|
|
143
|
+
white-space: nowrap;
|
|
144
|
+
pointer-events: none;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-end][hidden] {
|
|
148
|
+
display: none;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Tooltip: the inverse glass, 12 px radius, 8 × 12 padding (Melonmind). */
|
|
152
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-tooltip] {
|
|
153
|
+
position: absolute;
|
|
154
|
+
top: 0;
|
|
155
|
+
left: 0;
|
|
156
|
+
z-index: 2;
|
|
157
|
+
display: grid;
|
|
158
|
+
gap: 0.2rem;
|
|
159
|
+
min-width: 8rem;
|
|
160
|
+
max-width: 16rem;
|
|
161
|
+
padding: 0.5rem 0.75rem;
|
|
162
|
+
border-radius: 12px;
|
|
163
|
+
background: var(--mk-glass-inverse);
|
|
164
|
+
box-shadow: var(--mk-glass-bevel);
|
|
165
|
+
backdrop-filter: blur(16px);
|
|
166
|
+
color: var(--mk-on-glass-inverse);
|
|
167
|
+
font-size: 0.8125rem;
|
|
168
|
+
line-height: 1.3;
|
|
169
|
+
pointer-events: none;
|
|
170
|
+
transform-origin: 50% 100%;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-tooltip][hidden] {
|
|
174
|
+
display: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-tooltip] strong {
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-row] {
|
|
182
|
+
display: grid;
|
|
183
|
+
grid-template-columns: 0.6rem minmax(0, 1fr) auto;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 0.5rem;
|
|
186
|
+
opacity: 0.9;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-row] i {
|
|
190
|
+
width: 0.6rem;
|
|
191
|
+
height: 0.6rem;
|
|
192
|
+
border-radius: 3px;
|
|
193
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--mk-on-glass-inverse) 30%, transparent);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-row] b {
|
|
197
|
+
font-weight: 500;
|
|
198
|
+
font-variant-numeric: tabular-nums;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-row] i[data-mk-mono] {
|
|
202
|
+
height: 2px;
|
|
203
|
+
border-radius: 2px;
|
|
204
|
+
background: currentColor;
|
|
205
|
+
box-shadow: none;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-row] i[data-mk-dash] {
|
|
209
|
+
height: 0;
|
|
210
|
+
border-top: 2px dashed currentColor;
|
|
211
|
+
border-radius: 0;
|
|
212
|
+
background: none;
|
|
213
|
+
box-shadow: none;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-row='total'] {
|
|
217
|
+
margin-top: 0.15rem;
|
|
218
|
+
padding-top: 0.25rem;
|
|
219
|
+
border-top: 1px solid color-mix(in srgb, var(--mk-on-glass-inverse) 25%, transparent);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-row='total'] i {
|
|
223
|
+
box-shadow: none;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Legend: outlined pills (Melonmind «— Ingresado»). */
|
|
227
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-legend] {
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-wrap: wrap;
|
|
230
|
+
gap: 0.4rem;
|
|
231
|
+
margin: 0;
|
|
232
|
+
padding: 0;
|
|
233
|
+
color: var(--mk-text);
|
|
234
|
+
font-size: 0.8125rem;
|
|
235
|
+
list-style: none;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-legend][hidden] {
|
|
239
|
+
display: none;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-legend] li {
|
|
243
|
+
display: inline-flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
gap: 0.45rem;
|
|
246
|
+
padding: 0.2rem 0.7rem;
|
|
247
|
+
border: 1px solid var(--mk-outline-soft);
|
|
248
|
+
border-radius: var(--mk-radius-full);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-swatch] {
|
|
252
|
+
width: 0.7rem;
|
|
253
|
+
height: 0.7rem;
|
|
254
|
+
border-radius: 3px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-swatch][data-mk-type='line'],
|
|
258
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-swatch][data-mk-type='area'] {
|
|
259
|
+
width: 0.9rem;
|
|
260
|
+
height: 2px;
|
|
261
|
+
border-radius: 2px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-swatch][data-mk-dash] {
|
|
265
|
+
height: 0;
|
|
266
|
+
background: none !important;
|
|
267
|
+
border-top: 2px dashed currentColor;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-empty] {
|
|
271
|
+
display: grid;
|
|
272
|
+
place-items: center;
|
|
273
|
+
min-height: 10rem;
|
|
274
|
+
margin: 0;
|
|
275
|
+
border-radius: 24px;
|
|
276
|
+
background: var(--mk-surface-2);
|
|
277
|
+
color: var(--mk-muted);
|
|
278
|
+
font-size: 0.875rem;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
:is(.mk-chart, mk-chart) [data-mk-chart-empty][hidden] {
|
|
282
|
+
display: none;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@media (prefers-reduced-motion: reduce) {
|
|
286
|
+
:is(.mk-chart, mk-chart) [data-mk-part='series'] path {
|
|
287
|
+
transition: none;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@media (forced-colors: active) {
|
|
292
|
+
:is(.mk-chart, mk-chart) [data-mk-part='line'] {
|
|
293
|
+
stroke: CanvasText !important;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import { createChart, type ChartInstance, type ChartOptions } from './engine';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <mk-chart class="mk-chart" type="bar" stacked label="Sesiones por semana"
|
|
6
|
+
* labels="S1,S2,S3" series='[{"name":"Ana","values":[4,6,5]},{"name":"Luis","values":[2,3,4]}]'></mk-chart>
|
|
7
|
+
* Attribute changes redraw in place. Emits mk:select ({index, label, values, x, y, series}); tooltip="false" leaves the card to chart-tooltip.
|
|
8
|
+
*/
|
|
9
|
+
export class MkChartElement extends MkElement<ChartInstance> {
|
|
10
|
+
static options: Record<string, OptionType> = {
|
|
11
|
+
type: 'string',
|
|
12
|
+
labels: 'list',
|
|
13
|
+
series: 'json',
|
|
14
|
+
points: 'json',
|
|
15
|
+
stacked: 'boolean',
|
|
16
|
+
label: 'string',
|
|
17
|
+
height: 'number',
|
|
18
|
+
emptyLabel: 'string',
|
|
19
|
+
legend: 'boolean',
|
|
20
|
+
tooltip: 'boolean',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
protected create(options: Record<string, unknown>) {
|
|
24
|
+
return createChart(this, options as ChartOptions);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
attributeChangedCallback(_name: string, previous: string | null, next: string | null) {
|
|
28
|
+
if (previous === next || !this.instance) return;
|
|
29
|
+
this.instance.update({ stacked: false, legend: undefined, ...(this.options as ChartOptions) });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
update(options: ChartOptions) {
|
|
33
|
+
this.instance?.update(options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
select(index: number | null) {
|
|
37
|
+
this.instance?.select(index);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const defineChart = () => defineElement('mk-chart', MkChartElement);
|