@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,173 @@
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 { burst, rollDigits } from '../../motion/micro';
6
+
7
+ /*
8
+ * Progress: a bar or a ring (Melon Mind's MetricsRing: radius (size - stroke) / 2,
9
+ * dasharray from the ratio, starting at 12 o'clock). The root is the progressbar;
10
+ * the drawing is one decorative [data-mk-progress-visual] child. ARIA follows a
11
+ * new value at once; the drawing glides to it with GSAP (Melonmind's playful
12
+ * swing, .38,.49,0,1.5): going up, a light runs at the head and the figure rolls
13
+ * up; going down, the figure rolls down. Reaching the maximum fires a small burst.
14
+ * null is indeterminate. Nothing is live: assistive tech reads the value on arrival.
15
+ */
16
+
17
+ export type ProgressVariant = 'linear' | 'ring';
18
+
19
+ export interface ProgressOptions {
20
+ /** Current value; null is indeterminate. Default: min. */
21
+ value?: number | null;
22
+ min?: number;
23
+ max?: number;
24
+ label?: string;
25
+ variant?: ProgressVariant;
26
+ /** Write the percentage beside the bar (inside the ring). */
27
+ showValue?: boolean;
28
+ /** Text for the value (also aria-valuetext). Default: rounded percentage. */
29
+ formatValue?: (value: number, ratio: number) => string;
30
+ /** Glide duration in seconds (default .7). */
31
+ duration?: number;
32
+ }
33
+
34
+ export interface ProgressInstance extends Instance {
35
+ setValue(value: number | null): void;
36
+ readonly value: number | null;
37
+ }
38
+
39
+ const SVG = 'http://www.w3.org/2000/svg';
40
+ const RING_SIZE = 48;
41
+ const RING_STROKE = 5;
42
+ const RADIUS = (RING_SIZE - RING_STROKE) / 2;
43
+ const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
44
+
45
+ const finite = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value);
46
+
47
+ export function createProgress(root: HTMLElement | null, options: ProgressOptions = {}): ProgressInstance {
48
+ if (!root) return inert({ setValue() {}, value: null });
49
+ registerMotion();
50
+ const scope = createScope();
51
+ const min = finite(options.min) ? options.min : 0;
52
+ const max = finite(options.max) && options.max > min ? options.max : Math.max(min + 1, 100);
53
+ const variant: ProgressVariant = options.variant === 'ring' ? 'ring' : 'linear';
54
+ const duration = finite(options.duration) ? Math.max(0, options.duration) : 0.7;
55
+ const clamp = (n: number) => Math.min(max, Math.max(min, n));
56
+ let value: number | null = options.value === null ? null : clamp(finite(options.value) ? options.value : min);
57
+
58
+ const visual = document.createElement('span');
59
+ visual.dataset.mkProgressVisual = '';
60
+ visual.setAttribute('aria-hidden', 'true');
61
+ let arc: SVGCircleElement | null = null;
62
+ let head: HTMLElement | null = null;
63
+ let ring: SVGSVGElement | null = null;
64
+ if (variant === 'ring') {
65
+ ring = document.createElementNS(SVG, 'svg');
66
+ ring.setAttribute('viewBox', `0 0 ${RING_SIZE} ${RING_SIZE}`);
67
+ ring.dataset.mkPart = 'ring';
68
+ const circle = (part: string) => {
69
+ const element = document.createElementNS(SVG, 'circle');
70
+ element.setAttribute('cx', String(RING_SIZE / 2));
71
+ element.setAttribute('cy', String(RING_SIZE / 2));
72
+ element.setAttribute('r', String(RADIUS));
73
+ element.setAttribute('fill', 'none');
74
+ element.setAttribute('stroke-width', String(RING_STROKE));
75
+ element.dataset.mkPart = part;
76
+ ring!.append(element);
77
+ return element;
78
+ };
79
+ circle('track');
80
+ arc = circle('arc');
81
+ arc.setAttribute('stroke-linecap', 'round');
82
+ visual.append(ring);
83
+ } else {
84
+ const track = document.createElement('span');
85
+ track.dataset.mkPart = 'track';
86
+ const bar = document.createElement('span');
87
+ bar.dataset.mkPart = 'bar';
88
+ head = document.createElement('span');
89
+ head.dataset.mkPart = 'head';
90
+ track.append(bar, head);
91
+ visual.append(track);
92
+ }
93
+ const text = options.showValue ? document.createElement('span') : null;
94
+ if (text) {
95
+ text.dataset.mkPart = 'value';
96
+ visual.append(text);
97
+ }
98
+ root.append(visual);
99
+
100
+ scope.attr(root, 'style', root.getAttribute('style'));
101
+ scope.attr(root, 'role', 'progressbar');
102
+ scope.attr(root, 'aria-valuemin', String(min));
103
+ scope.attr(root, 'aria-valuemax', String(max));
104
+ if (options.label) scope.attr(root, 'aria-label', options.label);
105
+ scope.attr(root, 'data-mk-variant', variant);
106
+ scope.attr(root, 'data-mk-ready', '');
107
+
108
+ const ratioOf = (n: number | null) => (n === null ? 0 : (n - min) / (max - min));
109
+ const labelOf = (n: number | null) => (n === null ? '' : options.formatValue ? options.formatValue(n, ratioOf(n)) : `${Math.round(ratioOf(n) * 100)} %`);
110
+ /** What is drawn now (it trails `value` while gliding). */
111
+ const shown = { ratio: ratioOf(value) };
112
+ const draw = () => {
113
+ const ratio = Math.min(1, Math.max(0, shown.ratio));
114
+ root.style.setProperty('--mk-progress', ratio.toFixed(4));
115
+ if (arc) {
116
+ const length = value === null ? CIRCUMFERENCE * 0.25 : CIRCUMFERENCE * ratio;
117
+ arc.setAttribute('stroke-dasharray', `${length.toFixed(2)} ${CIRCUMFERENCE.toFixed(2)}`);
118
+ }
119
+ };
120
+ let glide: gsap.core.Tween | null = null;
121
+ const motion: gsap.core.Animation[] = [];
122
+ scope.add(() => {
123
+ glide?.kill();
124
+ for (const animation of motion.splice(0)) animation.progress(1).kill();
125
+ visual.remove();
126
+ });
127
+
128
+ const render = (previous: number | null, animate: boolean) => {
129
+ const label = labelOf(value);
130
+ scope.attr(root, 'aria-valuenow', value === null ? null : String(value));
131
+ scope.attr(root, 'aria-valuetext', value !== null && options.formatValue ? label : null);
132
+ scope.attr(root, 'data-mk-indeterminate', value === null ? '' : null);
133
+ scope.attr(root, 'data-mk-complete', value !== null && value >= max ? '' : null);
134
+ const target = ratioOf(value);
135
+ const direction: 1 | -1 = previous === null || value === null || value >= previous ? 1 : -1;
136
+ glide?.kill();
137
+ if (!animate || reduced() || value === null || previous === null || duration === 0) {
138
+ shown.ratio = target;
139
+ draw();
140
+ if (text) text.textContent = label;
141
+ return;
142
+ }
143
+ glide = gsap.to(shown, { ratio: target, duration, ease: EASE.swatch, onUpdate: draw });
144
+ if (text) {
145
+ const roll = rollDigits(text, label, direction);
146
+ if (roll) motion.push(roll);
147
+ }
148
+ // Going up: a light runs at the head of the bar; the ring breathes.
149
+ if (direction > 0 && head) motion.push(gsap.fromTo(head, { opacity: 1, scale: 0.4 }, { opacity: 0, scale: 1.6, duration: 0.6, ease: 'power2.out' }));
150
+ if (ring) motion.push(gsap.fromTo(ring, { scale: direction > 0 ? 1.06 : 0.95 }, { scale: 1, duration: 0.5, ease: EASE.click, clearProps: 'scale,transform' }));
151
+ if (value >= max && previous < max) {
152
+ const done = burst(variant === 'ring' ? visual : (text ?? visual), { count: 6, distance: 22, ring: variant === 'ring' });
153
+ if (done) motion.push(done);
154
+ }
155
+ if (motion.length > 12) motion.splice(0, motion.length - 12);
156
+ };
157
+ render(null, false);
158
+
159
+ return {
160
+ setValue(next) {
161
+ if (scope.disposed) return;
162
+ const previous = value;
163
+ if (next === null) value = null;
164
+ else if (finite(next)) value = clamp(next);
165
+ else return;
166
+ render(previous, true);
167
+ },
168
+ get value() {
169
+ return value;
170
+ },
171
+ destroy: () => scope.dispose(),
172
+ };
173
+ }
@@ -0,0 +1,139 @@
1
+ /* progress — Melonmind line: a neutral track, the accent only for the value; a ring (MetricsRing) with the figure inside.
2
+ The fill is drawn from --mk-progress, which the engine glides with GSAP. */
3
+ .mk-progress,
4
+ mk-progress {
5
+ --mk-progress-height: 10px;
6
+ --mk-progress-size: 4.5rem;
7
+ --mk-progress-track: var(--mk-surface-4);
8
+ --mk-progress-fill: var(--mk-accent);
9
+ display: block;
10
+ color: var(--mk-text);
11
+ font-family: var(--mk-font);
12
+ }
13
+
14
+ :is(.mk-progress, mk-progress) [data-mk-progress-visual] {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 0.875rem;
18
+ }
19
+
20
+ /* Linear */
21
+ :is(.mk-progress, mk-progress) span[data-mk-part='track'] {
22
+ position: relative;
23
+ flex: 1;
24
+ height: var(--mk-progress-height);
25
+ border-radius: var(--mk-radius-full);
26
+ background: var(--mk-progress-track);
27
+ }
28
+
29
+ :is(.mk-progress, mk-progress) [data-mk-part='bar'] {
30
+ position: absolute;
31
+ inset: 0;
32
+ border-radius: inherit;
33
+ background: var(--mk-progress-fill);
34
+ clip-path: inset(0 calc((1 - var(--mk-progress, 0)) * 100%) 0 0 round var(--mk-radius-full));
35
+ }
36
+
37
+ /* The light at the head of the bar (flashes when the value goes up). */
38
+ :is(.mk-progress, mk-progress) [data-mk-part='head'] {
39
+ position: absolute;
40
+ top: 50%;
41
+ left: calc(var(--mk-progress, 0) * 100%);
42
+ width: calc(var(--mk-progress-height) * 2.4);
43
+ aspect-ratio: 1;
44
+ border-radius: 50%;
45
+ background: radial-gradient(circle, color-mix(in srgb, var(--mk-progress-fill) 70%, white) 0%, transparent 70%);
46
+ opacity: 0;
47
+ pointer-events: none;
48
+ translate: -50% -50%;
49
+ }
50
+
51
+ :is(.mk-progress, mk-progress)[data-mk-indeterminate] [data-mk-part='bar'] {
52
+ clip-path: none;
53
+ width: 38%;
54
+ animation: mk-progress-slide 1.4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
55
+ }
56
+
57
+ :is(.mk-progress, mk-progress) [data-mk-part='value'] {
58
+ min-width: 4.5ch;
59
+ color: var(--mk-muted);
60
+ font-size: 0.875rem;
61
+ font-weight: 500;
62
+ font-variant-numeric: tabular-nums;
63
+ text-align: right;
64
+ }
65
+
66
+ :is(.mk-progress, mk-progress)[data-mk-complete] [data-mk-part='value'] {
67
+ color: var(--mk-text);
68
+ }
69
+
70
+ /* Ring */
71
+ :is(.mk-progress, mk-progress)[data-mk-variant='ring'] {
72
+ display: inline-block;
73
+ width: var(--mk-progress-size);
74
+ }
75
+
76
+ :is(.mk-progress, mk-progress)[data-mk-variant='ring'] [data-mk-progress-visual] {
77
+ position: relative;
78
+ display: grid;
79
+ place-items: center;
80
+ aspect-ratio: 1;
81
+ }
82
+
83
+ :is(.mk-progress, mk-progress) [data-mk-part='ring'] {
84
+ grid-area: 1 / 1;
85
+ width: 100%;
86
+ height: 100%;
87
+ overflow: visible;
88
+ rotate: -90deg;
89
+ }
90
+
91
+ :is(.mk-progress, mk-progress) circle[data-mk-part='track'] {
92
+ stroke: var(--mk-progress-track);
93
+ }
94
+
95
+ :is(.mk-progress, mk-progress) [data-mk-part='arc'] {
96
+ stroke: var(--mk-progress-fill);
97
+ }
98
+
99
+ :is(.mk-progress, mk-progress)[data-mk-variant='ring'][data-mk-indeterminate] [data-mk-part='ring'] {
100
+ animation: mk-progress-spin 1.1s linear infinite;
101
+ }
102
+
103
+ :is(.mk-progress, mk-progress)[data-mk-variant='ring'] [data-mk-part='value'] {
104
+ grid-area: 1 / 1;
105
+ min-width: 0;
106
+ color: var(--mk-text);
107
+ font-size: calc(var(--mk-progress-size) * 0.24);
108
+ letter-spacing: -0.02em;
109
+ text-align: center;
110
+ }
111
+
112
+ @keyframes mk-progress-slide {
113
+ from {
114
+ translate: -100% 0;
115
+ }
116
+ to {
117
+ translate: 265% 0;
118
+ }
119
+ }
120
+
121
+ @keyframes mk-progress-spin {
122
+ from {
123
+ rotate: -90deg;
124
+ }
125
+ to {
126
+ rotate: 270deg;
127
+ }
128
+ }
129
+
130
+ /* Reduced motion: values jump; indeterminate becomes a still, partial fill. */
131
+ @media (prefers-reduced-motion: reduce) {
132
+ :is(.mk-progress, mk-progress) :is([data-mk-part='bar'], [data-mk-part='ring']) {
133
+ animation: none !important;
134
+ }
135
+
136
+ :is(.mk-progress, mk-progress)[data-mk-indeterminate] [data-mk-part='bar'] {
137
+ opacity: 0.55;
138
+ }
139
+ }
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createProgress, type ProgressInstance, type ProgressOptions } from './engine';
5
+
6
+ export interface ProgressProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, ProgressOptions {}
7
+
8
+ /** <Progress value={42} label="Subida" showValue />; value={null} is indeterminate. The engine owns the drawing and ARIA. */
9
+ export function Progress({ value, min, max, label, variant, showValue, formatValue, className, ...props }: ProgressProps) {
10
+ const ref = useRef<HTMLDivElement>(null);
11
+ const instance = useRef<ProgressInstance | null>(null);
12
+ const current = useLatest(value);
13
+ const format = useLatest(formatValue);
14
+ const formatted = formatValue !== undefined;
15
+ useIsomorphicLayoutEffect(() => {
16
+ const created = createProgress(ref.current, {
17
+ value: current.current,
18
+ min,
19
+ max,
20
+ label,
21
+ variant,
22
+ showValue,
23
+ formatValue: formatted ? (n, ratio) => format.current?.(n, ratio) ?? '' : undefined,
24
+ });
25
+ instance.current = created;
26
+ return () => {
27
+ created.destroy();
28
+ instance.current = null;
29
+ };
30
+ }, [min, max, label, variant, showValue, formatted]);
31
+ useIsomorphicLayoutEffect(() => {
32
+ instance.current?.setValue(value === undefined ? (min ?? 0) : value);
33
+ }, [value]);
34
+ return <div {...props} ref={ref} className={['mk-progress', className].filter(Boolean).join(' ')} />;
35
+ }
@@ -0,0 +1,32 @@
1
+ # questionnaire
2
+
3
+ Preguntas por pasos sobre un formulario nativo: texto, respuesta única y múltiple, anterior/siguiente, progreso, validación de obligatorios y finalización. Compone el motor de [steps](../steps/README.md) (entrada/salida en flujo, contador y foco en el título); la persistencia es de la app.
4
+
5
+ ```html
6
+ <mk-questionnaire>
7
+ <form class="mk-questionnaire">
8
+ <section data-mk-question>
9
+ <h3>¿Cómo te llamas?</h3>
10
+ <input name="name" required aria-label="Nombre" />
11
+ <button type="button" data-mk-question-next>Siguiente</button>
12
+ </section>
13
+ <section data-mk-question>
14
+ <h3>¿Qué te interesa?</h3>
15
+ <label><input type="checkbox" name="topics" value="diseño" required /> Diseño</label>
16
+ <label><input type="checkbox" name="topics" value="código" required /> Código</label>
17
+ <button type="button" data-mk-question-prev>Anterior</button>
18
+ <button type="submit">Finalizar</button>
19
+ </section>
20
+ <p data-mk-question-error role="alert"></p>
21
+ <div data-mk-question-complete hidden>¡Gracias! <button type="reset">Empezar de nuevo</button></div>
22
+ </form>
23
+ </mk-questionnaire>
24
+ ```
25
+
26
+ Vacío, `<mk-questionnaire questions='[{"id":"name","label":"Nombre","type":"text","required":true}]'>` crea el mismo marcado. React: `<Questionnaire questions={questions} onComplete={save} complete={(answers) => <Summary answers={answers} />} />` con `Question {id, label, type: 'text'|'textarea'|'single'|'multiple', options?, required?, description?, placeholder?}` y `defaultAnswers`.
27
+
28
+ Opciones: `index`, `labels {required, choice}`, `onChange(index, answers)`, `onComplete(answers)`. API: `next(): Promise<boolean>`, `prev()`, `reset()`, `answers`, `index`, `count`, `complete`, `destroy()`. `readAnswers(form)` devuelve `QuestionnaireAnswers` (`string` o `string[]` por nombre de campo: casillas y selects múltiples dan arrays).
29
+
30
+ Siguiente valida el paso actual: los campos obligatorios vacíos o inválidos y los grupos de casillas/radios obligatorios (basta uno marcado por grupo). Marca `aria-invalid`, escribe el mensaje en `[data-mk-question-error]`, enfoca el primer campo inválido y emite `mk:invalid`. Un campo corregido deja de estar inválido al momento. Los botones `[data-mk-question-next|prev]` son propios: nunca saltan la validación. Enviar en el último paso completa una sola vez (`mk:complete` con `{answers}`), muestra `[data-mk-question-complete]` y evita la navegación. El reset nativo limpia respuestas, errores y finalización y vuelve al primer paso. Cada cambio de paso emite `mk:question` `{index, previous, answers}`.
31
+
32
+ Las respuestas viven en los controles nativos, así que se conservan al volver atrás y entre renders de React. `destroy()` restaura atributos, textos y visibilidad. Variables: `--mk-question-radius` (24px), `--mk-question-option-radius` (16px); `--mk-question-progress` lo escribe el motor. Movimiento reducido: los pasos cambian sin flujo y la barra sin transición.
@@ -0,0 +1,118 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import type { Instance } from '../../core/types';
3
+ import { createQuestionnaire, type QuestionnaireInstance } from './engine';
4
+
5
+ interface QuestionData {
6
+ id: string;
7
+ label: string;
8
+ type: 'text' | 'textarea' | 'single' | 'multiple';
9
+ options?: Array<string | { value: string; label: string }>;
10
+ required?: boolean;
11
+ description?: string;
12
+ }
13
+
14
+ const el = <K extends keyof HTMLElementTagNameMap>(tag: K, attrs: Record<string, string> = {}, text?: string) => {
15
+ const element = document.createElement(tag);
16
+ for (const [name, value] of Object.entries(attrs)) element.setAttribute(name, value);
17
+ if (text !== undefined) element.textContent = text;
18
+ return element;
19
+ };
20
+
21
+ /** The same form the React <Questionnaire> renders, built with DOM APIs (all text as text). */
22
+ function buildForm(questions: QuestionData[]) {
23
+ const form = el('form', { class: 'mk-questionnaire', novalidate: '' });
24
+ const progress = el('div', { 'data-mk-question-progress': '', 'aria-hidden': 'true' });
25
+ progress.append(el('span', { 'data-mk-question-bar': '' }));
26
+ form.append(progress);
27
+ questions.forEach((question, i) => {
28
+ const titleId = `${question.id}-title`;
29
+ const section = el('section', { 'data-mk-question': '', 'aria-labelledby': titleId });
30
+ if (i) section.hidden = true;
31
+ section.append(el('p', { 'data-mk-question-step': '' }, `Pregunta ${i + 1} de ${questions.length}`));
32
+ const title = el('h3', { id: titleId, 'data-mk-step-title': '', 'data-mk-flow-item': '' }, question.label);
33
+ if (question.required) title.append(el('span', { 'data-mk-question-required': '', 'aria-hidden': 'true' }, ' *'));
34
+ section.append(title);
35
+ if (question.description) section.append(el('p', { 'data-mk-question-description': '', 'data-mk-flow-item': '' }, question.description));
36
+ const field = el('div', { 'data-mk-question-field': '', 'data-mk-flow-item': '' });
37
+ if (question.type === 'text' || question.type === 'textarea') {
38
+ const input = question.type === 'text' ? el('input', { type: 'text' }) : el('textarea', { rows: '4' });
39
+ input.name = question.id;
40
+ input.required = Boolean(question.required);
41
+ input.setAttribute('aria-labelledby', titleId);
42
+ field.append(input);
43
+ } else {
44
+ const group = el('div', { role: question.type === 'single' ? 'radiogroup' : 'group', 'aria-labelledby': titleId, 'data-mk-question-options': '' });
45
+ for (const raw of question.options ?? []) {
46
+ const option = typeof raw === 'string' ? { value: raw, label: raw } : raw;
47
+ const label = el('label', { 'data-mk-question-option': '' });
48
+ const input = el('input', { type: question.type === 'single' ? 'radio' : 'checkbox' });
49
+ input.name = question.id;
50
+ input.value = option.value;
51
+ input.required = Boolean(question.required);
52
+ label.append(input, el('span', {}, option.label));
53
+ group.append(label);
54
+ }
55
+ field.append(group);
56
+ }
57
+ section.append(field);
58
+ const actions = el('div', { 'data-mk-question-actions': '', 'data-mk-flow-item': '' });
59
+ if (i) actions.append(el('button', { type: 'button', class: 'mk-button mk-button--text', 'data-mk-question-prev': '' }, 'Anterior'));
60
+ actions.append(
61
+ i < questions.length - 1
62
+ ? el('button', { type: 'button', class: 'mk-button', 'data-mk-question-next': '' }, 'Siguiente')
63
+ : el('button', { type: 'submit', class: 'mk-button' }, 'Finalizar'),
64
+ );
65
+ section.append(actions);
66
+ form.append(section);
67
+ });
68
+ form.append(el('p', { 'data-mk-question-error': '', role: 'alert' }));
69
+ const done = el('div', { 'data-mk-question-complete': '' });
70
+ done.hidden = true;
71
+ done.append(el('p', {}, '¡Gracias! Respuestas enviadas.'), el('button', { type: 'reset', class: 'mk-button mk-button--tonal' }, 'Empezar de nuevo'));
72
+ form.append(done);
73
+ return form;
74
+ }
75
+
76
+ /**
77
+ * <mk-questionnaire><form>…<section data-mk-question>…</section>…</form></mk-questionnaire>
78
+ * or, empty, <mk-questionnaire questions='[{"id":"name","label":"Nombre","type":"text","required":true}]'>.
79
+ * Emits mk:question ({index, previous, answers}), mk:invalid and mk:complete ({answers}).
80
+ */
81
+ export class MkQuestionnaireElement extends MkElement<Instance> {
82
+ static options: Record<string, OptionType> = { questions: 'json', index: 'number' };
83
+
84
+ questionnaire: QuestionnaireInstance | null = null;
85
+
86
+ protected create(options: Record<string, unknown>): Instance {
87
+ const questions = Array.isArray(options.questions) ? (options.questions as QuestionData[]) : null;
88
+ const built = questions && !this.querySelector('[data-mk-question]') ? buildForm(questions) : null;
89
+ if (built) this.append(built);
90
+ const engine = createQuestionnaire(this, { index: options.index as number | undefined });
91
+ this.questionnaire = engine;
92
+ return {
93
+ destroy: () => {
94
+ engine.destroy();
95
+ built?.remove();
96
+ this.questionnaire = null;
97
+ },
98
+ };
99
+ }
100
+
101
+ next() {
102
+ return this.questionnaire?.next() ?? Promise.resolve(false);
103
+ }
104
+
105
+ prev() {
106
+ return this.questionnaire?.prev() ?? Promise.resolve();
107
+ }
108
+
109
+ reset() {
110
+ this.questionnaire?.reset();
111
+ }
112
+
113
+ get answers() {
114
+ return this.questionnaire?.answers ?? {};
115
+ }
116
+ }
117
+
118
+ export const defineQuestionnaire = () => defineElement('mk-questionnaire', MkQuestionnaireElement);