@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,36 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createCommandSearch, type CommandItem, type CommandSearchInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-command-search class="mk-command-search" items='[{"id":"charts","label":"Charts","group":"Páginas","href":"/chart"}]'>
6
+ * <button data-mk-command-open>Buscar <kbd>⌘K</kbd></button>
7
+ * </mk-command-search>
8
+ * ⌘K / Ctrl+K or the button opens it. Emits mk:select ({item}, cancelable), mk:open and mk:close.
9
+ */
10
+ export class MkCommandSearchElement extends MkElement<CommandSearchInstance> {
11
+ static options: Record<string, OptionType> = { items: 'json', placeholder: 'string', emptyLabel: 'string', label: 'string', shortcut: 'boolean', limit: 'number' };
12
+
13
+ protected create(options: Record<string, unknown>) {
14
+ return createCommandSearch(this, options);
15
+ }
16
+
17
+ attributeChangedCallback(name: string, previous: string | null, next: string | null) {
18
+ if (previous === next || !this.instance) return;
19
+ if (name === 'items') this.instance.setItems((this.options.items as CommandItem[] | undefined) ?? []);
20
+ else super.attributeChangedCallback(name, previous, next);
21
+ }
22
+
23
+ open() {
24
+ this.instance?.open();
25
+ }
26
+
27
+ close() {
28
+ this.instance?.close();
29
+ }
30
+
31
+ setItems(items: CommandItem[]) {
32
+ this.instance?.setItems(items);
33
+ }
34
+ }
35
+
36
+ export const defineCommandSearch = () => defineElement('mk-command-search', MkCommandSearchElement);
@@ -0,0 +1,356 @@
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 { blurIn } from '../../motion/micro';
6
+ import { morphClose, morphOpen, releaseShared, showOrigin } from '../../motion/morph';
7
+ import { fadeBackdrop, openModal } from '../../motion/surface';
8
+ import { matchScore } from './match';
9
+
10
+ /*
11
+ * Command search: a command palette in a modal <dialog> on Melonmind's dark
12
+ * glass. ⌘K / Ctrl+K or a [data-mk-command-open] button opens it; it grows out
13
+ * of the button that opened it (motion/morph.ts container transform: the
14
+ * button's colour fades into the panel, the shape travels .7 s) and folds back
15
+ * into it on close. Typing filters by fuzzy
16
+ * match with the letters marked (as text); results are grouped. A pill glides
17
+ * under the active result on the swing curve; arrows, Home and End move it,
18
+ * Enter picks it (onSelect, mk:select; an href navigates), Escape closes and
19
+ * focus goes back. Combobox + listbox semantics with aria-activedescendant.
20
+ */
21
+
22
+ export interface CommandItem {
23
+ id: string;
24
+ label: string;
25
+ /** Section title (e.g. 'Páginas'). */
26
+ group?: string;
27
+ /** Extra words that also match. */
28
+ keywords?: string[];
29
+ /** Small text on the right (a shortcut, a path). */
30
+ hint?: string;
31
+ /** Navigate here when chosen (unless mk:select is cancelled). */
32
+ href?: string;
33
+ /** A short glyph shown before the label (default →). */
34
+ icon?: string;
35
+ }
36
+
37
+ export interface CommandSearchOptions {
38
+ items?: CommandItem[];
39
+ placeholder?: string;
40
+ emptyLabel?: string;
41
+ /** Accessible name of the palette. */
42
+ label?: string;
43
+ /** ⌘K / Ctrl+K opens it (default true). */
44
+ shortcut?: boolean;
45
+ /** Most results shown (default 50). */
46
+ limit?: number;
47
+ onSelect?: (item: CommandItem) => void;
48
+ onOpenChange?: (open: boolean) => void;
49
+ }
50
+
51
+ export interface CommandSearchInstance extends Instance {
52
+ /** `origin`: the control it grows from; omitted, the clicked or on-screen trigger; null, none (scale-in). */
53
+ open(origin?: HTMLElement | null): void;
54
+ close(): void;
55
+ setItems(items: CommandItem[]): void;
56
+ readonly isOpen: boolean;
57
+ }
58
+
59
+ const SEARCH_ICON =
60
+ '<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="6.5"/><path d="M16 16l4 4"/></svg>';
61
+ let sequence = 0;
62
+
63
+ const el = <K extends keyof HTMLElementTagNameMap>(tag: K, attrs: Record<string, string> = {}, text?: string) => {
64
+ const element = document.createElement(tag);
65
+ for (const [name, value] of Object.entries(attrs)) element.setAttribute(name, value);
66
+ if (text !== undefined) element.textContent = text;
67
+ return element;
68
+ };
69
+
70
+ /** `text` with the ranges wrapped in <mark>, built as text nodes. */
71
+ function marked(text: string, ranges: Array<[number, number]>) {
72
+ const fragment = document.createDocumentFragment();
73
+ let at = 0;
74
+ for (const [start, end] of ranges) {
75
+ if (start > at) fragment.append(text.slice(at, start));
76
+ fragment.append(el('mark', {}, text.slice(start, end)));
77
+ at = end;
78
+ }
79
+ if (at < text.length) fragment.append(text.slice(at));
80
+ return fragment;
81
+ }
82
+
83
+ export function createCommandSearch(root: HTMLElement | null, options: CommandSearchOptions = {}): CommandSearchInstance {
84
+ if (!root) return inert({ open() {}, close() {}, setItems() {}, isOpen: false });
85
+ registerMotion();
86
+ const scope = createScope();
87
+ const uid = `mk-command-${++sequence}`;
88
+ let items = [...(options.items ?? [])];
89
+ const limit = Number.isFinite(options.limit) && options.limit! > 0 ? Math.floor(options.limit!) : 50;
90
+
91
+ const dialog = el('dialog', { 'data-mk-command': '', 'aria-label': options.label ?? 'Buscar' }) as HTMLDialogElement;
92
+ const panel = el('div', { 'data-mk-command-panel': '' });
93
+ const field = el('div', { 'data-mk-command-field': '' });
94
+ const glass = el('span', { 'data-mk-command-icon': '', 'aria-hidden': 'true' });
95
+ glass.innerHTML = SEARCH_ICON;
96
+ const input = el('input', {
97
+ type: 'text',
98
+ role: 'combobox',
99
+ 'aria-expanded': 'true',
100
+ 'aria-controls': `${uid}-list`,
101
+ 'aria-autocomplete': 'list',
102
+ autocomplete: 'off',
103
+ spellcheck: 'false',
104
+ placeholder: options.placeholder ?? 'Buscar…',
105
+ 'aria-label': options.label ?? 'Buscar',
106
+ });
107
+ field.append(glass, input);
108
+ const list = el('div', { 'data-mk-command-list': '', role: 'listbox', id: `${uid}-list`, 'aria-label': 'Resultados' });
109
+ const pill = el('span', { 'data-mk-command-pill': '', 'aria-hidden': 'true' });
110
+ const empty = el('p', { 'data-mk-command-empty': '' }, options.emptyLabel ?? 'Sin resultados');
111
+ empty.hidden = true;
112
+ const live = el('span', { class: 'mk-sr-only', role: 'status' });
113
+ const footer = el('footer', { 'data-mk-command-footer': '', 'aria-hidden': 'true' });
114
+ footer.append(el('kbd', {}, '↑'), el('kbd', {}, '↓'), el('span', {}, 'Navegar'), el('kbd', {}, '↵'), el('span', {}, 'Seleccionar'));
115
+ const scroller = el('div', { 'data-mk-command-scroll': '' });
116
+ scroller.append(pill, list, empty);
117
+ panel.append(field, scroller, footer, live);
118
+ dialog.append(panel);
119
+ root.append(dialog);
120
+ scope.add(() => dialog.remove());
121
+ scope.attr(root, 'data-mk-ready', '');
122
+
123
+ let isOpen = false;
124
+ let opener: HTMLElement | null = null;
125
+ /** The control the panel grows from and folds back into. */
126
+ let from: HTMLElement | null = null;
127
+ let closing = false;
128
+ let backdropOut: Animation | null = null;
129
+ const onScreen = (element: HTMLElement) => {
130
+ const box = element.getBoundingClientRect();
131
+ return box.width > 0 && box.height > 0 && box.bottom > 0 && box.right > 0 && box.top < innerHeight && box.left < innerWidth;
132
+ };
133
+ const originFor = (given: HTMLElement | null | undefined) => {
134
+ if (given !== undefined) return given;
135
+ const trigger = root.querySelector<HTMLElement>('[data-mk-command-open]');
136
+ return trigger && onScreen(trigger) ? trigger : null;
137
+ };
138
+ let shown: CommandItem[] = [];
139
+ let active = -1;
140
+ const effects = new Set<gsap.core.Animation>();
141
+ const keep = (animation: gsap.core.Animation | null) => {
142
+ if (!animation) return;
143
+ effects.add(animation);
144
+ // Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
145
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
146
+ };
147
+ scope.add(() => {
148
+ for (const effect of effects) effect.kill();
149
+ effects.clear();
150
+ gsap.killTweensOf(pill);
151
+ });
152
+
153
+ const options_ = () => [...list.querySelectorAll<HTMLElement>('[role="option"]')];
154
+ const movePill = (animate: boolean) => {
155
+ const option = options_()[active];
156
+ if (!option) {
157
+ pill.style.opacity = '0';
158
+ return;
159
+ }
160
+ const box = { y: option.offsetTop, height: option.offsetHeight, width: option.offsetWidth, x: option.offsetLeft };
161
+ if (!animate || reduced() || pill.style.opacity === '0') {
162
+ gsap.set(pill, box);
163
+ pill.style.opacity = '1';
164
+ return;
165
+ }
166
+ keep(gsap.to(pill, { ...box, duration: 0.3, ease: EASE.swatch, overwrite: 'auto' }));
167
+ };
168
+
169
+ const setActive = (index: number, animate = true) => {
170
+ const all = options_();
171
+ if (!all.length) {
172
+ active = -1;
173
+ input.removeAttribute('aria-activedescendant');
174
+ movePill(false);
175
+ return;
176
+ }
177
+ active = (index + all.length) % all.length;
178
+ all.forEach((option, i) => option.setAttribute('aria-selected', String(i === active)));
179
+ input.setAttribute('aria-activedescendant', all[active]!.id);
180
+ all[active]!.scrollIntoView?.({ block: 'nearest' });
181
+ movePill(animate);
182
+ };
183
+
184
+ const render = (animate: boolean) => {
185
+ const query = input.value;
186
+ const scored = items
187
+ .map((item, order) => {
188
+ const own = matchScore(query, item.label);
189
+ const extra = own ? null : [item.group ?? '', ...(item.keywords ?? [])].map((word) => matchScore(query, word)).find(Boolean);
190
+ const result = own ?? (extra ? { score: extra.score - 20, ranges: [] as Array<[number, number]> } : null);
191
+ return result ? { item, order, ...result } : null;
192
+ })
193
+ .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry));
194
+ if (query.trim()) scored.sort((a, b) => b.score - a.score || a.order - b.order);
195
+ const top = scored.slice(0, limit);
196
+ shown = top.map((entry) => entry.item);
197
+ list.replaceChildren();
198
+ const groups = new Map<string, HTMLElement>();
199
+ top.forEach((entry, i) => {
200
+ const name = entry.item.group ?? '';
201
+ let group = groups.get(name);
202
+ if (!group) {
203
+ group = el('div', { role: 'group', 'data-mk-command-group': '' });
204
+ if (name) {
205
+ const title = el('div', { 'data-mk-command-group-label': '', id: `${uid}-g${groups.size}` }, name);
206
+ group.setAttribute('aria-labelledby', title.id);
207
+ group.append(title);
208
+ }
209
+ groups.set(name, group);
210
+ list.append(group);
211
+ }
212
+ const option = el('div', { role: 'option', id: `${uid}-o${i}`, 'data-mk-command-item': '', 'data-mk-index': String(i), 'aria-selected': 'false' });
213
+ option.append(el('span', { 'data-mk-command-arrow': '', 'aria-hidden': 'true' }, entry.item.icon ?? '→'));
214
+ const label = el('span', { 'data-mk-command-label': '' });
215
+ label.append(marked(entry.item.label, entry.ranges));
216
+ option.append(label);
217
+ if (entry.item.hint) option.append(el('span', { 'data-mk-command-hint': '' }, entry.item.hint));
218
+ group.append(option);
219
+ });
220
+ // Options were built in score order but sit under their group: index them in document order.
221
+ shown = options_().map((option, i) => {
222
+ const item = top[Number(option.dataset.mkIndex)]!.item;
223
+ option.dataset.mkIndex = String(i);
224
+ option.id = `${uid}-o${i}`;
225
+ return item;
226
+ });
227
+ empty.hidden = top.length > 0;
228
+ live.textContent = top.length ? `${top.length} ${top.length === 1 ? 'resultado' : 'resultados'}` : (options.emptyLabel ?? 'Sin resultados');
229
+ setActive(0, false);
230
+ if (animate) keep(blurIn(options_().slice(0, 12), { stagger: 0.025, duration: 0.35 }));
231
+ };
232
+
233
+ const finish = () => {
234
+ backdropOut?.cancel();
235
+ backdropOut = null;
236
+ if (dialog.open) dialog.close();
237
+ showOrigin(from);
238
+ releaseShared(from);
239
+ from = null;
240
+ closing = false;
241
+ isOpen = false;
242
+ options.onOpenChange?.(false);
243
+ root.dispatchEvent(new CustomEvent('mk:close', { bubbles: true }));
244
+ if (opener?.isConnected) opener.focus({ preventScroll: true });
245
+ opener = null;
246
+ };
247
+
248
+ const open = (origin?: HTMLElement | null) => {
249
+ if (scope.disposed || isOpen) return;
250
+ const focused = document.activeElement;
251
+ opener = focused instanceof HTMLElement && focused !== document.body ? focused : null;
252
+ from = originFor(origin);
253
+ isOpen = true;
254
+ input.value = '';
255
+ openModal(dialog);
256
+ // The rows grow with the panel: no entrance of their own on open.
257
+ render(false);
258
+ input.focus();
259
+ void fadeBackdrop(dialog, 'in');
260
+ morphOpen(panel, from, { hideOrigin: true });
261
+ options.onOpenChange?.(true);
262
+ root.dispatchEvent(new CustomEvent('mk:open', { bubbles: true, detail: { origin: from } }));
263
+ };
264
+
265
+ const close = () => {
266
+ if (!isOpen || closing) return;
267
+ closing = true;
268
+ // The backdrop fades with the fold; finish() drops this animation once the dialog is closed.
269
+ if (!reduced() && typeof dialog.animate === 'function') {
270
+ try {
271
+ backdropOut = dialog.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 300, easing: 'linear', fill: 'forwards', pseudoElement: '::backdrop' });
272
+ } catch {
273
+ backdropOut = null;
274
+ }
275
+ }
276
+ morphClose(panel, from, finish);
277
+ };
278
+
279
+ const choose = (index: number) => {
280
+ const item = shown[index];
281
+ if (!item) return;
282
+ const event = new CustomEvent('mk:select', { bubbles: true, cancelable: true, detail: { item } });
283
+ options.onSelect?.(item);
284
+ const go = root.dispatchEvent(event) && item.href;
285
+ close();
286
+ if (go) location.assign(item.href!);
287
+ };
288
+
289
+ scope.on(input, 'input', () => render(false));
290
+ scope.on(input, 'keydown', (event) => {
291
+ const key = (event as KeyboardEvent).key;
292
+ const count = options_().length;
293
+ const moves: Record<string, number> = { ArrowDown: active + 1, ArrowUp: active - 1, Home: 0, End: count - 1 };
294
+ if (key in moves && count) {
295
+ event.preventDefault();
296
+ setActive(moves[key]!);
297
+ } else if (key === 'Enter') {
298
+ event.preventDefault();
299
+ choose(active);
300
+ }
301
+ });
302
+ scope.on(list, 'pointermove', (event) => {
303
+ const option = (event.target as Element | null)?.closest<HTMLElement>('[role="option"]');
304
+ if (option) {
305
+ const index = Number(option.dataset.mkIndex);
306
+ if (index !== active) setActive(index);
307
+ }
308
+ });
309
+ scope.on(list, 'click', (event) => {
310
+ const option = (event.target as Element | null)?.closest<HTMLElement>('[role="option"]');
311
+ if (option) choose(Number(option.dataset.mkIndex));
312
+ });
313
+ scope.on(dialog, 'cancel', (event) => {
314
+ event.preventDefault();
315
+ close();
316
+ });
317
+ scope.on(dialog, 'click', (event) => {
318
+ if (event.target === dialog) close();
319
+ });
320
+ scope.on(root, 'click', (event) => {
321
+ const trigger = (event.target as Element | null)?.closest('[data-mk-command-open]');
322
+ if (trigger && root.contains(trigger)) {
323
+ event.preventDefault();
324
+ open(trigger as HTMLElement);
325
+ }
326
+ });
327
+ if (options.shortcut !== false) {
328
+ scope.on(document, 'keydown', (event) => {
329
+ const key = event as KeyboardEvent;
330
+ if ((key.metaKey || key.ctrlKey) && key.key.toLowerCase() === 'k') {
331
+ key.preventDefault();
332
+ if (isOpen) close();
333
+ else open();
334
+ }
335
+ });
336
+ }
337
+ scope.add(() => {
338
+ backdropOut?.cancel();
339
+ showOrigin(from);
340
+ releaseShared(from);
341
+ if (dialog.open) dialog.close();
342
+ });
343
+
344
+ return {
345
+ open,
346
+ close,
347
+ setItems(next) {
348
+ items = [...next];
349
+ if (isOpen) render(false);
350
+ },
351
+ get isOpen() {
352
+ return isOpen;
353
+ },
354
+ destroy: () => scope.dispose(),
355
+ };
356
+ }
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Fuzzy matching for the command search: the query's letters must appear in
3
+ * order (case and accents ignored). Word starts and runs of consecutive letters
4
+ * score higher; gaps cost. Returns the matched ranges for highlighting.
5
+ */
6
+
7
+ export interface Match {
8
+ score: number;
9
+ /** [start, end) ranges in the original text. */
10
+ ranges: Array<[number, number]>;
11
+ }
12
+
13
+ const fold = (text: string) => text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
14
+
15
+ export function matchScore(query: string, text: string): Match | null {
16
+ const needle = fold(query.trim());
17
+ if (!needle) return { score: 0, ranges: [] };
18
+ // Folding removes combining marks, so map folded positions back to the original text.
19
+ const map: number[] = [];
20
+ let hay = '';
21
+ for (let i = 0; i < text.length; i++) {
22
+ const folded = fold(text[i]!);
23
+ for (const char of folded) {
24
+ hay += char;
25
+ map.push(i);
26
+ }
27
+ }
28
+ // A plain substring wins outright (best at a word start).
29
+ const direct = hay.indexOf(needle);
30
+ if (direct >= 0) {
31
+ const start = direct === 0 || /[\s\-_/.]/.test(hay[direct - 1]!) ? 30 : 15;
32
+ return { score: 100 + start - direct * 0.5, ranges: [[map[direct]!, map[direct + needle.length - 1]! + 1]] };
33
+ }
34
+ let score = 0;
35
+ let at = 0;
36
+ let previous = -2;
37
+ const ranges: Array<[number, number]> = [];
38
+ for (const char of needle) {
39
+ if (char === ' ') continue;
40
+ const found = hay.indexOf(char, at);
41
+ if (found < 0) return null;
42
+ const wordStart = found === 0 || /[\s\-_/.]/.test(hay[found - 1]!);
43
+ score += wordStart ? 8 : found === previous + 1 ? 5 : 1;
44
+ score -= Math.min(4, (found - at) * 0.2);
45
+ const original = map[found]!;
46
+ const last = ranges[ranges.length - 1];
47
+ if (last && last[1] === original) last[1] = original + 1;
48
+ else ranges.push([original, original + 1]);
49
+ previous = found;
50
+ at = found + 1;
51
+ }
52
+ return { score, ranges };
53
+ }
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes, type MutableRefObject, type ReactNode } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createCommandSearch, type CommandItem, type CommandSearchInstance } from './engine';
5
+
6
+ export interface CommandSearchProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'onSelect' | 'children'> {
7
+ items: CommandItem[];
8
+ placeholder?: string;
9
+ emptyLabel?: string;
10
+ label?: string;
11
+ shortcut?: boolean;
12
+ limit?: number;
13
+ /** The trigger's content (default: «Buscar» and the shortcut). null hides the trigger. */
14
+ trigger?: ReactNode;
15
+ onSelect?: (item: CommandItem) => void;
16
+ onOpenChange?: (open: boolean) => void;
17
+ instanceRef?: MutableRefObject<CommandSearchInstance | null>;
18
+ }
19
+
20
+ /** <CommandSearch items={commands} onSelect={(item) => navigate(item.id)} />: ⌘K / Ctrl+K or the trigger opens it. */
21
+ export function CommandSearch({ items, placeholder, emptyLabel, label, shortcut, limit, trigger, onSelect, onOpenChange, instanceRef, className, ...props }: CommandSearchProps) {
22
+ const ref = useRef<HTMLSpanElement>(null);
23
+ const instance = useRef<CommandSearchInstance | null>(null);
24
+ const select = useLatest(onSelect);
25
+ const change = useLatest(onOpenChange);
26
+ const latest = useLatest(items);
27
+ useIsomorphicLayoutEffect(() => {
28
+ const created = createCommandSearch(ref.current, {
29
+ items: latest.current,
30
+ placeholder,
31
+ emptyLabel,
32
+ label,
33
+ shortcut,
34
+ limit,
35
+ onSelect: (item) => select.current?.(item),
36
+ onOpenChange: (open) => change.current?.(open),
37
+ });
38
+ instance.current = created;
39
+ if (instanceRef) instanceRef.current = created;
40
+ return () => {
41
+ created.destroy();
42
+ instance.current = null;
43
+ if (instanceRef) instanceRef.current = null;
44
+ };
45
+ // eslint-disable-next-line react-hooks/exhaustive-deps
46
+ }, [placeholder, emptyLabel, label, shortcut, limit]);
47
+ const key = JSON.stringify(items);
48
+ useIsomorphicLayoutEffect(() => {
49
+ instance.current?.setItems(latest.current);
50
+ // eslint-disable-next-line react-hooks/exhaustive-deps
51
+ }, [key]);
52
+ return (
53
+ <span {...props} ref={ref} className={['mk-command-search', className].filter(Boolean).join(' ')}>
54
+ {trigger === null ? null : (
55
+ <button type="button" data-mk-command-open="" aria-haspopup="dialog">
56
+ {trigger ?? (
57
+ <>
58
+ <span>Buscar…</span>
59
+ <kbd aria-hidden="true">⌘K</kbd>
60
+ </>
61
+ )}
62
+ </button>
63
+ )}
64
+ </span>
65
+ );
66
+ }
@@ -0,0 +1,31 @@
1
+ # context-menu
2
+
3
+ Menú contextual sobre el motor de [menu](../menu/README.md): misma superficie translúcida, cascada de opciones, flechas/Inicio/Fin, Enter, Escape, opciones deshabilitadas y separadores. Se abre con clic derecho en el puntero, con la tecla Menú o Shift+F10 junto al elemento enfocado, y con pulsación larga en táctil. Aparece con escala donde se abre (sin morph desde un origen invisible), se mantiene dentro del viewport y devuelve el foco al elemento enfocado antes de abrir.
4
+
5
+ ```html
6
+ <mk-context-menu class="mk-context-menu">
7
+ <article class="file-tile">Informe.pdf</article>
8
+ <dialog class="mk-dialog mk-menu" data-mk-context-menu aria-label="Acciones del archivo">
9
+ <button data-mk-item data-mk-value="open">Abrir</button>
10
+ <button data-mk-item data-mk-value="rename">Renombrar</button>
11
+ <hr data-mk-separator />
12
+ <button data-mk-item data-mk-value="delete" disabled>Eliminar</button>
13
+ </dialog>
14
+ </mk-context-menu>
15
+ ```
16
+
17
+ `defineElements()` o `createContextMenu(root, options)`; el área es `[data-mk-context-trigger]` o, si no hay, el root (recibe `tabindex="0"` si no es enfocable). React:
18
+
19
+ ```tsx
20
+ <ContextMenu
21
+ label="Acciones"
22
+ onSelect={(value) => run(value)}
23
+ menu={<><ContextMenu.Item value="open">Abrir</ContextMenu.Item><ContextMenu.Separator /><ContextMenu.Item value="delete" disabled>Eliminar</ContextMenu.Item></>}
24
+ >
25
+ <FileTile />
26
+ </ContextMenu>
27
+ ```
28
+
29
+ Opciones: las de Menu (`variant`, `closeOnSelect`, `onSelect`, `labels`…) más `overrideEditable` (por defecto los inputs y el texto editable conservan el menú del navegador), `longPress=550` ms (0 lo desactiva) y `onOpenChange(open)`. API: `openAt(x, y, origin?)`, `open(origin?)`, `close()`, `isOpen`, `menu`, `destroy()`. Eventos de Menu/Dialog: `mk:select` `{value, item}`, `mk:open`, `mk:closed`. El área recibe `aria-haspopup="menu"` y `aria-expanded` mientras está abierto.
30
+
31
+ En móvil (menos de 680 px) el menú se centra como el resto de menús anclados. `destroy()` cierra sin animación y restaura atributos.
@@ -0,0 +1,22 @@
1
+ /* context-menu — the area that answers a right click; its menu is .mk-menu, placed at the pointer. */
2
+ mk-context-menu {
3
+ display: block;
4
+ }
5
+
6
+ .mk-context-menu,
7
+ mk-context-menu {
8
+ -webkit-touch-callout: none;
9
+ }
10
+
11
+ :is(.mk-context-menu, mk-context-menu):focus-visible,
12
+ :is(.mk-context-menu, mk-context-menu) [data-mk-context-trigger]:focus-visible {
13
+ outline: 2px solid var(--mk-accent);
14
+ outline-offset: 3px;
15
+ }
16
+
17
+ /* The area pressed while its menu is open. */
18
+ :is(.mk-context-menu, mk-context-menu)[aria-expanded='true'],
19
+ :is(.mk-context-menu, mk-context-menu) [data-mk-context-trigger][aria-expanded='true'] {
20
+ outline: 2px solid color-mix(in srgb, var(--mk-accent) 45%, transparent);
21
+ outline-offset: 3px;
22
+ }
@@ -0,0 +1,40 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createContextMenu, type ContextMenuInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-context-menu class="mk-context-menu">
6
+ * <article>…the area…</article>
7
+ * <dialog class="mk-dialog mk-menu" data-mk-context-menu aria-label="Acciones">
8
+ * <button data-mk-item data-mk-value="rename">Renombrar</button>
9
+ * <hr data-mk-separator />
10
+ * <button data-mk-item data-mk-value="delete" disabled>Eliminar</button>
11
+ * </dialog>
12
+ * </mk-context-menu>
13
+ * Emits mk:select (detail.value, detail.item) from the dialog.
14
+ */
15
+ export class MkContextMenuElement extends MkElement<ContextMenuInstance> {
16
+ static options: Record<string, OptionType> = { keepOpen: 'boolean', overrideEditable: 'boolean', longPress: 'number', variant: 'string' };
17
+
18
+ protected create(options: Record<string, unknown>) {
19
+ const { keepOpen, ...rest } = options;
20
+ return createContextMenu(this, { ...rest, closeOnSelect: !keepOpen });
21
+ }
22
+
23
+ openAt(x: number, y: number) {
24
+ this.instance?.openAt(x, y);
25
+ }
26
+
27
+ open(origin?: HTMLElement | null) {
28
+ this.instance?.open(origin);
29
+ }
30
+
31
+ close() {
32
+ this.instance?.close();
33
+ }
34
+
35
+ get isOpen() {
36
+ return this.instance?.isOpen ?? false;
37
+ }
38
+ }
39
+
40
+ export const defineContextMenu = () => defineElement('mk-context-menu', MkContextMenuElement);