@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.
Files changed (365) hide show
  1. package/README.md +155 -93
  2. package/dist/auto/index.d.ts +53 -3
  3. package/dist/auto/index.js +101 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-IKQ5FODN.js → chunk-6P4P7NU6.js} +2 -218
  6. package/dist/chunk-6P4P7NU6.js.map +1 -0
  7. package/dist/chunk-CU6SXW3E.js +20334 -0
  8. package/dist/chunk-CU6SXW3E.js.map +1 -0
  9. package/dist/{chunk-XAKC2QY7.js → chunk-EY4BJKC4.js} +3 -28
  10. package/dist/chunk-EY4BJKC4.js.map +1 -0
  11. package/dist/chunk-QP4QJOEN.js +72 -0
  12. package/dist/chunk-QP4QJOEN.js.map +1 -0
  13. package/dist/css/components/activity-feed.css +240 -0
  14. package/dist/css/components/attachment.css +266 -0
  15. package/dist/css/components/breadcrumb.css +110 -0
  16. package/dist/css/components/chart-tooltip.css +168 -0
  17. package/dist/css/components/chart.css +296 -0
  18. package/dist/css/components/code-block.css +264 -0
  19. package/dist/css/components/color-area.css +82 -0
  20. package/dist/css/components/color-field.css +67 -0
  21. package/dist/css/components/color-picker.css +131 -0
  22. package/dist/css/components/color-slider.css +113 -0
  23. package/dist/css/components/command-search.css +252 -0
  24. package/dist/css/components/context-menu.css +23 -0
  25. package/dist/css/components/credit-card.css +288 -0
  26. package/dist/css/components/data-table.css +591 -0
  27. package/dist/css/components/date-range-picker.css +168 -0
  28. package/dist/css/components/date-time-picker.css +178 -0
  29. package/dist/css/components/drawer.css +58 -0
  30. package/dist/css/components/editable-table.css +201 -0
  31. package/dist/css/components/empty-state.css +61 -0
  32. package/dist/css/components/file-explorer.css +633 -0
  33. package/dist/css/components/filter-builder.css +232 -0
  34. package/dist/css/components/hold-confirm.css +87 -0
  35. package/dist/css/components/hover-card.css +13 -7
  36. package/dist/css/components/image-preview.css +195 -0
  37. package/dist/css/components/json-viewer.css +272 -0
  38. package/dist/css/components/like-button.css +78 -0
  39. package/dist/css/components/link-preview.css +275 -0
  40. package/dist/css/components/loading-button.css +77 -0
  41. package/dist/css/components/multi-select.css +267 -0
  42. package/dist/css/components/notification-bell.css +127 -0
  43. package/dist/css/components/password-strength.css +183 -0
  44. package/dist/css/components/popover.css +25 -0
  45. package/dist/css/components/progress.css +140 -0
  46. package/dist/css/components/questionnaire.css +217 -0
  47. package/dist/css/components/range-calendar.css +186 -0
  48. package/dist/css/components/range-slider.css +192 -0
  49. package/dist/css/components/resizable-panel.css +243 -0
  50. package/dist/css/components/rich-text-editor.css +284 -0
  51. package/dist/css/components/scratch-card.css +75 -0
  52. package/dist/css/components/skeleton.css +108 -0
  53. package/dist/css/components/split-view.css +19 -0
  54. package/dist/css/components/star-rating.css +80 -0
  55. package/dist/css/components/stat-card.css +164 -0
  56. package/dist/css/components/swipe-actions.css +105 -0
  57. package/dist/css/components/tag-input.css +244 -0
  58. package/dist/css/components/time-field.css +88 -0
  59. package/dist/css/components/timeline.css +219 -0
  60. package/dist/css/components/tooltip.css +79 -0
  61. package/dist/css/components/tree-view.css +210 -0
  62. package/dist/css/components/virtual-list.css +195 -0
  63. package/dist/css/tokens.css +185 -181
  64. package/dist/elements/index.d.ts +674 -5
  65. package/dist/elements/index.js +1361 -8
  66. package/dist/elements/index.js.map +1 -1
  67. package/dist/{engine-GDGwQnJE.d.ts → engine-Cn6IhE3z.d.ts} +1 -1
  68. package/dist/engine-QECQnGz1.d.ts +33 -0
  69. package/dist/index.css +9104 -189
  70. package/dist/index.d.ts +89 -5
  71. package/dist/index.js +243 -9
  72. package/dist/index.js.map +1 -1
  73. package/dist/react/index.d.ts +579 -23
  74. package/dist/react/index.js +2042 -7
  75. package/dist/react/index.js.map +1 -1
  76. package/dist/{reduced-BmBzKH94.d.ts → reduced-BBUusld6.d.ts} +9 -2
  77. package/dist/reveal-DEvp6U6T.d.ts +2381 -0
  78. package/dist/{store-DYb3tTsZ.d.ts → store-DjkTBX7f.d.ts} +2 -2
  79. package/package.json +1 -1
  80. package/registry.json +406 -1
  81. package/src/auto/auto-init.ts +98 -0
  82. package/src/components/activity-feed/README.md +56 -0
  83. package/src/components/activity-feed/activity-feed.css +239 -0
  84. package/src/components/activity-feed/element.ts +36 -0
  85. package/src/components/activity-feed/engine.ts +351 -0
  86. package/src/components/activity-feed/react.tsx +32 -0
  87. package/src/components/attachment/README.md +27 -0
  88. package/src/components/attachment/attachment.css +265 -0
  89. package/src/components/attachment/element.ts +50 -0
  90. package/src/components/attachment/engine.ts +469 -0
  91. package/src/components/attachment/react.tsx +65 -0
  92. package/src/components/breadcrumb/README.md +38 -0
  93. package/src/components/breadcrumb/breadcrumb.css +109 -0
  94. package/src/components/breadcrumb/element.ts +27 -0
  95. package/src/components/breadcrumb/engine.ts +249 -0
  96. package/src/components/breadcrumb/react.tsx +30 -0
  97. package/src/components/chart/README.md +24 -0
  98. package/src/components/chart/chart.css +295 -0
  99. package/src/components/chart/element.ts +41 -0
  100. package/src/components/chart/engine.ts +811 -0
  101. package/src/components/chart/react.tsx +50 -0
  102. package/src/components/chart-tooltip/README.md +53 -0
  103. package/src/components/chart-tooltip/chart-tooltip.css +167 -0
  104. package/src/components/chart-tooltip/element.ts +37 -0
  105. package/src/components/chart-tooltip/engine.ts +442 -0
  106. package/src/components/chart-tooltip/react.tsx +47 -0
  107. package/src/components/code-block/README.md +19 -0
  108. package/src/components/code-block/code-block.css +263 -0
  109. package/src/components/code-block/element.ts +34 -0
  110. package/src/components/code-block/engine.ts +254 -0
  111. package/src/components/code-block/highlight.ts +244 -0
  112. package/src/components/code-block/react.tsx +94 -0
  113. package/src/components/color-area/README.md +50 -0
  114. package/src/components/color-area/color-area.css +81 -0
  115. package/src/components/color-area/element.ts +26 -0
  116. package/src/components/color-area/engine.ts +246 -0
  117. package/src/components/color-area/react.tsx +42 -0
  118. package/src/components/color-field/README.md +43 -0
  119. package/src/components/color-field/color-field.css +66 -0
  120. package/src/components/color-field/element.ts +26 -0
  121. package/src/components/color-field/engine.ts +174 -0
  122. package/src/components/color-field/react.tsx +42 -0
  123. package/src/components/color-picker/README.md +45 -0
  124. package/src/components/color-picker/color-picker.css +130 -0
  125. package/src/components/color-picker/element.ts +45 -0
  126. package/src/components/color-picker/engine.ts +314 -0
  127. package/src/components/color-picker/react.tsx +47 -0
  128. package/src/components/color-slider/README.md +55 -0
  129. package/src/components/color-slider/color-slider.css +112 -0
  130. package/src/components/color-slider/element.ts +26 -0
  131. package/src/components/color-slider/engine.ts +234 -0
  132. package/src/components/color-slider/react.tsx +45 -0
  133. package/src/components/command-search/README.md +15 -0
  134. package/src/components/command-search/command-search.css +251 -0
  135. package/src/components/command-search/element.ts +36 -0
  136. package/src/components/command-search/engine.ts +356 -0
  137. package/src/components/command-search/match.ts +53 -0
  138. package/src/components/command-search/react.tsx +66 -0
  139. package/src/components/context-menu/README.md +31 -0
  140. package/src/components/context-menu/context-menu.css +22 -0
  141. package/src/components/context-menu/element.ts +40 -0
  142. package/src/components/context-menu/engine.ts +153 -0
  143. package/src/components/context-menu/react.tsx +57 -0
  144. package/src/components/credit-card/README.md +20 -0
  145. package/src/components/credit-card/card.ts +77 -0
  146. package/src/components/credit-card/credit-card.css +287 -0
  147. package/src/components/credit-card/element.ts +33 -0
  148. package/src/components/credit-card/engine.ts +410 -0
  149. package/src/components/credit-card/react.tsx +36 -0
  150. package/src/components/data-table/README.md +83 -0
  151. package/src/components/data-table/data-table.css +590 -0
  152. package/src/components/data-table/element.ts +54 -0
  153. package/src/components/data-table/engine.ts +1059 -0
  154. package/src/components/data-table/react.tsx +94 -0
  155. package/src/components/date-range-picker/README.md +51 -0
  156. package/src/components/date-range-picker/date-range-picker.css +167 -0
  157. package/src/components/date-range-picker/element.ts +52 -0
  158. package/src/components/date-range-picker/engine.ts +352 -0
  159. package/src/components/date-range-picker/react.tsx +57 -0
  160. package/src/components/date-time-picker/README.md +48 -0
  161. package/src/components/date-time-picker/date-time-picker.css +177 -0
  162. package/src/components/date-time-picker/element.ts +47 -0
  163. package/src/components/date-time-picker/engine.ts +350 -0
  164. package/src/components/date-time-picker/react.tsx +54 -0
  165. package/src/components/dialog/README.md +103 -102
  166. package/src/components/dialog/element.ts +123 -118
  167. package/src/components/dialog/engine.ts +449 -432
  168. package/src/components/drawer/README.md +28 -0
  169. package/src/components/drawer/drawer.css +57 -0
  170. package/src/components/drawer/element.ts +21 -0
  171. package/src/components/drawer/engine.ts +56 -0
  172. package/src/components/drawer/react.tsx +85 -0
  173. package/src/components/editable-table/README.md +67 -0
  174. package/src/components/editable-table/editable-table.css +200 -0
  175. package/src/components/editable-table/element.ts +45 -0
  176. package/src/components/editable-table/engine.ts +775 -0
  177. package/src/components/editable-table/react.tsx +79 -0
  178. package/src/components/empty-state/README.md +41 -0
  179. package/src/components/empty-state/element.ts +23 -0
  180. package/src/components/empty-state/empty-state.css +60 -0
  181. package/src/components/empty-state/engine.ts +232 -0
  182. package/src/components/empty-state/react.tsx +34 -0
  183. package/src/components/file-explorer/README.md +67 -0
  184. package/src/components/file-explorer/element.ts +39 -0
  185. package/src/components/file-explorer/engine.ts +1307 -0
  186. package/src/components/file-explorer/file-explorer.css +632 -0
  187. package/src/components/file-explorer/react.tsx +77 -0
  188. package/src/components/filter-builder/README.md +47 -0
  189. package/src/components/filter-builder/element.ts +34 -0
  190. package/src/components/filter-builder/engine.ts +365 -0
  191. package/src/components/filter-builder/filter-builder.css +231 -0
  192. package/src/components/filter-builder/react.tsx +36 -0
  193. package/src/components/hold-confirm/README.md +13 -0
  194. package/src/components/hold-confirm/element.ts +24 -0
  195. package/src/components/hold-confirm/engine.ts +216 -0
  196. package/src/components/hold-confirm/hold-confirm.css +86 -0
  197. package/src/components/hold-confirm/react.tsx +28 -0
  198. package/src/components/hover-card/README.md +6 -2
  199. package/src/components/hover-card/engine.ts +68 -6
  200. package/src/components/hover-card/hover-card.css +13 -7
  201. package/src/components/image-preview/README.md +16 -0
  202. package/src/components/image-preview/element.ts +26 -0
  203. package/src/components/image-preview/engine.ts +325 -0
  204. package/src/components/image-preview/image-preview.css +194 -0
  205. package/src/components/image-preview/react.tsx +43 -0
  206. package/src/components/json-viewer/README.md +56 -0
  207. package/src/components/json-viewer/element.ts +37 -0
  208. package/src/components/json-viewer/engine.ts +440 -0
  209. package/src/components/json-viewer/json-viewer.css +271 -0
  210. package/src/components/json-viewer/react.tsx +56 -0
  211. package/src/components/like-button/README.md +13 -0
  212. package/src/components/like-button/element.ts +47 -0
  213. package/src/components/like-button/engine.ts +145 -0
  214. package/src/components/like-button/like-button.css +77 -0
  215. package/src/components/like-button/react.tsx +39 -0
  216. package/src/components/link-preview/README.md +19 -0
  217. package/src/components/link-preview/element.ts +29 -0
  218. package/src/components/link-preview/engine.ts +192 -0
  219. package/src/components/link-preview/link-preview.css +274 -0
  220. package/src/components/link-preview/react.tsx +101 -0
  221. package/src/components/loading-button/README.md +45 -0
  222. package/src/components/loading-button/element.ts +34 -0
  223. package/src/components/loading-button/engine.ts +201 -0
  224. package/src/components/loading-button/loading-button.css +76 -0
  225. package/src/components/loading-button/react.tsx +43 -0
  226. package/src/components/multi-select/README.md +48 -0
  227. package/src/components/multi-select/element.ts +28 -0
  228. package/src/components/multi-select/engine.ts +294 -0
  229. package/src/components/multi-select/multi-select.css +266 -0
  230. package/src/components/multi-select/react.tsx +39 -0
  231. package/src/components/notification-bell/README.md +14 -0
  232. package/src/components/notification-bell/element.ts +48 -0
  233. package/src/components/notification-bell/engine.ts +202 -0
  234. package/src/components/notification-bell/notification-bell.css +126 -0
  235. package/src/components/notification-bell/react.tsx +57 -0
  236. package/src/components/password-strength/README.md +17 -0
  237. package/src/components/password-strength/element.ts +22 -0
  238. package/src/components/password-strength/engine.ts +212 -0
  239. package/src/components/password-strength/password-strength.css +182 -0
  240. package/src/components/password-strength/react.tsx +36 -0
  241. package/src/components/password-strength/score.ts +62 -0
  242. package/src/components/popover/README.md +44 -0
  243. package/src/components/popover/element.ts +31 -0
  244. package/src/components/popover/engine.ts +114 -0
  245. package/src/components/popover/popover.css +24 -0
  246. package/src/components/popover/react.tsx +50 -0
  247. package/src/components/progress/README.md +17 -0
  248. package/src/components/progress/element.ts +30 -0
  249. package/src/components/progress/engine.ts +173 -0
  250. package/src/components/progress/progress.css +139 -0
  251. package/src/components/progress/react.tsx +35 -0
  252. package/src/components/questionnaire/README.md +32 -0
  253. package/src/components/questionnaire/element.ts +118 -0
  254. package/src/components/questionnaire/engine.ts +255 -0
  255. package/src/components/questionnaire/questionnaire.css +216 -0
  256. package/src/components/questionnaire/react.tsx +140 -0
  257. package/src/components/range-calendar/README.md +58 -0
  258. package/src/components/range-calendar/element.ts +42 -0
  259. package/src/components/range-calendar/engine.ts +390 -0
  260. package/src/components/range-calendar/range-calendar.css +185 -0
  261. package/src/components/range-calendar/react.tsx +56 -0
  262. package/src/components/range-slider/README.md +23 -0
  263. package/src/components/range-slider/element.ts +53 -0
  264. package/src/components/range-slider/engine.ts +316 -0
  265. package/src/components/range-slider/range-slider.css +191 -0
  266. package/src/components/range-slider/react.tsx +66 -0
  267. package/src/components/resizable-panel/README.md +61 -0
  268. package/src/components/resizable-panel/element.ts +36 -0
  269. package/src/components/resizable-panel/engine.ts +551 -0
  270. package/src/components/resizable-panel/react.tsx +74 -0
  271. package/src/components/resizable-panel/resizable-panel.css +242 -0
  272. package/src/components/rich-text-editor/README.md +57 -0
  273. package/src/components/rich-text-editor/element.ts +34 -0
  274. package/src/components/rich-text-editor/engine.ts +673 -0
  275. package/src/components/rich-text-editor/react.tsx +60 -0
  276. package/src/components/rich-text-editor/rich-text-editor.css +283 -0
  277. package/src/components/scratch-card/README.md +15 -0
  278. package/src/components/scratch-card/element.ts +30 -0
  279. package/src/components/scratch-card/engine.ts +257 -0
  280. package/src/components/scratch-card/react.tsx +40 -0
  281. package/src/components/scratch-card/scratch-card.css +74 -0
  282. package/src/components/skeleton/README.md +15 -0
  283. package/src/components/skeleton/element.ts +32 -0
  284. package/src/components/skeleton/engine.ts +104 -0
  285. package/src/components/skeleton/react.tsx +41 -0
  286. package/src/components/skeleton/skeleton.css +107 -0
  287. package/src/components/split-view/README.md +50 -0
  288. package/src/components/split-view/element.ts +34 -0
  289. package/src/components/split-view/engine.ts +114 -0
  290. package/src/components/split-view/react.tsx +63 -0
  291. package/src/components/split-view/split-view.css +18 -0
  292. package/src/components/star-rating/README.md +14 -0
  293. package/src/components/star-rating/element.ts +26 -0
  294. package/src/components/star-rating/engine.ts +217 -0
  295. package/src/components/star-rating/react.tsx +33 -0
  296. package/src/components/star-rating/star-rating.css +79 -0
  297. package/src/components/stat-card/README.md +42 -0
  298. package/src/components/stat-card/element.ts +40 -0
  299. package/src/components/stat-card/engine.ts +283 -0
  300. package/src/components/stat-card/react.tsx +36 -0
  301. package/src/components/stat-card/stat-card.css +163 -0
  302. package/src/components/swipe-actions/README.md +19 -0
  303. package/src/components/swipe-actions/element.ts +26 -0
  304. package/src/components/swipe-actions/engine.ts +260 -0
  305. package/src/components/swipe-actions/react.tsx +80 -0
  306. package/src/components/swipe-actions/swipe-actions.css +104 -0
  307. package/src/components/tag-input/README.md +42 -0
  308. package/src/components/tag-input/element.ts +39 -0
  309. package/src/components/tag-input/engine.ts +313 -0
  310. package/src/components/tag-input/react.tsx +54 -0
  311. package/src/components/tag-input/tag-input.css +243 -0
  312. package/src/components/time-field/README.md +54 -0
  313. package/src/components/time-field/element.ts +38 -0
  314. package/src/components/time-field/engine.ts +110 -0
  315. package/src/components/time-field/react.tsx +45 -0
  316. package/src/components/time-field/time-field.css +87 -0
  317. package/src/components/timeline/README.md +57 -0
  318. package/src/components/timeline/element.ts +31 -0
  319. package/src/components/timeline/engine.ts +300 -0
  320. package/src/components/timeline/react.tsx +59 -0
  321. package/src/components/timeline/timeline.css +218 -0
  322. package/src/components/tooltip/README.md +46 -0
  323. package/src/components/tooltip/element.ts +22 -0
  324. package/src/components/tooltip/engine.ts +288 -0
  325. package/src/components/tooltip/react.tsx +30 -0
  326. package/src/components/tooltip/tooltip.css +78 -0
  327. package/src/components/tree-view/README.md +61 -0
  328. package/src/components/tree-view/element.ts +32 -0
  329. package/src/components/tree-view/engine.ts +569 -0
  330. package/src/components/tree-view/react.tsx +78 -0
  331. package/src/components/tree-view/tree-view.css +209 -0
  332. package/src/components/virtual-list/README.md +81 -0
  333. package/src/components/virtual-list/element.ts +42 -0
  334. package/src/components/virtual-list/engine.ts +678 -0
  335. package/src/components/virtual-list/react.tsx +101 -0
  336. package/src/components/virtual-list/virtual-list.css +194 -0
  337. package/src/core/chips.ts +196 -0
  338. package/src/core/color.ts +304 -0
  339. package/src/core/date-range.ts +67 -0
  340. package/src/core/dates.ts +11 -1
  341. package/src/core/files.ts +107 -0
  342. package/src/core/filter.ts +229 -0
  343. package/src/core/gsap.ts +80 -65
  344. package/src/core/listbox.ts +332 -0
  345. package/src/core/mark.ts +29 -0
  346. package/src/core/position.ts +89 -0
  347. package/src/core/relative-time.ts +77 -0
  348. package/src/core/segment-field.ts +373 -0
  349. package/src/core/segments.ts +153 -0
  350. package/src/core/tree.ts +116 -0
  351. package/src/core/virtual.ts +79 -0
  352. package/src/css/tokens.css +186 -182
  353. package/src/elements/index.ts +147 -0
  354. package/src/index.ts +72 -0
  355. package/src/motion/micro.ts +248 -0
  356. package/src/motion/sheet.ts +88 -65
  357. package/src/motion/surface.ts +9 -0
  358. package/src/react/index.ts +57 -0
  359. package/dist/chunk-IKQ5FODN.js.map +0 -1
  360. package/dist/chunk-J6OCFNGU.js +0 -177
  361. package/dist/chunk-J6OCFNGU.js.map +0 -1
  362. package/dist/chunk-TFNRIZHU.js +0 -3977
  363. package/dist/chunk-TFNRIZHU.js.map +0 -1
  364. package/dist/chunk-XAKC2QY7.js.map +0 -1
  365. package/dist/reveal-jsX-uwUz.d.ts +0 -625
