@yewdiel21/motion-kit 0.9.1 → 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 +72 -0
- package/dist/auto/index.d.ts +72 -3
- package/dist/auto/index.js +139 -3
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-BFC27F2G.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-XDWBVFHQ.js → chunk-EY4BJKC4.js} +1203 -29
- 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/calendar.css +159 -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 +20 -0
- package/dist/css/components/image-preview.css +195 -0
- package/dist/css/components/input-otp.css +10 -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/pagination.css +11 -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/quantity.css +16 -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/slot-picker.css +126 -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/studio-dock.css +35 -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/trailer.css +37 -0
- package/dist/css/components/tree-view.css +210 -0
- package/dist/css/components/virtual-list.css +195 -0
- package/dist/css/controls.css +65 -0
- package/dist/css/tokens.css +185 -181
- package/dist/elements/index.d.ts +742 -5
- package/dist/elements/index.js +1527 -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 +10113 -181
- package/dist/index.d.ts +89 -5
- package/dist/index.js +285 -9
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +678 -23
- package/dist/react/index.js +2389 -7
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-B98f8LPs.d.ts → reduced-BBUusld6.d.ts} +206 -2
- package/dist/reveal-DEvp6U6T.d.ts +2381 -0
- package/dist/{store-pHiUvxP8.d.ts → store-DjkTBX7f.d.ts} +2 -2
- package/package.json +1 -1
- package/registry.json +583 -104
- package/skills/motion-kit/SKILL.md +209 -189
- package/src/auto/auto-init.ts +136 -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/calendar/README.md +82 -0
- package/src/components/calendar/calendar.css +158 -0
- package/src/components/calendar/element.ts +27 -0
- package/src/components/calendar/engine.ts +412 -0
- package/src/components/calendar/react.tsx +69 -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 +20 -0
- package/src/components/hover-card/element.ts +8 -0
- package/src/components/hover-card/engine.ts +131 -0
- package/src/components/hover-card/hover-card.css +19 -0
- package/src/components/hover-card/react.tsx +19 -0
- 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/input-otp/README.md +18 -0
- package/src/components/input-otp/element.ts +14 -0
- package/src/components/input-otp/engine.ts +106 -0
- package/src/components/input-otp/input-otp.css +9 -0
- package/src/components/input-otp/react.tsx +21 -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/pagination/README.md +17 -0
- package/src/components/pagination/element.ts +14 -0
- package/src/components/pagination/engine.ts +102 -0
- package/src/components/pagination/pagination.css +10 -0
- package/src/components/pagination/react.tsx +22 -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/quantity/README.md +20 -0
- package/src/components/quantity/element.ts +14 -0
- package/src/components/quantity/engine.ts +85 -0
- package/src/components/quantity/quantity.css +15 -0
- package/src/components/quantity/react.tsx +21 -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/slot-picker/README.md +68 -0
- package/src/components/slot-picker/element.ts +27 -0
- package/src/components/slot-picker/engine.ts +412 -0
- package/src/components/slot-picker/react.tsx +76 -0
- package/src/components/slot-picker/slot-picker.css +125 -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/studio-dock/README.md +44 -0
- package/src/components/studio-dock/element.ts +11 -0
- package/src/components/studio-dock/engine.ts +212 -0
- package/src/components/studio-dock/react.tsx +23 -0
- package/src/components/studio-dock/studio-dock.css +34 -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/trailer/README.md +28 -0
- package/src/components/trailer/element.ts +13 -0
- package/src/components/trailer/engine.ts +189 -0
- package/src/components/trailer/react.tsx +16 -0
- package/src/components/trailer/trailer.css +36 -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 +108 -0
- 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/controls/README.md +14 -0
- package/src/css/controls/status.css +63 -0
- package/src/css/tokens.css +186 -182
- package/src/elements/index.ts +171 -0
- package/src/index.ts +97 -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 +78 -0
- package/src/react/use-action-button.ts +6 -0
- package/src/react/use-editorial-reveal.ts +7 -0
- package/src/react/use-film-hero.ts +6 -0
- package/src/react/use-font-cycle.ts +7 -0
- package/src/react/use-image-zoom.ts +5 -0
- package/src/react/use-joined-cta.ts +5 -0
- package/src/react/use-local-clock.ts +5 -0
- package/src/react/use-scroll-dock.ts +5 -0
- package/src/react/use-sticker.ts +5 -0
- package/src/react/use-studio-dock.ts +8 -0
- package/src/react/use-thumbnail-nav.ts +7 -0
- package/src/react/use-trailer.ts +6 -0
- package/src/react/use-weight-hover.ts +5 -0
- package/src/recipes/action-button/README.md +31 -0
- package/src/recipes/action-button/action-button.css +113 -0
- package/src/recipes/action-button/create-action-button.ts +56 -0
- package/src/recipes/editorial-reveal/README.md +35 -0
- package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
- package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
- package/src/recipes/film-hero/README.md +23 -0
- package/src/recipes/film-hero/create-film-hero.ts +65 -0
- package/src/recipes/film-hero/film-hero.css +29 -0
- package/src/recipes/font-cycle/README.md +11 -0
- package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
- package/src/recipes/font-cycle/font-cycle.css +36 -0
- package/src/recipes/image-zoom/README.md +16 -0
- package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
- package/src/recipes/image-zoom/image-zoom.css +7 -0
- package/src/recipes/joined-cta/README.md +13 -0
- package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
- package/src/recipes/joined-cta/joined-cta.css +17 -0
- package/src/recipes/local-clock/README.md +31 -0
- package/src/recipes/local-clock/create-local-clock.ts +61 -0
- package/src/recipes/scroll-dock/README.md +46 -0
- package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
- package/src/recipes/scroll-dock/scroll-dock.css +70 -0
- package/src/recipes/sticker/README.md +59 -0
- package/src/recipes/sticker/create-sticker.ts +490 -0
- package/src/recipes/sticker/sticker.css +112 -0
- package/src/recipes/thumbnail-nav/README.md +16 -0
- package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
- package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
- package/src/recipes/weight-hover/README.md +37 -0
- package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
- package/src/recipes/weight-hover/weight-hover.css +7 -0
- package/dist/chunk-BFC27F2G.js.map +0 -1
- package/dist/chunk-J6OCFNGU.js +0 -177
- package/dist/chunk-J6OCFNGU.js.map +0 -1
- package/dist/chunk-JQMJQSXD.js +0 -2374
- package/dist/chunk-JQMJQSXD.js.map +0 -1
- package/dist/chunk-XDWBVFHQ.js.map +0 -1
- package/dist/reveal-C0hCDcB_.d.ts +0 -415
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* range-calendar — a compact month (or two) on the Melon Mind surfaces: 2.5 rem days, the range as a soft band with
|
|
2
|
+
round ends at the range ends and at the edges of each week, ends as accent circles, a dashed preview band while
|
|
3
|
+
choosing the end, today with a ring. 20 px outer corners, 8 px buttons. */
|
|
4
|
+
.mk-range-calendar,
|
|
5
|
+
mk-range-calendar {
|
|
6
|
+
--mk-range-day: 2.5rem;
|
|
7
|
+
--mk-range-band: var(--mk-accent-soft);
|
|
8
|
+
display: inline-grid;
|
|
9
|
+
gap: 0.75rem;
|
|
10
|
+
max-width: 100%;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
padding: 0.75rem;
|
|
13
|
+
border-radius: 20px;
|
|
14
|
+
background: var(--mk-surface-1);
|
|
15
|
+
color: var(--mk-text);
|
|
16
|
+
font-variant-numeric: tabular-nums;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-header] {
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: auto 1fr auto;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: 0.5rem;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-title] {
|
|
27
|
+
margin: 0;
|
|
28
|
+
font-size: 0.9375rem;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
text-align: center;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
:is(.mk-range-calendar, mk-range-calendar) :is([data-mk-range-prev], [data-mk-range-next]) {
|
|
34
|
+
display: grid;
|
|
35
|
+
place-items: center;
|
|
36
|
+
width: 2.25rem;
|
|
37
|
+
height: 2.25rem;
|
|
38
|
+
padding: 0;
|
|
39
|
+
border: 0;
|
|
40
|
+
border-radius: 8px;
|
|
41
|
+
background: var(--mk-surface-3);
|
|
42
|
+
color: inherit;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:is(.mk-range-calendar, mk-range-calendar) :is([data-mk-range-prev], [data-mk-range-next]):focus-visible {
|
|
47
|
+
outline: 3px solid color-mix(in srgb, var(--mk-accent) 45%, transparent);
|
|
48
|
+
outline-offset: 2px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-months] {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
gap: 1.25rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-month] {
|
|
58
|
+
display: grid;
|
|
59
|
+
grid-template-columns: repeat(7, var(--mk-range-day));
|
|
60
|
+
row-gap: 2px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:is(.mk-range-calendar, mk-range-calendar) :is([data-mk-range-weekdays], [data-mk-range-week]) {
|
|
64
|
+
display: contents;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-weekday] {
|
|
68
|
+
display: grid;
|
|
69
|
+
place-items: center;
|
|
70
|
+
height: 2rem;
|
|
71
|
+
color: var(--mk-muted);
|
|
72
|
+
font-size: 0.75rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell] {
|
|
76
|
+
position: relative;
|
|
77
|
+
height: var(--mk-range-day);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day-band] {
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset: 3px 0;
|
|
83
|
+
background: transparent;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell]:has(> [data-mk-in-range]) [data-mk-day-band] {
|
|
87
|
+
background: var(--mk-range-band);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* The tentative range: a lighter band with a dashed edge around the whole run (top and bottom on every day, the
|
|
91
|
+
sides only at its ends and at the edges of each week). */
|
|
92
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell]:has(> [data-mk-preview]) [data-mk-day-band] {
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
border-block: 1px dashed color-mix(in srgb, var(--mk-accent) 55%, transparent);
|
|
95
|
+
background: color-mix(in srgb, var(--mk-range-band) 55%, transparent);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell]:has(> [data-mk-preview]):is(:has(> [data-mk-preview-start]), :first-child) [data-mk-day-band] {
|
|
99
|
+
border-left: 1px dashed color-mix(in srgb, var(--mk-accent) 55%, transparent);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell]:has(> [data-mk-preview]):is(:has(> [data-mk-preview-end]), :last-child) [data-mk-day-band] {
|
|
103
|
+
border-right: 1px dashed color-mix(in srgb, var(--mk-accent) 55%, transparent);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Round ends: the range (or preview) ends and the first/last day of each week row (rows are display: contents, so
|
|
107
|
+
every row still holds exactly seven cells and :first-child / :last-child are its ends). The layout hangs on data
|
|
108
|
+
attributes, not roles: the copy that folds back into the button when a popover closes has no roles. */
|
|
109
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell]:is(:has(> [data-mk-range-start], > [data-mk-preview-start]), :first-child) [data-mk-day-band] {
|
|
110
|
+
border-top-left-radius: 999px;
|
|
111
|
+
border-bottom-left-radius: 999px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell]:is(:has(> [data-mk-range-end], > [data-mk-preview-end]), :last-child) [data-mk-day-band] {
|
|
115
|
+
border-top-right-radius: 999px;
|
|
116
|
+
border-bottom-right-radius: 999px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* A month's first and last day also close the band (the cells around them are empty). */
|
|
120
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell][data-mk-outside] + [data-mk-range-cell] [data-mk-day-band] {
|
|
121
|
+
border-top-left-radius: 999px;
|
|
122
|
+
border-bottom-left-radius: 999px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-range-cell]:has(+ [data-mk-range-cell][data-mk-outside]) [data-mk-day-band] {
|
|
126
|
+
border-top-right-radius: 999px;
|
|
127
|
+
border-bottom-right-radius: 999px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day] {
|
|
131
|
+
position: relative;
|
|
132
|
+
z-index: 1;
|
|
133
|
+
width: 100%;
|
|
134
|
+
height: 100%;
|
|
135
|
+
padding: 0;
|
|
136
|
+
border: 0;
|
|
137
|
+
border-radius: 999px;
|
|
138
|
+
background: transparent;
|
|
139
|
+
color: inherit;
|
|
140
|
+
font: inherit;
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
transition: background-color 0.2s ease, color 0.2s ease;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day]:hover {
|
|
146
|
+
background: var(--mk-surface-3);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day]:is([data-mk-range-start], [data-mk-range-end], [data-mk-anchor]) {
|
|
150
|
+
background: var(--mk-accent);
|
|
151
|
+
color: var(--mk-on-accent);
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day][data-mk-today]:not([data-mk-range-start], [data-mk-range-end], [data-mk-anchor]) {
|
|
156
|
+
box-shadow: inset 0 0 0 1.5px var(--mk-text);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day][aria-disabled='true'] {
|
|
160
|
+
color: var(--mk-muted);
|
|
161
|
+
text-decoration: line-through;
|
|
162
|
+
cursor: not-allowed;
|
|
163
|
+
opacity: 0.55;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day][aria-disabled='true']:hover {
|
|
167
|
+
background: transparent;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day]:focus-visible {
|
|
171
|
+
outline: 3px solid color-mix(in srgb, var(--mk-accent) 45%, transparent);
|
|
172
|
+
outline-offset: 1px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (max-width: 380px) {
|
|
176
|
+
:is(.mk-range-calendar, mk-range-calendar) {
|
|
177
|
+
--mk-range-day: 2.25rem;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@media (prefers-reduced-motion: reduce) {
|
|
182
|
+
:is(.mk-range-calendar, mk-range-calendar) [data-mk-day] {
|
|
183
|
+
transition: none;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, type HTMLAttributes } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
|
|
4
|
+
import { rangeKeys, toRange } from '../../core/date-range';
|
|
5
|
+
import type { DateInput } from '../../core/dates';
|
|
6
|
+
import { createRangeCalendar, type RangeCalendarInstance, type RangeCalendarOptions } from './engine';
|
|
7
|
+
|
|
8
|
+
type RangeValue = { start: DateInput; end: DateInput } | null;
|
|
9
|
+
|
|
10
|
+
export interface RangeCalendarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>, Omit<RangeCalendarOptions, 'value'> {
|
|
11
|
+
/** Controlled range; without it the calendar keeps its own (defaultValue). */
|
|
12
|
+
value?: RangeValue;
|
|
13
|
+
defaultValue?: RangeValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const keyOf = (value: RangeValue | undefined) => {
|
|
17
|
+
const range = toRange(value);
|
|
18
|
+
return range ? JSON.stringify(rangeKeys(range)) : 'null';
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** <RangeCalendar value={range} onChange={setRange} visibleMonths={2} />. */
|
|
22
|
+
export function RangeCalendar({ value, defaultValue, month, visibleMonths, firstDay, min, max, unavailable, locale, today, labels, onChange, className, ...props }: RangeCalendarProps) {
|
|
23
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
24
|
+
const instance = useRef<RangeCalendarInstance | null>(null);
|
|
25
|
+
const change = useLatest(onChange);
|
|
26
|
+
const test = useLatest(unavailable);
|
|
27
|
+
const start = useRef<RangeValue>(value !== undefined ? value : (defaultValue ?? null));
|
|
28
|
+
const rules = typeof unavailable === 'function' ? 'function' : JSON.stringify(unavailable ?? null);
|
|
29
|
+
useIsomorphicLayoutEffect(() => {
|
|
30
|
+
const created = createRangeCalendar(ref.current, {
|
|
31
|
+
value: value !== undefined ? value : start.current,
|
|
32
|
+
month,
|
|
33
|
+
visibleMonths,
|
|
34
|
+
firstDay,
|
|
35
|
+
min,
|
|
36
|
+
max,
|
|
37
|
+
// A function is read through a ref so a new closure each render does not rebuild the calendar.
|
|
38
|
+
unavailable: typeof unavailable === 'function' ? (day) => (typeof test.current === 'function' ? test.current(day) : false) : unavailable,
|
|
39
|
+
locale,
|
|
40
|
+
today,
|
|
41
|
+
labels,
|
|
42
|
+
onChange: (range) => change.current?.(range),
|
|
43
|
+
});
|
|
44
|
+
instance.current = created;
|
|
45
|
+
return () => {
|
|
46
|
+
start.current = created.value;
|
|
47
|
+
created.destroy();
|
|
48
|
+
instance.current = null;
|
|
49
|
+
};
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, [String(month ?? ''), visibleMonths, firstDay, String(min ?? ''), String(max ?? ''), rules, locale, String(today ?? '')]);
|
|
52
|
+
useIsomorphicLayoutEffect(() => {
|
|
53
|
+
if (value !== undefined && instance.current && keyOf(value) !== keyOf(instance.current.value)) instance.current.setValue(value);
|
|
54
|
+
}, [keyOf(value)]);
|
|
55
|
+
return <div {...props} ref={ref} className={['mk-range-calendar', className].filter(Boolean).join(' ')} />;
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# range-slider
|
|
2
|
+
|
|
3
|
+
Valor o intervalo sobre `<input type="range">` nativos: teclado, toque, formularios y `disabled` vienen de la plataforma. Dos inputs forman un intervalo cuyos extremos nunca se cruzan.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<mk-range-slider class="mk-range-slider" min="0" max="1000" step="50" label="Presupuesto">
|
|
7
|
+
<div data-mk-range-track>
|
|
8
|
+
<input type="range" data-mk-range-input value="200" />
|
|
9
|
+
<input type="range" data-mk-range-input value="800" />
|
|
10
|
+
</div>
|
|
11
|
+
<output data-mk-range-output></output>
|
|
12
|
+
</mk-range-slider>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`defineElements()`, `createRangeSlider(root, options)` o `<RangeSlider min={0} max={1000} defaultValue={[200, 800]} label="Presupuesto" onChange={setRange} />`. Sin inputs en el root, el motor crea uno (o dos si `value` es un array).
|
|
16
|
+
|
|
17
|
+
Opciones: `min=0`, `max=100`, `step=1`, `value` (número o `[a, b]`), `disabled`, `name` (un intervalo envía `name[0]` y `name[1]`), `label` (aria-label; en intervalo «· mínimo/· máximo»), `formatValue(n)` (output y `aria-valuetext`), `onInput(value)`, `onChange(value)`. Sin opción, los límites se leen de los atributos del primer input; un `value` autorado que el navegador recortó al rango por defecto (0–100) se respeta. API: `setValue(value, notify=true)` (con `notify=false` no avisa ni cambia el valor confirmado, así un padre controlado puede reflejar cada `input` sin perder el `change`), `value` (copia), `destroy()`. En el custom element, `el.formatValue = (n) => …` aplica un formateador.
|
|
18
|
+
|
|
19
|
+
`input` actualiza valor, relleno y output al instante y emite `mk:input`; `change` confirma una sola vez cuando el valor cambió y emite `mk:change` con `{value, previous}`. Los valores se ajustan al paso desde `min` sin errores decimales, se acotan al mayor `min + n·step ≤ max` e ignoran NaN. Al arrastrar un extremo más allá del otro se queda en el otro.
|
|
20
|
+
|
|
21
|
+
El reset nativo del formulario recupera los valores por defecto de los inputs sin callbacks (un reset cancelado no cambia nada). `disabled` ignora la entrada del usuario. `destroy()` restaura valores, atributos, estilos y texto del output. React conserva la edición no controlada entre renders y la ajusta a nuevos límites; con `value` es controlado.
|
|
22
|
+
|
|
23
|
+
Movimiento: el motor dibuja relleno, pulgares y una burbuja con el valor (`[data-mk-range-visual]`; los inputs nativos quedan como zona de toque transparente). Al pulsar, el pulgar crece y la burbuja aparece; arrastrar sigue al puntero sin retraso; `setValue` y el teclado se deslizan; confirmar un cambio da un pequeño rebote al output. Variables: `--mk-range-thumb` (1.5rem), `--mk-range-height` (8px), `--mk-range-track`, `--mk-range-fill`. El output no es región viva: los inputs anuncian su propio valor. Movimiento reducido quita escalas y deslizamientos.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import { createRangeSlider, type RangeSliderInstance, type RangeValue } from './engine';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <mk-range-slider class="mk-range-slider" min="0" max="100" step="5" label="Precio" value="[20,80]">
|
|
6
|
+
* <div data-mk-range-track><input type="range" data-mk-range-input value="20"><input type="range" data-mk-range-input value="80"></div>
|
|
7
|
+
* <output data-mk-range-output></output>
|
|
8
|
+
* </mk-range-slider>
|
|
9
|
+
* Empty, it makes its input(s). Emits `mk:input` while dragging and `mk:change` ({value, previous}) on commit.
|
|
10
|
+
* `el.formatValue = (n) => `${n} €`` formats the output and aria-valuetext.
|
|
11
|
+
*/
|
|
12
|
+
export class MkRangeSliderElement extends MkElement<RangeSliderInstance> {
|
|
13
|
+
static options: Record<string, OptionType> = { min: 'number', max: 'number', step: 'number', value: 'json', disabled: 'boolean', name: 'string', label: 'string' };
|
|
14
|
+
|
|
15
|
+
private format: ((value: number) => string) | undefined;
|
|
16
|
+
|
|
17
|
+
/** Formatter for the output and aria-valuetext (code, so a property rather than an attribute). */
|
|
18
|
+
get formatValue() {
|
|
19
|
+
return this.format;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
set formatValue(format: ((value: number) => string) | undefined) {
|
|
23
|
+
this.format = format;
|
|
24
|
+
this.rebuild(true);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected create(options: Record<string, unknown>) {
|
|
28
|
+
return createRangeSlider(this, { ...options, formatValue: this.format });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Rebuild with the current attributes; keepValue keeps the edited value over the `value` attribute. */
|
|
32
|
+
private rebuild(keepValue: boolean) {
|
|
33
|
+
if (!this.isConnected || !this.instance) return;
|
|
34
|
+
const value = this.instance.value;
|
|
35
|
+
this.instance.destroy();
|
|
36
|
+
this.instance = this.create(keepValue ? { ...this.options, value } : { value, ...this.options });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Bounds rebuild the engine but keep the edited value unless `value` is set.
|
|
40
|
+
attributeChangedCallback(_name: string, previous: string | null, next: string | null) {
|
|
41
|
+
if (previous !== next) this.rebuild(false);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setValue(value: RangeValue, notify?: boolean) {
|
|
45
|
+
this.instance?.setValue(value, notify);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get value() {
|
|
49
|
+
return this.instance?.value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const defineRangeSlider = () => defineElement('mk-range-slider', MkRangeSliderElement);
|
|
@@ -0,0 +1,316 @@
|
|
|
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
|
+
* Range slider on native <input type="range"> (keyboard, touch and forms come
|
|
9
|
+
* from the platform). One input is a value, two are an interval whose thumbs
|
|
10
|
+
* never cross. `input` updates value and output at once; `change` commits (onChange /
|
|
11
|
+
* mk:change) only when the committed value differs. The native thumbs are
|
|
12
|
+
* transparent hit areas: the engine draws its own fill, thumbs and value
|
|
13
|
+
* bubbles and moves them with GSAP, tightly while dragging and with Melonmind's
|
|
14
|
+
* swing (.38,.49,0,1.5) for keys, presets, setValue and form reset. A grabbed
|
|
15
|
+
* thumb grows on the click curve and shows its value above it.
|
|
16
|
+
* Parts: [data-mk-range-input] (made when absent), [data-mk-range-track], [data-mk-range-output].
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type RangeValue = number | [number, number];
|
|
20
|
+
|
|
21
|
+
export interface RangeSliderOptions {
|
|
22
|
+
min?: number;
|
|
23
|
+
max?: number;
|
|
24
|
+
step?: number;
|
|
25
|
+
value?: RangeValue;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Form name; an interval submits name[0] and name[1]. */
|
|
28
|
+
name?: string;
|
|
29
|
+
/** Accessible name of the input(s). */
|
|
30
|
+
label?: string;
|
|
31
|
+
/** Text for the output and aria-valuetext. */
|
|
32
|
+
formatValue?: (value: number) => string;
|
|
33
|
+
onInput?: (value: RangeValue) => void;
|
|
34
|
+
onChange?: (value: RangeValue) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RangeSliderInstance extends Instance {
|
|
38
|
+
setValue(value: RangeValue, notify?: boolean): void;
|
|
39
|
+
readonly value: RangeValue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const finite = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value);
|
|
43
|
+
const attrNumber = (input: HTMLInputElement | undefined, name: string) => {
|
|
44
|
+
const raw = input?.getAttribute(name);
|
|
45
|
+
return raw !== null && raw !== undefined && raw.trim() !== '' && Number.isFinite(Number(raw)) ? Number(raw) : undefined;
|
|
46
|
+
};
|
|
47
|
+
const decimals = (n: number) => {
|
|
48
|
+
const [, fraction = ''] = String(n).split('.');
|
|
49
|
+
return fraction.length;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function createRangeSlider(root: HTMLElement | null, options: RangeSliderOptions = {}): RangeSliderInstance {
|
|
53
|
+
if (!root) return inert({ setValue() {}, value: 0 });
|
|
54
|
+
registerMotion();
|
|
55
|
+
const scope = createScope();
|
|
56
|
+
let inputs = [...root.querySelectorAll<HTMLInputElement>('input[data-mk-range-input]')].slice(0, 2);
|
|
57
|
+
if (!inputs.length) {
|
|
58
|
+
const count = Array.isArray(options.value) ? 2 : 1;
|
|
59
|
+
inputs = Array.from({ length: count }, () => {
|
|
60
|
+
const input = document.createElement('input');
|
|
61
|
+
input.type = 'range';
|
|
62
|
+
input.dataset.mkRangeInput = '';
|
|
63
|
+
root.append(input);
|
|
64
|
+
scope.add(() => input.remove());
|
|
65
|
+
return input;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const pair = inputs.length === 2;
|
|
69
|
+
const first = inputs[0];
|
|
70
|
+
const min = finite(options.min) ? options.min : attrNumber(first, 'min') ?? 0;
|
|
71
|
+
const maxOption = finite(options.max) ? options.max : attrNumber(first, 'max') ?? 100;
|
|
72
|
+
const max = Math.max(min, maxOption);
|
|
73
|
+
const stepOption = finite(options.step) ? options.step : attrNumber(first, 'step') ?? 1;
|
|
74
|
+
const step = stepOption > 0 ? stepOption : 1;
|
|
75
|
+
const places = Math.min(10, Math.max(decimals(step), decimals(min)));
|
|
76
|
+
const fix = (n: number) => Number(n.toFixed(places));
|
|
77
|
+
const upper = Math.min(max, fix(min + Math.floor((max - min) / step + 1e-9) * step));
|
|
78
|
+
const normalize = (n: number) => Math.max(min, Math.min(upper, fix(min + Math.round((n - min) / step) * step)));
|
|
79
|
+
const disabled = options.disabled ?? inputs.some((input) => input.disabled);
|
|
80
|
+
const output = root.querySelector<HTMLElement>('[data-mk-range-output]');
|
|
81
|
+
const format = (n: number) => (options.formatValue ? options.formatValue(n) : String(n));
|
|
82
|
+
const originals = inputs.map((input) => input.value);
|
|
83
|
+
|
|
84
|
+
const ordered = (list: number[]) => (pair ? [...list].sort((a, b) => a - b) : list);
|
|
85
|
+
const read = () => inputs.map((input) => (input.value.trim() !== '' && Number.isFinite(Number(input.value)) ? normalize(Number(input.value)) : NaN));
|
|
86
|
+
/**
|
|
87
|
+
* Starting values. The parser clamps value="500" to the input's own range (0–100 by
|
|
88
|
+
* default) before min/max are set here: an authored value that was only clamped wins.
|
|
89
|
+
*/
|
|
90
|
+
const authored = () => {
|
|
91
|
+
const current = read();
|
|
92
|
+
return inputs.map((input, i) => {
|
|
93
|
+
const written = attrNumber(input, 'value');
|
|
94
|
+
if (written === undefined) return current[i]!;
|
|
95
|
+
const low = attrNumber(input, 'min') ?? 0;
|
|
96
|
+
const high = Math.max(low, attrNumber(input, 'max') ?? 100);
|
|
97
|
+
return Number(input.value) === Math.min(high, Math.max(low, written)) ? normalize(written) : current[i]!;
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const initial = (() => {
|
|
101
|
+
const wanted = options.value;
|
|
102
|
+
const fromOption = Array.isArray(wanted) ? wanted : finite(wanted) ? [wanted, wanted] : null;
|
|
103
|
+
const fromInputs = authored();
|
|
104
|
+
return inputs.map((_, i) => {
|
|
105
|
+
const candidate = fromOption?.[i];
|
|
106
|
+
if (finite(candidate)) return normalize(candidate);
|
|
107
|
+
return Number.isFinite(fromInputs[i]) ? fromInputs[i]! : i === 0 ? min : upper;
|
|
108
|
+
});
|
|
109
|
+
})();
|
|
110
|
+
let values = ordered(initial);
|
|
111
|
+
let committed = [...values];
|
|
112
|
+
|
|
113
|
+
// First registered, last undone: values return after min/max/step are authored again.
|
|
114
|
+
scope.add(() => inputs.forEach((input, i) => (input.value = originals[i]!)));
|
|
115
|
+
scope.attr(root, 'style', root.getAttribute('style'));
|
|
116
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
117
|
+
scope.attr(root, 'data-mk-range', pair ? 'interval' : 'single');
|
|
118
|
+
inputs.forEach((input, i) => {
|
|
119
|
+
scope.attr(input, 'min', String(min));
|
|
120
|
+
scope.attr(input, 'max', String(upper));
|
|
121
|
+
scope.attr(input, 'step', String(step));
|
|
122
|
+
scope.attr(input, 'disabled', disabled ? '' : null);
|
|
123
|
+
if (pair) scope.attr(input, 'style', input.getAttribute('style'));
|
|
124
|
+
if (options.name) scope.attr(input, 'name', pair ? `${options.name}[${i}]` : options.name);
|
|
125
|
+
if (options.label) scope.attr(input, 'aria-label', pair ? `${options.label} · ${i === 0 ? 'mínimo' : 'máximo'}` : options.label);
|
|
126
|
+
});
|
|
127
|
+
if (output) {
|
|
128
|
+
const text = output.textContent;
|
|
129
|
+
// The inputs announce their own value; a live <output> would repeat every step of a drag.
|
|
130
|
+
scope.attr(output, 'aria-live', 'off');
|
|
131
|
+
scope.add(() => {
|
|
132
|
+
output.textContent = text;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The drawn fill, thumbs and bubbles (decorative; the inputs stay the control).
|
|
137
|
+
const track = root.querySelector<HTMLElement>('[data-mk-range-track]') ?? first!.parentElement ?? root;
|
|
138
|
+
const visual = document.createElement('span');
|
|
139
|
+
visual.dataset.mkRangeVisual = '';
|
|
140
|
+
visual.setAttribute('aria-hidden', 'true');
|
|
141
|
+
const fill = document.createElement('span');
|
|
142
|
+
fill.dataset.mkPart = 'fill';
|
|
143
|
+
visual.append(fill);
|
|
144
|
+
const thumbs = inputs.map(() => {
|
|
145
|
+
const thumb = document.createElement('span');
|
|
146
|
+
thumb.dataset.mkPart = 'thumb';
|
|
147
|
+
const bubble = document.createElement('span');
|
|
148
|
+
bubble.dataset.mkPart = 'bubble';
|
|
149
|
+
thumb.append(bubble);
|
|
150
|
+
visual.append(thumb);
|
|
151
|
+
return { thumb, bubble };
|
|
152
|
+
});
|
|
153
|
+
track.append(visual);
|
|
154
|
+
|
|
155
|
+
const span = upper - min || 1;
|
|
156
|
+
const ratio = (n: number) => (n - min) / span;
|
|
157
|
+
/** What is drawn (ratios); it trails `values` while gliding. */
|
|
158
|
+
const shown = { a: pair ? ratio(values[0]!) : 0, b: ratio(pair ? values[1]! : values[0]!) };
|
|
159
|
+
const draw = () => {
|
|
160
|
+
root.style.setProperty('--mk-range-start', shown.a.toFixed(4));
|
|
161
|
+
root.style.setProperty('--mk-range-end', shown.b.toFixed(4));
|
|
162
|
+
};
|
|
163
|
+
let glide: gsap.core.Tween | null = null;
|
|
164
|
+
const effects = new Set<gsap.core.Animation>();
|
|
165
|
+
const trackEffect = (animation: gsap.core.Animation | null) => {
|
|
166
|
+
if (!animation) return;
|
|
167
|
+
effects.add(animation);
|
|
168
|
+
// Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
|
|
169
|
+
for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
|
|
170
|
+
};
|
|
171
|
+
scope.add(() => {
|
|
172
|
+
glide?.kill();
|
|
173
|
+
for (const effect of effects) effect.kill();
|
|
174
|
+
effects.clear();
|
|
175
|
+
gsap.killTweensOf(thumbs.flatMap(({ thumb, bubble }) => [thumb, bubble]));
|
|
176
|
+
visual.remove();
|
|
177
|
+
});
|
|
178
|
+
let dragging = -1;
|
|
179
|
+
const bubbleFor = (i: number, on: boolean) => {
|
|
180
|
+
const part = thumbs[i];
|
|
181
|
+
if (!part) return;
|
|
182
|
+
part.thumb.toggleAttribute('data-mk-active', on);
|
|
183
|
+
if (reduced()) {
|
|
184
|
+
part.bubble.style.opacity = on ? '1' : '';
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
gsap.killTweensOf(part.bubble);
|
|
188
|
+
if (on) trackEffect(gsap.fromTo(part.bubble, { opacity: 0, y: 4, scale: 0.9 }, { opacity: 1, y: 0, scale: 1, duration: 0.3, ease: EASE.pop }));
|
|
189
|
+
else trackEffect(gsap.to(part.bubble, { opacity: 0, y: 4, duration: 0.2, delay: 0.35, ease: 'power2.in', clearProps: 'transform' }));
|
|
190
|
+
};
|
|
191
|
+
const grow = (i: number, on: boolean) => {
|
|
192
|
+
const part = thumbs[i];
|
|
193
|
+
if (!part || reduced()) return;
|
|
194
|
+
trackEffect(gsap.to(part.thumb, { scale: on ? 1.22 : 1, duration: on ? 0.35 : 0.5, ease: EASE.click, overwrite: 'auto' }));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
type Mode = 'set' | 'drag' | 'glide';
|
|
198
|
+
const current = (): RangeValue => (pair ? [values[0]!, values[1]!] : values[0]!);
|
|
199
|
+
const render = (mode: Mode = 'glide') => {
|
|
200
|
+
const target = { a: pair ? ratio(values[0]!) : 0, b: ratio(pair ? values[1]! : values[0]!) };
|
|
201
|
+
glide?.kill();
|
|
202
|
+
if (mode === 'set' || reduced()) {
|
|
203
|
+
Object.assign(shown, target);
|
|
204
|
+
draw();
|
|
205
|
+
} else glide = gsap.to(shown, { ...target, duration: mode === 'drag' ? 0.14 : 0.5, ease: mode === 'drag' ? 'power3.out' : EASE.swatch, onUpdate: draw });
|
|
206
|
+
inputs.forEach((input, i) => {
|
|
207
|
+
input.value = String(values[i]);
|
|
208
|
+
if (options.formatValue) scope.attr(input, 'aria-valuetext', format(values[i]!));
|
|
209
|
+
// The upper thumb stays reachable when both meet at the right end.
|
|
210
|
+
if (pair) input.style.zIndex = i === 0 && values[0]! >= upper ? '3' : '';
|
|
211
|
+
thumbs[i]!.bubble.textContent = format(values[i]!);
|
|
212
|
+
});
|
|
213
|
+
if (output) output.textContent = pair ? `${format(values[0]!)} – ${format(values[1]!)}` : format(values[0]!);
|
|
214
|
+
};
|
|
215
|
+
const same = (a: number[], b: number[]) => a.length === b.length && a.every((n, i) => n === b[i]);
|
|
216
|
+
const emit = (type: 'mk:input' | 'mk:change', previous?: RangeValue) =>
|
|
217
|
+
root.dispatchEvent(new CustomEvent(type, { bubbles: true, detail: type === 'mk:change' ? { value: current(), previous } : { value: current() } }));
|
|
218
|
+
const commit = (notify: boolean) => {
|
|
219
|
+
if (same(values, committed)) return;
|
|
220
|
+
const previous: RangeValue = pair ? [committed[0]!, committed[1]!] : committed[0]!;
|
|
221
|
+
committed = [...values];
|
|
222
|
+
if (!notify) return;
|
|
223
|
+
options.onChange?.(current());
|
|
224
|
+
emit('mk:change', previous);
|
|
225
|
+
};
|
|
226
|
+
/** The thumb `i` moved: keep it on the grid and behind/ahead of the other. */
|
|
227
|
+
const take = (i: number) => {
|
|
228
|
+
const raw = inputs[i]!.value;
|
|
229
|
+
const n = raw.trim() !== '' && Number.isFinite(Number(raw)) ? normalize(Number(raw)) : NaN;
|
|
230
|
+
if (!Number.isFinite(n)) return false;
|
|
231
|
+
let next = n;
|
|
232
|
+
if (pair) next = i === 0 ? Math.min(next, values[1]!) : Math.max(next, values[0]!);
|
|
233
|
+
const changed = next !== values[i];
|
|
234
|
+
values[i] = next;
|
|
235
|
+
return changed;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
inputs.forEach((input, i) => {
|
|
239
|
+
// Inputs and the inputs of a destroyed instance are ignored by the scope.
|
|
240
|
+
scope.on(input, 'input', () => {
|
|
241
|
+
if (disabled) return render('set');
|
|
242
|
+
const changed = take(i);
|
|
243
|
+
render(dragging === i ? 'drag' : 'glide');
|
|
244
|
+
if (!changed) return;
|
|
245
|
+
// A key step nudges its thumb.
|
|
246
|
+
if (dragging !== i && !reduced()) trackEffect(gsap.fromTo(thumbs[i]!.thumb, { scale: 1.18 }, { scale: 1, duration: 0.45, ease: EASE.click }));
|
|
247
|
+
options.onInput?.(current());
|
|
248
|
+
emit('mk:input');
|
|
249
|
+
});
|
|
250
|
+
scope.on(input, 'change', () => {
|
|
251
|
+
if (disabled) return render('set');
|
|
252
|
+
take(i);
|
|
253
|
+
render(dragging === i ? 'drag' : 'glide');
|
|
254
|
+
const before = committed.join();
|
|
255
|
+
commit(true);
|
|
256
|
+
if (committed.join() !== before) trackEffect(press(output));
|
|
257
|
+
});
|
|
258
|
+
scope.on(input, 'pointerdown', () => {
|
|
259
|
+
if (disabled) return;
|
|
260
|
+
dragging = i;
|
|
261
|
+
grow(i, true);
|
|
262
|
+
bubbleFor(i, true);
|
|
263
|
+
const release = () => {
|
|
264
|
+
removeEventListener('pointerup', release);
|
|
265
|
+
removeEventListener('pointercancel', release);
|
|
266
|
+
if (dragging !== i) return;
|
|
267
|
+
dragging = -1;
|
|
268
|
+
grow(i, false);
|
|
269
|
+
if (document.activeElement !== input || !input.matches(':focus-visible')) bubbleFor(i, false);
|
|
270
|
+
};
|
|
271
|
+
addEventListener('pointerup', release);
|
|
272
|
+
addEventListener('pointercancel', release);
|
|
273
|
+
scope.add(release);
|
|
274
|
+
});
|
|
275
|
+
scope.on(input, 'focus', () => {
|
|
276
|
+
thumbs[i]!.thumb.toggleAttribute('data-mk-focus', input.matches(':focus-visible'));
|
|
277
|
+
if (input.matches(':focus-visible')) bubbleFor(i, true);
|
|
278
|
+
});
|
|
279
|
+
scope.on(input, 'blur', () => {
|
|
280
|
+
thumbs[i]!.thumb.removeAttribute('data-mk-focus');
|
|
281
|
+
if (dragging !== i) bubbleFor(i, false);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
const form = first?.form;
|
|
285
|
+
if (form) {
|
|
286
|
+
scope.on(form, 'reset', (event) => {
|
|
287
|
+
// The native reset writes the default values after its listeners: read them next.
|
|
288
|
+
scope.timeout(() => {
|
|
289
|
+
if (event.defaultPrevented || scope.disposed) return;
|
|
290
|
+
const restored = read().map((n, i) => (Number.isFinite(n) ? n : i === 0 ? min : upper));
|
|
291
|
+
values = ordered(restored);
|
|
292
|
+
committed = [...values];
|
|
293
|
+
render('glide');
|
|
294
|
+
}, 0);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
render('set');
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
setValue(next, notify = true) {
|
|
301
|
+
if (scope.disposed) return;
|
|
302
|
+
const list = Array.isArray(next) ? next : [next, next];
|
|
303
|
+
const candidate = inputs.map((_, i) => (finite(list[i]) ? normalize(list[i]!) : values[i]!));
|
|
304
|
+
values = ordered(candidate);
|
|
305
|
+
// A controlled parent echoing the drag follows tightly; anything else glides.
|
|
306
|
+
render(dragging >= 0 ? 'drag' : 'glide');
|
|
307
|
+
// Silent sets (a controlled parent echoing a drag) leave the committed value alone,
|
|
308
|
+
// so the user's change still reports.
|
|
309
|
+
if (notify) commit(true);
|
|
310
|
+
},
|
|
311
|
+
get value() {
|
|
312
|
+
return current();
|
|
313
|
+
},
|
|
314
|
+
destroy: () => scope.dispose(),
|
|
315
|
+
};
|
|
316
|
+
}
|