@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,212 @@
1
+ import { gsap, 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
+
6
+ export type StudioDockView = 'menu' | 'contact' | 'reel';
7
+ export interface StudioDockState { open: boolean; view: StudioDockView; page: number; }
8
+ export interface StudioDockOptions {
9
+ /** Seconds. Reference sheet: 1.3 s, outBack(.52). */
10
+ duration?: number;
11
+ ease?: string;
12
+ /** Move focus into a newly opened view. Defaults to true. */
13
+ focusOnOpen?: boolean;
14
+ /** Contain Tab within the open panel and its toggle. Defaults to true. */
15
+ trapFocus?: boolean;
16
+ onChange?: (state: StudioDockState) => void;
17
+ }
18
+ export interface StudioDockInstance extends Instance {
19
+ open(view?: StudioDockView): void;
20
+ close(): void;
21
+ toggle(): void;
22
+ selectPage(index: number): void;
23
+ readonly state: StudioDockState;
24
+ }
25
+
26
+ let nextId = 0;
27
+ /** Panel distance from the dock's edge (bar + gap); --mk-studio-dock-offset overrides it. */
28
+ const PANEL_OFFSET = 76;
29
+ const VIEWPORT_GAP = 12;
30
+ const MIN_SPACE = 320;
31
+ const FOCUSABLE = 'a[href],button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),video[controls],[tabindex]:not([tabindex="-1"])';
32
+
33
+ /** A contained studio sheet. Markup remains useful without JavaScript; fixed positioning is CSS opt-in. */
34
+ export function createStudioDock(root: HTMLElement | null, options: StudioDockOptions = {}): StudioDockInstance {
35
+ const toggleButton = root?.querySelector<HTMLButtonElement>('[data-mk-studio-toggle]');
36
+ const panel = root?.querySelector<HTMLElement>('[data-mk-studio-panel]');
37
+ if (!root || !toggleButton || !panel) return inert({ open() {}, close() {}, toggle() {}, selectPage() {}, state: { open: false, view: 'menu' as const, page: 0 } });
38
+ registerMotion();
39
+ const scope = createScope();
40
+ const context = gsap.context(() => {}, root);
41
+ scope.add(() => context.revert());
42
+ const views = [...panel.querySelectorAll<HTMLElement>('[data-mk-studio-view]')];
43
+ const pages = [...panel.querySelectorAll<HTMLElement>('[data-mk-studio-page]')];
44
+ const pageButtons = [...root.querySelectorAll<HTMLElement>('[data-mk-studio-page-to]')];
45
+ const slide = root.querySelector<HTMLElement>('[data-mk-studio-slide]');
46
+ const tracked = [root, panel, ...(slide ? [slide] : [])];
47
+ tracked.forEach(el => { const style = el.getAttribute('style'); scope.add(() => style === null ? el.removeAttribute('style') : el.setAttribute('style', style)); });
48
+ scope.attr(panel, 'id', panel.id || `mk-studio-panel-${++nextId}`);
49
+ scope.attr(panel, 'role', panel.getAttribute('role') || 'dialog');
50
+ scope.attr(panel, 'aria-label', panel.getAttribute('aria-label') || 'Studio navigation');
51
+ scope.attr(panel, 'tabindex', '-1');
52
+ scope.attr(toggleButton, 'aria-controls', panel.id);
53
+ scope.attr(toggleButton, 'aria-expanded', 'false');
54
+ scope.attr(panel, 'hidden', '');
55
+ scope.attr(panel, 'aria-hidden', 'true');
56
+ scope.attr(root, 'data-mk-ready', '');
57
+ let state: StudioDockState = { open: false, view: 'menu', page: 0 };
58
+ let previousFocus: HTMLElement | null = null;
59
+ let animation: gsap.core.Tween | undefined;
60
+ let slideStart: number | null = null;
61
+ let slideDistance = 0;
62
+ let slideMoved = false;
63
+ let suppressSlideClick = false;
64
+
65
+ // The sheet opens above the bar. Cap it to the room actually left in the viewport
66
+ // (it scrolls inside) and open it below the bar when above is too tight.
67
+ const place = (decide = false) => {
68
+ const view = root.ownerDocument.defaultView;
69
+ if (!state.open || !view) return;
70
+ const rect = root.getBoundingClientRect();
71
+ const offset = parseFloat(view.getComputedStyle(root).getPropertyValue('--mk-studio-dock-offset')) || PANEL_OFFSET;
72
+ const above = rect.bottom - offset - VIEWPORT_GAP;
73
+ const below = view.innerHeight - rect.top - offset - VIEWPORT_GAP;
74
+ // The direction is chosen on open only, so scrolling never flips an open sheet.
75
+ if (decide) scope.attr(root, 'data-mk-studio-below', above < Math.min(panel.scrollHeight || MIN_SPACE, MIN_SPACE) && below > above ? '' : null);
76
+ const flip = root.hasAttribute('data-mk-studio-below');
77
+ root.style.setProperty('--mk-studio-dock-space', `${Math.max(MIN_SPACE / 2, Math.floor(flip ? below : above))}px`);
78
+ };
79
+ const hidden = () => root.hasAttribute('data-mk-studio-below')
80
+ ? { clipPath: 'inset(0% 0% 100% 0% round 18px)', y: -16, opacity: 0 }
81
+ : { clipPath: 'inset(100% 0% 0% 0% round 18px)', y: 16, opacity: 0 };
82
+ const notify = () => {
83
+ const value = { ...state };
84
+ options.onChange?.(value);
85
+ root.dispatchEvent(new CustomEvent('mk:studio-dock', { bubbles: true, detail: value }));
86
+ };
87
+ const isTabbable = (el: HTMLElement) => el.tabIndex >= 0 && !el.closest('[hidden], [inert]') && el.getAttribute('aria-disabled') !== 'true';
88
+ const visibleFocus = () => [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(isTabbable);
89
+ const focusView = () => { if (options.focusOnOpen !== false) (visibleFocus()[0] ?? panel).focus({ preventScroll: true }); };
90
+ const setView = (view: StudioDockView) => {
91
+ state.view = views.length && !views.some(el => el.dataset.mkStudioView === view) ? 'menu' : view;
92
+ views.forEach(el => scope.attr(el, 'hidden', el.dataset.mkStudioView === state.view ? null : ''));
93
+ scope.attr(root, 'data-mk-studio-active', state.view);
94
+ panel.querySelectorAll<HTMLVideoElement>('video').forEach(video => { if (state.view !== 'reel') video.pause(); });
95
+ };
96
+ const selectPage = (index: number, announce = true) => {
97
+ if (scope.disposed || !pages.length || !Number.isFinite(index)) return;
98
+ state.page = ((Math.round(index) % pages.length) + pages.length) % pages.length;
99
+ pages.forEach((el, i) => scope.attr(el, 'hidden', i === state.page ? null : ''));
100
+ pageButtons.forEach(el => scope.attr(el, 'aria-current', Number(el.dataset.mkStudioPageTo) === state.page ? 'true' : null));
101
+ if (announce) notify();
102
+ };
103
+ const open = (view: StudioDockView = 'menu') => {
104
+ if (scope.disposed) return;
105
+ const wasOpen = state.open;
106
+ if (wasOpen && state.view === view) return;
107
+ if (!wasOpen) {
108
+ const active = root.ownerDocument.activeElement;
109
+ previousFocus = active instanceof HTMLElement && active !== root.ownerDocument.body && !panel.contains(active) ? active : toggleButton;
110
+ }
111
+ animation?.kill();
112
+ state.open = true;
113
+ scope.attr(panel, 'hidden', null);
114
+ scope.attr(panel, 'inert', null);
115
+ scope.attr(panel, 'aria-hidden', 'false');
116
+ scope.attr(root, 'data-mk-studio-open', '');
117
+ scope.attr(toggleButton, 'aria-expanded', 'true');
118
+ setView(view);
119
+ place(!wasOpen);
120
+ if (!reduced()) context.add(() => {
121
+ animation = gsap.fromTo(panel, hidden(), { clipPath: 'inset(0% 0% 0% 0% round 18px)', y: 0, opacity: 1, duration: Math.max(0, options.duration ?? 1.3), ease: options.ease ?? 'back.out(0.52)', overwrite: true });
122
+ });
123
+ else context.add(() => { gsap.set(panel, { clipPath: 'inset(0% round 18px)', y: 0, opacity: 1 }); });
124
+ focusView();
125
+ notify();
126
+ };
127
+ const close = (restoreFocus = true) => {
128
+ if (scope.disposed || !state.open) return;
129
+ animation?.kill();
130
+ state.open = false;
131
+ scope.attr(toggleButton, 'aria-expanded', 'false');
132
+ scope.attr(root, 'data-mk-studio-open', null);
133
+ // Stop focus immediately while the visual sheet collapses.
134
+ scope.attr(panel, 'inert', '');
135
+ scope.attr(panel, 'aria-hidden', 'true');
136
+ if (reduced()) scope.attr(panel, 'hidden', '');
137
+ else context.add(() => {
138
+ animation = gsap.to(panel, { ...hidden(), duration: Math.max(0, options.duration ?? 1.3), ease: options.ease ?? 'back.out(0.52)', overwrite: true, onComplete: () => { if (!scope.disposed && !state.open) scope.attr(panel, 'hidden', ''); } });
139
+ });
140
+ panel.querySelectorAll<HTMLVideoElement>('video').forEach(video => video.pause());
141
+ if (restoreFocus) (previousFocus?.isConnected ? previousFocus : toggleButton).focus({ preventScroll: true });
142
+ notify();
143
+ };
144
+ scope.on(root, 'click', event => {
145
+ const target = event.target instanceof Element ? event.target : null;
146
+ if (!target) return;
147
+ if (target.closest('[data-mk-studio-toggle]') === toggleButton) state.open ? close() : open();
148
+ else if (target.closest('[data-mk-studio-close]')) close();
149
+ else if (target.closest('[data-mk-studio-slide]')) {
150
+ if (suppressSlideClick) { event.preventDefault(); suppressSlideClick = false; }
151
+ else open('contact');
152
+ } else {
153
+ const view = target.closest<HTMLElement>('[data-mk-studio-to]')?.dataset.mkStudioTo;
154
+ if (view === 'menu' || view === 'contact' || view === 'reel') open(view);
155
+ const page = target.closest<HTMLElement>('[data-mk-studio-page-to]');
156
+ if (page) selectPage(Number(page.dataset.mkStudioPageTo));
157
+ }
158
+ });
159
+ if (root.ownerDocument.defaultView) scope.on(root.ownerDocument.defaultView, 'resize', () => place());
160
+ scope.on(root.ownerDocument, 'scroll', () => place(), { passive: true, capture: true });
161
+ scope.on(root.ownerDocument, 'pointerdown', event => {
162
+ if (state.open && event.target instanceof Node && !root.contains(event.target)) close(false);
163
+ });
164
+ scope.on(root.ownerDocument, 'keydown', event => {
165
+ if (!state.open || event.defaultPrevented) return;
166
+ if (event.key === 'Escape') { event.preventDefault(); close(); return; }
167
+ if (!(event.target instanceof Node) || !root.contains(event.target)) return;
168
+ if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') && (event.target as Element).closest('[data-mk-studio-pages]')) {
169
+ event.preventDefault(); selectPage(state.page + (event.key === 'ArrowRight' ? 1 : -1));
170
+ pageButtons.find(el => Number(el.dataset.mkStudioPageTo) === state.page)?.focus();
171
+ }
172
+ if (event.key === 'Tab' && options.trapFocus !== false) {
173
+ // querySelectorAll keeps the authored DOM order, including a toggle before OR after the panel.
174
+ const elements = [...root.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(el => (el === toggleButton || panel.contains(el)) && isTabbable(el));
175
+ if (!elements.length) { event.preventDefault(); panel.focus(); return; }
176
+ const first = elements[0]!, last = elements.at(-1)!;
177
+ if (event.shiftKey && (root.ownerDocument.activeElement === first || root.ownerDocument.activeElement === panel)) { event.preventDefault(); last.focus(); }
178
+ else if (!event.shiftKey && root.ownerDocument.activeElement === last) { event.preventDefault(); first.focus(); }
179
+ }
180
+ });
181
+ if (slide) {
182
+ scope.on(slide, 'pointerdown', event => { if (event.button !== 0) return; slideStart = event.clientX; slideDistance = 0; slideMoved = false; });
183
+ scope.on(root.ownerDocument, 'pointermove', event => {
184
+ if (slideStart === null) return;
185
+ const width = Math.max(0, (slide.parentElement?.clientWidth ?? 0) - slide.offsetWidth);
186
+ slideMoved ||= Math.abs(event.clientX - slideStart) > 5;
187
+ slideDistance = Math.max(0, Math.min(width, event.clientX - slideStart));
188
+ if (!reduced()) slide.style.setProperty('--mk-studio-drag', `${slideDistance}px`);
189
+ });
190
+ const release = (cancel = false) => {
191
+ if (slideStart === null) return;
192
+ const width = Math.max(0, (slide.parentElement?.clientWidth ?? 0) - slide.offsetWidth);
193
+ suppressSlideClick = slideMoved;
194
+ slideStart = null;
195
+ slide.style.removeProperty('--mk-studio-drag');
196
+ if (!cancel && width > 0 && slideDistance / width >= .7) open('contact');
197
+ scope.timeout(() => { suppressSlideClick = false; }, 150);
198
+ };
199
+ scope.on(root.ownerDocument, 'pointerup', () => release());
200
+ scope.on(root.ownerDocument, 'pointercancel', () => release(true));
201
+ }
202
+ const clocks = [...root.querySelectorAll<HTMLElement>('[data-mk-studio-timezone]')];
203
+ const clockOriginals = clocks.map(el => ({ el, text: el.textContent, style: el.getAttribute('style') }));
204
+ const tick = () => clocks.forEach(el => {
205
+ try { el.textContent = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit', timeZone: el.dataset.mkStudioTimezone }).format(new Date()); } catch { /* Invalid caller time zone leaves the original label intact. */ }
206
+ });
207
+ if (clocks.length) { tick(); const timer = setInterval(tick, 60_000); scope.add(() => clearInterval(timer)); }
208
+ scope.add(() => clockOriginals.forEach(({ el, text, style }) => { el.textContent = text; if (style === null) el.removeAttribute('style'); else el.setAttribute('style', style); }));
209
+ setView('menu');
210
+ selectPage(0, false);
211
+ return { open, close, toggle: () => state.open ? close() : open(), selectPage, get state() { return { ...state }; }, destroy() { if (scope.disposed) return; const focused = root.contains(root.ownerDocument.activeElement); animation?.kill(); panel.querySelectorAll<HTMLVideoElement>('video').forEach(video => video.pause()); scope.dispose(); if (focused) toggleButton.focus({ preventScroll: true }); } };
212
+ }
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createStudioDock, type StudioDockInstance, type StudioDockOptions } from './engine';
5
+
6
+ export interface StudioDockProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>, StudioDockOptions {
7
+ /** Default stays inside its positioned demo/app panel. */
8
+ fixed?: boolean;
9
+ instanceRef?: { current: StudioDockInstance | null };
10
+ }
11
+ /** Composable light-DOM shell; use the data-mk-studio-* slots shown in the README. */
12
+ export function StudioDock({ duration, ease, focusOnOpen, trapFocus, onChange, fixed = false, instanceRef, className, children, ...props }: StudioDockProps) {
13
+ const ref = useRef<HTMLDivElement>(null);
14
+ const change = useLatest(onChange);
15
+ useIsomorphicLayoutEffect(() => {
16
+ const instance = createStudioDock(ref.current, { duration, ease, focusOnOpen, trapFocus, onChange: state => change.current?.(state) });
17
+ if (instanceRef) instanceRef.current = instance;
18
+ return () => { instance.destroy(); if (instanceRef) instanceRef.current = null; };
19
+ }, [duration, ease, focusOnOpen, trapFocus]);
20
+ return <div {...props} ref={ref} className={['mk-studio-dock', fixed && 'mk-studio-dock--fixed', className].filter(Boolean).join(' ')}>{children}</div>;
21
+ }
22
+
23
+ export { useStudioDock } from '../../react/use-studio-dock';
@@ -0,0 +1,34 @@
1
+ .mk-studio-dock { position: relative; isolation: isolate; width: min(343px, 100%); color: var(--mk-text); font: inherit; }
2
+ .mk-studio-dock--fixed { position: fixed; z-index: 70; inset-block-end: 24px; inset-inline-start: 50%; translate: -50% 0; width: min(343px, calc(100vw - 32px)); }
3
+ .mk-studio-dock__bar { display: flex; align-items: center; gap: 16px; min-height: 64px; padding: 12px; border-radius: 18px; background: var(--mk-surface-2); }
4
+ .mk-studio-dock__brand, .mk-studio-dock__toggle { display: grid; place-items: center; inline-size: 40px; block-size: 40px; flex: none; border: 0; border-radius: 10px; background: var(--mk-surface); color: inherit; text-decoration: none; font: inherit; }
5
+ .mk-studio-dock__label { flex: 1; font-size: .9rem; }
6
+ .mk-studio-dock__toggle { cursor: pointer; }
7
+ .mk-studio-dock__toggle span { transition: transform .4s; }
8
+ .mk-studio-dock[data-mk-studio-open] .mk-studio-dock__toggle span { transform: rotate(45deg); }
9
+ .mk-studio-dock__panel { position: absolute; z-index: 2; inset-inline: 0; inset-block-end: var(--mk-studio-dock-offset, 76px); max-height: min(660px, 75dvh, var(--mk-studio-dock-space, 660px)); overflow: auto; overscroll-behavior: contain; padding: 12px; border: 1px solid var(--mk-outline-soft); border-radius: 18px; background: var(--mk-surface-2); box-shadow: 0 16px 60px #0002; }
10
+ /* Not enough room above the bar: the engine opens the sheet below it. */
11
+ .mk-studio-dock[data-mk-studio-below] .mk-studio-dock__panel { inset-block: var(--mk-studio-dock-offset, 76px) auto; }
12
+ .mk-studio-dock [hidden] { display: none !important; }
13
+ .mk-studio-dock__grid, .mk-studio-dock__page { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
14
+ .mk-studio-dock__tile { min-height: 130px; display: flex; flex-direction: column; justify-content: flex-end; gap: 4px; padding: 12px; border-radius: 10px; background: var(--mk-surface); color: inherit; text-decoration: none; transition: transform .4s cubic-bezier(.2, 1.4, .4, 1); }
15
+ .mk-studio-dock__tile small { color: var(--mk-muted); }
16
+ .mk-studio-dock__tile:is(:hover, :focus-visible) { transform: scale(.94); }
17
+ .mk-studio-dock__reel { display: flex; justify-content: space-between; align-items: end; min-height: 154px; margin-block: 12px; padding: 16px; border-radius: 10px; background: var(--mk-accent); color: var(--mk-on-accent); }
18
+ .mk-studio-dock__reel button { border: 1px solid currentColor; border-radius: 50%; padding: 14px; background: transparent; color: inherit; font: inherit; cursor: pointer; }
19
+ .mk-studio-dock__slide-track { position: relative; display: flex; align-items: center; height: 64px; padding: 4px; margin-block: 12px; border-radius: 48px; background: var(--mk-surface-3, var(--mk-surface)); }
20
+ .mk-studio-dock__slide-track > span { position: absolute; inset-inline: 60px 12px; text-align: center; font-size: .75rem; pointer-events: none; }
21
+ .mk-studio-dock [data-mk-studio-slide] { position: relative; display: grid; place-items: center; width: 56px; height: 56px; border: 0; border-radius: 50%; background: var(--mk-surface); color: inherit; font-size: 20px; cursor: pointer; touch-action: pan-y; transform: translateX(var(--mk-studio-drag, 0px)); }
22
+ .mk-studio-dock__clock { position: relative; aspect-ratio: 1; display: grid; place-content: center; gap: 4px; text-align: center; border: 10px solid var(--mk-surface); border-radius: 50%; background: var(--mk-surface-2); }
23
+ .mk-studio-dock__clock::after { content: ''; position: absolute; inset: 8px; border: 1px solid var(--mk-outline-soft); border-radius: 50%; }
24
+ .mk-studio-dock__clock small { color: var(--mk-muted); }
25
+ .mk-studio-dock__pages { margin-block-start: 12px; }
26
+ .mk-studio-dock__pagination { display: flex; justify-content: center; gap: 8px; margin-block-start: 12px; }
27
+ .mk-studio-dock__pagination button { width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50%; background: var(--mk-surface); color: inherit; font: inherit; cursor: pointer; }
28
+ .mk-studio-dock__pagination [aria-current] { background: var(--mk-accent); color: var(--mk-on-accent); }
29
+ .mk-studio-dock__view { display: grid; gap: 16px; padding: 12px 0; }
30
+ .mk-studio-dock__view input, .mk-studio-dock__view textarea { width: 100%; padding: 12px; border: 1px solid var(--mk-outline-soft); border-radius: 8px; background: var(--mk-surface); color: inherit; font: inherit; }
31
+ .mk-studio-dock__view label { display: grid; gap: 6px; }
32
+ .mk-studio-dock__view video { width: 100%; border-radius: 10px; }
33
+ .mk-studio-dock :is(button,a,input,textarea):focus-visible { outline: 2px solid var(--mk-accent); outline-offset: 3px; }
34
+ @media (prefers-reduced-motion: reduce) { .mk-studio-dock *, .mk-studio-dock *::after { transition: none !important; animation: none !important; } }
@@ -0,0 +1,19 @@
1
+ # swipe-actions
2
+
3
+ «Deslizar para ver opciones» en filas de lista: un arrastre horizontal sobre la fila (`[data-mk-swipe-row]`) aparta su cara (`[data-mk-swipe-content]`) y muestra detrás las acciones de ese lado (`[data-mk-swipe-actions="left|right"]` con botones `[data-mk-swipe-action]`). Los iconos crecen mientras aparecen; al soltar, la fila se abre o se cierra con la curva swing. Un deslizamiento largo (60 %) ejecuta la acción principal del lado: la cara sale y, si la acción lleva `data-mk-swipe-remove`, la fila se pliega (blur y alto a 0) antes de emitir `mk:action`, para que la app la quite de sus datos.
4
+
5
+ ```html
6
+ <mk-swipe-actions class="mk-swipe-actions">
7
+ <div data-mk-swipe-row>
8
+ <div data-mk-swipe-actions="left"><button data-mk-swipe-action="archive" aria-label="Archivar">A</button></div>
9
+ <div data-mk-swipe-content>Factura de octubre</div>
10
+ <div data-mk-swipe-actions="right"><button data-mk-swipe-action="delete" data-mk-swipe-remove data-mk-tone="danger" aria-label="Eliminar">×</button></div>
11
+ </div>
12
+ </mk-swipe-actions>
13
+ ```
14
+
15
+ `defineElements()`, `createSwipeActions(root, options)` o `<SwipeActions onAction={run}><SwipeActions.Row right={[{ id: 'delete', label: 'Eliminar', remove: true, tone: 'danger' }]}>…</SwipeActions.Row></SwipeActions>`.
16
+
17
+ Opciones: `fullSwipe=0.6` (0 lo desactiva), `onAction(action, row)`. API: `open(row, side)`, `close()`, `openRow`, `destroy()`. Eventos `mk:action` `{action, row}` y `mk:open` `{row, side}`. La acción principal es la marcada con `data-mk-swipe-primary` o, si no hay, la más exterior del lado. Una sola fila abierta; Escape o un toque fuera la cierran. Las acciones son botones reales: enfocarlas con teclado abre su fila. El desplazamiento vertical sigue siendo nativo (`touch-action: pan-y`).
18
+
19
+ Tonos: `data-mk-tone="danger|accent"` (por defecto, el color del texto). Movimiento reducido: la fila se abre, se cierra y se pliega sin animación.
@@ -0,0 +1,26 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createSwipeActions, type SwipeActionsInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-swipe-actions class="mk-swipe-actions">
6
+ * <div data-mk-swipe-row>
7
+ * <div data-mk-swipe-actions="left"><button data-mk-swipe-action="archive" aria-label="Archivar">…</button></div>
8
+ * <div data-mk-swipe-content>…the row…</div>
9
+ * <div data-mk-swipe-actions="right"><button data-mk-swipe-action="delete" data-mk-swipe-remove aria-label="Eliminar">…</button></div>
10
+ * </div>
11
+ * </mk-swipe-actions>
12
+ * Emits mk:action ({action, row}) and mk:open ({row, side}).
13
+ */
14
+ export class MkSwipeActionsElement extends MkElement<SwipeActionsInstance> {
15
+ static options: Record<string, OptionType> = { fullSwipe: 'number' };
16
+
17
+ protected create(options: Record<string, unknown>) {
18
+ return createSwipeActions(this, options);
19
+ }
20
+
21
+ close() {
22
+ this.instance?.close();
23
+ }
24
+ }
25
+
26
+ export const defineSwipeActions = () => defineElement('mk-swipe-actions', MkSwipeActionsElement);
@@ -0,0 +1,260 @@
1
+ import { gsap, EASE, registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { createScope } from '../../core/scope';
4
+ import { inert, type Instance } from '../../core/types';
5
+ import { press } from '../../motion/micro';
6
+
7
+ /*
8
+ * Swipe actions: a horizontal drag on a list row ([data-mk-swipe-row]) slides
9
+ * its face ([data-mk-swipe-content]) aside and shows the actions behind it
10
+ * ([data-mk-swipe-actions="left|right"] with buttons [data-mk-swipe-action]).
11
+ * The icons grow as the drag passes the opening point; letting go snaps the row
12
+ * open or shut on the swing curve. A long swipe (60 %) runs the side's primary
13
+ * action: the face slides out and, for [data-mk-swipe-remove] actions, the row
14
+ * folds away (blur, height to 0) before mk:action fires so the app can drop it.
15
+ * One row open at a time; Escape or a tap elsewhere shuts it. The actions are
16
+ * real buttons: focusing one opens its row. Vertical scrolling stays native.
17
+ */
18
+
19
+ export type SwipeSide = 'left' | 'right';
20
+
21
+ export interface SwipeActionsOptions {
22
+ /** Share of the row width that runs the primary action (default .6; 0 turns it off). */
23
+ fullSwipe?: number;
24
+ onAction?: (action: string, row: HTMLElement) => void;
25
+ }
26
+
27
+ export interface SwipeActionsInstance extends Instance {
28
+ open(row: HTMLElement, side: SwipeSide): void;
29
+ close(): void;
30
+ readonly openRow: HTMLElement | null;
31
+ }
32
+
33
+ const ROW = '[data-mk-swipe-row]';
34
+ const SLOP = 8;
35
+
36
+ export function createSwipeActions(root: HTMLElement | null, options: SwipeActionsOptions = {}): SwipeActionsInstance {
37
+ if (!root) return inert({ open() {}, close() {}, openRow: null });
38
+ registerMotion();
39
+ const scope = createScope();
40
+ const full = Number.isFinite(options.fullSwipe) ? Math.max(0, Math.min(0.95, options.fullSwipe!)) : 0.6;
41
+ scope.attr(root, 'data-mk-ready', '');
42
+
43
+ let current: { row: HTMLElement; side: SwipeSide } | null = null;
44
+ const effects = new Set<gsap.core.Animation>();
45
+ const keep = (animation: gsap.core.Animation | null) => {
46
+ if (!animation) return;
47
+ effects.add(animation);
48
+ // Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
49
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
50
+ };
51
+ const touched = new Set<HTMLElement>();
52
+ scope.add(() => {
53
+ for (const effect of effects) effect.kill();
54
+ effects.clear();
55
+ for (const element of touched) {
56
+ gsap.set(element, { clearProps: 'transform,height,opacity,filter,marginTop,marginBottom,paddingTop,paddingBottom,overflow' });
57
+ element.removeAttribute('data-mk-open');
58
+ element.removeAttribute('data-mk-armed');
59
+ element.style.removeProperty('--mk-swipe-reveal');
60
+ if (element.getAttribute('style') === '') element.removeAttribute('style');
61
+ }
62
+ });
63
+
64
+ const parts = (row: HTMLElement) => ({
65
+ face: row.querySelector<HTMLElement>('[data-mk-swipe-content]'),
66
+ left: row.querySelector<HTMLElement>('[data-mk-swipe-actions="left"]'),
67
+ right: row.querySelector<HTMLElement>('[data-mk-swipe-actions="right"]'),
68
+ });
69
+ const widthOf = (actions: HTMLElement | null) => actions?.getBoundingClientRect().width || actions?.offsetWidth || 0;
70
+ const primaryOf = (actions: HTMLElement | null) => actions?.querySelector<HTMLButtonElement>('[data-mk-swipe-primary]') ?? (actions?.dataset.mkSwipeActions === 'right' ? actions.querySelector<HTMLButtonElement>('[data-mk-swipe-action]:last-of-type') : actions?.querySelector<HTMLButtonElement>('[data-mk-swipe-action]')) ?? null;
71
+
72
+ const move = (row: HTMLElement, x: number, animate: boolean) => {
73
+ const { face } = parts(row);
74
+ if (!face) return;
75
+ touched.add(face);
76
+ touched.add(row);
77
+ if (!animate || reduced()) gsap.set(face, { x });
78
+ else keep(gsap.to(face, { x, duration: 0.5, ease: EASE.swatch, overwrite: 'auto' }));
79
+ };
80
+
81
+ const close = (animate = true) => {
82
+ if (!current) return;
83
+ const { row } = current;
84
+ current = null;
85
+ row.removeAttribute('data-mk-open');
86
+ row.removeAttribute('data-mk-armed');
87
+ row.style.removeProperty('--mk-swipe-reveal');
88
+ move(row, 0, animate);
89
+ };
90
+
91
+ const open = (row: HTMLElement, side: SwipeSide, animate = true) => {
92
+ if (scope.disposed || !root.contains(row)) return;
93
+ const actions = parts(row)[side];
94
+ if (!actions) return;
95
+ if (current && current.row !== row) close();
96
+ current = { row, side };
97
+ row.setAttribute('data-mk-open', side);
98
+ row.style.setProperty('--mk-swipe-reveal', '1');
99
+ touched.add(row);
100
+ move(row, side === 'left' ? widthOf(actions) : -widthOf(actions), animate);
101
+ root.dispatchEvent(new CustomEvent('mk:open', { bubbles: true, detail: { row, side } }));
102
+ };
103
+
104
+ /** Run an action: emit; a removing action folds the row away first. */
105
+ const run = (button: HTMLButtonElement, row: HTMLElement, swiped: boolean) => {
106
+ const action = button.dataset.mkSwipeAction ?? button.textContent?.trim() ?? '';
107
+ const emit = () => {
108
+ options.onAction?.(action, row);
109
+ root.dispatchEvent(new CustomEvent('mk:action', { bubbles: true, detail: { action, row } }));
110
+ };
111
+ keep(press(button));
112
+ if (!button.hasAttribute('data-mk-swipe-remove')) {
113
+ if (current?.row === row) close();
114
+ return emit();
115
+ }
116
+ current = null;
117
+ row.removeAttribute('data-mk-open');
118
+ touched.add(row);
119
+ if (reduced()) {
120
+ gsap.set(row, { height: 0, opacity: 0, overflow: 'hidden', paddingTop: 0, paddingBottom: 0, marginTop: 0, marginBottom: 0 });
121
+ return emit();
122
+ }
123
+ const { face } = parts(row);
124
+ const width = row.getBoundingClientRect().width || 400;
125
+ const side = Number(gsap.getProperty(face, 'x')) >= 0 ? 1 : -1;
126
+ keep(
127
+ gsap
128
+ .timeline({ onComplete: emit })
129
+ .to(face, { x: side * width * (swiped ? 1.05 : 1), duration: 0.3, ease: 'power2.in' })
130
+ .to(row, { height: 0, opacity: 0, filter: 'blur(16px)', paddingTop: 0, paddingBottom: 0, marginTop: 0, marginBottom: 0, overflow: 'hidden', duration: 0.25, ease: 'power2.in' }),
131
+ );
132
+ };
133
+
134
+ // The drag.
135
+ /** The click that ends a drag is not a tap. */
136
+ let dragged = false;
137
+ let drag: { row: HTMLElement; x0: number; y0: number; start: number; id: number; horizontal: boolean | null } | null = null;
138
+ scope.on(root, 'pointerdown', (event) => {
139
+ const pointer = event as PointerEvent;
140
+ if (pointer.button !== 0) return;
141
+ const target = pointer.target as Element | null;
142
+ if (target?.closest('[data-mk-swipe-action], a, input, select, textarea')) return;
143
+ const row = target?.closest<HTMLElement>(ROW);
144
+ const face = row && parts(row).face;
145
+ if (!row || !face || !root.contains(row)) return;
146
+ gsap.killTweensOf(face, 'x');
147
+ drag = { row, x0: pointer.clientX, y0: pointer.clientY, start: Number(gsap.getProperty(face, 'x')) || 0, id: pointer.pointerId, horizontal: null };
148
+ });
149
+ scope.on(root, 'pointermove', (event) => {
150
+ const pointer = event as PointerEvent;
151
+ if (!drag || pointer.pointerId !== drag.id) return;
152
+ const dx = pointer.clientX - drag.x0;
153
+ const dy = pointer.clientY - drag.y0;
154
+ if (drag.horizontal === null) {
155
+ if (Math.abs(dx) < SLOP && Math.abs(dy) < SLOP) return;
156
+ drag.horizontal = Math.abs(dx) > Math.abs(dy);
157
+ if (!drag.horizontal) {
158
+ drag = null;
159
+ return;
160
+ }
161
+ if (current && current.row !== drag.row) close();
162
+ (pointer.target as Element).setPointerCapture?.(pointer.pointerId);
163
+ drag.row.setAttribute('data-mk-dragging', '');
164
+ }
165
+ const { left, right } = parts(drag.row);
166
+ let x = drag.start + dx;
167
+ // No actions on a side: it resists.
168
+ if ((x > 0 && !left) || (x < 0 && !right)) x *= 0.15;
169
+ const actions = x > 0 ? left : right;
170
+ const reach = widthOf(actions);
171
+ const width = drag.row.getBoundingClientRect().width || 1;
172
+ // Past the actions the drag slows down, unless a long swipe is on.
173
+ if (reach && Math.abs(x) > reach && !full) x = Math.sign(x) * (reach + (Math.abs(x) - reach) * 0.3);
174
+ drag.row.toggleAttribute('data-mk-armed', Boolean(full) && Math.abs(x) >= width * full);
175
+ drag.row.style.setProperty('--mk-swipe-reveal', String(Math.min(1, reach ? Math.abs(x) / reach : 0)));
176
+ touched.add(drag.row);
177
+ move(drag.row, x, false);
178
+ });
179
+ const release = (event: Event) => {
180
+ const pointer = event as PointerEvent;
181
+ if (!drag || pointer.pointerId !== drag.id) return;
182
+ const { row, horizontal } = drag;
183
+ drag = null;
184
+ row.removeAttribute('data-mk-dragging');
185
+ if (!horizontal) return;
186
+ dragged = true;
187
+ setTimeout(() => (dragged = false), 0);
188
+ const face = parts(row).face!;
189
+ const x = Number(gsap.getProperty(face, 'x')) || 0;
190
+ const side: SwipeSide = x > 0 ? 'left' : 'right';
191
+ const actions = parts(row)[side];
192
+ const width = row.getBoundingClientRect().width || 1;
193
+ row.removeAttribute('data-mk-armed');
194
+ const primary = primaryOf(actions);
195
+ if (full && actions && primary && Math.abs(x) >= width * full) return run(primary, row, true);
196
+ if (actions && Math.abs(x) > widthOf(actions) / 2) open(row, side);
197
+ else {
198
+ if (current?.row === row) current = null;
199
+ row.removeAttribute('data-mk-open');
200
+ row.style.removeProperty('--mk-swipe-reveal');
201
+ move(row, 0, true);
202
+ }
203
+ };
204
+ scope.on(root, 'pointerup', release);
205
+ scope.on(root, 'pointercancel', release);
206
+ // A click after a drag is not a tap on the row.
207
+ scope.on(
208
+ root,
209
+ 'click',
210
+ (event) => {
211
+ if (dragged) {
212
+ dragged = false;
213
+ event.preventDefault();
214
+ event.stopPropagation();
215
+ return;
216
+ }
217
+ const button = (event.target as Element | null)?.closest<HTMLButtonElement>('[data-mk-swipe-action]');
218
+ const row = button?.closest<HTMLElement>(ROW);
219
+ if (button && row && root.contains(row)) {
220
+ run(button, row, false);
221
+ return;
222
+ }
223
+ if (current && !(event.target as Element | null)?.closest('[data-mk-swipe-actions]')) {
224
+ const inside = (event.target as Element | null)?.closest(ROW) === current.row;
225
+ close();
226
+ if (inside) event.preventDefault();
227
+ }
228
+ },
229
+ true,
230
+ );
231
+ scope.on(document, 'pointerdown', (event) => {
232
+ if (current && !current.row.contains(event.target as Node)) close();
233
+ });
234
+ scope.on(root, 'keydown', (event) => {
235
+ if ((event as KeyboardEvent).key === 'Escape' && current) {
236
+ const row = current.row;
237
+ close();
238
+ parts(row).face?.focus?.();
239
+ }
240
+ });
241
+ // Keyboard: focusing an action opens its row.
242
+ scope.on(root, 'focusin', (event) => {
243
+ const actions = (event.target as Element | null)?.closest<HTMLElement>('[data-mk-swipe-actions]');
244
+ const row = actions?.closest<HTMLElement>(ROW);
245
+ if (actions && row && current?.row !== row) open(row, actions.dataset.mkSwipeActions as SwipeSide);
246
+ });
247
+ scope.on(root, 'focusout', (event) => {
248
+ const next = (event as FocusEvent).relatedTarget as Element | null;
249
+ if (current && !current.row.contains(next)) close();
250
+ });
251
+
252
+ return {
253
+ open: (row, side) => open(row, side),
254
+ close: () => close(),
255
+ get openRow() {
256
+ return current?.row ?? null;
257
+ },
258
+ destroy: () => scope.dispose(),
259
+ };
260
+ }