@@ -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
+ }
@@ -0,0 +1,191 @@
1
+ /* range-slider — Melonmind line: a neutral track, the accent for the chosen span, white thumbs with a soft
2
+ shadow and a glass value bubble. The native thumbs stay as transparent hit areas; the engine draws and moves the rest. */
3
+ .mk-range-slider,
4
+ mk-range-slider {
5
+ --mk-range-thumb: 1.5rem;
6
+ --mk-range-height: 8px;
7
+ --mk-range-track: var(--mk-surface-4);
8
+ --mk-range-fill: var(--mk-accent);
9
+ display: grid;
10
+ grid-template-columns: minmax(0, 1fr) auto;
11
+ align-items: center;
12
+ gap: 1rem;
13
+ color: var(--mk-text);
14
+ font-family: var(--mk-font);
15
+ }
16
+
17
+ :is(.mk-range-slider, mk-range-slider) [data-mk-range-track] {
18
+ position: relative;
19
+ display: grid;
20
+ align-items: center;
21
+ min-width: 0;
22
+ height: 2.75rem;
23
+ }
24
+
25
+ :is(.mk-range-slider, mk-range-slider) [data-mk-range-visual] {
26
+ position: absolute;
27
+ inset: 0;
28
+ pointer-events: none;
29
+ }
30
+
31
+ /* Track */
32
+ :is(.mk-range-slider, mk-range-slider) [data-mk-range-visual]::before {
33
+ content: '';
34
+ position: absolute;
35
+ top: 50%;
36
+ inset-inline: 0;
37
+ height: var(--mk-range-height);
38
+ border-radius: var(--mk-radius-full);
39
+ background: var(--mk-range-track);
40
+ translate: 0 -50%;
41
+ }
42
+
43
+ /* The span between the thumb centres. */
44
+ :is(.mk-range-slider, mk-range-slider) [data-mk-part='fill'] {
45
+ position: absolute;
46
+ top: 50%;
47
+ left: calc((100% - var(--mk-range-thumb)) * var(--mk-range-start, 0) + var(--mk-range-thumb) / 2);
48
+ right: calc(100% - ((100% - var(--mk-range-thumb)) * var(--mk-range-end, 0) + var(--mk-range-thumb) / 2));
49
+ height: var(--mk-range-height);
50
+ border-radius: var(--mk-radius-full);
51
+ background: var(--mk-range-fill);
52
+ translate: 0 -50%;
53
+ }
54
+
55
+ :is(.mk-range-slider, mk-range-slider)[data-mk-range='single'] [data-mk-part='fill'] {
56
+ left: 0;
57
+ }
58
+
59
+ :is(.mk-range-slider, mk-range-slider) [data-mk-part='thumb'] {
60
+ position: absolute;
61
+ top: 50%;
62
+ left: calc((100% - var(--mk-range-thumb)) * var(--mk-range-start, 0) + var(--mk-range-thumb) / 2);
63
+ width: var(--mk-range-thumb);
64
+ height: var(--mk-range-thumb);
65
+ margin: calc(var(--mk-range-thumb) / -2) 0 0 calc(var(--mk-range-thumb) / -2);
66
+ border-radius: 50%;
67
+ background: var(--mk-surface-1);
68
+ box-shadow:
69
+ 0 0 0 1px color-mix(in srgb, var(--mk-text) 8%, transparent),
70
+ 0 2px 8px color-mix(in srgb, var(--mk-shadow-color) 22%, transparent);
71
+ }
72
+
73
+ /* Single value: its one thumb sits at the end of the fill. */
74
+ :is(.mk-range-slider, mk-range-slider) [data-mk-part='thumb']:last-child {
75
+ left: calc((100% - var(--mk-range-thumb)) * var(--mk-range-end, 0) + var(--mk-range-thumb) / 2);
76
+ }
77
+
78
+ :is(.mk-range-slider, mk-range-slider) [data-mk-part='thumb']::after {
79
+ content: '';
80
+ position: absolute;
81
+ inset: 30%;
82
+ border-radius: 50%;
83
+ background: var(--mk-range-fill);
84
+ }
85
+
86
+ :is(.mk-range-slider, mk-range-slider) [data-mk-part='thumb'][data-mk-focus] {
87
+ box-shadow:
88
+ 0 0 0 4px color-mix(in srgb, var(--mk-accent) 35%, transparent),
89
+ 0 2px 8px color-mix(in srgb, var(--mk-shadow-color) 22%, transparent);
90
+ }
91
+
92
+ :is(.mk-range-slider, mk-range-slider) [data-mk-part='bubble'] {
93
+ position: absolute;
94
+ bottom: calc(100% + 10px);
95
+ left: 50%;
96
+ min-width: 2.5rem;
97
+ padding: 0.25rem 0.6rem;
98
+ width: max-content;
99
+ translate: -50% 0;
100
+ border-radius: var(--mk-radius-full);
101
+ background: var(--mk-glass-inverse);
102
+ box-shadow: var(--mk-glass-bevel);
103
+ backdrop-filter: blur(16px);
104
+ color: var(--mk-on-glass-inverse);
105
+ font-size: 0.8125rem;
106
+ font-weight: 500;
107
+ font-variant-numeric: tabular-nums;
108
+ text-align: center;
109
+ white-space: nowrap;
110
+ opacity: 0;
111
+ }
112
+
113
+ /* The native inputs: transparent, the same size, on top for pointer and keyboard. */
114
+ :is(.mk-range-slider, mk-range-slider) [data-mk-range-track] input[type='range'] {
115
+ position: absolute;
116
+ inset: 0;
117
+ z-index: 1;
118
+ width: 100%;
119
+ height: 100%;
120
+ margin: 0;
121
+ background: transparent;
122
+ appearance: none;
123
+ -webkit-appearance: none;
124
+ cursor: grab;
125
+ }
126
+
127
+ :is(.mk-range-slider, mk-range-slider) [data-mk-range-track] input[type='range']:active {
128
+ cursor: grabbing;
129
+ }
130
+
131
+ :is(.mk-range-slider, mk-range-slider)[data-mk-range='interval'] [data-mk-range-track] input[type='range'] {
132
+ pointer-events: none;
133
+ }
134
+
135
+ :is(.mk-range-slider, mk-range-slider) input[type='range']::-webkit-slider-runnable-track {
136
+ height: 100%;
137
+ background: transparent;
138
+ }
139
+
140
+ :is(.mk-range-slider, mk-range-slider) input[type='range']::-moz-range-track {
141
+ background: transparent;
142
+ }
143
+
144
+ :is(.mk-range-slider, mk-range-slider) input[type='range']::-webkit-slider-thumb {
145
+ -webkit-appearance: none;
146
+ appearance: none;
147
+ width: var(--mk-range-thumb);
148
+ height: 2.75rem;
149
+ border: 0;
150
+ background: transparent;
151
+ pointer-events: auto;
152
+ }
153
+
154
+ :is(.mk-range-slider, mk-range-slider) input[type='range']::-moz-range-thumb {
155
+ width: var(--mk-range-thumb);
156
+ height: 2.75rem;
157
+ border: 0;
158
+ border-radius: 0;
159
+ background: transparent;
160
+ pointer-events: auto;
161
+ }
162
+
163
+ :is(.mk-range-slider, mk-range-slider) input[type='range']:focus-visible {
164
+ outline: none;
165
+ }
166
+
167
+ :is(.mk-range-slider, mk-range-slider) input[type='range']:disabled {
168
+ cursor: not-allowed;
169
+ }
170
+
171
+ :is(.mk-range-slider, mk-range-slider):has(input:disabled) [data-mk-range-visual] {
172
+ opacity: 0.45;
173
+ }
174
+
175
+ :is(.mk-range-slider, mk-range-slider) [data-mk-range-output] {
176
+ min-width: 4ch;
177
+ padding: 0.4rem 0.85rem;
178
+ border-radius: var(--mk-radius-full);
179
+ background: var(--mk-surface-3);
180
+ font-size: 0.875rem;
181
+ font-weight: 500;
182
+ font-variant-numeric: tabular-nums;
183
+ text-align: center;
184
+ white-space: nowrap;
185
+ }
186
+
187
+ @media (prefers-reduced-motion: reduce) {
188
+ :is(.mk-range-slider, mk-range-slider) [data-mk-part='thumb'][data-mk-active] [data-mk-part='bubble'] {
189
+ opacity: 1;
190
+ }
191
+ }
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createRangeSlider, type RangeSliderInstance, type RangeSliderOptions, type RangeValue } from './engine';
5
+
6
+ export interface RangeSliderProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'onInput' | 'defaultValue'>, RangeSliderOptions {
7
+ /** Uncontrolled start; an array makes an interval. */
8
+ defaultValue?: RangeValue;
9
+ /** Show the formatted value beside the track (default true). */
10
+ showOutput?: boolean;
11
+ }
12
+
13
+ /**
14
+ * <RangeSlider min={0} max={100} defaultValue={[20, 80]} label="Precio" onChange={setRange} />.
15
+ * Controlled with `value`; otherwise the edited value survives rerenders and new bounds.
16
+ */
17
+ export function RangeSlider({ min, max, step, value, defaultValue, disabled, name, label, formatValue, onInput, onChange, showOutput = true, className, ...props }: RangeSliderProps) {
18
+ const ref = useRef<HTMLDivElement>(null);
19
+ const instance = useRef<RangeSliderInstance | null>(null);
20
+ const edited = useRef<RangeValue | undefined>(undefined);
21
+ const input = useLatest(onInput);
22
+ const change = useLatest(onChange);
23
+ const format = useLatest(formatValue);
24
+ const formatted = formatValue !== undefined;
25
+ const start = value ?? defaultValue;
26
+ const count = Array.isArray(start) ? 2 : 1;
27
+ const current = useLatest(value);
28
+ useIsomorphicLayoutEffect(() => {
29
+ const created = createRangeSlider(ref.current, {
30
+ min,
31
+ max,
32
+ step,
33
+ disabled,
34
+ name,
35
+ label,
36
+ value: current.current ?? edited.current ?? start,
37
+ formatValue: formatted ? (n) => format.current?.(n) ?? String(n) : undefined,
38
+ onInput: (next) => input.current?.(next),
39
+ onChange: (next) => change.current?.(next),
40
+ });
41
+ instance.current = created;
42
+ return () => {
43
+ edited.current = created.value;
44
+ created.destroy();
45
+ instance.current = null;
46
+ };
47
+ // `start` only seeds the first creation; edits and `value` take over after it.
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, [min, max, step, disabled, name, label, formatted, count]);
50
+ const valueKey = JSON.stringify(value);
51
+ useIsomorphicLayoutEffect(() => {
52
+ if (value !== undefined) instance.current?.setValue(value, false);
53
+ // eslint-disable-next-line react-hooks/exhaustive-deps
54
+ }, [valueKey]);
55
+ const initial = Array.isArray(start) ? start : [start ?? min ?? 0];
56
+ return (
57
+ <div {...props} ref={ref} className={['mk-range-slider', className].filter(Boolean).join(' ')}>
58
+ <div data-mk-range-track="">
59
+ {initial.map((n, i) => (
60
+ <input key={i} type="range" data-mk-range-input="" defaultValue={n} />
61
+ ))}
62
+ </div>
63
+ {showOutput && <output data-mk-range-output="" />}
64
+ </div>
65
+ );
66
+ }