@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,260 @@
|
|
|
1
|
+
import { gsap, EASE, registerMotion } from '../../core/gsap';
|
|
2
|
+
import { reduced } from '../../core/reduced';
|
|
3
|
+
import { createScope } from '../../core/scope';
|
|
4
|
+
import { inert, type Instance } from '../../core/types';
|
|
5
|
+
import { press } from '../../motion/micro';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Swipe actions: a horizontal drag on a list row ([data-mk-swipe-row]) slides
|
|
9
|
+
* its face ([data-mk-swipe-content]) aside and shows the actions behind it
|
|
10
|
+
* ([data-mk-swipe-actions="left|right"] with buttons [data-mk-swipe-action]).
|
|
11
|
+
* The icons grow as the drag passes the opening point; letting go snaps the row
|
|
12
|
+
* open or shut on the swing curve. A long swipe (60 %) runs the side's primary
|
|
13
|
+
* action: the face slides out and, for [data-mk-swipe-remove] actions, the row
|
|
14
|
+
* folds away (blur, height to 0) before mk:action fires so the app can drop it.
|
|
15
|
+
* One row open at a time; Escape or a tap elsewhere shuts it. The actions are
|
|
16
|
+
* real buttons: focusing one opens its row. Vertical scrolling stays native.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type SwipeSide = 'left' | 'right';
|
|
20
|
+
|
|
21
|
+
export interface SwipeActionsOptions {
|
|
22
|
+
/** Share of the row width that runs the primary action (default .6; 0 turns it off). */
|
|
23
|
+
fullSwipe?: number;
|
|
24
|
+
onAction?: (action: string, row: HTMLElement) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SwipeActionsInstance extends Instance {
|
|
28
|
+
open(row: HTMLElement, side: SwipeSide): void;
|
|
29
|
+
close(): void;
|
|
30
|
+
readonly openRow: HTMLElement | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ROW = '[data-mk-swipe-row]';
|
|
34
|
+
const SLOP = 8;
|
|
35
|
+
|
|
36
|
+
export function createSwipeActions(root: HTMLElement | null, options: SwipeActionsOptions = {}): SwipeActionsInstance {
|
|
37
|
+
if (!root) return inert({ open() {}, close() {}, openRow: null });
|
|
38
|
+
registerMotion();
|
|
39
|
+
const scope = createScope();
|
|
40
|
+
const full = Number.isFinite(options.fullSwipe) ? Math.max(0, Math.min(0.95, options.fullSwipe!)) : 0.6;
|
|
41
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
42
|
+
|
|
43
|
+
let current: { row: HTMLElement; side: SwipeSide } | null = null;
|
|
44
|
+
const effects = new Set<gsap.core.Animation>();
|
|
45
|
+
const keep = (animation: gsap.core.Animation | null) => {
|
|
46
|
+
if (!animation) return;
|
|
47
|
+
effects.add(animation);
|
|
48
|
+
// Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
|
|
49
|
+
for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
|
|
50
|
+
};
|
|
51
|
+
const touched = new Set<HTMLElement>();
|
|
52
|
+
scope.add(() => {
|
|
53
|
+
for (const effect of effects) effect.kill();
|
|
54
|
+
effects.clear();
|
|
55
|
+
for (const element of touched) {
|
|
56
|
+
gsap.set(element, { clearProps: 'transform,height,opacity,filter,marginTop,marginBottom,paddingTop,paddingBottom,overflow' });
|
|
57
|
+
element.removeAttribute('data-mk-open');
|
|
58
|
+
element.removeAttribute('data-mk-armed');
|
|
59
|
+
element.style.removeProperty('--mk-swipe-reveal');
|
|
60
|
+
if (element.getAttribute('style') === '') element.removeAttribute('style');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const parts = (row: HTMLElement) => ({
|
|
65
|
+
face: row.querySelector<HTMLElement>('[data-mk-swipe-content]'),
|
|
66
|
+
left: row.querySelector<HTMLElement>('[data-mk-swipe-actions="left"]'),
|
|
67
|
+
right: row.querySelector<HTMLElement>('[data-mk-swipe-actions="right"]'),
|
|
68
|
+
});
|
|
69
|
+
const widthOf = (actions: HTMLElement | null) => actions?.getBoundingClientRect().width || actions?.offsetWidth || 0;
|
|
70
|
+
const primaryOf = (actions: HTMLElement | null) => actions?.querySelector<HTMLButtonElement>('[data-mk-swipe-primary]') ?? (actions?.dataset.mkSwipeActions === 'right' ? actions.querySelector<HTMLButtonElement>('[data-mk-swipe-action]:last-of-type') : actions?.querySelector<HTMLButtonElement>('[data-mk-swipe-action]')) ?? null;
|
|
71
|
+
|
|
72
|
+
const move = (row: HTMLElement, x: number, animate: boolean) => {
|
|
73
|
+
const { face } = parts(row);
|
|
74
|
+
if (!face) return;
|
|
75
|
+
touched.add(face);
|
|
76
|
+
touched.add(row);
|
|
77
|
+
if (!animate || reduced()) gsap.set(face, { x });
|
|
78
|
+
else keep(gsap.to(face, { x, duration: 0.5, ease: EASE.swatch, overwrite: 'auto' }));
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const close = (animate = true) => {
|
|
82
|
+
if (!current) return;
|
|
83
|
+
const { row } = current;
|
|
84
|
+
current = null;
|
|
85
|
+
row.removeAttribute('data-mk-open');
|
|
86
|
+
row.removeAttribute('data-mk-armed');
|
|
87
|
+
row.style.removeProperty('--mk-swipe-reveal');
|
|
88
|
+
move(row, 0, animate);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const open = (row: HTMLElement, side: SwipeSide, animate = true) => {
|
|
92
|
+
if (scope.disposed || !root.contains(row)) return;
|
|
93
|
+
const actions = parts(row)[side];
|
|
94
|
+
if (!actions) return;
|
|
95
|
+
if (current && current.row !== row) close();
|
|
96
|
+
current = { row, side };
|
|
97
|
+
row.setAttribute('data-mk-open', side);
|
|
98
|
+
row.style.setProperty('--mk-swipe-reveal', '1');
|
|
99
|
+
touched.add(row);
|
|
100
|
+
move(row, side === 'left' ? widthOf(actions) : -widthOf(actions), animate);
|
|
101
|
+
root.dispatchEvent(new CustomEvent('mk:open', { bubbles: true, detail: { row, side } }));
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** Run an action: emit; a removing action folds the row away first. */
|
|
105
|
+
const run = (button: HTMLButtonElement, row: HTMLElement, swiped: boolean) => {
|
|
106
|
+
const action = button.dataset.mkSwipeAction ?? button.textContent?.trim() ?? '';
|
|
107
|
+
const emit = () => {
|
|
108
|
+
options.onAction?.(action, row);
|
|
109
|
+
root.dispatchEvent(new CustomEvent('mk:action', { bubbles: true, detail: { action, row } }));
|
|
110
|
+
};
|
|
111
|
+
keep(press(button));
|
|
112
|
+
if (!button.hasAttribute('data-mk-swipe-remove')) {
|
|
113
|
+
if (current?.row === row) close();
|
|
114
|
+
return emit();
|
|
115
|
+
}
|
|
116
|
+
current = null;
|
|
117
|
+
row.removeAttribute('data-mk-open');
|
|
118
|
+
touched.add(row);
|
|
119
|
+
if (reduced()) {
|
|
120
|
+
gsap.set(row, { height: 0, opacity: 0, overflow: 'hidden', paddingTop: 0, paddingBottom: 0, marginTop: 0, marginBottom: 0 });
|
|
121
|
+
return emit();
|
|
122
|
+
}
|
|
123
|
+
const { face } = parts(row);
|
|
124
|
+
const width = row.getBoundingClientRect().width || 400;
|
|
125
|
+
const side = Number(gsap.getProperty(face, 'x')) >= 0 ? 1 : -1;
|
|
126
|
+
keep(
|
|
127
|
+
gsap
|
|
128
|
+
.timeline({ onComplete: emit })
|
|
129
|
+
.to(face, { x: side * width * (swiped ? 1.05 : 1), duration: 0.3, ease: 'power2.in' })
|
|
130
|
+
.to(row, { height: 0, opacity: 0, filter: 'blur(16px)', paddingTop: 0, paddingBottom: 0, marginTop: 0, marginBottom: 0, overflow: 'hidden', duration: 0.25, ease: 'power2.in' }),
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// The drag.
|
|
135
|
+
/** The click that ends a drag is not a tap. */
|
|
136
|
+
let dragged = false;
|
|
137
|
+
let drag: { row: HTMLElement; x0: number; y0: number; start: number; id: number; horizontal: boolean | null } | null = null;
|
|
138
|
+
scope.on(root, 'pointerdown', (event) => {
|
|
139
|
+
const pointer = event as PointerEvent;
|
|
140
|
+
if (pointer.button !== 0) return;
|
|
141
|
+
const target = pointer.target as Element | null;
|
|
142
|
+
if (target?.closest('[data-mk-swipe-action], a, input, select, textarea')) return;
|
|
143
|
+
const row = target?.closest<HTMLElement>(ROW);
|
|
144
|
+
const face = row && parts(row).face;
|
|
145
|
+
if (!row || !face || !root.contains(row)) return;
|
|
146
|
+
gsap.killTweensOf(face, 'x');
|
|
147
|
+
drag = { row, x0: pointer.clientX, y0: pointer.clientY, start: Number(gsap.getProperty(face, 'x')) || 0, id: pointer.pointerId, horizontal: null };
|
|
148
|
+
});
|
|
149
|
+
scope.on(root, 'pointermove', (event) => {
|
|
150
|
+
const pointer = event as PointerEvent;
|
|
151
|
+
if (!drag || pointer.pointerId !== drag.id) return;
|
|
152
|
+
const dx = pointer.clientX - drag.x0;
|
|
153
|
+
const dy = pointer.clientY - drag.y0;
|
|
154
|
+
if (drag.horizontal === null) {
|
|
155
|
+
if (Math.abs(dx) < SLOP && Math.abs(dy) < SLOP) return;
|
|
156
|
+
drag.horizontal = Math.abs(dx) > Math.abs(dy);
|
|
157
|
+
if (!drag.horizontal) {
|
|
158
|
+
drag = null;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (current && current.row !== drag.row) close();
|
|
162
|
+
(pointer.target as Element).setPointerCapture?.(pointer.pointerId);
|
|
163
|
+
drag.row.setAttribute('data-mk-dragging', '');
|
|
164
|
+
}
|
|
165
|
+
const { left, right } = parts(drag.row);
|
|
166
|
+
let x = drag.start + dx;
|
|
167
|
+
// No actions on a side: it resists.
|
|
168
|
+
if ((x > 0 && !left) || (x < 0 && !right)) x *= 0.15;
|
|
169
|
+
const actions = x > 0 ? left : right;
|
|
170
|
+
const reach = widthOf(actions);
|
|
171
|
+
const width = drag.row.getBoundingClientRect().width || 1;
|
|
172
|
+
// Past the actions the drag slows down, unless a long swipe is on.
|
|
173
|
+
if (reach && Math.abs(x) > reach && !full) x = Math.sign(x) * (reach + (Math.abs(x) - reach) * 0.3);
|
|
174
|
+
drag.row.toggleAttribute('data-mk-armed', Boolean(full) && Math.abs(x) >= width * full);
|
|
175
|
+
drag.row.style.setProperty('--mk-swipe-reveal', String(Math.min(1, reach ? Math.abs(x) / reach : 0)));
|
|
176
|
+
touched.add(drag.row);
|
|
177
|
+
move(drag.row, x, false);
|
|
178
|
+
});
|
|
179
|
+
const release = (event: Event) => {
|
|
180
|
+
const pointer = event as PointerEvent;
|
|
181
|
+
if (!drag || pointer.pointerId !== drag.id) return;
|
|
182
|
+
const { row, horizontal } = drag;
|
|
183
|
+
drag = null;
|
|
184
|
+
row.removeAttribute('data-mk-dragging');
|
|
185
|
+
if (!horizontal) return;
|
|
186
|
+
dragged = true;
|
|
187
|
+
setTimeout(() => (dragged = false), 0);
|
|
188
|
+
const face = parts(row).face!;
|
|
189
|
+
const x = Number(gsap.getProperty(face, 'x')) || 0;
|
|
190
|
+
const side: SwipeSide = x > 0 ? 'left' : 'right';
|
|
191
|
+
const actions = parts(row)[side];
|
|
192
|
+
const width = row.getBoundingClientRect().width || 1;
|
|
193
|
+
row.removeAttribute('data-mk-armed');
|
|
194
|
+
const primary = primaryOf(actions);
|
|
195
|
+
if (full && actions && primary && Math.abs(x) >= width * full) return run(primary, row, true);
|
|
196
|
+
if (actions && Math.abs(x) > widthOf(actions) / 2) open(row, side);
|
|
197
|
+
else {
|
|
198
|
+
if (current?.row === row) current = null;
|
|
199
|
+
row.removeAttribute('data-mk-open');
|
|
200
|
+
row.style.removeProperty('--mk-swipe-reveal');
|
|
201
|
+
move(row, 0, true);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
scope.on(root, 'pointerup', release);
|
|
205
|
+
scope.on(root, 'pointercancel', release);
|
|
206
|
+
// A click after a drag is not a tap on the row.
|
|
207
|
+
scope.on(
|
|
208
|
+
root,
|
|
209
|
+
'click',
|
|
210
|
+
(event) => {
|
|
211
|
+
if (dragged) {
|
|
212
|
+
dragged = false;
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
event.stopPropagation();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const button = (event.target as Element | null)?.closest<HTMLButtonElement>('[data-mk-swipe-action]');
|
|
218
|
+
const row = button?.closest<HTMLElement>(ROW);
|
|
219
|
+
if (button && row && root.contains(row)) {
|
|
220
|
+
run(button, row, false);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (current && !(event.target as Element | null)?.closest('[data-mk-swipe-actions]')) {
|
|
224
|
+
const inside = (event.target as Element | null)?.closest(ROW) === current.row;
|
|
225
|
+
close();
|
|
226
|
+
if (inside) event.preventDefault();
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
true,
|
|
230
|
+
);
|
|
231
|
+
scope.on(document, 'pointerdown', (event) => {
|
|
232
|
+
if (current && !current.row.contains(event.target as Node)) close();
|
|
233
|
+
});
|
|
234
|
+
scope.on(root, 'keydown', (event) => {
|
|
235
|
+
if ((event as KeyboardEvent).key === 'Escape' && current) {
|
|
236
|
+
const row = current.row;
|
|
237
|
+
close();
|
|
238
|
+
parts(row).face?.focus?.();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
// Keyboard: focusing an action opens its row.
|
|
242
|
+
scope.on(root, 'focusin', (event) => {
|
|
243
|
+
const actions = (event.target as Element | null)?.closest<HTMLElement>('[data-mk-swipe-actions]');
|
|
244
|
+
const row = actions?.closest<HTMLElement>(ROW);
|
|
245
|
+
if (actions && row && current?.row !== row) open(row, actions.dataset.mkSwipeActions as SwipeSide);
|
|
246
|
+
});
|
|
247
|
+
scope.on(root, 'focusout', (event) => {
|
|
248
|
+
const next = (event as FocusEvent).relatedTarget as Element | null;
|
|
249
|
+
if (current && !current.row.contains(next)) close();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
open: (row, side) => open(row, side),
|
|
254
|
+
close: () => close(),
|
|
255
|
+
get openRow() {
|
|
256
|
+
return current?.row ?? null;
|
|
257
|
+
},
|
|
258
|
+
destroy: () => scope.dispose(),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, type HTMLAttributes, type ReactNode } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
|
|
4
|
+
import { createSwipeActions } from './engine';
|
|
5
|
+
|
|
6
|
+
export interface SwipeAction {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
/** Glyph or icon inside the round button. */
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
tone?: 'neutral' | 'danger' | 'accent';
|
|
12
|
+
/** The row folds away after it runs (the app then removes it). */
|
|
13
|
+
remove?: boolean;
|
|
14
|
+
/** Run by a long swipe on its side. */
|
|
15
|
+
primary?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SwipeRowProps extends Omit<HTMLAttributes<HTMLLIElement>, 'children'> {
|
|
19
|
+
left?: SwipeAction[];
|
|
20
|
+
right?: SwipeAction[];
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Actions({ side, actions }: { side: 'left' | 'right'; actions: SwipeAction[] }) {
|
|
25
|
+
return (
|
|
26
|
+
<div data-mk-swipe-actions={side}>
|
|
27
|
+
{actions.map((action) => (
|
|
28
|
+
<button
|
|
29
|
+
key={action.id}
|
|
30
|
+
type="button"
|
|
31
|
+
data-mk-swipe-action={action.id}
|
|
32
|
+
data-mk-tone={action.tone}
|
|
33
|
+
data-mk-swipe-remove={action.remove ? '' : undefined}
|
|
34
|
+
data-mk-swipe-primary={action.primary ? '' : undefined}
|
|
35
|
+
aria-label={action.label}
|
|
36
|
+
>
|
|
37
|
+
{action.icon ?? action.label}
|
|
38
|
+
</button>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One row: its face (children) and the actions behind each side. */
|
|
45
|
+
function Row({ left, right, children, ...props }: SwipeRowProps) {
|
|
46
|
+
return (
|
|
47
|
+
<li {...props} data-mk-swipe-row="">
|
|
48
|
+
{left?.length ? <Actions side="left" actions={left} /> : null}
|
|
49
|
+
<div data-mk-swipe-content="">{children}</div>
|
|
50
|
+
{right?.length ? <Actions side="right" actions={right} /> : null}
|
|
51
|
+
</li>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SwipeActionsProps extends Omit<HTMLAttributes<HTMLUListElement>, 'onChange'> {
|
|
56
|
+
fullSwipe?: number;
|
|
57
|
+
/** An action ran (a removing one after its row folded away): update your data here. */
|
|
58
|
+
onAction?: (action: string, row: HTMLElement) => void;
|
|
59
|
+
children?: ReactNode;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Root({ fullSwipe, onAction, className, children, ...props }: SwipeActionsProps) {
|
|
63
|
+
const ref = useRef<HTMLUListElement>(null);
|
|
64
|
+
const action = useLatest(onAction);
|
|
65
|
+
useIsomorphicLayoutEffect(() => {
|
|
66
|
+
const instance = createSwipeActions(ref.current, { fullSwipe, onAction: (id, row) => action.current?.(id, row) });
|
|
67
|
+
return () => instance.destroy();
|
|
68
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
69
|
+
}, [fullSwipe]);
|
|
70
|
+
return (
|
|
71
|
+
<ul {...props} ref={ref} className={['mk-swipe-actions', className].filter(Boolean).join(' ')}>
|
|
72
|
+
{children}
|
|
73
|
+
</ul>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* <SwipeActions onAction={(id, row) => …}><SwipeActions.Row key={mail.id} data-id={mail.id} right={[{ id: 'delete', label: 'Eliminar', remove: true, tone: 'danger' }]}>…</SwipeActions.Row></SwipeActions>
|
|
79
|
+
*/
|
|
80
|
+
export const SwipeActions = Object.assign(Root, { Row });
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* swipe-actions — Melonmind list rows (20 px radius, surface-1) that slide aside to show round action buttons
|
|
2
|
+
behind them; the icons grow as they appear and the primary one stretches when a long swipe is armed. */
|
|
3
|
+
.mk-swipe-actions,
|
|
4
|
+
mk-swipe-actions {
|
|
5
|
+
display: grid;
|
|
6
|
+
gap: 0.5rem;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
list-style: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-row] {
|
|
13
|
+
position: relative;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
border-radius: 20px;
|
|
16
|
+
background: var(--mk-surface-3);
|
|
17
|
+
isolation: isolate;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-content] {
|
|
21
|
+
position: relative;
|
|
22
|
+
z-index: 1;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 0.85rem;
|
|
26
|
+
min-height: 4rem;
|
|
27
|
+
padding: 0.75rem 1rem;
|
|
28
|
+
border-radius: inherit;
|
|
29
|
+
background: var(--mk-surface-1);
|
|
30
|
+
box-shadow: inset 0 0 0 1px var(--mk-outline-soft);
|
|
31
|
+
touch-action: pan-y;
|
|
32
|
+
user-select: none;
|
|
33
|
+
cursor: grab;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-row][data-mk-dragging] [data-mk-swipe-content] {
|
|
37
|
+
cursor: grabbing;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-actions] {
|
|
41
|
+
position: absolute;
|
|
42
|
+
top: 0;
|
|
43
|
+
bottom: 0;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 0.4rem;
|
|
47
|
+
padding: 0 0.6rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-actions='left'] {
|
|
51
|
+
left: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-actions='right'] {
|
|
55
|
+
right: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action] {
|
|
59
|
+
display: grid;
|
|
60
|
+
place-items: center;
|
|
61
|
+
gap: 0.15rem;
|
|
62
|
+
width: 3rem;
|
|
63
|
+
height: 3rem;
|
|
64
|
+
padding: 0;
|
|
65
|
+
border: 0;
|
|
66
|
+
border-radius: 50%;
|
|
67
|
+
background: var(--mk-text);
|
|
68
|
+
color: var(--mk-bg);
|
|
69
|
+
font: inherit;
|
|
70
|
+
font-size: 0.6875rem;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
scale: calc(0.6 + var(--mk-swipe-reveal, 1) * 0.4);
|
|
73
|
+
transition:
|
|
74
|
+
width 0.35s cubic-bezier(0.38, 0.49, 0, 1.5),
|
|
75
|
+
border-radius 0.35s cubic-bezier(0.38, 0.49, 0, 1.5);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action][data-mk-tone='danger'] {
|
|
79
|
+
background: var(--mk-error);
|
|
80
|
+
color: var(--mk-on-error);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action][data-mk-tone='accent'] {
|
|
84
|
+
background: var(--mk-accent);
|
|
85
|
+
color: var(--mk-on-accent);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action]:focus-visible {
|
|
89
|
+
outline: 2px solid var(--mk-accent);
|
|
90
|
+
outline-offset: 2px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* A long swipe is armed: the primary action stretches into a pill. */
|
|
94
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-row][data-mk-armed] [data-mk-swipe-action]:is([data-mk-swipe-primary], :only-child) {
|
|
95
|
+
width: 5.5rem;
|
|
96
|
+
border-radius: 64px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@media (prefers-reduced-motion: reduce) {
|
|
100
|
+
:is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action] {
|
|
101
|
+
transition: none;
|
|
102
|
+
scale: 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# tag-input
|
|
2
|
+
|
|
3
|
+
Entrada de etiquetas libres con autocompletado. Enter o una coma añaden lo escrito (o la sugerencia resaltada); al pegar «a, b; c» se añade cada una, una tras otra. Los chips nuevos aparecen con un pop y los que se quitan se pliegan. Las sugerencias salen del campo con una píldora que se desliza por la activa y las letras escritas marcadas.
|
|
4
|
+
|
|
5
|
+
Una etiqueta repetida hace sonar el chip que ya la tiene (sacudida y anillo). Si `validate`, `restrict` o `max` rechazan una, el campo se sacude y debajo dice por qué (`aria-invalid` y el mensaje ligado al campo). Retroceso con el campo vacío marca el último chip y luego lo quita; ← llega a los chips.
|
|
6
|
+
|
|
7
|
+
## HTML / Astro
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<mk-tag-input class="mk-tag-input" label="Etiquetas" value="diseño,urgente"
|
|
11
|
+
suggestions="diseño,código,contenido,urgente,cliente" max="6" name="etiquetas"></mk-tag-input>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## React
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<TagInput value={tags} onChange={setTags} suggestions={known}
|
|
18
|
+
validate={(tag) => (tag.length > 24 ? 'Máximo 24 letras' : null)} />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Opciones
|
|
22
|
+
|
|
23
|
+
| Opción | Atributo | Por defecto | Qué hace |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| `value` | `value` (lista con comas) | — | Etiquetas iniciales. |
|
|
26
|
+
| `suggestions` | `suggestions` (lista) | — | Se ofrecen al escribir. |
|
|
27
|
+
| `placeholder` | `placeholder` | `'Añadir etiqueta…'` | Texto del campo vacío. |
|
|
28
|
+
| `label` | `label` | el `<label>` escrito, o «Etiquetas» | Nombre accesible. |
|
|
29
|
+
| `name` | `name` | — | Campos ocultos `name[]`. |
|
|
30
|
+
| `max` | `max` | sin límite | Máximo de etiquetas. |
|
|
31
|
+
| `restrict` | `restrict` | `false` | Solo etiquetas de las sugerencias. |
|
|
32
|
+
| `allowDuplicates` | `allow-duplicates` | `false` | Permitir repetidas (sin distinguir mayúsculas ni tildes). |
|
|
33
|
+
| `validate(tag, tags)` | — | — | Devuelve un mensaje para rechazarla. |
|
|
34
|
+
| `transform(tag)` | — | quitar espacios sobrantes | Limpia la etiqueta antes de añadirla. |
|
|
35
|
+
| `disabled` | `disabled` | `false` | Desactivado. |
|
|
36
|
+
| `onChange(tags)` | — | — | Cada cambio. |
|
|
37
|
+
|
|
38
|
+
## API y eventos
|
|
39
|
+
|
|
40
|
+
`setValue(tags, notify = false)`, `add(tag)` (devuelve `false` si se rechaza), `remove(tag)`, `setSuggestions(list)`, `value`, `destroy()`. Evento `mk:change` con `{ value }`.
|
|
41
|
+
|
|
42
|
+
Con movimiento reducido los chips aparecen y desaparecen sin animación.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import { createTagInput, type TagInputInstance } from './engine';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <mk-tag-input class="mk-tag-input" label="Etiquetas" value="diseño,urgente" suggestions="diseño,código,urgente" max="5" name="tags">
|
|
6
|
+
* </mk-tag-input>
|
|
7
|
+
* Emits mk:change ({value}).
|
|
8
|
+
*/
|
|
9
|
+
export class MkTagInputElement extends MkElement<TagInputInstance> {
|
|
10
|
+
static options: Record<string, OptionType> = {
|
|
11
|
+
value: 'list',
|
|
12
|
+
suggestions: 'list',
|
|
13
|
+
placeholder: 'string',
|
|
14
|
+
label: 'string',
|
|
15
|
+
name: 'string',
|
|
16
|
+
max: 'number',
|
|
17
|
+
restrict: 'boolean',
|
|
18
|
+
allowDuplicates: 'boolean',
|
|
19
|
+
disabled: 'boolean',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
protected create(options: Record<string, unknown>) {
|
|
23
|
+
return createTagInput(this, options);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
attributeChangedCallback(name: string, previous: string | null, next: string | null) {
|
|
27
|
+
if (previous === next || !this.instance) return;
|
|
28
|
+
const list = (next ?? '').split(',').map((item) => item.trim()).filter(Boolean);
|
|
29
|
+
if (name === 'value') this.instance.setValue(list);
|
|
30
|
+
else if (name === 'suggestions') this.instance.setSuggestions(list);
|
|
31
|
+
else super.attributeChangedCallback(name, previous, next);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get value() {
|
|
35
|
+
return this.instance?.value ?? [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const defineTagInput = () => defineElement('mk-tag-input', MkTagInputElement);
|