@yewdiel21/motion-kit 0.9.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. package/README.md +72 -0
  2. package/dist/auto/index.d.ts +72 -3
  3. package/dist/auto/index.js +139 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-BFC27F2G.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-XDWBVFHQ.js → chunk-EY4BJKC4.js} +1203 -29
  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/calendar.css +159 -0
  17. package/dist/css/components/chart-tooltip.css +168 -0
  18. package/dist/css/components/chart.css +296 -0
  19. package/dist/css/components/code-block.css +264 -0
  20. package/dist/css/components/color-area.css +82 -0
  21. package/dist/css/components/color-field.css +67 -0
  22. package/dist/css/components/color-picker.css +131 -0
  23. package/dist/css/components/color-slider.css +113 -0
  24. package/dist/css/components/command-search.css +252 -0
  25. package/dist/css/components/context-menu.css +23 -0
  26. package/dist/css/components/credit-card.css +288 -0
  27. package/dist/css/components/data-table.css +591 -0
  28. package/dist/css/components/date-range-picker.css +168 -0
  29. package/dist/css/components/date-time-picker.css +178 -0
  30. package/dist/css/components/drawer.css +58 -0
  31. package/dist/css/components/editable-table.css +201 -0
  32. package/dist/css/components/empty-state.css +61 -0
  33. package/dist/css/components/file-explorer.css +633 -0
  34. package/dist/css/components/filter-builder.css +232 -0
  35. package/dist/css/components/hold-confirm.css +87 -0
  36. package/dist/css/components/hover-card.css +20 -0
  37. package/dist/css/components/image-preview.css +195 -0
  38. package/dist/css/components/input-otp.css +10 -0
  39. package/dist/css/components/json-viewer.css +272 -0
  40. package/dist/css/components/like-button.css +78 -0
  41. package/dist/css/components/link-preview.css +275 -0
  42. package/dist/css/components/loading-button.css +77 -0
  43. package/dist/css/components/multi-select.css +267 -0
  44. package/dist/css/components/notification-bell.css +127 -0
  45. package/dist/css/components/pagination.css +11 -0
  46. package/dist/css/components/password-strength.css +183 -0
  47. package/dist/css/components/popover.css +25 -0
  48. package/dist/css/components/progress.css +140 -0
  49. package/dist/css/components/quantity.css +16 -0
  50. package/dist/css/components/questionnaire.css +217 -0
  51. package/dist/css/components/range-calendar.css +186 -0
  52. package/dist/css/components/range-slider.css +192 -0
  53. package/dist/css/components/resizable-panel.css +243 -0
  54. package/dist/css/components/rich-text-editor.css +284 -0
  55. package/dist/css/components/scratch-card.css +75 -0
  56. package/dist/css/components/skeleton.css +108 -0
  57. package/dist/css/components/slot-picker.css +126 -0
  58. package/dist/css/components/split-view.css +19 -0
  59. package/dist/css/components/star-rating.css +80 -0
  60. package/dist/css/components/stat-card.css +164 -0
  61. package/dist/css/components/studio-dock.css +35 -0
  62. package/dist/css/components/swipe-actions.css +105 -0
  63. package/dist/css/components/tag-input.css +244 -0
  64. package/dist/css/components/time-field.css +88 -0
  65. package/dist/css/components/timeline.css +219 -0
  66. package/dist/css/components/tooltip.css +79 -0
  67. package/dist/css/components/trailer.css +37 -0
  68. package/dist/css/components/tree-view.css +210 -0
  69. package/dist/css/components/virtual-list.css +195 -0
  70. package/dist/css/controls.css +65 -0
  71. package/dist/css/tokens.css +185 -181
  72. package/dist/elements/index.d.ts +742 -5
  73. package/dist/elements/index.js +1527 -8
  74. package/dist/elements/index.js.map +1 -1
  75. package/dist/{engine-GDGwQnJE.d.ts → engine-Cn6IhE3z.d.ts} +1 -1
  76. package/dist/engine-QECQnGz1.d.ts +33 -0
  77. package/dist/index.css +10113 -181
  78. package/dist/index.d.ts +89 -5
  79. package/dist/index.js +285 -9
  80. package/dist/index.js.map +1 -1
  81. package/dist/react/index.d.ts +678 -23
  82. package/dist/react/index.js +2389 -7
  83. package/dist/react/index.js.map +1 -1
  84. package/dist/{reduced-B98f8LPs.d.ts → reduced-BBUusld6.d.ts} +206 -2
  85. package/dist/reveal-DEvp6U6T.d.ts +2381 -0
  86. package/dist/{store-pHiUvxP8.d.ts → store-DjkTBX7f.d.ts} +2 -2
  87. package/package.json +1 -1
  88. package/registry.json +583 -104
  89. package/skills/motion-kit/SKILL.md +209 -189
  90. package/src/auto/auto-init.ts +136 -0
  91. package/src/components/activity-feed/README.md +56 -0
  92. package/src/components/activity-feed/activity-feed.css +239 -0
  93. package/src/components/activity-feed/element.ts +36 -0
  94. package/src/components/activity-feed/engine.ts +351 -0
  95. package/src/components/activity-feed/react.tsx +32 -0
  96. package/src/components/attachment/README.md +27 -0
  97. package/src/components/attachment/attachment.css +265 -0
  98. package/src/components/attachment/element.ts +50 -0
  99. package/src/components/attachment/engine.ts +469 -0
  100. package/src/components/attachment/react.tsx +65 -0
  101. package/src/components/breadcrumb/README.md +38 -0
  102. package/src/components/breadcrumb/breadcrumb.css +109 -0
  103. package/src/components/breadcrumb/element.ts +27 -0
  104. package/src/components/breadcrumb/engine.ts +249 -0
  105. package/src/components/breadcrumb/react.tsx +30 -0
  106. package/src/components/calendar/README.md +82 -0
  107. package/src/components/calendar/calendar.css +158 -0
  108. package/src/components/calendar/element.ts +27 -0
  109. package/src/components/calendar/engine.ts +412 -0
  110. package/src/components/calendar/react.tsx +69 -0
  111. package/src/components/chart/README.md +24 -0
  112. package/src/components/chart/chart.css +295 -0
  113. package/src/components/chart/element.ts +41 -0
  114. package/src/components/chart/engine.ts +811 -0
  115. package/src/components/chart/react.tsx +50 -0
  116. package/src/components/chart-tooltip/README.md +53 -0
  117. package/src/components/chart-tooltip/chart-tooltip.css +167 -0
  118. package/src/components/chart-tooltip/element.ts +37 -0
  119. package/src/components/chart-tooltip/engine.ts +442 -0
  120. package/src/components/chart-tooltip/react.tsx +47 -0
  121. package/src/components/code-block/README.md +19 -0
  122. package/src/components/code-block/code-block.css +263 -0
  123. package/src/components/code-block/element.ts +34 -0
  124. package/src/components/code-block/engine.ts +254 -0
  125. package/src/components/code-block/highlight.ts +244 -0
  126. package/src/components/code-block/react.tsx +94 -0
  127. package/src/components/color-area/README.md +50 -0
  128. package/src/components/color-area/color-area.css +81 -0
  129. package/src/components/color-area/element.ts +26 -0
  130. package/src/components/color-area/engine.ts +246 -0
  131. package/src/components/color-area/react.tsx +42 -0
  132. package/src/components/color-field/README.md +43 -0
  133. package/src/components/color-field/color-field.css +66 -0
  134. package/src/components/color-field/element.ts +26 -0
  135. package/src/components/color-field/engine.ts +174 -0
  136. package/src/components/color-field/react.tsx +42 -0
  137. package/src/components/color-picker/README.md +45 -0
  138. package/src/components/color-picker/color-picker.css +130 -0
  139. package/src/components/color-picker/element.ts +45 -0
  140. package/src/components/color-picker/engine.ts +314 -0
  141. package/src/components/color-picker/react.tsx +47 -0
  142. package/src/components/color-slider/README.md +55 -0
  143. package/src/components/color-slider/color-slider.css +112 -0
  144. package/src/components/color-slider/element.ts +26 -0
  145. package/src/components/color-slider/engine.ts +234 -0
  146. package/src/components/color-slider/react.tsx +45 -0
  147. package/src/components/command-search/README.md +15 -0
  148. package/src/components/command-search/command-search.css +251 -0
  149. package/src/components/command-search/element.ts +36 -0
  150. package/src/components/command-search/engine.ts +356 -0
  151. package/src/components/command-search/match.ts +53 -0
  152. package/src/components/command-search/react.tsx +66 -0
  153. package/src/components/context-menu/README.md +31 -0
  154. package/src/components/context-menu/context-menu.css +22 -0
  155. package/src/components/context-menu/element.ts +40 -0
  156. package/src/components/context-menu/engine.ts +153 -0
  157. package/src/components/context-menu/react.tsx +57 -0
  158. package/src/components/credit-card/README.md +20 -0
  159. package/src/components/credit-card/card.ts +77 -0
  160. package/src/components/credit-card/credit-card.css +287 -0
  161. package/src/components/credit-card/element.ts +33 -0
  162. package/src/components/credit-card/engine.ts +410 -0
  163. package/src/components/credit-card/react.tsx +36 -0
  164. package/src/components/data-table/README.md +83 -0
  165. package/src/components/data-table/data-table.css +590 -0
  166. package/src/components/data-table/element.ts +54 -0
  167. package/src/components/data-table/engine.ts +1059 -0
  168. package/src/components/data-table/react.tsx +94 -0
  169. package/src/components/date-range-picker/README.md +51 -0
  170. package/src/components/date-range-picker/date-range-picker.css +167 -0
  171. package/src/components/date-range-picker/element.ts +52 -0
  172. package/src/components/date-range-picker/engine.ts +352 -0
  173. package/src/components/date-range-picker/react.tsx +57 -0
  174. package/src/components/date-time-picker/README.md +48 -0
  175. package/src/components/date-time-picker/date-time-picker.css +177 -0
  176. package/src/components/date-time-picker/element.ts +47 -0
  177. package/src/components/date-time-picker/engine.ts +350 -0
  178. package/src/components/date-time-picker/react.tsx +54 -0
  179. package/src/components/dialog/README.md +103 -102
  180. package/src/components/dialog/element.ts +123 -118
  181. package/src/components/dialog/engine.ts +449 -432
  182. package/src/components/drawer/README.md +28 -0
  183. package/src/components/drawer/drawer.css +57 -0
  184. package/src/components/drawer/element.ts +21 -0
  185. package/src/components/drawer/engine.ts +56 -0
  186. package/src/components/drawer/react.tsx +85 -0
  187. package/src/components/editable-table/README.md +67 -0
  188. package/src/components/editable-table/editable-table.css +200 -0
  189. package/src/components/editable-table/element.ts +45 -0
  190. package/src/components/editable-table/engine.ts +775 -0
  191. package/src/components/editable-table/react.tsx +79 -0
  192. package/src/components/empty-state/README.md +41 -0
  193. package/src/components/empty-state/element.ts +23 -0
  194. package/src/components/empty-state/empty-state.css +60 -0
  195. package/src/components/empty-state/engine.ts +232 -0
  196. package/src/components/empty-state/react.tsx +34 -0
  197. package/src/components/file-explorer/README.md +67 -0
  198. package/src/components/file-explorer/element.ts +39 -0
  199. package/src/components/file-explorer/engine.ts +1307 -0
  200. package/src/components/file-explorer/file-explorer.css +632 -0
  201. package/src/components/file-explorer/react.tsx +77 -0
  202. package/src/components/filter-builder/README.md +47 -0
  203. package/src/components/filter-builder/element.ts +34 -0
  204. package/src/components/filter-builder/engine.ts +365 -0
  205. package/src/components/filter-builder/filter-builder.css +231 -0
  206. package/src/components/filter-builder/react.tsx +36 -0
  207. package/src/components/hold-confirm/README.md +13 -0
  208. package/src/components/hold-confirm/element.ts +24 -0
  209. package/src/components/hold-confirm/engine.ts +216 -0
  210. package/src/components/hold-confirm/hold-confirm.css +86 -0
  211. package/src/components/hold-confirm/react.tsx +28 -0
  212. package/src/components/hover-card/README.md +20 -0
  213. package/src/components/hover-card/element.ts +8 -0
  214. package/src/components/hover-card/engine.ts +131 -0
  215. package/src/components/hover-card/hover-card.css +19 -0
  216. package/src/components/hover-card/react.tsx +19 -0
  217. package/src/components/image-preview/README.md +16 -0
  218. package/src/components/image-preview/element.ts +26 -0
  219. package/src/components/image-preview/engine.ts +325 -0
  220. package/src/components/image-preview/image-preview.css +194 -0
  221. package/src/components/image-preview/react.tsx +43 -0
  222. package/src/components/input-otp/README.md +18 -0
  223. package/src/components/input-otp/element.ts +14 -0
  224. package/src/components/input-otp/engine.ts +106 -0
  225. package/src/components/input-otp/input-otp.css +9 -0
  226. package/src/components/input-otp/react.tsx +21 -0
  227. package/src/components/json-viewer/README.md +56 -0
  228. package/src/components/json-viewer/element.ts +37 -0
  229. package/src/components/json-viewer/engine.ts +440 -0
  230. package/src/components/json-viewer/json-viewer.css +271 -0
  231. package/src/components/json-viewer/react.tsx +56 -0
  232. package/src/components/like-button/README.md +13 -0
  233. package/src/components/like-button/element.ts +47 -0
  234. package/src/components/like-button/engine.ts +145 -0
  235. package/src/components/like-button/like-button.css +77 -0
  236. package/src/components/like-button/react.tsx +39 -0
  237. package/src/components/link-preview/README.md +19 -0
  238. package/src/components/link-preview/element.ts +29 -0
  239. package/src/components/link-preview/engine.ts +192 -0
  240. package/src/components/link-preview/link-preview.css +274 -0
  241. package/src/components/link-preview/react.tsx +101 -0
  242. package/src/components/loading-button/README.md +45 -0
  243. package/src/components/loading-button/element.ts +34 -0
  244. package/src/components/loading-button/engine.ts +201 -0
  245. package/src/components/loading-button/loading-button.css +76 -0
  246. package/src/components/loading-button/react.tsx +43 -0
  247. package/src/components/multi-select/README.md +48 -0
  248. package/src/components/multi-select/element.ts +28 -0
  249. package/src/components/multi-select/engine.ts +294 -0
  250. package/src/components/multi-select/multi-select.css +266 -0
  251. package/src/components/multi-select/react.tsx +39 -0
  252. package/src/components/notification-bell/README.md +14 -0
  253. package/src/components/notification-bell/element.ts +48 -0
  254. package/src/components/notification-bell/engine.ts +202 -0
  255. package/src/components/notification-bell/notification-bell.css +126 -0
  256. package/src/components/notification-bell/react.tsx +57 -0
  257. package/src/components/pagination/README.md +17 -0
  258. package/src/components/pagination/element.ts +14 -0
  259. package/src/components/pagination/engine.ts +102 -0
  260. package/src/components/pagination/pagination.css +10 -0
  261. package/src/components/pagination/react.tsx +22 -0
  262. package/src/components/password-strength/README.md +17 -0
  263. package/src/components/password-strength/element.ts +22 -0
  264. package/src/components/password-strength/engine.ts +212 -0
  265. package/src/components/password-strength/password-strength.css +182 -0
  266. package/src/components/password-strength/react.tsx +36 -0
  267. package/src/components/password-strength/score.ts +62 -0
  268. package/src/components/popover/README.md +44 -0
  269. package/src/components/popover/element.ts +31 -0
  270. package/src/components/popover/engine.ts +114 -0
  271. package/src/components/popover/popover.css +24 -0
  272. package/src/components/popover/react.tsx +50 -0
  273. package/src/components/progress/README.md +17 -0
  274. package/src/components/progress/element.ts +30 -0
  275. package/src/components/progress/engine.ts +173 -0
  276. package/src/components/progress/progress.css +139 -0
  277. package/src/components/progress/react.tsx +35 -0
  278. package/src/components/quantity/README.md +20 -0
  279. package/src/components/quantity/element.ts +14 -0
  280. package/src/components/quantity/engine.ts +85 -0
  281. package/src/components/quantity/quantity.css +15 -0
  282. package/src/components/quantity/react.tsx +21 -0
  283. package/src/components/questionnaire/README.md +32 -0
  284. package/src/components/questionnaire/element.ts +118 -0
  285. package/src/components/questionnaire/engine.ts +255 -0
  286. package/src/components/questionnaire/questionnaire.css +216 -0
  287. package/src/components/questionnaire/react.tsx +140 -0
  288. package/src/components/range-calendar/README.md +58 -0
  289. package/src/components/range-calendar/element.ts +42 -0
  290. package/src/components/range-calendar/engine.ts +390 -0
  291. package/src/components/range-calendar/range-calendar.css +185 -0
  292. package/src/components/range-calendar/react.tsx +56 -0
  293. package/src/components/range-slider/README.md +23 -0
  294. package/src/components/range-slider/element.ts +53 -0
  295. package/src/components/range-slider/engine.ts +316 -0
  296. package/src/components/range-slider/range-slider.css +191 -0
  297. package/src/components/range-slider/react.tsx +66 -0
  298. package/src/components/resizable-panel/README.md +61 -0
  299. package/src/components/resizable-panel/element.ts +36 -0
  300. package/src/components/resizable-panel/engine.ts +551 -0
  301. package/src/components/resizable-panel/react.tsx +74 -0
  302. package/src/components/resizable-panel/resizable-panel.css +242 -0
  303. package/src/components/rich-text-editor/README.md +57 -0
  304. package/src/components/rich-text-editor/element.ts +34 -0
  305. package/src/components/rich-text-editor/engine.ts +673 -0
  306. package/src/components/rich-text-editor/react.tsx +60 -0
  307. package/src/components/rich-text-editor/rich-text-editor.css +283 -0
  308. package/src/components/scratch-card/README.md +15 -0
  309. package/src/components/scratch-card/element.ts +30 -0
  310. package/src/components/scratch-card/engine.ts +257 -0
  311. package/src/components/scratch-card/react.tsx +40 -0
  312. package/src/components/scratch-card/scratch-card.css +74 -0
  313. package/src/components/skeleton/README.md +15 -0
  314. package/src/components/skeleton/element.ts +32 -0
  315. package/src/components/skeleton/engine.ts +104 -0
  316. package/src/components/skeleton/react.tsx +41 -0
  317. package/src/components/skeleton/skeleton.css +107 -0
  318. package/src/components/slot-picker/README.md +68 -0
  319. package/src/components/slot-picker/element.ts +27 -0
  320. package/src/components/slot-picker/engine.ts +412 -0
  321. package/src/components/slot-picker/react.tsx +76 -0
  322. package/src/components/slot-picker/slot-picker.css +125 -0
  323. package/src/components/split-view/README.md +50 -0
  324. package/src/components/split-view/element.ts +34 -0
  325. package/src/components/split-view/engine.ts +114 -0
  326. package/src/components/split-view/react.tsx +63 -0
  327. package/src/components/split-view/split-view.css +18 -0
  328. package/src/components/star-rating/README.md +14 -0
  329. package/src/components/star-rating/element.ts +26 -0
  330. package/src/components/star-rating/engine.ts +217 -0
  331. package/src/components/star-rating/react.tsx +33 -0
  332. package/src/components/star-rating/star-rating.css +79 -0
  333. package/src/components/stat-card/README.md +42 -0
  334. package/src/components/stat-card/element.ts +40 -0
  335. package/src/components/stat-card/engine.ts +283 -0
  336. package/src/components/stat-card/react.tsx +36 -0
  337. package/src/components/stat-card/stat-card.css +163 -0
  338. package/src/components/studio-dock/README.md +44 -0
  339. package/src/components/studio-dock/element.ts +11 -0
  340. package/src/components/studio-dock/engine.ts +212 -0
  341. package/src/components/studio-dock/react.tsx +23 -0
  342. package/src/components/studio-dock/studio-dock.css +34 -0
  343. package/src/components/swipe-actions/README.md +19 -0
  344. package/src/components/swipe-actions/element.ts +26 -0
  345. package/src/components/swipe-actions/engine.ts +260 -0
  346. package/src/components/swipe-actions/react.tsx +80 -0
  347. package/src/components/swipe-actions/swipe-actions.css +104 -0
  348. package/src/components/tag-input/README.md +42 -0
  349. package/src/components/tag-input/element.ts +39 -0
  350. package/src/components/tag-input/engine.ts +313 -0
  351. package/src/components/tag-input/react.tsx +54 -0
  352. package/src/components/tag-input/tag-input.css +243 -0
  353. package/src/components/time-field/README.md +54 -0
  354. package/src/components/time-field/element.ts +38 -0
  355. package/src/components/time-field/engine.ts +110 -0
  356. package/src/components/time-field/react.tsx +45 -0
  357. package/src/components/time-field/time-field.css +87 -0
  358. package/src/components/timeline/README.md +57 -0
  359. package/src/components/timeline/element.ts +31 -0
  360. package/src/components/timeline/engine.ts +300 -0
  361. package/src/components/timeline/react.tsx +59 -0
  362. package/src/components/timeline/timeline.css +218 -0
  363. package/src/components/tooltip/README.md +46 -0
  364. package/src/components/tooltip/element.ts +22 -0
  365. package/src/components/tooltip/engine.ts +288 -0
  366. package/src/components/tooltip/react.tsx +30 -0
  367. package/src/components/tooltip/tooltip.css +78 -0
  368. package/src/components/trailer/README.md +28 -0
  369. package/src/components/trailer/element.ts +13 -0
  370. package/src/components/trailer/engine.ts +189 -0
  371. package/src/components/trailer/react.tsx +16 -0
  372. package/src/components/trailer/trailer.css +36 -0
  373. package/src/components/tree-view/README.md +61 -0
  374. package/src/components/tree-view/element.ts +32 -0
  375. package/src/components/tree-view/engine.ts +569 -0
  376. package/src/components/tree-view/react.tsx +78 -0
  377. package/src/components/tree-view/tree-view.css +209 -0
  378. package/src/components/virtual-list/README.md +81 -0
  379. package/src/components/virtual-list/element.ts +42 -0
  380. package/src/components/virtual-list/engine.ts +678 -0
  381. package/src/components/virtual-list/react.tsx +101 -0
  382. package/src/components/virtual-list/virtual-list.css +194 -0
  383. package/src/core/chips.ts +196 -0
  384. package/src/core/color.ts +304 -0
  385. package/src/core/date-range.ts +67 -0
  386. package/src/core/dates.ts +108 -0
  387. package/src/core/files.ts +107 -0
  388. package/src/core/filter.ts +229 -0
  389. package/src/core/gsap.ts +80 -65
  390. package/src/core/listbox.ts +332 -0
  391. package/src/core/mark.ts +29 -0
  392. package/src/core/position.ts +89 -0
  393. package/src/core/relative-time.ts +77 -0
  394. package/src/core/segment-field.ts +373 -0
  395. package/src/core/segments.ts +153 -0
  396. package/src/core/tree.ts +116 -0
  397. package/src/core/virtual.ts +79 -0
  398. package/src/css/controls/README.md +14 -0
  399. package/src/css/controls/status.css +63 -0
  400. package/src/css/tokens.css +186 -182
  401. package/src/elements/index.ts +171 -0
  402. package/src/index.ts +97 -0
  403. package/src/motion/micro.ts +248 -0
  404. package/src/motion/sheet.ts +88 -65
  405. package/src/motion/surface.ts +9 -0
  406. package/src/react/index.ts +78 -0
  407. package/src/react/use-action-button.ts +6 -0
  408. package/src/react/use-editorial-reveal.ts +7 -0
  409. package/src/react/use-film-hero.ts +6 -0
  410. package/src/react/use-font-cycle.ts +7 -0
  411. package/src/react/use-image-zoom.ts +5 -0
  412. package/src/react/use-joined-cta.ts +5 -0
  413. package/src/react/use-local-clock.ts +5 -0
  414. package/src/react/use-scroll-dock.ts +5 -0
  415. package/src/react/use-sticker.ts +5 -0
  416. package/src/react/use-studio-dock.ts +8 -0
  417. package/src/react/use-thumbnail-nav.ts +7 -0
  418. package/src/react/use-trailer.ts +6 -0
  419. package/src/react/use-weight-hover.ts +5 -0
  420. package/src/recipes/action-button/README.md +31 -0
  421. package/src/recipes/action-button/action-button.css +113 -0
  422. package/src/recipes/action-button/create-action-button.ts +56 -0
  423. package/src/recipes/editorial-reveal/README.md +35 -0
  424. package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
  425. package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
  426. package/src/recipes/film-hero/README.md +23 -0
  427. package/src/recipes/film-hero/create-film-hero.ts +65 -0
  428. package/src/recipes/film-hero/film-hero.css +29 -0
  429. package/src/recipes/font-cycle/README.md +11 -0
  430. package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
  431. package/src/recipes/font-cycle/font-cycle.css +36 -0
  432. package/src/recipes/image-zoom/README.md +16 -0
  433. package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
  434. package/src/recipes/image-zoom/image-zoom.css +7 -0
  435. package/src/recipes/joined-cta/README.md +13 -0
  436. package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
  437. package/src/recipes/joined-cta/joined-cta.css +17 -0
  438. package/src/recipes/local-clock/README.md +31 -0
  439. package/src/recipes/local-clock/create-local-clock.ts +61 -0
  440. package/src/recipes/scroll-dock/README.md +46 -0
  441. package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
  442. package/src/recipes/scroll-dock/scroll-dock.css +70 -0
  443. package/src/recipes/sticker/README.md +59 -0
  444. package/src/recipes/sticker/create-sticker.ts +490 -0
  445. package/src/recipes/sticker/sticker.css +112 -0
  446. package/src/recipes/thumbnail-nav/README.md +16 -0
  447. package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
  448. package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
  449. package/src/recipes/weight-hover/README.md +37 -0
  450. package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
  451. package/src/recipes/weight-hover/weight-hover.css +7 -0
  452. package/dist/chunk-BFC27F2G.js.map +0 -1
  453. package/dist/chunk-J6OCFNGU.js +0 -177
  454. package/dist/chunk-J6OCFNGU.js.map +0 -1
  455. package/dist/chunk-JQMJQSXD.js +0 -2374
  456. package/dist/chunk-JQMJQSXD.js.map +0 -1
  457. package/dist/chunk-XDWBVFHQ.js.map +0 -1
  458. package/dist/reveal-C0hCDcB_.d.ts +0 -415
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes, type ReactNode } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createSwipeActions } from './engine';
5
+
6
+ export interface SwipeAction {
7
+ id: string;
8
+ label: string;
9
+ /** Glyph or icon inside the round button. */
10
+ icon?: ReactNode;
11
+ tone?: 'neutral' | 'danger' | 'accent';
12
+ /** The row folds away after it runs (the app then removes it). */
13
+ remove?: boolean;
14
+ /** Run by a long swipe on its side. */
15
+ primary?: boolean;
16
+ }
17
+
18
+ export interface SwipeRowProps extends Omit<HTMLAttributes<HTMLLIElement>, 'children'> {
19
+ left?: SwipeAction[];
20
+ right?: SwipeAction[];
21
+ children?: ReactNode;
22
+ }
23
+
24
+ function Actions({ side, actions }: { side: 'left' | 'right'; actions: SwipeAction[] }) {
25
+ return (
26
+ <div data-mk-swipe-actions={side}>
27
+ {actions.map((action) => (
28
+ <button
29
+ key={action.id}
30
+ type="button"
31
+ data-mk-swipe-action={action.id}
32
+ data-mk-tone={action.tone}
33
+ data-mk-swipe-remove={action.remove ? '' : undefined}
34
+ data-mk-swipe-primary={action.primary ? '' : undefined}
35
+ aria-label={action.label}
36
+ >
37
+ {action.icon ?? action.label}
38
+ </button>
39
+ ))}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ /** One row: its face (children) and the actions behind each side. */
45
+ function Row({ left, right, children, ...props }: SwipeRowProps) {
46
+ return (
47
+ <li {...props} data-mk-swipe-row="">
48
+ {left?.length ? <Actions side="left" actions={left} /> : null}
49
+ <div data-mk-swipe-content="">{children}</div>
50
+ {right?.length ? <Actions side="right" actions={right} /> : null}
51
+ </li>
52
+ );
53
+ }
54
+
55
+ export interface SwipeActionsProps extends Omit<HTMLAttributes<HTMLUListElement>, 'onChange'> {
56
+ fullSwipe?: number;
57
+ /** An action ran (a removing one after its row folded away): update your data here. */
58
+ onAction?: (action: string, row: HTMLElement) => void;
59
+ children?: ReactNode;
60
+ }
61
+
62
+ function Root({ fullSwipe, onAction, className, children, ...props }: SwipeActionsProps) {
63
+ const ref = useRef<HTMLUListElement>(null);
64
+ const action = useLatest(onAction);
65
+ useIsomorphicLayoutEffect(() => {
66
+ const instance = createSwipeActions(ref.current, { fullSwipe, onAction: (id, row) => action.current?.(id, row) });
67
+ return () => instance.destroy();
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, [fullSwipe]);
70
+ return (
71
+ <ul {...props} ref={ref} className={['mk-swipe-actions', className].filter(Boolean).join(' ')}>
72
+ {children}
73
+ </ul>
74
+ );
75
+ }
76
+
77
+ /**
78
+ * <SwipeActions onAction={(id, row) => …}><SwipeActions.Row key={mail.id} data-id={mail.id} right={[{ id: 'delete', label: 'Eliminar', remove: true, tone: 'danger' }]}>…</SwipeActions.Row></SwipeActions>
79
+ */
80
+ export const SwipeActions = Object.assign(Root, { Row });
@@ -0,0 +1,104 @@
1
+ /* swipe-actions — Melonmind list rows (20 px radius, surface-1) that slide aside to show round action buttons
2
+ behind them; the icons grow as they appear and the primary one stretches when a long swipe is armed. */
3
+ .mk-swipe-actions,
4
+ mk-swipe-actions {
5
+ display: grid;
6
+ gap: 0.5rem;
7
+ margin: 0;
8
+ padding: 0;
9
+ list-style: none;
10
+ }
11
+
12
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-row] {
13
+ position: relative;
14
+ overflow: hidden;
15
+ border-radius: 20px;
16
+ background: var(--mk-surface-3);
17
+ isolation: isolate;
18
+ }
19
+
20
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-content] {
21
+ position: relative;
22
+ z-index: 1;
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 0.85rem;
26
+ min-height: 4rem;
27
+ padding: 0.75rem 1rem;
28
+ border-radius: inherit;
29
+ background: var(--mk-surface-1);
30
+ box-shadow: inset 0 0 0 1px var(--mk-outline-soft);
31
+ touch-action: pan-y;
32
+ user-select: none;
33
+ cursor: grab;
34
+ }
35
+
36
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-row][data-mk-dragging] [data-mk-swipe-content] {
37
+ cursor: grabbing;
38
+ }
39
+
40
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-actions] {
41
+ position: absolute;
42
+ top: 0;
43
+ bottom: 0;
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 0.4rem;
47
+ padding: 0 0.6rem;
48
+ }
49
+
50
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-actions='left'] {
51
+ left: 0;
52
+ }
53
+
54
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-actions='right'] {
55
+ right: 0;
56
+ }
57
+
58
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action] {
59
+ display: grid;
60
+ place-items: center;
61
+ gap: 0.15rem;
62
+ width: 3rem;
63
+ height: 3rem;
64
+ padding: 0;
65
+ border: 0;
66
+ border-radius: 50%;
67
+ background: var(--mk-text);
68
+ color: var(--mk-bg);
69
+ font: inherit;
70
+ font-size: 0.6875rem;
71
+ cursor: pointer;
72
+ scale: calc(0.6 + var(--mk-swipe-reveal, 1) * 0.4);
73
+ transition:
74
+ width 0.35s cubic-bezier(0.38, 0.49, 0, 1.5),
75
+ border-radius 0.35s cubic-bezier(0.38, 0.49, 0, 1.5);
76
+ }
77
+
78
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action][data-mk-tone='danger'] {
79
+ background: var(--mk-error);
80
+ color: var(--mk-on-error);
81
+ }
82
+
83
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action][data-mk-tone='accent'] {
84
+ background: var(--mk-accent);
85
+ color: var(--mk-on-accent);
86
+ }
87
+
88
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action]:focus-visible {
89
+ outline: 2px solid var(--mk-accent);
90
+ outline-offset: 2px;
91
+ }
92
+
93
+ /* A long swipe is armed: the primary action stretches into a pill. */
94
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-row][data-mk-armed] [data-mk-swipe-action]:is([data-mk-swipe-primary], :only-child) {
95
+ width: 5.5rem;
96
+ border-radius: 64px;
97
+ }
98
+
99
+ @media (prefers-reduced-motion: reduce) {
100
+ :is(.mk-swipe-actions, mk-swipe-actions) [data-mk-swipe-action] {
101
+ transition: none;
102
+ scale: 1;
103
+ }
104
+ }
@@ -0,0 +1,42 @@
1
+ # tag-input
2
+
3
+ Entrada de etiquetas libres con autocompletado. Enter o una coma añaden lo escrito (o la sugerencia resaltada); al pegar «a, b; c» se añade cada una, una tras otra. Los chips nuevos aparecen con un pop y los que se quitan se pliegan. Las sugerencias salen del campo con una píldora que se desliza por la activa y las letras escritas marcadas.
4
+
5
+ Una etiqueta repetida hace sonar el chip que ya la tiene (sacudida y anillo). Si `validate`, `restrict` o `max` rechazan una, el campo se sacude y debajo dice por qué (`aria-invalid` y el mensaje ligado al campo). Retroceso con el campo vacío marca el último chip y luego lo quita; ← llega a los chips.
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <mk-tag-input class="mk-tag-input" label="Etiquetas" value="diseño,urgente"
11
+ suggestions="diseño,código,contenido,urgente,cliente" max="6" name="etiquetas"></mk-tag-input>
12
+ ```
13
+
14
+ ## React
15
+
16
+ ```tsx
17
+ <TagInput value={tags} onChange={setTags} suggestions={known}
18
+ validate={(tag) => (tag.length > 24 ? 'Máximo 24 letras' : null)} />
19
+ ```
20
+
21
+ ## Opciones
22
+
23
+ | Opción | Atributo | Por defecto | Qué hace |
24
+ |---|---|---|---|
25
+ | `value` | `value` (lista con comas) | — | Etiquetas iniciales. |
26
+ | `suggestions` | `suggestions` (lista) | — | Se ofrecen al escribir. |
27
+ | `placeholder` | `placeholder` | `'Añadir etiqueta…'` | Texto del campo vacío. |
28
+ | `label` | `label` | el `<label>` escrito, o «Etiquetas» | Nombre accesible. |
29
+ | `name` | `name` | — | Campos ocultos `name[]`. |
30
+ | `max` | `max` | sin límite | Máximo de etiquetas. |
31
+ | `restrict` | `restrict` | `false` | Solo etiquetas de las sugerencias. |
32
+ | `allowDuplicates` | `allow-duplicates` | `false` | Permitir repetidas (sin distinguir mayúsculas ni tildes). |
33
+ | `validate(tag, tags)` | — | — | Devuelve un mensaje para rechazarla. |
34
+ | `transform(tag)` | — | quitar espacios sobrantes | Limpia la etiqueta antes de añadirla. |
35
+ | `disabled` | `disabled` | `false` | Desactivado. |
36
+ | `onChange(tags)` | — | — | Cada cambio. |
37
+
38
+ ## API y eventos
39
+
40
+ `setValue(tags, notify = false)`, `add(tag)` (devuelve `false` si se rechaza), `remove(tag)`, `setSuggestions(list)`, `value`, `destroy()`. Evento `mk:change` con `{ value }`.
41
+
42
+ Con movimiento reducido los chips aparecen y desaparecen sin animación.
@@ -0,0 +1,39 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createTagInput, type TagInputInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-tag-input class="mk-tag-input" label="Etiquetas" value="diseño,urgente" suggestions="diseño,código,urgente" max="5" name="tags">
6
+ * </mk-tag-input>
7
+ * Emits mk:change ({value}).
8
+ */
9
+ export class MkTagInputElement extends MkElement<TagInputInstance> {
10
+ static options: Record<string, OptionType> = {
11
+ value: 'list',
12
+ suggestions: 'list',
13
+ placeholder: 'string',
14
+ label: 'string',
15
+ name: 'string',
16
+ max: 'number',
17
+ restrict: 'boolean',
18
+ allowDuplicates: 'boolean',
19
+ disabled: 'boolean',
20
+ };
21
+
22
+ protected create(options: Record<string, unknown>) {
23
+ return createTagInput(this, options);
24
+ }
25
+
26
+ attributeChangedCallback(name: string, previous: string | null, next: string | null) {
27
+ if (previous === next || !this.instance) return;
28
+ const list = (next ?? '').split(',').map((item) => item.trim()).filter(Boolean);
29
+ if (name === 'value') this.instance.setValue(list);
30
+ else if (name === 'suggestions') this.instance.setSuggestions(list);
31
+ else super.attributeChangedCallback(name, previous, next);
32
+ }
33
+
34
+ get value() {
35
+ return this.instance?.value ?? [];
36
+ }
37
+ }
38
+
39
+ export const defineTagInput = () => defineElement('mk-tag-input', MkTagInputElement);
@@ -0,0 +1,313 @@
1
+ import { registerMotion } from '../../core/gsap';
2
+ import { createScope } from '../../core/scope';
3
+ import { inert, type Instance } from '../../core/types';
4
+ import { createChips } from '../../core/chips';
5
+ import { createListbox, filterOptions } from '../../core/listbox';
6
+ import { shake } from '../../motion/micro';
7
+
8
+ /*
9
+ * Tag input: free tags as chips, with suggestions. Enter or a comma adds what
10
+ * was typed (or the highlighted suggestion); pasting "a, b; c" adds each, one
11
+ * after another. New chips pop in and removed ones fold away
12
+ * (core/chips.ts); the suggestions open out of the field with a gliding
13
+ * active pill and the typed letters marked (core/listbox.ts). A duplicate
14
+ * rings the chip that already has it; a tag that `validate`, `restrict` or
15
+ * `max` refuses shakes the field and says why under it (aria-invalid, the
16
+ * message described by the input). Backspace in an empty input arms the last
17
+ * chip, then removes it; ← reaches the chips.
18
+ */
19
+
20
+ export interface TagInputOptions {
21
+ value?: string[];
22
+ /** Offered while typing (fuzzy, accents ignored). */
23
+ suggestions?: string[];
24
+ placeholder?: string;
25
+ /** Accessible name (default: the authored <label>, else 'Etiquetas'). */
26
+ label?: string;
27
+ /** Hidden inputs name[] for forms. */
28
+ name?: string;
29
+ max?: number;
30
+ /** Only tags from the suggestions. */
31
+ restrict?: boolean;
32
+ allowDuplicates?: boolean;
33
+ /** Return a message to refuse a tag. */
34
+ validate?: (tag: string, tags: string[]) => string | null | undefined | void;
35
+ /** Clean a tag before adding it (default: trim and collapse spaces). */
36
+ transform?: (tag: string) => string;
37
+ disabled?: boolean;
38
+ onChange?: (tags: string[]) => void;
39
+ }
40
+
41
+ export interface TagInputInstance extends Instance {
42
+ setValue(tags: string[], notify?: boolean): void;
43
+ /** Add a tag as if typed; false when refused. */
44
+ add(tag: string): boolean;
45
+ remove(tag: string): void;
46
+ setSuggestions(suggestions: string[]): void;
47
+ readonly value: string[];
48
+ }
49
+
50
+ const fold = (text: string) => text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
51
+ const SEPARATORS = /[,;\n]+/;
52
+ let sequence = 0;
53
+
54
+ export function createTagInput(root: HTMLElement | null, settings: TagInputOptions = {}): TagInputInstance {
55
+ if (!root) return inert({ setValue() {}, add: () => false, remove() {}, setSuggestions() {}, value: [] });
56
+ registerMotion();
57
+ const scope = createScope();
58
+ const uid = `mk-tags-${++sequence}`;
59
+ const max = Number.isFinite(settings.max) && settings.max! > 0 ? Math.floor(settings.max!) : Infinity;
60
+ const clean = settings.transform ?? ((tag: string) => tag.replace(/\s+/g, ' ').trim());
61
+ let suggestions = [...(settings.suggestions ?? [])];
62
+ let tags: string[] = [];
63
+ const labelText = settings.label ?? root.querySelector('label')?.textContent?.trim() ?? 'Etiquetas';
64
+
65
+ const field = document.createElement('div');
66
+ field.dataset.mkTagField = '';
67
+ const chips = createChips({ disabled: settings.disabled, onRemove: (tag) => remove(tag, true), onExit: () => input.focus() });
68
+ scope.add(() => chips.destroy());
69
+ const input = document.createElement('input');
70
+ input.type = 'text';
71
+ input.dataset.mkTagInput = '';
72
+ input.setAttribute('role', 'combobox');
73
+ input.setAttribute('aria-autocomplete', 'list');
74
+ input.setAttribute('aria-label', labelText);
75
+ input.setAttribute('aria-describedby', `${uid}-error`);
76
+ input.autocomplete = 'off';
77
+ input.spellcheck = false;
78
+ input.enterKeyHint = 'enter';
79
+ input.disabled = Boolean(settings.disabled);
80
+ field.append(chips.element, input);
81
+ const error = document.createElement('p');
82
+ error.id = `${uid}-error`;
83
+ error.dataset.mkTagError = '';
84
+ error.hidden = true;
85
+ const status = document.createElement('span');
86
+ status.className = 'mk-sr-only';
87
+ status.setAttribute('role', 'status');
88
+ const hiddenHost = document.createElement('span');
89
+ hiddenHost.hidden = true;
90
+ root.append(field, error, status, hiddenHost);
91
+ scope.add(() => {
92
+ field.remove();
93
+ error.remove();
94
+ status.remove();
95
+ hiddenHost.remove();
96
+ });
97
+ scope.attr(root, 'data-mk-ready', '');
98
+
99
+ const listbox = createListbox({
100
+ input,
101
+ anchor: field,
102
+ host: root,
103
+ label: 'Sugerencias',
104
+ emptyLabel: 'Sin sugerencias',
105
+ onPick: (option) => {
106
+ if (add(option.value, true)) {
107
+ input.value = '';
108
+ listbox.hide();
109
+ }
110
+ },
111
+ });
112
+ scope.add(() => listbox.destroy());
113
+
114
+ const placeholder = () => {
115
+ input.placeholder = tags.length >= max ? '' : (settings.placeholder ?? 'Añadir etiqueta…');
116
+ };
117
+ const showError = (message: string | null) => {
118
+ error.textContent = message ?? '';
119
+ error.hidden = !message;
120
+ if (message) input.setAttribute('aria-invalid', 'true');
121
+ else input.removeAttribute('aria-invalid');
122
+ };
123
+ const sync = (animate: boolean) => {
124
+ chips.set(tags.map((tag) => ({ value: tag, label: tag })), animate);
125
+ placeholder();
126
+ if (settings.name) {
127
+ hiddenHost.replaceChildren(
128
+ ...tags.map((tag) => {
129
+ const hidden = document.createElement('input');
130
+ hidden.type = 'hidden';
131
+ hidden.name = `${settings.name}[]`;
132
+ hidden.value = tag;
133
+ return hidden;
134
+ }),
135
+ );
136
+ }
137
+ };
138
+ const emit = () => {
139
+ settings.onChange?.([...tags]);
140
+ root.dispatchEvent(new CustomEvent('mk:change', { bubbles: true, detail: { value: [...tags] } }));
141
+ };
142
+
143
+ /** The suggestions not chosen yet that match the typed text (all of them for an empty query). */
144
+ const available = (query: string) => {
145
+ const chosen = new Set(tags.map(fold));
146
+ return filterOptions(
147
+ suggestions.filter((tag) => !chosen.has(fold(tag))).map((tag) => ({ value: tag, label: tag })),
148
+ query,
149
+ ).slice(0, 50);
150
+ };
151
+ /** While typing: matching suggestions, or none (a new tag needs no empty list). */
152
+ const offer = () => {
153
+ const query = input.value;
154
+ const matches = query.trim() && suggestions.length ? available(query) : [];
155
+ if (!matches.length) return listbox.hide();
156
+ listbox.show(matches, query);
157
+ };
158
+
159
+ /** Add one tag; false (and why, shaken) when refused. */
160
+ const add = (raw: string, animate: boolean, notify = true) => {
161
+ const tag = clean(raw);
162
+ if (!tag || settings.disabled) return false;
163
+ const refuse = (message: string) => {
164
+ showError(message);
165
+ shake(field);
166
+ return false;
167
+ };
168
+ const twin = tags.find((existing) => fold(existing) === fold(tag));
169
+ if (twin && !settings.allowDuplicates) {
170
+ chips.flash(twin);
171
+ status.textContent = `Ya existe «${twin}»`;
172
+ return false;
173
+ }
174
+ if (tags.length >= max) return refuse(`Máximo ${max} ${max === 1 ? 'etiqueta' : 'etiquetas'}`);
175
+ const known = suggestions.find((suggestion) => fold(suggestion) === fold(tag));
176
+ if (settings.restrict && !known) return refuse(`«${tag}» no está en la lista`);
177
+ const message = settings.validate?.(known ?? tag, [...tags]);
178
+ if (message) return refuse(message);
179
+ tags = [...tags, known ?? tag];
180
+ showError(null);
181
+ chips.arm(null);
182
+ sync(animate);
183
+ status.textContent = `${known ?? tag} añadida`;
184
+ if (notify) emit();
185
+ return true;
186
+ };
187
+
188
+ const remove = (tag: string, notify: boolean) => {
189
+ if (!tags.includes(tag) || settings.disabled) return;
190
+ tags = tags.filter((existing) => existing !== tag);
191
+ chips.arm(null);
192
+ sync(true);
193
+ status.textContent = `${tag} quitada`;
194
+ if (notify) emit();
195
+ };
196
+
197
+ const commit = () => {
198
+ if (!input.value.trim()) return false;
199
+ if (add(input.value, true)) {
200
+ input.value = '';
201
+ listbox.hide();
202
+ return true;
203
+ }
204
+ return false;
205
+ };
206
+
207
+ scope.on(field, 'pointerdown', (event) => {
208
+ const target = event.target as Element | null;
209
+ if (target?.closest('[data-mk-chip-remove]')) return;
210
+ if (target !== input) event.preventDefault();
211
+ input.focus();
212
+ });
213
+ scope.on(input, 'input', () => {
214
+ showError(null);
215
+ chips.arm(null);
216
+ // A typed separator (mobile keyboards have no key events for it) commits what came before.
217
+ if (SEPARATORS.test(input.value)) {
218
+ const parts = input.value.split(SEPARATORS);
219
+ const rest = parts.pop() ?? '';
220
+ for (const part of parts) add(part, true);
221
+ input.value = rest;
222
+ }
223
+ offer();
224
+ });
225
+ scope.on(input, 'paste', (event) => {
226
+ const text = (event as ClipboardEvent).clipboardData?.getData('text') ?? '';
227
+ if (!SEPARATORS.test(text)) return;
228
+ event.preventDefault();
229
+ const parts = text.split(SEPARATORS).map(clean).filter(Boolean);
230
+ let added = 0;
231
+ for (const part of parts) if (add(part, true, false)) added++;
232
+ if (added) emit();
233
+ });
234
+ scope.on(input, 'keydown', (event) => {
235
+ const key = event as KeyboardEvent;
236
+ switch (key.key) {
237
+ case 'Enter':
238
+ if (listbox.isOpen && listbox.active) {
239
+ key.preventDefault();
240
+ listbox.pick();
241
+ } else if (input.value.trim()) {
242
+ key.preventDefault();
243
+ commit();
244
+ }
245
+ return;
246
+ case ',':
247
+ case ';':
248
+ key.preventDefault();
249
+ commit();
250
+ return;
251
+ case 'ArrowDown':
252
+ case 'ArrowUp':
253
+ if (!suggestions.length) return;
254
+ key.preventDefault();
255
+ if (!listbox.isOpen) listbox.show(available(input.value), input.value);
256
+ listbox.move(key.key === 'ArrowDown' ? 1 : -1);
257
+ return;
258
+ case 'Escape':
259
+ if (listbox.isOpen) {
260
+ key.preventDefault();
261
+ listbox.hide();
262
+ }
263
+ return;
264
+ case 'Backspace':
265
+ if (input.value || !tags.length) return;
266
+ key.preventDefault();
267
+ if (chips.armed) remove(chips.armed, true);
268
+ else chips.arm(tags[tags.length - 1]!);
269
+ return;
270
+ case 'ArrowLeft':
271
+ if (input.selectionStart === 0 && input.selectionEnd === 0 && chips.focusLast()) {
272
+ key.preventDefault();
273
+ listbox.hide();
274
+ }
275
+ return;
276
+ case 'Tab':
277
+ listbox.hide();
278
+ return;
279
+ }
280
+ });
281
+ scope.on(input, 'blur', () => {
282
+ chips.arm(null);
283
+ listbox.hide();
284
+ });
285
+
286
+ for (const tag of settings.value ?? []) add(tag, false, false);
287
+ sync(false);
288
+
289
+ return {
290
+ setValue(next, notify = false) {
291
+ if (scope.disposed) return;
292
+ tags = [];
293
+ for (const tag of next) {
294
+ const cleaned = clean(tag);
295
+ if (cleaned && (settings.allowDuplicates || !tags.some((existing) => fold(existing) === fold(cleaned)))) tags.push(cleaned);
296
+ }
297
+ tags = tags.slice(0, max);
298
+ showError(null);
299
+ sync(true);
300
+ if (notify) emit();
301
+ },
302
+ add: (tag) => add(tag, true),
303
+ remove: (tag) => remove(tag, true),
304
+ setSuggestions(next) {
305
+ suggestions = [...next];
306
+ if (listbox.isOpen) offer();
307
+ },
308
+ get value() {
309
+ return [...tags];
310
+ },
311
+ destroy: () => scope.dispose(),
312
+ };
313
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createTagInput, type TagInputInstance, type TagInputOptions } from './engine';
5
+
6
+ export interface TagInputProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>, TagInputOptions {
7
+ /** Controlled tags; without it the field keeps its own (defaultValue). */
8
+ value?: string[];
9
+ defaultValue?: string[];
10
+ }
11
+
12
+ /** <TagInput value={tags} onChange={setTags} suggestions={known} max={8} />. */
13
+ export function TagInput({ value, defaultValue, suggestions, placeholder, label, name, max, restrict, allowDuplicates, validate, transform, disabled, onChange, className, ...props }: TagInputProps) {
14
+ const ref = useRef<HTMLDivElement>(null);
15
+ const instance = useRef<TagInputInstance | null>(null);
16
+ const change = useLatest(onChange);
17
+ const check = useLatest(validate);
18
+ const shape = useLatest(transform);
19
+ const start = useRef(value ?? defaultValue ?? []);
20
+ useIsomorphicLayoutEffect(() => {
21
+ const created = createTagInput(ref.current, {
22
+ value: value ?? start.current,
23
+ suggestions,
24
+ placeholder,
25
+ label,
26
+ name,
27
+ max,
28
+ restrict,
29
+ allowDuplicates,
30
+ disabled,
31
+ validate: validate ? (tag, tags) => check.current?.(tag, tags) : undefined,
32
+ transform: transform ? (tag) => shape.current?.(tag) ?? tag : undefined,
33
+ onChange: (next) => change.current?.(next),
34
+ });
35
+ instance.current = created;
36
+ return () => {
37
+ start.current = created.value;
38
+ created.destroy();
39
+ instance.current = null;
40
+ };
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [placeholder, label, name, max, restrict, allowDuplicates, disabled, Boolean(validate), Boolean(transform)]);
43
+ const suggestionsKey = JSON.stringify(suggestions ?? null);
44
+ useIsomorphicLayoutEffect(() => {
45
+ if (suggestions) instance.current?.setSuggestions(suggestions);
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, [suggestionsKey]);
48
+ const valueKey = JSON.stringify(value ?? null);
49
+ useIsomorphicLayoutEffect(() => {
50
+ if (value && instance.current && JSON.stringify(instance.current.value) !== valueKey) instance.current.setValue(value);
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ }, [valueKey]);
53
+ return <div {...props} ref={ref} className={['mk-tag-input', className].filter(Boolean).join(' ')} />;
54
+ }