@yewdiel21/motion-kit 0.10.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +155 -93
  2. package/dist/auto/index.d.ts +53 -3
  3. package/dist/auto/index.js +101 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-IKQ5FODN.js → chunk-6P4P7NU6.js} +2 -218
  6. package/dist/chunk-6P4P7NU6.js.map +1 -0
  7. package/dist/chunk-CU6SXW3E.js +20334 -0
  8. package/dist/chunk-CU6SXW3E.js.map +1 -0
  9. package/dist/{chunk-XAKC2QY7.js → chunk-EY4BJKC4.js} +3 -28
  10. package/dist/chunk-EY4BJKC4.js.map +1 -0
  11. package/dist/chunk-QP4QJOEN.js +72 -0
  12. package/dist/chunk-QP4QJOEN.js.map +1 -0
  13. package/dist/css/components/activity-feed.css +240 -0
  14. package/dist/css/components/attachment.css +266 -0
  15. package/dist/css/components/breadcrumb.css +110 -0
  16. package/dist/css/components/chart-tooltip.css +168 -0
  17. package/dist/css/components/chart.css +296 -0
  18. package/dist/css/components/code-block.css +264 -0
  19. package/dist/css/components/color-area.css +82 -0
  20. package/dist/css/components/color-field.css +67 -0
  21. package/dist/css/components/color-picker.css +131 -0
  22. package/dist/css/components/color-slider.css +113 -0
  23. package/dist/css/components/command-search.css +252 -0
  24. package/dist/css/components/context-menu.css +23 -0
  25. package/dist/css/components/credit-card.css +288 -0
  26. package/dist/css/components/data-table.css +591 -0
  27. package/dist/css/components/date-range-picker.css +168 -0
  28. package/dist/css/components/date-time-picker.css +178 -0
  29. package/dist/css/components/drawer.css +58 -0
  30. package/dist/css/components/editable-table.css +201 -0
  31. package/dist/css/components/empty-state.css +61 -0
  32. package/dist/css/components/file-explorer.css +633 -0
  33. package/dist/css/components/filter-builder.css +232 -0
  34. package/dist/css/components/hold-confirm.css +87 -0
  35. package/dist/css/components/hover-card.css +13 -7
  36. package/dist/css/components/image-preview.css +195 -0
  37. package/dist/css/components/json-viewer.css +272 -0
  38. package/dist/css/components/like-button.css +78 -0
  39. package/dist/css/components/link-preview.css +275 -0
  40. package/dist/css/components/loading-button.css +77 -0
  41. package/dist/css/components/multi-select.css +267 -0
  42. package/dist/css/components/notification-bell.css +127 -0
  43. package/dist/css/components/password-strength.css +183 -0
  44. package/dist/css/components/popover.css +25 -0
  45. package/dist/css/components/progress.css +140 -0
  46. package/dist/css/components/questionnaire.css +217 -0
  47. package/dist/css/components/range-calendar.css +186 -0
  48. package/dist/css/components/range-slider.css +192 -0
  49. package/dist/css/components/resizable-panel.css +243 -0
  50. package/dist/css/components/rich-text-editor.css +284 -0
  51. package/dist/css/components/scratch-card.css +75 -0
  52. package/dist/css/components/skeleton.css +108 -0
  53. package/dist/css/components/split-view.css +19 -0
  54. package/dist/css/components/star-rating.css +80 -0
  55. package/dist/css/components/stat-card.css +164 -0
  56. package/dist/css/components/swipe-actions.css +105 -0
  57. package/dist/css/components/tag-input.css +244 -0
  58. package/dist/css/components/time-field.css +88 -0
  59. package/dist/css/components/timeline.css +219 -0
  60. package/dist/css/components/tooltip.css +79 -0
  61. package/dist/css/components/tree-view.css +210 -0
  62. package/dist/css/components/virtual-list.css +195 -0
  63. package/dist/css/tokens.css +185 -181
  64. package/dist/elements/index.d.ts +674 -5
  65. package/dist/elements/index.js +1361 -8
  66. package/dist/elements/index.js.map +1 -1
  67. package/dist/{engine-GDGwQnJE.d.ts → engine-Cn6IhE3z.d.ts} +1 -1
  68. package/dist/engine-QECQnGz1.d.ts +33 -0
  69. package/dist/index.css +9104 -189
  70. package/dist/index.d.ts +89 -5
  71. package/dist/index.js +243 -9
  72. package/dist/index.js.map +1 -1
  73. package/dist/react/index.d.ts +579 -23
  74. package/dist/react/index.js +2042 -7
  75. package/dist/react/index.js.map +1 -1
  76. package/dist/{reduced-BmBzKH94.d.ts → reduced-BBUusld6.d.ts} +9 -2
  77. package/dist/reveal-DEvp6U6T.d.ts +2381 -0
  78. package/dist/{store-DYb3tTsZ.d.ts → store-DjkTBX7f.d.ts} +2 -2
  79. package/package.json +1 -1
  80. package/registry.json +406 -1
  81. package/src/auto/auto-init.ts +98 -0
  82. package/src/components/activity-feed/README.md +56 -0
  83. package/src/components/activity-feed/activity-feed.css +239 -0
  84. package/src/components/activity-feed/element.ts +36 -0
  85. package/src/components/activity-feed/engine.ts +351 -0
  86. package/src/components/activity-feed/react.tsx +32 -0
  87. package/src/components/attachment/README.md +27 -0
  88. package/src/components/attachment/attachment.css +265 -0
  89. package/src/components/attachment/element.ts +50 -0
  90. package/src/components/attachment/engine.ts +469 -0
  91. package/src/components/attachment/react.tsx +65 -0
  92. package/src/components/breadcrumb/README.md +38 -0
  93. package/src/components/breadcrumb/breadcrumb.css +109 -0
  94. package/src/components/breadcrumb/element.ts +27 -0
  95. package/src/components/breadcrumb/engine.ts +249 -0
  96. package/src/components/breadcrumb/react.tsx +30 -0
  97. package/src/components/chart/README.md +24 -0
  98. package/src/components/chart/chart.css +295 -0
  99. package/src/components/chart/element.ts +41 -0
  100. package/src/components/chart/engine.ts +811 -0
  101. package/src/components/chart/react.tsx +50 -0
  102. package/src/components/chart-tooltip/README.md +53 -0
  103. package/src/components/chart-tooltip/chart-tooltip.css +167 -0
  104. package/src/components/chart-tooltip/element.ts +37 -0
  105. package/src/components/chart-tooltip/engine.ts +442 -0
  106. package/src/components/chart-tooltip/react.tsx +47 -0
  107. package/src/components/code-block/README.md +19 -0
  108. package/src/components/code-block/code-block.css +263 -0
  109. package/src/components/code-block/element.ts +34 -0
  110. package/src/components/code-block/engine.ts +254 -0
  111. package/src/components/code-block/highlight.ts +244 -0
  112. package/src/components/code-block/react.tsx +94 -0
  113. package/src/components/color-area/README.md +50 -0
  114. package/src/components/color-area/color-area.css +81 -0
  115. package/src/components/color-area/element.ts +26 -0
  116. package/src/components/color-area/engine.ts +246 -0
  117. package/src/components/color-area/react.tsx +42 -0
  118. package/src/components/color-field/README.md +43 -0
  119. package/src/components/color-field/color-field.css +66 -0
  120. package/src/components/color-field/element.ts +26 -0
  121. package/src/components/color-field/engine.ts +174 -0
  122. package/src/components/color-field/react.tsx +42 -0
  123. package/src/components/color-picker/README.md +45 -0
  124. package/src/components/color-picker/color-picker.css +130 -0
  125. package/src/components/color-picker/element.ts +45 -0
  126. package/src/components/color-picker/engine.ts +314 -0
  127. package/src/components/color-picker/react.tsx +47 -0
  128. package/src/components/color-slider/README.md +55 -0
  129. package/src/components/color-slider/color-slider.css +112 -0
  130. package/src/components/color-slider/element.ts +26 -0
  131. package/src/components/color-slider/engine.ts +234 -0
  132. package/src/components/color-slider/react.tsx +45 -0
  133. package/src/components/command-search/README.md +15 -0
  134. package/src/components/command-search/command-search.css +251 -0
  135. package/src/components/command-search/element.ts +36 -0
  136. package/src/components/command-search/engine.ts +356 -0
  137. package/src/components/command-search/match.ts +53 -0
  138. package/src/components/command-search/react.tsx +66 -0
  139. package/src/components/context-menu/README.md +31 -0
  140. package/src/components/context-menu/context-menu.css +22 -0
  141. package/src/components/context-menu/element.ts +40 -0
  142. package/src/components/context-menu/engine.ts +153 -0
  143. package/src/components/context-menu/react.tsx +57 -0
  144. package/src/components/credit-card/README.md +20 -0
  145. package/src/components/credit-card/card.ts +77 -0
  146. package/src/components/credit-card/credit-card.css +287 -0
  147. package/src/components/credit-card/element.ts +33 -0
  148. package/src/components/credit-card/engine.ts +410 -0
  149. package/src/components/credit-card/react.tsx +36 -0
  150. package/src/components/data-table/README.md +83 -0
  151. package/src/components/data-table/data-table.css +590 -0
  152. package/src/components/data-table/element.ts +54 -0
  153. package/src/components/data-table/engine.ts +1059 -0
  154. package/src/components/data-table/react.tsx +94 -0
  155. package/src/components/date-range-picker/README.md +51 -0
  156. package/src/components/date-range-picker/date-range-picker.css +167 -0
  157. package/src/components/date-range-picker/element.ts +52 -0
  158. package/src/components/date-range-picker/engine.ts +352 -0
  159. package/src/components/date-range-picker/react.tsx +57 -0
  160. package/src/components/date-time-picker/README.md +48 -0
  161. package/src/components/date-time-picker/date-time-picker.css +177 -0
  162. package/src/components/date-time-picker/element.ts +47 -0
  163. package/src/components/date-time-picker/engine.ts +350 -0
  164. package/src/components/date-time-picker/react.tsx +54 -0
  165. package/src/components/dialog/README.md +103 -102
  166. package/src/components/dialog/element.ts +123 -118
  167. package/src/components/dialog/engine.ts +449 -432
  168. package/src/components/drawer/README.md +28 -0
  169. package/src/components/drawer/drawer.css +57 -0
  170. package/src/components/drawer/element.ts +21 -0
  171. package/src/components/drawer/engine.ts +56 -0
  172. package/src/components/drawer/react.tsx +85 -0
  173. package/src/components/editable-table/README.md +67 -0
  174. package/src/components/editable-table/editable-table.css +200 -0
  175. package/src/components/editable-table/element.ts +45 -0
  176. package/src/components/editable-table/engine.ts +775 -0
  177. package/src/components/editable-table/react.tsx +79 -0
  178. package/src/components/empty-state/README.md +41 -0
  179. package/src/components/empty-state/element.ts +23 -0
  180. package/src/components/empty-state/empty-state.css +60 -0
  181. package/src/components/empty-state/engine.ts +232 -0
  182. package/src/components/empty-state/react.tsx +34 -0
  183. package/src/components/file-explorer/README.md +67 -0
  184. package/src/components/file-explorer/element.ts +39 -0
  185. package/src/components/file-explorer/engine.ts +1307 -0
  186. package/src/components/file-explorer/file-explorer.css +632 -0
  187. package/src/components/file-explorer/react.tsx +77 -0
  188. package/src/components/filter-builder/README.md +47 -0
  189. package/src/components/filter-builder/element.ts +34 -0
  190. package/src/components/filter-builder/engine.ts +365 -0
  191. package/src/components/filter-builder/filter-builder.css +231 -0
  192. package/src/components/filter-builder/react.tsx +36 -0
  193. package/src/components/hold-confirm/README.md +13 -0
  194. package/src/components/hold-confirm/element.ts +24 -0
  195. package/src/components/hold-confirm/engine.ts +216 -0
  196. package/src/components/hold-confirm/hold-confirm.css +86 -0
  197. package/src/components/hold-confirm/react.tsx +28 -0
  198. package/src/components/hover-card/README.md +6 -2
  199. package/src/components/hover-card/engine.ts +68 -6
  200. package/src/components/hover-card/hover-card.css +13 -7
  201. package/src/components/image-preview/README.md +16 -0
  202. package/src/components/image-preview/element.ts +26 -0
  203. package/src/components/image-preview/engine.ts +325 -0
  204. package/src/components/image-preview/image-preview.css +194 -0
  205. package/src/components/image-preview/react.tsx +43 -0
  206. package/src/components/json-viewer/README.md +56 -0
  207. package/src/components/json-viewer/element.ts +37 -0
  208. package/src/components/json-viewer/engine.ts +440 -0
  209. package/src/components/json-viewer/json-viewer.css +271 -0
  210. package/src/components/json-viewer/react.tsx +56 -0
  211. package/src/components/like-button/README.md +13 -0
  212. package/src/components/like-button/element.ts +47 -0
  213. package/src/components/like-button/engine.ts +145 -0
  214. package/src/components/like-button/like-button.css +77 -0
  215. package/src/components/like-button/react.tsx +39 -0
  216. package/src/components/link-preview/README.md +19 -0
  217. package/src/components/link-preview/element.ts +29 -0
  218. package/src/components/link-preview/engine.ts +192 -0
  219. package/src/components/link-preview/link-preview.css +274 -0
  220. package/src/components/link-preview/react.tsx +101 -0
  221. package/src/components/loading-button/README.md +45 -0
  222. package/src/components/loading-button/element.ts +34 -0
  223. package/src/components/loading-button/engine.ts +201 -0
  224. package/src/components/loading-button/loading-button.css +76 -0
  225. package/src/components/loading-button/react.tsx +43 -0
  226. package/src/components/multi-select/README.md +48 -0
  227. package/src/components/multi-select/element.ts +28 -0
  228. package/src/components/multi-select/engine.ts +294 -0
  229. package/src/components/multi-select/multi-select.css +266 -0
  230. package/src/components/multi-select/react.tsx +39 -0
  231. package/src/components/notification-bell/README.md +14 -0
  232. package/src/components/notification-bell/element.ts +48 -0
  233. package/src/components/notification-bell/engine.ts +202 -0
  234. package/src/components/notification-bell/notification-bell.css +126 -0
  235. package/src/components/notification-bell/react.tsx +57 -0
  236. package/src/components/password-strength/README.md +17 -0
  237. package/src/components/password-strength/element.ts +22 -0
  238. package/src/components/password-strength/engine.ts +212 -0
  239. package/src/components/password-strength/password-strength.css +182 -0
  240. package/src/components/password-strength/react.tsx +36 -0
  241. package/src/components/password-strength/score.ts +62 -0
  242. package/src/components/popover/README.md +44 -0
  243. package/src/components/popover/element.ts +31 -0
  244. package/src/components/popover/engine.ts +114 -0
  245. package/src/components/popover/popover.css +24 -0
  246. package/src/components/popover/react.tsx +50 -0
  247. package/src/components/progress/README.md +17 -0
  248. package/src/components/progress/element.ts +30 -0
  249. package/src/components/progress/engine.ts +173 -0
  250. package/src/components/progress/progress.css +139 -0
  251. package/src/components/progress/react.tsx +35 -0
  252. package/src/components/questionnaire/README.md +32 -0
  253. package/src/components/questionnaire/element.ts +118 -0
  254. package/src/components/questionnaire/engine.ts +255 -0
  255. package/src/components/questionnaire/questionnaire.css +216 -0
  256. package/src/components/questionnaire/react.tsx +140 -0
  257. package/src/components/range-calendar/README.md +58 -0
  258. package/src/components/range-calendar/element.ts +42 -0
  259. package/src/components/range-calendar/engine.ts +390 -0
  260. package/src/components/range-calendar/range-calendar.css +185 -0
  261. package/src/components/range-calendar/react.tsx +56 -0
  262. package/src/components/range-slider/README.md +23 -0
  263. package/src/components/range-slider/element.ts +53 -0
  264. package/src/components/range-slider/engine.ts +316 -0
  265. package/src/components/range-slider/range-slider.css +191 -0
  266. package/src/components/range-slider/react.tsx +66 -0
  267. package/src/components/resizable-panel/README.md +61 -0
  268. package/src/components/resizable-panel/element.ts +36 -0
  269. package/src/components/resizable-panel/engine.ts +551 -0
  270. package/src/components/resizable-panel/react.tsx +74 -0
  271. package/src/components/resizable-panel/resizable-panel.css +242 -0
  272. package/src/components/rich-text-editor/README.md +57 -0
  273. package/src/components/rich-text-editor/element.ts +34 -0
  274. package/src/components/rich-text-editor/engine.ts +673 -0
  275. package/src/components/rich-text-editor/react.tsx +60 -0
  276. package/src/components/rich-text-editor/rich-text-editor.css +283 -0
  277. package/src/components/scratch-card/README.md +15 -0
  278. package/src/components/scratch-card/element.ts +30 -0
  279. package/src/components/scratch-card/engine.ts +257 -0
  280. package/src/components/scratch-card/react.tsx +40 -0
  281. package/src/components/scratch-card/scratch-card.css +74 -0
  282. package/src/components/skeleton/README.md +15 -0
  283. package/src/components/skeleton/element.ts +32 -0
  284. package/src/components/skeleton/engine.ts +104 -0
  285. package/src/components/skeleton/react.tsx +41 -0
  286. package/src/components/skeleton/skeleton.css +107 -0
  287. package/src/components/split-view/README.md +50 -0
  288. package/src/components/split-view/element.ts +34 -0
  289. package/src/components/split-view/engine.ts +114 -0
  290. package/src/components/split-view/react.tsx +63 -0
  291. package/src/components/split-view/split-view.css +18 -0
  292. package/src/components/star-rating/README.md +14 -0
  293. package/src/components/star-rating/element.ts +26 -0
  294. package/src/components/star-rating/engine.ts +217 -0
  295. package/src/components/star-rating/react.tsx +33 -0
  296. package/src/components/star-rating/star-rating.css +79 -0
  297. package/src/components/stat-card/README.md +42 -0
  298. package/src/components/stat-card/element.ts +40 -0
  299. package/src/components/stat-card/engine.ts +283 -0
  300. package/src/components/stat-card/react.tsx +36 -0
  301. package/src/components/stat-card/stat-card.css +163 -0
  302. package/src/components/swipe-actions/README.md +19 -0
  303. package/src/components/swipe-actions/element.ts +26 -0
  304. package/src/components/swipe-actions/engine.ts +260 -0
  305. package/src/components/swipe-actions/react.tsx +80 -0
  306. package/src/components/swipe-actions/swipe-actions.css +104 -0
  307. package/src/components/tag-input/README.md +42 -0
  308. package/src/components/tag-input/element.ts +39 -0
  309. package/src/components/tag-input/engine.ts +313 -0
  310. package/src/components/tag-input/react.tsx +54 -0
  311. package/src/components/tag-input/tag-input.css +243 -0
  312. package/src/components/time-field/README.md +54 -0
  313. package/src/components/time-field/element.ts +38 -0
  314. package/src/components/time-field/engine.ts +110 -0
  315. package/src/components/time-field/react.tsx +45 -0
  316. package/src/components/time-field/time-field.css +87 -0
  317. package/src/components/timeline/README.md +57 -0
  318. package/src/components/timeline/element.ts +31 -0
  319. package/src/components/timeline/engine.ts +300 -0
  320. package/src/components/timeline/react.tsx +59 -0
  321. package/src/components/timeline/timeline.css +218 -0
  322. package/src/components/tooltip/README.md +46 -0
  323. package/src/components/tooltip/element.ts +22 -0
  324. package/src/components/tooltip/engine.ts +288 -0
  325. package/src/components/tooltip/react.tsx +30 -0
  326. package/src/components/tooltip/tooltip.css +78 -0
  327. package/src/components/tree-view/README.md +61 -0
  328. package/src/components/tree-view/element.ts +32 -0
  329. package/src/components/tree-view/engine.ts +569 -0
  330. package/src/components/tree-view/react.tsx +78 -0
  331. package/src/components/tree-view/tree-view.css +209 -0
  332. package/src/components/virtual-list/README.md +81 -0
  333. package/src/components/virtual-list/element.ts +42 -0
  334. package/src/components/virtual-list/engine.ts +678 -0
  335. package/src/components/virtual-list/react.tsx +101 -0
  336. package/src/components/virtual-list/virtual-list.css +194 -0
  337. package/src/core/chips.ts +196 -0
  338. package/src/core/color.ts +304 -0
  339. package/src/core/date-range.ts +67 -0
  340. package/src/core/dates.ts +11 -1
  341. package/src/core/files.ts +107 -0
  342. package/src/core/filter.ts +229 -0
  343. package/src/core/gsap.ts +80 -65
  344. package/src/core/listbox.ts +332 -0
  345. package/src/core/mark.ts +29 -0
  346. package/src/core/position.ts +89 -0
  347. package/src/core/relative-time.ts +77 -0
  348. package/src/core/segment-field.ts +373 -0
  349. package/src/core/segments.ts +153 -0
  350. package/src/core/tree.ts +116 -0
  351. package/src/core/virtual.ts +79 -0
  352. package/src/css/tokens.css +186 -182
  353. package/src/elements/index.ts +147 -0
  354. package/src/index.ts +72 -0
  355. package/src/motion/micro.ts +248 -0
  356. package/src/motion/sheet.ts +88 -65
  357. package/src/motion/surface.ts +9 -0
  358. package/src/react/index.ts +57 -0
  359. package/dist/chunk-IKQ5FODN.js.map +0 -1
  360. package/dist/chunk-J6OCFNGU.js +0 -177
  361. package/dist/chunk-J6OCFNGU.js.map +0 -1
  362. package/dist/chunk-TFNRIZHU.js +0 -3977
  363. package/dist/chunk-TFNRIZHU.js.map +0 -1
  364. package/dist/chunk-XAKC2QY7.js.map +0 -1
  365. package/dist/reveal-jsX-uwUz.d.ts +0 -625
@@ -0,0 +1,153 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, warnOnce, type Instance } from '../../core/types';
3
+ import { createMenu, type MenuInstance, type MenuOptions } from '../menu/engine';
4
+
5
+ /*
6
+ * Context menu: the options menu (same surface, cascade, arrows/Home/End,
7
+ * Enter, Escape, disabled items) opened by a right click at the pointer, by
8
+ * the Menu key or Shift+F10 beside the focused element, or by a long press on
9
+ * touch. It scales in where it opens (no morph from a phantom source), stays
10
+ * inside the viewport and gives focus back to the element focused before it.
11
+ * Inputs and editable text keep the browser's own menu unless overrideEditable.
12
+ * Parts: [data-mk-context-trigger] (else the root) and dialog[data-mk-context-menu].
13
+ */
14
+
15
+ export interface ContextMenuOptions extends Omit<MenuOptions, 'triggers' | 'anchor'> {
16
+ /** Replace the browser menu on inputs and editable text too. */
17
+ overrideEditable?: boolean;
18
+ /** Touch long press that opens it, in ms (0: off). Default 550. */
19
+ longPress?: number;
20
+ onOpenChange?: (open: boolean) => void;
21
+ }
22
+
23
+ export interface ContextMenuInstance extends Instance {
24
+ /** Open at viewport coordinates; focus returns to `origin` (default: the focused element). */
25
+ openAt(x: number, y: number, origin?: HTMLElement | null): void;
26
+ /** Open beside `origin` (default: the focused element in the trigger, else the trigger). */
27
+ open(origin?: HTMLElement | null): void;
28
+ close(): void;
29
+ readonly isOpen: boolean;
30
+ readonly menu: MenuInstance | null;
31
+ }
32
+
33
+ const TRIGGER = '[data-mk-context-trigger]';
34
+ const EDITABLE = 'input, textarea, select, [contenteditable]:not([contenteditable="false"])';
35
+ const SLOP = 10;
36
+
37
+ const pointBox = (x: number, y: number) =>
38
+ ({ x, y, left: x, top: y, right: x, bottom: y, width: 0, height: 0, toJSON: () => ({ x, y }) }) as DOMRect;
39
+
40
+ const focusable = (element: Element | null): element is HTMLElement =>
41
+ element instanceof HTMLElement && element !== document.body && element.isConnected && !element.hasAttribute('disabled') && (element.tabIndex >= 0 || element.matches('a[href], button, input, select, textarea, [tabindex]'));
42
+
43
+ export function createContextMenu(root: HTMLElement | null, options: ContextMenuOptions = {}): ContextMenuInstance {
44
+ const trigger = root ? (root.matches(TRIGGER) ? root : root.querySelector<HTMLElement>(TRIGGER) ?? root) : null;
45
+ const dialog = root?.querySelector<HTMLElement>('[data-mk-context-menu]') ?? null;
46
+ if (!root || !trigger || !dialog) {
47
+ warnOnce('context-menu: needs a [data-mk-context-menu] <dialog> inside the root');
48
+ return inert({ openAt() {}, open() {}, close() {}, isOpen: false, menu: null });
49
+ }
50
+ const scope = createScope();
51
+ const { overrideEditable = false, longPress = 550, onOpenChange, ...menuOptions } = options;
52
+ let box: DOMRect | null = null;
53
+ let returnTo: HTMLElement | null = null;
54
+
55
+ const menu = createMenu(dialog, { ...menuOptions, triggers: false, anchor: () => box });
56
+ scope.add(() => menu.destroy());
57
+ if (!focusable(trigger)) scope.attr(trigger, 'tabindex', '0');
58
+ scope.attr(trigger, 'aria-haspopup', 'menu');
59
+ scope.attr(root, 'data-mk-ready', '');
60
+
61
+ const inMenu = (target: EventTarget | null) => target instanceof Node && dialog.contains(target);
62
+ const editable = (target: EventTarget | null) => !overrideEditable && target instanceof Element && Boolean(target.closest(EDITABLE));
63
+
64
+ const show = (rect: DOMRect, origin: HTMLElement | null) => {
65
+ if (scope.disposed || menu.isOpen) return;
66
+ box = rect;
67
+ const active = document.activeElement;
68
+ returnTo = origin ?? (focusable(active) && !inMenu(active) ? active : focusable(trigger) ? trigger : null);
69
+ // null: no morph source; the menu scales in where it is placed.
70
+ menu.open(null);
71
+ };
72
+ const openAt = (x: number, y: number, origin?: HTMLElement | null) => {
73
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return;
74
+ show(pointBox(x, y), origin ?? null);
75
+ };
76
+ const open = (origin?: HTMLElement | null) => {
77
+ const active = document.activeElement;
78
+ const from = origin ?? (active instanceof HTMLElement && trigger.contains(active) && !inMenu(active) ? active : trigger);
79
+ show(from.getBoundingClientRect(), from);
80
+ };
81
+
82
+ scope.on(dialog, 'mk:open', () => {
83
+ scope.attr(trigger, 'aria-expanded', 'true');
84
+ onOpenChange?.(true);
85
+ });
86
+ scope.on(dialog, 'mk:closed', () => {
87
+ scope.attr(trigger, 'aria-expanded', null);
88
+ box = null;
89
+ const target = returnTo;
90
+ returnTo = null;
91
+ if (target?.isConnected) target.focus({ preventScroll: true });
92
+ onOpenChange?.(false);
93
+ });
94
+
95
+ scope.on(trigger, 'contextmenu', (event) => {
96
+ if (inMenu(event.target)) {
97
+ event.preventDefault();
98
+ return;
99
+ }
100
+ if (editable(event.target)) return;
101
+ event.preventDefault();
102
+ if (menu.isOpen) return;
103
+ const mouse = event as MouseEvent;
104
+ // A keyboard-made contextmenu has no pointer position worth using.
105
+ if (mouse.button === 0 && mouse.clientX === 0 && mouse.clientY === 0) open();
106
+ else openAt(mouse.clientX, mouse.clientY);
107
+ });
108
+ scope.on(trigger, 'keydown', (event) => {
109
+ const key = event as KeyboardEvent;
110
+ if (inMenu(key.target) || !(key.key === 'ContextMenu' || (key.key === 'F10' && key.shiftKey))) return;
111
+ if (editable(key.target)) return;
112
+ key.preventDefault();
113
+ open(key.target instanceof HTMLElement && trigger.contains(key.target) ? key.target : trigger);
114
+ });
115
+
116
+ if (longPress > 0) {
117
+ let timer: ReturnType<typeof setTimeout> | undefined;
118
+ let start: { x: number; y: number } | null = null;
119
+ const cancel = () => {
120
+ clearTimeout(timer);
121
+ timer = undefined;
122
+ start = null;
123
+ };
124
+ scope.on(trigger, 'pointerdown', (event) => {
125
+ const pointer = event as PointerEvent;
126
+ if (pointer.pointerType !== 'touch' || inMenu(pointer.target) || editable(pointer.target)) return;
127
+ cancel();
128
+ start = { x: pointer.clientX, y: pointer.clientY };
129
+ timer = setTimeout(() => {
130
+ const at = start;
131
+ cancel();
132
+ if (at) openAt(at.x, at.y, focusable(trigger) ? trigger : null);
133
+ }, longPress);
134
+ });
135
+ scope.on(trigger, 'pointermove', (event) => {
136
+ const pointer = event as PointerEvent;
137
+ if (start && Math.hypot(pointer.clientX - start.x, pointer.clientY - start.y) > SLOP) cancel();
138
+ });
139
+ for (const type of ['pointerup', 'pointercancel', 'pointerleave']) scope.on(trigger, type, cancel);
140
+ scope.add(cancel);
141
+ }
142
+
143
+ return {
144
+ openAt,
145
+ open,
146
+ close: () => menu.close(),
147
+ get isOpen() {
148
+ return menu.isOpen;
149
+ },
150
+ menu,
151
+ destroy: () => scope.dispose(),
152
+ };
153
+ }
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes, type ReactNode } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import type { DialogVariant } from '../dialog/engine';
5
+ import { Menu } from '../menu/react';
6
+ import { createContextMenu, type ContextMenuInstance } from './engine';
7
+
8
+ export interface ContextMenuProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {
9
+ /** The options: <ContextMenu.Item value="…">, <ContextMenu.Separator />. */
10
+ menu: ReactNode;
11
+ /** Accessible name of the menu. */
12
+ label?: string;
13
+ onSelect?: (value: string, item: HTMLElement) => void;
14
+ onOpenChange?: (open: boolean) => void;
15
+ closeOnSelect?: boolean;
16
+ overrideEditable?: boolean;
17
+ longPress?: number;
18
+ variant?: Exclude<DialogVariant, 'sheet' | 'drawer'>;
19
+ instanceRef?: { current: ContextMenuInstance | null };
20
+ }
21
+
22
+ function Root({ menu, label = 'Acciones', onSelect, onOpenChange, closeOnSelect = true, overrideEditable, longPress, variant, instanceRef, className, children, ...props }: ContextMenuProps) {
23
+ const ref = useRef<HTMLDivElement>(null);
24
+ const select = useLatest(onSelect);
25
+ const change = useLatest(onOpenChange);
26
+ useIsomorphicLayoutEffect(() => {
27
+ const created = createContextMenu(ref.current, {
28
+ closeOnSelect,
29
+ overrideEditable,
30
+ longPress,
31
+ variant,
32
+ onSelect: (value, item) => select.current?.(value, item),
33
+ onOpenChange: (open) => change.current?.(open),
34
+ });
35
+ if (instanceRef) instanceRef.current = created;
36
+ return () => {
37
+ created.destroy();
38
+ if (instanceRef) instanceRef.current = null;
39
+ };
40
+ // eslint-disable-next-line react-hooks/exhaustive-deps
41
+ }, [closeOnSelect, overrideEditable, longPress, variant]);
42
+ return (
43
+ <div {...props} ref={ref} className={['mk-context-menu', className].filter(Boolean).join(' ')} data-mk-context-trigger="">
44
+ {children}
45
+ <dialog data-mk-context-menu="" className="mk-dialog mk-menu" aria-label={label}>
46
+ {menu}
47
+ </dialog>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ /**
53
+ * context menu for React: the children are the area that answers a right click,
54
+ * Menu key, Shift+F10 or long press; `menu` holds the options.
55
+ * <ContextMenu menu={<><ContextMenu.Item value="rename">Renombrar</ContextMenu.Item><ContextMenu.Separator /></>} onSelect={run}>…</ContextMenu>
56
+ */
57
+ export const ContextMenu = Object.assign(Root, { Item: Menu.Item, Separator: Menu.Separator });
@@ -0,0 +1,20 @@
1
+ # credit-card
2
+
3
+ Tarjeta de crédito que se rellena mientras escribes y gira en 3D al llegar al CVC. Los campos son nativos (`autocomplete` cc-number, cc-name, cc-exp y cc-csc); la tarjeta es decorativa (`aria-hidden`).
4
+
5
+ ```html
6
+ <form>
7
+ <mk-credit-card class="mk-credit-card"></mk-credit-card>
8
+ <button class="mk-button">Pagar</button>
9
+ </form>
10
+ ```
11
+
12
+ `defineElements()`, `createCreditCard(root, options)` o `<CreditCard onComplete={…} />`. Con `input[data-mk-card-field="number|name|expiry|cvc"]` propios se usan esos; si faltan, el motor los crea.
13
+
14
+ Microinteracciones (GSAP): cada carácter nuevo entra en su hueco rodando desde abajo con blur; un marco se desliza hasta la parte de la tarjeta que se edita; la marca aparece con `pop`; enfocar el CVC gira la tarjeta 180° (0.8 s, curva swing de Melonmind) y salir la devuelve; un puntero fino la inclina un poco; un dato inválido hace temblar campo y tarjeta.
15
+
16
+ Opciones: `labels` (textos de campos y errores), `names` (nombres de formulario), `tilt=true`, `onChange({brand, complete, valid})`, `onComplete()`. API: `values` (`{number, name, expiry, cvc}`), `brand`, `valid`, `focus(field)`, `flip(back?)`, `destroy()`. Eventos `mk:change` y `mk:complete`, sin datos de la tarjeta.
17
+
18
+ Validación de formato: marca por prefijo (Visa, Mastercard, Amex, Discover; la marca se muestra como texto, sin logotipos), agrupación 4-4-4-4 o 4-6-5, Luhn, caducidad MM/AA no vencida y longitud del CVC (4 en Amex). Utilidades puras: `detectBrand`, `formatNumber`, `luhn`, `parseExpiry`, `cvcLength`. No procesa pagos ni guarda datos.
19
+
20
+ Variables: `--mk-card-width` (22rem), `--mk-card-bg`, `--mk-card-text`, `--mk-card-radius`. Movimiento reducido: los cambios y el giro son instantáneos.
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Card number helpers (pure): brand by prefix, grouping, Luhn, expiry and CVC
3
+ * length. They validate format only; payment checks belong to the app's processor.
4
+ */
5
+
6
+ export type CardBrand = 'visa' | 'mastercard' | 'amex' | 'discover' | 'unknown';
7
+
8
+ /** Text shown on the card for each brand (no logos). */
9
+ export const BRAND_LABELS: Record<CardBrand, string> = { visa: 'VISA', mastercard: 'Mastercard', amex: 'AMEX', discover: 'Discover', unknown: '' };
10
+
11
+ const only = (text: string) => String(text ?? '').replace(/\D/g, '');
12
+
13
+ export function detectBrand(number: string): CardBrand {
14
+ const digits = only(number);
15
+ if (/^4/.test(digits)) return 'visa';
16
+ if (/^3[47]/.test(digits)) return 'amex';
17
+ if (/^(6011|65|64[4-9])/.test(digits)) return 'discover';
18
+ const two = Number(digits.slice(0, 2));
19
+ const four = Number(digits.slice(0, 4));
20
+ if ((two >= 51 && two <= 55) || (digits.length >= 4 && four >= 2221 && four <= 2720)) return 'mastercard';
21
+ return 'unknown';
22
+ }
23
+
24
+ /** Digit groups of a brand: Amex 4-6-5, the rest 4-4-4-4. */
25
+ export const cardGroups = (brand: CardBrand) => (brand === 'amex' ? [4, 6, 5] : [4, 4, 4, 4]);
26
+ export const cardLength = (brand: CardBrand) => cardGroups(brand).reduce((sum, n) => sum + n, 0);
27
+ export const cvcLength = (brand: CardBrand) => (brand === 'amex' ? 4 : 3);
28
+
29
+ /** Digits with a space between groups. */
30
+ export function formatNumber(number: string, brand = detectBrand(number)) {
31
+ const digits = only(number).slice(0, cardLength(brand));
32
+ const parts: string[] = [];
33
+ let at = 0;
34
+ for (const size of cardGroups(brand)) {
35
+ if (at >= digits.length) break;
36
+ parts.push(digits.slice(at, at + size));
37
+ at += size;
38
+ }
39
+ return parts.join(' ');
40
+ }
41
+
42
+ export function luhn(number: string) {
43
+ const digits = only(number);
44
+ if (digits.length < 12) return false;
45
+ let sum = 0;
46
+ for (let i = 0; i < digits.length; i++) {
47
+ let n = Number(digits[digits.length - 1 - i]);
48
+ if (i % 2 === 1) {
49
+ n *= 2;
50
+ if (n > 9) n -= 9;
51
+ }
52
+ sum += n;
53
+ }
54
+ return sum % 10 === 0;
55
+ }
56
+
57
+ export interface Expiry {
58
+ month: number;
59
+ year: number;
60
+ /** MM/YY as typed so far. */
61
+ text: string;
62
+ valid: boolean;
63
+ }
64
+
65
+ /** MM/YY from what was typed; valid when complete and not before the current month. */
66
+ export function parseExpiry(text: string, now = new Date()): Expiry {
67
+ const digits = only(text).slice(0, 4);
68
+ let formatted = digits;
69
+ // A first digit above 1 is a month on its own: 4 → 04.
70
+ if (/^[2-9]$/.test(digits)) formatted = `0${digits}`;
71
+ if (formatted.length >= 2) formatted = `${formatted.slice(0, 2)}/${formatted.slice(2)}`;
72
+ const month = Number(formatted.slice(0, 2));
73
+ const year = formatted.length === 5 ? 2000 + Number(formatted.slice(3, 5)) : NaN;
74
+ const end = new Date(year, month, 1);
75
+ const valid = formatted.length === 5 && month >= 1 && month <= 12 && end > new Date(now.getFullYear(), now.getMonth(), 1);
76
+ return { month, year, text: formatted, valid };
77
+ }
@@ -0,0 +1,287 @@
1
+ /* credit-card — a dark card that fills in as you type (front: chip, brand, number, holder, expiry; back: stripe and CVC),
2
+ above Melonmind fields (16 px radius, surface-container, 3 px focus ring). The engine rolls the characters in, glides
3
+ the frame and turns the card in 3D. */
4
+ .mk-credit-card,
5
+ mk-credit-card {
6
+ --mk-card-width: 22rem;
7
+ --mk-card-bg: linear-gradient(145deg, #2b2c30 0%, #121315 55%, #050506 100%);
8
+ --mk-card-text: #f2f3f5;
9
+ --mk-card-muted: rgb(242 243 245 / 0.55);
10
+ --mk-card-radius: 20px;
11
+ display: grid;
12
+ gap: 1.5rem;
13
+ justify-items: center;
14
+ color: var(--mk-text);
15
+ font-family: var(--mk-font);
16
+ }
17
+
18
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-visual] {
19
+ width: min(100%, var(--mk-card-width));
20
+ aspect-ratio: 1.586;
21
+ container-type: inline-size;
22
+ perspective: 1200px;
23
+ cursor: pointer;
24
+ user-select: none;
25
+ transform-style: preserve-3d;
26
+ }
27
+
28
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-inner] {
29
+ position: relative;
30
+ width: 100%;
31
+ height: 100%;
32
+ transform-style: preserve-3d;
33
+ }
34
+
35
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-face] {
36
+ position: absolute;
37
+ inset: 0;
38
+ overflow: hidden;
39
+ padding: 7% 7.5%;
40
+ border-radius: var(--mk-card-radius);
41
+ background: var(--mk-card-bg);
42
+ box-shadow:
43
+ inset 0 1px 0 rgb(255 255 255 / 0.12),
44
+ inset 0 0 0 1px rgb(255 255 255 / 0.06),
45
+ 0 24px 48px -20px rgb(0 0 0 / 0.6);
46
+ color: var(--mk-card-text);
47
+ backface-visibility: hidden;
48
+ -webkit-backface-visibility: hidden;
49
+ }
50
+
51
+ /* A soft sheen across the face. */
52
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-face]::before {
53
+ content: '';
54
+ position: absolute;
55
+ inset: 0;
56
+ background: radial-gradient(120% 80% at 15% 0%, rgb(255 255 255 / 0.1), transparent 55%);
57
+ pointer-events: none;
58
+ }
59
+
60
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-face='back'] {
61
+ padding: 0;
62
+ transform: rotateY(180deg);
63
+ }
64
+
65
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-chip] {
66
+ position: absolute;
67
+ top: 12%;
68
+ left: 7.5%;
69
+ width: 13%;
70
+ aspect-ratio: 1.3;
71
+ border-radius: 6px;
72
+ background:
73
+ linear-gradient(90deg, transparent 32%, rgb(0 0 0 / 0.25) 33% 35%, transparent 36% 64%, rgb(0 0 0 / 0.25) 65% 67%, transparent 68%),
74
+ linear-gradient(180deg, transparent 45%, rgb(0 0 0 / 0.25) 46% 54%, transparent 55%),
75
+ linear-gradient(135deg, #e9e1c4, #b9ad86);
76
+ }
77
+
78
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-brand] {
79
+ position: absolute;
80
+ top: 11%;
81
+ right: 7.5%;
82
+ font-size: clamp(0.9rem, 4.6cqi, 1.35rem);
83
+ font-style: italic;
84
+ font-weight: 800;
85
+ letter-spacing: 0.02em;
86
+ }
87
+
88
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-face='back'] [data-mk-card-brand] {
89
+ top: auto;
90
+ bottom: 11%;
91
+ }
92
+
93
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-region] {
94
+ position: absolute;
95
+ display: grid;
96
+ gap: 0.15rem;
97
+ }
98
+
99
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-region='number'] {
100
+ bottom: 13%;
101
+ left: 7.5%;
102
+ }
103
+
104
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-region='name'] {
105
+ bottom: 33%;
106
+ left: 7.5%;
107
+ max-width: 58%;
108
+ }
109
+
110
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-region='expiry'] {
111
+ right: 7.5%;
112
+ bottom: 33%;
113
+ text-align: right;
114
+ }
115
+
116
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-caption] {
117
+ color: var(--mk-card-muted);
118
+ font-size: 0.5625rem;
119
+ letter-spacing: 0.08em;
120
+ text-transform: uppercase;
121
+ }
122
+
123
+ :is(.mk-credit-card, mk-credit-card) :is([data-mk-card-number], [data-mk-card-name], [data-mk-card-expiry], [data-mk-card-cvc]) {
124
+ display: inline-flex;
125
+ overflow: hidden;
126
+ font-family: var(--mk-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
127
+ white-space: pre;
128
+ }
129
+
130
+ :is(.mk-credit-card, mk-credit-card) :is([data-mk-card-number], [data-mk-card-name], [data-mk-card-expiry], [data-mk-card-cvc]) > span {
131
+ display: inline-block;
132
+ }
133
+
134
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-number] {
135
+ font-size: clamp(1rem, 5.4cqi, 1.45rem);
136
+ font-weight: 500;
137
+ letter-spacing: 0.06em;
138
+ }
139
+
140
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-number] [data-mk-space] {
141
+ width: 0.6em;
142
+ }
143
+
144
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-name] {
145
+ overflow: hidden;
146
+ font-size: 0.8125rem;
147
+ font-weight: 500;
148
+ letter-spacing: 0.06em;
149
+ text-overflow: ellipsis;
150
+ }
151
+
152
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-expiry] {
153
+ justify-content: flex-end;
154
+ font-size: 0.8125rem;
155
+ font-weight: 500;
156
+ }
157
+
158
+ :is(.mk-credit-card, mk-credit-card) [data-mk-placeholder] {
159
+ color: var(--mk-card-muted);
160
+ }
161
+
162
+ /* The frame around the part being edited. */
163
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-frame] {
164
+ position: absolute;
165
+ top: 0;
166
+ left: 0;
167
+ border-radius: 10px;
168
+ box-shadow:
169
+ inset 0 0 0 1.5px rgb(255 255 255 / 0.55),
170
+ 0 0 18px rgb(255 255 255 / 0.12);
171
+ opacity: 0;
172
+ pointer-events: none;
173
+ }
174
+
175
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-stripe] {
176
+ position: absolute;
177
+ top: 14%;
178
+ inset-inline: 0;
179
+ height: 20%;
180
+ background: #050506;
181
+ }
182
+
183
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-signature] {
184
+ position: absolute;
185
+ top: 44%;
186
+ left: 7.5%;
187
+ right: 7.5%;
188
+ display: flex;
189
+ justify-content: flex-end;
190
+ align-items: center;
191
+ height: 18%;
192
+ padding: 0 0.75rem;
193
+ border-radius: 6px;
194
+ background: repeating-linear-gradient(135deg, #e9e9ec 0 6px, #dcdce0 6px 12px);
195
+ color: #121315;
196
+ }
197
+
198
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-cvc] {
199
+ padding: 0.15rem 0.45rem;
200
+ border-radius: 4px;
201
+ background: #fff;
202
+ font-size: 0.875rem;
203
+ font-style: italic;
204
+ font-weight: 600;
205
+ }
206
+
207
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-cvc] [data-mk-placeholder] {
208
+ color: #9a9ca3;
209
+ }
210
+
211
+ /* Fields (Melonmind input: surface-container-high, 16 px radius, 3 px focus ring). */
212
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-fields] {
213
+ display: grid;
214
+ grid-template-columns: 1fr 1fr;
215
+ gap: 0.75rem;
216
+ width: min(100%, var(--mk-card-width));
217
+ }
218
+
219
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-label] {
220
+ display: grid;
221
+ gap: 0.35rem;
222
+ min-width: 0;
223
+ color: var(--mk-muted);
224
+ font-size: 0.8125rem;
225
+ }
226
+
227
+ :is(.mk-credit-card, mk-credit-card) :is([data-mk-card-label='number'], [data-mk-card-label='name']) {
228
+ grid-column: 1 / -1;
229
+ }
230
+
231
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-field] {
232
+ width: 100%;
233
+ min-height: 3rem;
234
+ padding: 0 1rem;
235
+ border: 0;
236
+ border-radius: 16px;
237
+ outline: none;
238
+ background: var(--mk-surface-4);
239
+ color: var(--mk-text);
240
+ font: inherit;
241
+ font-size: 1rem;
242
+ letter-spacing: 0.02em;
243
+ transition:
244
+ box-shadow 0.5s,
245
+ background-color 0.5s;
246
+ }
247
+
248
+ :is(.mk-credit-card, mk-credit-card) :is([data-mk-card-field='number'], [data-mk-card-field='expiry'], [data-mk-card-field='cvc']) {
249
+ font-family: var(--mk-font-mono, ui-monospace, monospace);
250
+ font-variant-numeric: tabular-nums;
251
+ }
252
+
253
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-field]:hover {
254
+ background: var(--mk-surface-5);
255
+ }
256
+
257
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-field]:focus {
258
+ background: var(--mk-surface-2);
259
+ box-shadow:
260
+ 0 0 0 3px var(--mk-outline-soft),
261
+ 0 0.5px 2.5px rgb(0 0 0 / 0.3);
262
+ }
263
+
264
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-field][aria-invalid='true'] {
265
+ background: var(--mk-error-soft);
266
+ color: var(--mk-on-error-soft);
267
+ }
268
+
269
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-error] {
270
+ min-height: 0;
271
+ color: var(--mk-error);
272
+ font-size: 0.75rem;
273
+ }
274
+
275
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-error]:empty {
276
+ display: none;
277
+ }
278
+
279
+ mk-credit-card {
280
+ display: grid;
281
+ }
282
+
283
+ @media (prefers-reduced-motion: reduce) {
284
+ :is(.mk-credit-card, mk-credit-card) [data-mk-card-field] {
285
+ transition: none;
286
+ }
287
+ }
@@ -0,0 +1,33 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createCreditCard, type CreditCardField, type CreditCardInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-credit-card class="mk-credit-card"></mk-credit-card>
6
+ * Makes the card and the four fields (or uses input[data-mk-card-field="number|name|expiry|cvc"] inside).
7
+ * Emits mk:change ({brand, complete, valid}) and mk:complete; the card data stays in the form.
8
+ */
9
+ export class MkCreditCardElement extends MkElement<CreditCardInstance> {
10
+ static options: Record<string, OptionType> = { tilt: 'boolean', labels: 'json', names: 'json' };
11
+
12
+ protected create(options: Record<string, unknown>) {
13
+ return createCreditCard(this, options);
14
+ }
15
+
16
+ focusField(field: CreditCardField) {
17
+ this.instance?.focus(field);
18
+ }
19
+
20
+ flip(back?: boolean) {
21
+ this.instance?.flip(back);
22
+ }
23
+
24
+ get values() {
25
+ return this.instance?.values;
26
+ }
27
+
28
+ get brand() {
29
+ return this.instance?.brand ?? 'unknown';
30
+ }
31
+ }
32
+
33
+ export const defineCreditCard = () => defineElement('mk-credit-card', MkCreditCardElement);