@yewdiel21/motion-kit 0.10.0 → 0.15.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 (459) hide show
  1. package/README.md +81 -0
  2. package/bin/motion-kit.mjs +3 -3
  3. package/dist/auto/index.d.ts +56 -3
  4. package/dist/auto/index.js +107 -3
  5. package/dist/auto/index.js.map +1 -1
  6. package/dist/{chunk-XAKC2QY7.js → chunk-6IRJYXJU.js} +3 -28
  7. package/dist/chunk-6IRJYXJU.js.map +1 -0
  8. package/dist/chunk-A6QQCF6V.js +21333 -0
  9. package/dist/chunk-A6QQCF6V.js.map +1 -0
  10. package/dist/chunk-JXRS3OEA.js +3638 -0
  11. package/dist/chunk-JXRS3OEA.js.map +1 -0
  12. package/dist/chunk-QP4QJOEN.js +72 -0
  13. package/dist/chunk-QP4QJOEN.js.map +1 -0
  14. package/dist/css/components/activity-feed.css +240 -0
  15. package/dist/css/components/add-to-cart.css +72 -0
  16. package/dist/css/components/attachment.css +266 -0
  17. package/dist/css/components/bidirectional-slider.css +168 -0
  18. package/dist/css/components/breadcrumb.css +110 -0
  19. package/dist/css/components/chart-tooltip.css +168 -0
  20. package/dist/css/components/chart.css +296 -0
  21. package/dist/css/components/code-block.css +264 -0
  22. package/dist/css/components/color-area.css +82 -0
  23. package/dist/css/components/color-field.css +67 -0
  24. package/dist/css/components/color-picker.css +131 -0
  25. package/dist/css/components/color-slider.css +113 -0
  26. package/dist/css/components/command-search.css +252 -0
  27. package/dist/css/components/context-menu.css +23 -0
  28. package/dist/css/components/credit-card.css +288 -0
  29. package/dist/css/components/data-table.css +686 -0
  30. package/dist/css/components/date-range-picker.css +168 -0
  31. package/dist/css/components/date-time-picker.css +178 -0
  32. package/dist/css/components/drawer.css +58 -0
  33. package/dist/css/components/editable-table.css +201 -0
  34. package/dist/css/components/empty-state.css +61 -0
  35. package/dist/css/components/file-explorer.css +633 -0
  36. package/dist/css/components/filter-builder.css +227 -0
  37. package/dist/css/components/heatmap-chart.css +391 -0
  38. package/dist/css/components/hold-confirm.css +87 -0
  39. package/dist/css/components/hover-card.css +19 -7
  40. package/dist/css/components/image-preview.css +283 -0
  41. package/dist/css/components/json-viewer.css +304 -0
  42. package/dist/css/components/like-button.css +78 -0
  43. package/dist/css/components/link-preview.css +275 -0
  44. package/dist/css/components/loading-button.css +77 -0
  45. package/dist/css/components/multi-select.css +267 -0
  46. package/dist/css/components/nav.css +102 -102
  47. package/dist/css/components/notification-bell.css +127 -0
  48. package/dist/css/components/orbit-menu.css +130 -0
  49. package/dist/css/components/pagination.css +10 -5
  50. package/dist/css/components/password-strength.css +183 -0
  51. package/dist/css/components/pie-chart.css +390 -0
  52. package/dist/css/components/pill-button.css +135 -0
  53. package/dist/css/components/popover.css +25 -0
  54. package/dist/css/components/progress.css +140 -0
  55. package/dist/css/components/questionnaire.css +217 -0
  56. package/dist/css/components/radar-chart.css +370 -0
  57. package/dist/css/components/radial-chart.css +398 -0
  58. package/dist/css/components/range-calendar.css +186 -0
  59. package/dist/css/components/range-slider.css +192 -0
  60. package/dist/css/components/resizable-panel.css +243 -0
  61. package/dist/css/components/rich-text-editor.css +284 -0
  62. package/dist/css/components/sankey-chart.css +358 -0
  63. package/dist/css/components/scratch-card.css +75 -0
  64. package/dist/css/components/scroll-island.css +111 -0
  65. package/dist/css/components/select.css +136 -0
  66. package/dist/css/components/skeleton.css +108 -0
  67. package/dist/css/components/split-view.css +19 -0
  68. package/dist/css/components/star-rating.css +80 -0
  69. package/dist/css/components/stat-card.css +164 -0
  70. package/dist/css/components/swipe-actions.css +105 -0
  71. package/dist/css/components/tag-input.css +244 -0
  72. package/dist/css/components/time-field.css +88 -0
  73. package/dist/css/components/timeline.css +219 -0
  74. package/dist/css/components/toolbar.css +122 -0
  75. package/dist/css/components/tooltip.css +79 -0
  76. package/dist/css/components/tree-view.css +289 -0
  77. package/dist/css/components/virtual-list.css +195 -0
  78. package/dist/css/components/voice-dots.css +70 -0
  79. package/dist/css/tokens.css +185 -181
  80. package/dist/elements/index.d.ts +849 -7
  81. package/dist/elements/index.js +1642 -8
  82. package/dist/elements/index.js.map +1 -1
  83. package/dist/{engine-GDGwQnJE.d.ts → engine-5zRIssTO.d.ts} +11 -1
  84. package/dist/engine-QECQnGz1.d.ts +33 -0
  85. package/dist/index.css +10890 -311
  86. package/dist/index.d.ts +89 -5
  87. package/dist/index.js +273 -9
  88. package/dist/index.js.map +1 -1
  89. package/dist/react/index.d.ts +686 -24
  90. package/dist/react/index.js +2345 -9
  91. package/dist/react/index.js.map +1 -1
  92. package/dist/{reduced-BmBzKH94.d.ts → reduced-6X_bns60.d.ts} +9 -2
  93. package/dist/reveal-BfyKKsw4.d.ts +2474 -0
  94. package/dist/store-CA2Qcun_.d.ts +453 -0
  95. package/package.json +9 -16
  96. package/registry.json +1096 -543
  97. package/src/auto/auto-init.ts +104 -0
  98. package/src/components/activity-feed/README.md +56 -0
  99. package/src/components/activity-feed/activity-feed.css +239 -0
  100. package/src/components/activity-feed/element.ts +36 -0
  101. package/src/components/activity-feed/engine.ts +351 -0
  102. package/src/components/activity-feed/react.tsx +32 -0
  103. package/src/components/add-to-cart/README.md +34 -0
  104. package/src/components/add-to-cart/add-to-cart.css +71 -0
  105. package/src/components/add-to-cart/element.ts +20 -0
  106. package/src/components/add-to-cart/engine.ts +206 -0
  107. package/src/components/add-to-cart/react.tsx +29 -0
  108. package/src/components/attachment/README.md +27 -0
  109. package/src/components/attachment/attachment.css +265 -0
  110. package/src/components/attachment/element.ts +50 -0
  111. package/src/components/attachment/engine.ts +469 -0
  112. package/src/components/attachment/react.tsx +65 -0
  113. package/src/components/bidirectional-slider/README.md +46 -0
  114. package/src/components/bidirectional-slider/bidirectional-slider.css +167 -0
  115. package/src/components/bidirectional-slider/element.ts +42 -0
  116. package/src/components/bidirectional-slider/engine.ts +289 -0
  117. package/src/components/bidirectional-slider/react.tsx +45 -0
  118. package/src/components/breadcrumb/README.md +38 -0
  119. package/src/components/breadcrumb/breadcrumb.css +109 -0
  120. package/src/components/breadcrumb/element.ts +27 -0
  121. package/src/components/breadcrumb/engine.ts +249 -0
  122. package/src/components/breadcrumb/react.tsx +30 -0
  123. package/src/components/chart/README.md +34 -0
  124. package/src/components/chart/chart.css +295 -0
  125. package/src/components/chart/element.ts +43 -0
  126. package/src/components/chart/engine.ts +838 -0
  127. package/src/components/chart/parts.ts +237 -0
  128. package/src/components/chart/react.tsx +50 -0
  129. package/src/components/chart/use-chart.ts +55 -0
  130. package/src/components/chart-tooltip/README.md +53 -0
  131. package/src/components/chart-tooltip/chart-tooltip.css +167 -0
  132. package/src/components/chart-tooltip/element.ts +37 -0
  133. package/src/components/chart-tooltip/engine.ts +442 -0
  134. package/src/components/chart-tooltip/react.tsx +47 -0
  135. package/src/components/code-block/README.md +19 -0
  136. package/src/components/code-block/code-block.css +263 -0
  137. package/src/components/code-block/element.ts +34 -0
  138. package/src/components/code-block/engine.ts +254 -0
  139. package/src/components/code-block/highlight.ts +244 -0
  140. package/src/components/code-block/react.tsx +94 -0
  141. package/src/components/color-area/README.md +50 -0
  142. package/src/components/color-area/color-area.css +81 -0
  143. package/src/components/color-area/element.ts +26 -0
  144. package/src/components/color-area/engine.ts +246 -0
  145. package/src/components/color-area/react.tsx +42 -0
  146. package/src/components/color-field/README.md +43 -0
  147. package/src/components/color-field/color-field.css +66 -0
  148. package/src/components/color-field/element.ts +26 -0
  149. package/src/components/color-field/engine.ts +174 -0
  150. package/src/components/color-field/react.tsx +42 -0
  151. package/src/components/color-picker/README.md +45 -0
  152. package/src/components/color-picker/color-picker.css +130 -0
  153. package/src/components/color-picker/element.ts +45 -0
  154. package/src/components/color-picker/engine.ts +314 -0
  155. package/src/components/color-picker/react.tsx +47 -0
  156. package/src/components/color-slider/README.md +55 -0
  157. package/src/components/color-slider/color-slider.css +112 -0
  158. package/src/components/color-slider/element.ts +26 -0
  159. package/src/components/color-slider/engine.ts +234 -0
  160. package/src/components/color-slider/react.tsx +45 -0
  161. package/src/components/command-search/README.md +15 -0
  162. package/src/components/command-search/command-search.css +251 -0
  163. package/src/components/command-search/element.ts +36 -0
  164. package/src/components/command-search/engine.ts +356 -0
  165. package/src/components/command-search/match.ts +53 -0
  166. package/src/components/command-search/react.tsx +66 -0
  167. package/src/components/context-menu/README.md +31 -0
  168. package/src/components/context-menu/context-menu.css +22 -0
  169. package/src/components/context-menu/element.ts +40 -0
  170. package/src/components/context-menu/engine.ts +153 -0
  171. package/src/components/context-menu/react.tsx +57 -0
  172. package/src/components/credit-card/README.md +20 -0
  173. package/src/components/credit-card/card.ts +77 -0
  174. package/src/components/credit-card/credit-card.css +287 -0
  175. package/src/components/credit-card/element.ts +33 -0
  176. package/src/components/credit-card/engine.ts +410 -0
  177. package/src/components/credit-card/react.tsx +36 -0
  178. package/src/components/data-table/README.md +83 -0
  179. package/src/components/data-table/data-table.css +685 -0
  180. package/src/components/data-table/element.ts +54 -0
  181. package/src/components/data-table/engine.ts +1111 -0
  182. package/src/components/data-table/react.tsx +94 -0
  183. package/src/components/date-range-picker/README.md +51 -0
  184. package/src/components/date-range-picker/date-range-picker.css +167 -0
  185. package/src/components/date-range-picker/element.ts +52 -0
  186. package/src/components/date-range-picker/engine.ts +352 -0
  187. package/src/components/date-range-picker/react.tsx +57 -0
  188. package/src/components/date-time-picker/README.md +48 -0
  189. package/src/components/date-time-picker/date-time-picker.css +177 -0
  190. package/src/components/date-time-picker/element.ts +47 -0
  191. package/src/components/date-time-picker/engine.ts +350 -0
  192. package/src/components/date-time-picker/react.tsx +54 -0
  193. package/src/components/dialog/README.md +103 -102
  194. package/src/components/dialog/element.ts +123 -118
  195. package/src/components/dialog/engine.ts +449 -432
  196. package/src/components/drawer/README.md +28 -0
  197. package/src/components/drawer/drawer.css +57 -0
  198. package/src/components/drawer/element.ts +21 -0
  199. package/src/components/drawer/engine.ts +56 -0
  200. package/src/components/drawer/react.tsx +85 -0
  201. package/src/components/editable-table/README.md +67 -0
  202. package/src/components/editable-table/editable-table.css +200 -0
  203. package/src/components/editable-table/element.ts +45 -0
  204. package/src/components/editable-table/engine.ts +831 -0
  205. package/src/components/editable-table/react.tsx +79 -0
  206. package/src/components/empty-state/README.md +41 -0
  207. package/src/components/empty-state/element.ts +23 -0
  208. package/src/components/empty-state/empty-state.css +60 -0
  209. package/src/components/empty-state/engine.ts +232 -0
  210. package/src/components/empty-state/react.tsx +34 -0
  211. package/src/components/file-explorer/README.md +67 -0
  212. package/src/components/file-explorer/element.ts +39 -0
  213. package/src/components/file-explorer/engine.ts +1307 -0
  214. package/src/components/file-explorer/file-explorer.css +632 -0
  215. package/src/components/file-explorer/react.tsx +77 -0
  216. package/src/components/filter-builder/README.md +47 -0
  217. package/src/components/filter-builder/element.ts +34 -0
  218. package/src/components/filter-builder/engine.ts +396 -0
  219. package/src/components/filter-builder/filter-builder.css +226 -0
  220. package/src/components/filter-builder/react.tsx +36 -0
  221. package/src/components/heatmap-chart/README.md +48 -0
  222. package/src/components/heatmap-chart/element.ts +21 -0
  223. package/src/components/heatmap-chart/engine.ts +281 -0
  224. package/src/components/heatmap-chart/heatmap-chart.css +93 -0
  225. package/src/components/heatmap-chart/react.tsx +14 -0
  226. package/src/components/hold-confirm/README.md +13 -0
  227. package/src/components/hold-confirm/element.ts +24 -0
  228. package/src/components/hold-confirm/engine.ts +216 -0
  229. package/src/components/hold-confirm/hold-confirm.css +86 -0
  230. package/src/components/hold-confirm/react.tsx +28 -0
  231. package/src/components/hover-card/README.md +6 -2
  232. package/src/components/hover-card/engine.ts +98 -7
  233. package/src/components/hover-card/hover-card.css +19 -7
  234. package/src/components/image-preview/README.md +16 -0
  235. package/src/components/image-preview/element.ts +26 -0
  236. package/src/components/image-preview/engine.ts +405 -0
  237. package/src/components/image-preview/image-preview.css +282 -0
  238. package/src/components/image-preview/react.tsx +43 -0
  239. package/src/components/json-viewer/README.md +56 -0
  240. package/src/components/json-viewer/element.ts +37 -0
  241. package/src/components/json-viewer/engine.ts +449 -0
  242. package/src/components/json-viewer/json-viewer.css +303 -0
  243. package/src/components/json-viewer/react.tsx +56 -0
  244. package/src/components/like-button/README.md +13 -0
  245. package/src/components/like-button/element.ts +47 -0
  246. package/src/components/like-button/engine.ts +145 -0
  247. package/src/components/like-button/like-button.css +77 -0
  248. package/src/components/like-button/react.tsx +39 -0
  249. package/src/components/link-preview/README.md +19 -0
  250. package/src/components/link-preview/element.ts +29 -0
  251. package/src/components/link-preview/engine.ts +192 -0
  252. package/src/components/link-preview/link-preview.css +274 -0
  253. package/src/components/link-preview/react.tsx +101 -0
  254. package/src/components/loading-button/README.md +45 -0
  255. package/src/components/loading-button/element.ts +34 -0
  256. package/src/components/loading-button/engine.ts +201 -0
  257. package/src/components/loading-button/loading-button.css +76 -0
  258. package/src/components/loading-button/react.tsx +43 -0
  259. package/src/components/multi-select/README.md +48 -0
  260. package/src/components/multi-select/element.ts +28 -0
  261. package/src/components/multi-select/engine.ts +294 -0
  262. package/src/components/multi-select/multi-select.css +266 -0
  263. package/src/components/multi-select/react.tsx +39 -0
  264. package/src/components/nav/README.md +12 -10
  265. package/src/components/nav/element.ts +5 -5
  266. package/src/components/nav/engine.ts +21 -14
  267. package/src/components/nav/nav.css +102 -102
  268. package/src/components/nav/react.tsx +4 -4
  269. package/src/components/notification-bell/README.md +14 -0
  270. package/src/components/notification-bell/element.ts +48 -0
  271. package/src/components/notification-bell/engine.ts +202 -0
  272. package/src/components/notification-bell/notification-bell.css +126 -0
  273. package/src/components/notification-bell/react.tsx +57 -0
  274. package/src/components/orbit-menu/README.md +46 -0
  275. package/src/components/orbit-menu/element.ts +27 -0
  276. package/src/components/orbit-menu/engine.ts +179 -0
  277. package/src/components/orbit-menu/orbit-menu.css +129 -0
  278. package/src/components/orbit-menu/react.tsx +44 -0
  279. package/src/components/pagination/README.md +1 -1
  280. package/src/components/pagination/engine.ts +50 -4
  281. package/src/components/pagination/pagination.css +10 -5
  282. package/src/components/password-strength/README.md +17 -0
  283. package/src/components/password-strength/element.ts +22 -0
  284. package/src/components/password-strength/engine.ts +212 -0
  285. package/src/components/password-strength/password-strength.css +182 -0
  286. package/src/components/password-strength/react.tsx +36 -0
  287. package/src/components/password-strength/score.ts +62 -0
  288. package/src/components/pie-chart/README.md +48 -0
  289. package/src/components/pie-chart/element.ts +30 -0
  290. package/src/components/pie-chart/engine.ts +283 -0
  291. package/src/components/pie-chart/pie-chart.css +92 -0
  292. package/src/components/pie-chart/react.tsx +14 -0
  293. package/src/components/pill-button/README.md +46 -0
  294. package/src/components/pill-button/element.ts +16 -0
  295. package/src/components/pill-button/engine.ts +77 -0
  296. package/src/components/pill-button/pill-button.css +134 -0
  297. package/src/components/pill-button/react.tsx +32 -0
  298. package/src/components/popover/README.md +44 -0
  299. package/src/components/popover/element.ts +31 -0
  300. package/src/components/popover/engine.ts +114 -0
  301. package/src/components/popover/popover.css +24 -0
  302. package/src/components/popover/react.tsx +50 -0
  303. package/src/components/progress/README.md +17 -0
  304. package/src/components/progress/element.ts +30 -0
  305. package/src/components/progress/engine.ts +173 -0
  306. package/src/components/progress/progress.css +139 -0
  307. package/src/components/progress/react.tsx +35 -0
  308. package/src/components/questionnaire/README.md +32 -0
  309. package/src/components/questionnaire/element.ts +118 -0
  310. package/src/components/questionnaire/engine.ts +255 -0
  311. package/src/components/questionnaire/questionnaire.css +216 -0
  312. package/src/components/questionnaire/react.tsx +140 -0
  313. package/src/components/radar-chart/README.md +48 -0
  314. package/src/components/radar-chart/element.ts +30 -0
  315. package/src/components/radar-chart/engine.ts +327 -0
  316. package/src/components/radar-chart/radar-chart.css +72 -0
  317. package/src/components/radar-chart/react.tsx +14 -0
  318. package/src/components/radial-chart/README.md +48 -0
  319. package/src/components/radial-chart/element.ts +30 -0
  320. package/src/components/radial-chart/engine.ts +290 -0
  321. package/src/components/radial-chart/radial-chart.css +100 -0
  322. package/src/components/radial-chart/react.tsx +14 -0
  323. package/src/components/range-calendar/README.md +58 -0
  324. package/src/components/range-calendar/element.ts +42 -0
  325. package/src/components/range-calendar/engine.ts +390 -0
  326. package/src/components/range-calendar/range-calendar.css +185 -0
  327. package/src/components/range-calendar/react.tsx +56 -0
  328. package/src/components/range-slider/README.md +23 -0
  329. package/src/components/range-slider/element.ts +53 -0
  330. package/src/components/range-slider/engine.ts +316 -0
  331. package/src/components/range-slider/range-slider.css +191 -0
  332. package/src/components/range-slider/react.tsx +66 -0
  333. package/src/components/resizable-panel/README.md +61 -0
  334. package/src/components/resizable-panel/element.ts +36 -0
  335. package/src/components/resizable-panel/engine.ts +551 -0
  336. package/src/components/resizable-panel/react.tsx +74 -0
  337. package/src/components/resizable-panel/resizable-panel.css +242 -0
  338. package/src/components/rich-text-editor/README.md +57 -0
  339. package/src/components/rich-text-editor/element.ts +34 -0
  340. package/src/components/rich-text-editor/engine.ts +673 -0
  341. package/src/components/rich-text-editor/react.tsx +60 -0
  342. package/src/components/rich-text-editor/rich-text-editor.css +283 -0
  343. package/src/components/sankey-chart/README.md +48 -0
  344. package/src/components/sankey-chart/element.ts +31 -0
  345. package/src/components/sankey-chart/engine.ts +401 -0
  346. package/src/components/sankey-chart/react.tsx +14 -0
  347. package/src/components/sankey-chart/sankey-chart.css +60 -0
  348. package/src/components/scratch-card/README.md +15 -0
  349. package/src/components/scratch-card/element.ts +30 -0
  350. package/src/components/scratch-card/engine.ts +257 -0
  351. package/src/components/scratch-card/react.tsx +40 -0
  352. package/src/components/scratch-card/scratch-card.css +74 -0
  353. package/src/components/scroll-island/README.md +34 -0
  354. package/src/components/scroll-island/element.ts +16 -0
  355. package/src/components/scroll-island/engine.ts +252 -0
  356. package/src/components/scroll-island/react.tsx +26 -0
  357. package/src/components/scroll-island/scroll-island.css +110 -0
  358. package/src/components/select/README.md +34 -0
  359. package/src/components/select/element.ts +26 -0
  360. package/src/components/select/engine.ts +267 -0
  361. package/src/components/select/react.tsx +33 -0
  362. package/src/components/select/select.css +135 -0
  363. package/src/components/skeleton/README.md +15 -0
  364. package/src/components/skeleton/element.ts +32 -0
  365. package/src/components/skeleton/engine.ts +104 -0
  366. package/src/components/skeleton/react.tsx +41 -0
  367. package/src/components/skeleton/skeleton.css +107 -0
  368. package/src/components/split-view/README.md +50 -0
  369. package/src/components/split-view/element.ts +34 -0
  370. package/src/components/split-view/engine.ts +114 -0
  371. package/src/components/split-view/react.tsx +63 -0
  372. package/src/components/split-view/split-view.css +18 -0
  373. package/src/components/star-rating/README.md +14 -0
  374. package/src/components/star-rating/element.ts +26 -0
  375. package/src/components/star-rating/engine.ts +217 -0
  376. package/src/components/star-rating/react.tsx +33 -0
  377. package/src/components/star-rating/star-rating.css +79 -0
  378. package/src/components/stat-card/README.md +42 -0
  379. package/src/components/stat-card/element.ts +40 -0
  380. package/src/components/stat-card/engine.ts +283 -0
  381. package/src/components/stat-card/react.tsx +36 -0
  382. package/src/components/stat-card/stat-card.css +163 -0
  383. package/src/components/swipe-actions/README.md +19 -0
  384. package/src/components/swipe-actions/element.ts +26 -0
  385. package/src/components/swipe-actions/engine.ts +260 -0
  386. package/src/components/swipe-actions/react.tsx +80 -0
  387. package/src/components/swipe-actions/swipe-actions.css +104 -0
  388. package/src/components/tag-input/README.md +42 -0
  389. package/src/components/tag-input/element.ts +39 -0
  390. package/src/components/tag-input/engine.ts +313 -0
  391. package/src/components/tag-input/react.tsx +54 -0
  392. package/src/components/tag-input/tag-input.css +243 -0
  393. package/src/components/time-field/README.md +54 -0
  394. package/src/components/time-field/element.ts +38 -0
  395. package/src/components/time-field/engine.ts +110 -0
  396. package/src/components/time-field/react.tsx +45 -0
  397. package/src/components/time-field/time-field.css +87 -0
  398. package/src/components/timeline/README.md +57 -0
  399. package/src/components/timeline/element.ts +31 -0
  400. package/src/components/timeline/engine.ts +300 -0
  401. package/src/components/timeline/react.tsx +59 -0
  402. package/src/components/timeline/timeline.css +218 -0
  403. package/src/components/toolbar/README.md +46 -0
  404. package/src/components/toolbar/element.ts +30 -0
  405. package/src/components/toolbar/engine.ts +230 -0
  406. package/src/components/toolbar/react.tsx +34 -0
  407. package/src/components/toolbar/toolbar.css +121 -0
  408. package/src/components/tooltip/README.md +46 -0
  409. package/src/components/tooltip/element.ts +22 -0
  410. package/src/components/tooltip/engine.ts +288 -0
  411. package/src/components/tooltip/react.tsx +30 -0
  412. package/src/components/tooltip/tooltip.css +78 -0
  413. package/src/components/tree-view/README.md +61 -0
  414. package/src/components/tree-view/element.ts +32 -0
  415. package/src/components/tree-view/engine.ts +743 -0
  416. package/src/components/tree-view/react.tsx +82 -0
  417. package/src/components/tree-view/tree-view.css +288 -0
  418. package/src/components/virtual-list/README.md +81 -0
  419. package/src/components/virtual-list/element.ts +42 -0
  420. package/src/components/virtual-list/engine.ts +678 -0
  421. package/src/components/virtual-list/react.tsx +101 -0
  422. package/src/components/virtual-list/virtual-list.css +194 -0
  423. package/src/components/voice-dots/README.md +48 -0
  424. package/src/components/voice-dots/element.ts +45 -0
  425. package/src/components/voice-dots/engine.ts +243 -0
  426. package/src/components/voice-dots/react.tsx +52 -0
  427. package/src/components/voice-dots/voice-dots.css +69 -0
  428. package/src/core/chips.ts +196 -0
  429. package/src/core/color.ts +304 -0
  430. package/src/core/date-range.ts +67 -0
  431. package/src/core/dates.ts +11 -1
  432. package/src/core/files.ts +107 -0
  433. package/src/core/filter.ts +229 -0
  434. package/src/core/gsap.ts +80 -65
  435. package/src/core/listbox.ts +332 -0
  436. package/src/core/mark.ts +29 -0
  437. package/src/core/position.ts +89 -0
  438. package/src/core/relative-time.ts +77 -0
  439. package/src/core/segment-field.ts +373 -0
  440. package/src/core/segments.ts +153 -0
  441. package/src/core/tree.ts +116 -0
  442. package/src/core/virtual.ts +79 -0
  443. package/src/css/tokens.css +186 -182
  444. package/src/elements/index.ts +186 -0
  445. package/src/index.ts +88 -0
  446. package/src/motion/liquid.ts +14 -4
  447. package/src/motion/micro.ts +248 -0
  448. package/src/motion/sheet.ts +88 -65
  449. package/src/motion/surface.ts +9 -0
  450. package/src/react/index.ts +71 -1
  451. package/dist/chunk-IKQ5FODN.js +0 -1617
  452. package/dist/chunk-IKQ5FODN.js.map +0 -1
  453. package/dist/chunk-J6OCFNGU.js +0 -177
  454. package/dist/chunk-J6OCFNGU.js.map +0 -1
  455. package/dist/chunk-TFNRIZHU.js +0 -3977
  456. package/dist/chunk-TFNRIZHU.js.map +0 -1
  457. package/dist/chunk-XAKC2QY7.js.map +0 -1
  458. package/dist/reveal-jsX-uwUz.d.ts +0 -625
  459. package/dist/store-DYb3tTsZ.d.ts +0 -195
@@ -0,0 +1,673 @@
1
+ import { gsap, EASE, registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { createScope } from '../../core/scope';
4
+ import { inert, type Instance } from '../../core/types';
5
+ import { press, shake } from '../../motion/micro';
6
+
7
+ /*
8
+ * Rich text editor: a writing area with a toolbar for bold, italic,
9
+ * underline, strikethrough, headings, lists, quotes, code and links. Buttons
10
+ * press with a bounce, and the formats under the caret light up as it moves
11
+ * (their pill grows in). Shortcuts: Ctrl/⌘+B, I, U, K, Shift+X (strikethrough),
12
+ * Ctrl/⌘+Z and Y. Markdown as you type: "# " and "## " make headings, "- " or
13
+ * "* " a list, "1. " a numbered one, "> " a quote, **bold** and `code` inline.
14
+ * Pasted text keeps only safe formatting (no styles, classes or scripts). The
15
+ * link editor grows out of its button; the counter turns red and the area
16
+ * shakes once when it passes maxLength. A hidden field carries the HTML for
17
+ * forms; the placeholder fades.
18
+ */
19
+
20
+ export type RichCommand = 'bold' | 'italic' | 'underline' | 'strike' | 'h2' | 'h3' | 'ul' | 'ol' | 'quote' | 'code' | 'link' | 'clear' | 'undo' | 'redo';
21
+
22
+ export interface RichTextEditorOptions {
23
+ /** The first content, as HTML (cleaned like a paste). */
24
+ value?: string;
25
+ placeholder?: string;
26
+ /** The buttons, in order; '|' separates groups (default: all). */
27
+ toolbar?: Array<RichCommand | '|'>;
28
+ /** Characters before the counter turns red (the text can go on). */
29
+ maxLength?: number;
30
+ /** 'words' (default), 'characters', or false. */
31
+ counter?: 'words' | 'characters' | false;
32
+ /** A hidden field with this name carries the HTML. */
33
+ name?: string;
34
+ /** Accessible name of the writing area (default 'Editor de texto'). */
35
+ label?: string;
36
+ readOnly?: boolean;
37
+ /** Markdown shortcuts while typing (default true). */
38
+ markdown?: boolean;
39
+ onChange?: (html: string, text: string) => void;
40
+ }
41
+
42
+ export interface RichTextEditorInstance extends Instance {
43
+ setValue(html: string): void;
44
+ focus(): void;
45
+ exec(command: RichCommand, value?: string): void;
46
+ readonly value: string;
47
+ readonly text: string;
48
+ readonly element: HTMLElement;
49
+ }
50
+
51
+ const SVG = (paths: string) =>
52
+ `<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
53
+ const BUTTONS: Record<RichCommand, { label: string; keys?: string; icon: string; toggle?: boolean }> = {
54
+ bold: { label: 'Negrita', keys: 'Ctrl+B', toggle: true, icon: SVG('<path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z"/>') },
55
+ italic: { label: 'Cursiva', keys: 'Ctrl+I', toggle: true, icon: SVG('<path d="M14 5h-4M14 19h-4M14 5l-4 14"/>') },
56
+ underline: { label: 'Subrayado', keys: 'Ctrl+U', toggle: true, icon: SVG('<path d="M7 4v7a5 5 0 0 0 10 0V4M5 20h14"/>') },
57
+ strike: { label: 'Tachado', keys: 'Ctrl+Mayús+X', toggle: true, icon: SVG('<path d="M5 12h14M16 6.5A4 4 0 0 0 12.5 5h-1A3.5 3.5 0 0 0 8 8.5c0 1.5 1 2.6 2.6 3.1M8 17.5A4 4 0 0 0 11.5 19h1a3.5 3.5 0 0 0 3.5-3.5"/>') },
58
+ h2: { label: 'Título', toggle: true, icon: SVG('<path d="M4 6v12M12 6v12M4 12h8M16 10l3-2v10"/>') },
59
+ h3: { label: 'Subtítulo', toggle: true, icon: SVG('<path d="M4 6v12M12 6v12M4 12h8M16 8.5a2.5 2.5 0 1 1 2.5 3.5 2.5 2.5 0 1 1-2.5 3.5"/>') },
60
+ ul: { label: 'Lista', toggle: true, icon: SVG('<path d="M9 6h11M9 12h11M9 18h11M4.5 6h.01M4.5 12h.01M4.5 18h.01"/>') },
61
+ ol: { label: 'Lista numerada', toggle: true, icon: SVG('<path d="M10 6h10M10 12h10M10 18h10M4 5l1.5-1v5M3.5 13.5a1.5 1.5 0 0 1 3 .5c0 1-3 2-3 3h3"/>') },
62
+ quote: { label: 'Cita', toggle: true, icon: SVG('<path d="M7 7h4v6a4 4 0 0 1-4 4M15 7h4v6a4 4 0 0 1-4 4"/>') },
63
+ code: { label: 'Código', toggle: true, icon: SVG('<path d="M9 8l-4 4 4 4M15 8l4 4-4 4"/>') },
64
+ link: { label: 'Enlace', keys: 'Ctrl+K', toggle: true, icon: SVG('<path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1"/>') },
65
+ clear: { label: 'Quitar formato', icon: SVG('<path d="M6 5h12M12 5l-3 14M15.5 15.5l4 4M19.5 15.5l-4 4"/>') },
66
+ undo: { label: 'Deshacer', keys: 'Ctrl+Z', icon: SVG('<path d="M9 7L4 12l5 5M4 12h11a5 5 0 0 1 0 10h-2"/>') },
67
+ redo: { label: 'Rehacer', keys: 'Ctrl+Y', icon: SVG('<path d="M15 7l5 5-5 5M20 12H9a5 5 0 0 0 0 10h2"/>') },
68
+ };
69
+ const DEFAULT_TOOLBAR: Array<RichCommand | '|'> = ['bold', 'italic', 'underline', 'strike', '|', 'h2', 'h3', '|', 'ul', 'ol', 'quote', 'code', '|', 'link', 'clear', '|', 'undo', 'redo'];
70
+ const ZWSP = '​';
71
+
72
+ // ── Cleaning HTML ──────────────────────────────────────────────────────────
73
+ const KEEP: Record<string, string> = {
74
+ p: 'p', div: 'p', br: 'br', strong: 'strong', b: 'strong', em: 'em', i: 'em', u: 'u', s: 's', strike: 's', del: 's',
75
+ h1: 'h2', h2: 'h2', h3: 'h3', h4: 'h3', h5: 'h3', h6: 'h3', ul: 'ul', ol: 'ol', li: 'li', blockquote: 'blockquote', code: 'code', pre: 'pre', a: 'a',
76
+ };
77
+ const DROP = new Set(['script', 'style', 'meta', 'link', 'iframe', 'object', 'embed', 'template', 'noscript', 'svg', 'math', 'head', 'title', 'form', 'input', 'button', 'select', 'textarea']);
78
+ const SAFE_URL = /^(https?:|mailto:|tel:|#|\/|\.{0,2}\/)/i;
79
+
80
+ /** HTML with only safe formatting: known tags (renamed to their kind), links with safe addresses, no attributes else. */
81
+ export function sanitizeHtml(html: string): string {
82
+ if (typeof DOMParser === 'undefined') return '';
83
+ const source = new DOMParser().parseFromString(`<body>${html}</body>`, 'text/html').body;
84
+ const out = document.createElement('div');
85
+ const copy = (from: Node, into: Node) => {
86
+ for (const node of from.childNodes) {
87
+ if (node.nodeType === Node.TEXT_NODE) {
88
+ into.appendChild(document.createTextNode((node.textContent ?? '').replace(new RegExp(ZWSP, 'g'), '')));
89
+ continue;
90
+ }
91
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
92
+ const element = node as Element;
93
+ const tag = element.tagName.toLowerCase();
94
+ if (DROP.has(tag)) continue;
95
+ const kind = KEEP[tag];
96
+ // Unknown wrappers (span, font, section…) give their content.
97
+ if (!kind) {
98
+ copy(element, into);
99
+ continue;
100
+ }
101
+ const made = document.createElement(kind);
102
+ if (kind === 'a') {
103
+ const href = element.getAttribute('href')?.trim() ?? '';
104
+ if (!SAFE_URL.test(href)) {
105
+ copy(element, into);
106
+ continue;
107
+ }
108
+ made.setAttribute('href', href);
109
+ if (/^https?:/i.test(href)) {
110
+ made.setAttribute('target', '_blank');
111
+ made.setAttribute('rel', 'noopener noreferrer');
112
+ }
113
+ }
114
+ copy(element, made);
115
+ into.appendChild(made);
116
+ }
117
+ };
118
+ copy(source, out);
119
+ return out.innerHTML;
120
+ }
121
+
122
+ const escapeText = (text: string) => {
123
+ const holder = document.createElement('div');
124
+ holder.textContent = text;
125
+ return holder.innerHTML;
126
+ };
127
+ /** Plain text as paragraphs (blank lines) and line breaks. */
128
+ const textToHtml = (text: string) =>
129
+ text
130
+ .replace(/\r\n?/g, '\n')
131
+ .split(/\n{2,}/)
132
+ .map((paragraph) => `<p>${escapeText(paragraph).replace(/\n/g, '<br>')}</p>`)
133
+ .join('');
134
+
135
+ export function createRichTextEditor(root: HTMLElement | null, options: RichTextEditorOptions = {}): RichTextEditorInstance {
136
+ if (!root) return inert({ setValue() {}, focus() {}, exec() {}, value: '', text: '', element: null as unknown as HTMLElement });
137
+ registerMotion();
138
+ const scope = createScope();
139
+ const markdown = options.markdown !== false;
140
+ const counterMode = options.counter === false ? null : (options.counter ?? (options.maxLength ? 'characters' : 'words'));
141
+ const effects = new Set<gsap.core.Animation>();
142
+ const keep = (animation: gsap.core.Animation | null) => {
143
+ if (!animation) return;
144
+ effects.add(animation);
145
+ // Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
146
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
147
+ };
148
+ // What was written inside the root is the first content (unless `value` is given).
149
+ const authored = root.innerHTML;
150
+ const holder = document.createElement('div');
151
+ holder.hidden = true;
152
+ holder.append(...root.childNodes);
153
+
154
+ // ── The parts ────────────────────────────────────────────────────────────
155
+ const toolbar = document.createElement('div');
156
+ toolbar.dataset.mkRteToolbar = '';
157
+ toolbar.setAttribute('role', 'toolbar');
158
+ toolbar.setAttribute('aria-label', 'Formato');
159
+ const buttons = new Map<RichCommand, HTMLButtonElement>();
160
+ let group = document.createElement('div');
161
+ group.dataset.mkRteGroup = '';
162
+ toolbar.append(group);
163
+ for (const entry of options.toolbar ?? DEFAULT_TOOLBAR) {
164
+ if (entry === '|') {
165
+ group = document.createElement('div');
166
+ group.dataset.mkRteGroup = '';
167
+ toolbar.append(group);
168
+ continue;
169
+ }
170
+ const spec = BUTTONS[entry];
171
+ if (!spec) continue;
172
+ const button = document.createElement('button');
173
+ button.type = 'button';
174
+ button.dataset.command = entry;
175
+ const label = spec.keys ? `${spec.label} (${spec.keys})` : spec.label;
176
+ button.setAttribute('aria-label', label);
177
+ button.title = label;
178
+ if (spec.toggle) button.setAttribute('aria-pressed', 'false');
179
+ button.innerHTML = spec.icon;
180
+ button.tabIndex = buttons.size ? -1 : 0;
181
+ group.append(button);
182
+ buttons.set(entry, button);
183
+ }
184
+ const content = document.createElement('div');
185
+ content.dataset.mkRteContent = '';
186
+ content.contentEditable = options.readOnly ? 'false' : 'true';
187
+ content.setAttribute('role', 'textbox');
188
+ content.setAttribute('aria-multiline', 'true');
189
+ content.setAttribute('aria-label', options.label ?? 'Editor de texto');
190
+ content.dataset.placeholder = options.placeholder ?? 'Escribe algo…';
191
+ content.spellcheck = true;
192
+ if (options.readOnly) content.setAttribute('aria-readonly', 'true');
193
+ const foot = document.createElement('div');
194
+ foot.dataset.mkRteFoot = '';
195
+ const counter = document.createElement('span');
196
+ counter.dataset.mkRteCounter = '';
197
+ counter.setAttribute('aria-live', 'polite');
198
+ foot.append(counter);
199
+ foot.hidden = !counterMode;
200
+ let hidden: HTMLInputElement | null = null;
201
+ if (options.name) {
202
+ hidden = document.createElement('input');
203
+ hidden.type = 'hidden';
204
+ hidden.name = options.name;
205
+ foot.append(hidden);
206
+ }
207
+ // The link editor.
208
+ const linkBox = document.createElement('div');
209
+ linkBox.dataset.mkRteLink = '';
210
+ linkBox.hidden = true;
211
+ linkBox.setAttribute('role', 'group');
212
+ linkBox.setAttribute('aria-label', 'Enlace');
213
+ const linkInput = document.createElement('input');
214
+ linkInput.type = 'url';
215
+ linkInput.placeholder = 'https://…';
216
+ linkInput.setAttribute('aria-label', 'Dirección del enlace');
217
+ const linkApply = document.createElement('button');
218
+ linkApply.type = 'button';
219
+ linkApply.dataset.mkRteLinkApply = '';
220
+ linkApply.textContent = 'Aplicar';
221
+ const linkRemove = document.createElement('button');
222
+ linkRemove.type = 'button';
223
+ linkRemove.dataset.mkRteLinkRemove = '';
224
+ linkRemove.textContent = 'Quitar';
225
+ linkBox.append(linkInput, linkApply, linkRemove);
226
+ const status = document.createElement('span');
227
+ status.className = 'mk-sr-only';
228
+ status.setAttribute('role', 'status');
229
+ if (!options.readOnly) root.append(toolbar);
230
+ root.append(content, linkBox, foot, status);
231
+ scope.attr(root, 'data-mk-ready', '');
232
+ if (options.readOnly) scope.attr(root, 'data-mk-readonly', '');
233
+ scope.add(() => {
234
+ for (const effect of effects) effect.kill();
235
+ effects.clear();
236
+ for (const part of [toolbar, content, linkBox, foot, status]) part.remove();
237
+ root.append(...holder.childNodes);
238
+ });
239
+
240
+ const run = (name: string, value?: string) => {
241
+ if (typeof document.execCommand !== 'function') return false;
242
+ try {
243
+ return document.execCommand(name, false, value);
244
+ } catch {
245
+ return false;
246
+ }
247
+ };
248
+ try {
249
+ document.execCommand?.('defaultParagraphSeparator', false, 'p');
250
+ } catch {
251
+ // Older engines: their default separator stands.
252
+ }
253
+
254
+ // ── Value ────────────────────────────────────────────────────────────────
255
+ const cleanValue = () => sanitizeHtml(content.innerHTML).replace(/<p><br><\/p>$/, '').replace(/^<p><\/p>$/, '');
256
+ const textValue = () => (content.innerText ?? content.textContent ?? '').replace(new RegExp(ZWSP, 'g'), '').replace(/\n{3,}/g, '\n\n').trim();
257
+ const BLOCKS = new Set(['P', 'H2', 'H3', 'UL', 'OL', 'BLOCKQUOTE', 'PRE']);
258
+ const setContent = (html: string) => {
259
+ const holder = document.createElement('div');
260
+ holder.innerHTML = sanitizeHtml(html);
261
+ // Blocks stay; runs of loose text and inline formatting at the top go in paragraphs; blank space between blocks goes.
262
+ const out: Node[] = [];
263
+ let paragraph: HTMLParagraphElement | null = null;
264
+ for (const node of [...holder.childNodes]) {
265
+ if (node.nodeType === Node.ELEMENT_NODE && BLOCKS.has((node as Element).tagName)) {
266
+ paragraph = null;
267
+ out.push(node);
268
+ continue;
269
+ }
270
+ if (node.nodeType === Node.TEXT_NODE && !node.textContent?.trim() && !paragraph) continue;
271
+ if (!paragraph) {
272
+ paragraph = document.createElement('p');
273
+ out.push(paragraph);
274
+ }
275
+ paragraph.append(node);
276
+ }
277
+ content.replaceChildren(...out);
278
+ if (!content.firstChild) content.innerHTML = '<p><br></p>';
279
+ };
280
+ setContent(options.value ?? authored);
281
+
282
+ let over = false;
283
+ let lastValue = '';
284
+ const paint = (notify: boolean) => {
285
+ const text = textValue();
286
+ content.toggleAttribute('data-empty', !text && !content.querySelector('li, img'));
287
+ if (counterMode) {
288
+ const characters = [...text].length;
289
+ const words = text ? text.split(/\s+/).filter(Boolean).length : 0;
290
+ if (options.maxLength) {
291
+ counter.textContent = `${characters} / ${options.maxLength}`;
292
+ const now = characters > options.maxLength;
293
+ if (now && !over) {
294
+ keep(shake(content, 3));
295
+ status.textContent = `Pasaste el límite de ${options.maxLength} caracteres`;
296
+ }
297
+ over = now;
298
+ root.toggleAttribute('data-mk-over', now);
299
+ content.setAttribute('aria-invalid', String(now));
300
+ } else counter.textContent = counterMode === 'words' ? `${words} ${words === 1 ? 'palabra' : 'palabras'}` : `${characters} ${characters === 1 ? 'carácter' : 'caracteres'}`;
301
+ }
302
+ const html = cleanValue();
303
+ if (hidden) hidden.value = html;
304
+ if (notify && html !== lastValue) {
305
+ lastValue = html;
306
+ options.onChange?.(html, text);
307
+ root.dispatchEvent(new CustomEvent('mk:change', { bubbles: true, detail: { html, text } }));
308
+ } else lastValue = html;
309
+ };
310
+ paint(false);
311
+
312
+ // ── Where the caret is ───────────────────────────────────────────────────
313
+ const selectionInside = () => {
314
+ const selection = window.getSelection();
315
+ if (!selection || !selection.rangeCount) return null;
316
+ const range = selection.getRangeAt(0);
317
+ return content.contains(range.commonAncestorContainer) ? range : null;
318
+ };
319
+ const closest = (node: Node | null, selector: string) => {
320
+ const element = node?.nodeType === Node.ELEMENT_NODE ? (node as Element) : (node?.parentElement ?? null);
321
+ const found = element?.closest(selector);
322
+ return found && content.contains(found) ? (found as HTMLElement) : null;
323
+ };
324
+ const block = (node: Node | null) => closest(node, 'p, h2, h3, li, blockquote, pre');
325
+ const states = (): Partial<Record<RichCommand, boolean>> => {
326
+ const range = selectionInside();
327
+ if (!range) return {};
328
+ const node = range.startContainer;
329
+ const query = (name: string) => {
330
+ try {
331
+ return Boolean(document.queryCommandState?.(name));
332
+ } catch {
333
+ return false;
334
+ }
335
+ };
336
+ return {
337
+ bold: query('bold') || Boolean(closest(node, 'strong, b')),
338
+ italic: query('italic') || Boolean(closest(node, 'em, i')),
339
+ underline: query('underline') || Boolean(closest(node, 'u')),
340
+ strike: query('strikeThrough') || Boolean(closest(node, 's, strike, del')),
341
+ h2: Boolean(closest(node, 'h2')),
342
+ h3: Boolean(closest(node, 'h3')),
343
+ ul: Boolean(closest(node, 'ul')),
344
+ ol: Boolean(closest(node, 'ol')),
345
+ quote: Boolean(closest(node, 'blockquote')),
346
+ code: Boolean(closest(node, 'code')),
347
+ link: Boolean(closest(node, 'a')),
348
+ };
349
+ };
350
+ const paintToolbar = () => {
351
+ const now = states();
352
+ for (const [command, button] of buttons) {
353
+ if (!button.hasAttribute('aria-pressed')) continue;
354
+ button.setAttribute('aria-pressed', String(Boolean(now[command])));
355
+ }
356
+ };
357
+ scope.on(document, 'selectionchange', () => {
358
+ if (selectionInside()) paintToolbar();
359
+ });
360
+
361
+ // ── Commands ─────────────────────────────────────────────────────────────
362
+ const toggleBlock = (tag: 'h2' | 'h3' | 'blockquote') => {
363
+ const range = selectionInside();
364
+ const inside = range ? closest(range.startContainer, tag) : null;
365
+ run('formatBlock', inside ? '<p>' : `<${tag}>`);
366
+ };
367
+ const toggleCode = () => {
368
+ const range = selectionInside();
369
+ if (!range) return;
370
+ const code = closest(range.startContainer, 'code');
371
+ if (code) {
372
+ // Out of code: its text stays, plain.
373
+ code.replaceWith(...code.childNodes);
374
+ return;
375
+ }
376
+ if (range.collapsed) return;
377
+ const element = document.createElement('code');
378
+ element.textContent = range.toString();
379
+ range.deleteContents();
380
+ range.insertNode(element);
381
+ const after = document.createTextNode(ZWSP);
382
+ element.after(after);
383
+ placeCaret(after, 1);
384
+ };
385
+ const placeCaret = (node: Node, offset: number) => {
386
+ const selection = window.getSelection();
387
+ if (!selection) return;
388
+ const range = document.createRange();
389
+ range.setStart(node, offset);
390
+ range.collapse(true);
391
+ selection.removeAllRanges();
392
+ selection.addRange(range);
393
+ };
394
+
395
+ let savedRange: Range | null = null;
396
+ const openLink = () => {
397
+ const range = selectionInside();
398
+ savedRange = range ? range.cloneRange() : null;
399
+ const current = range ? closest(range.startContainer, 'a') : null;
400
+ linkInput.value = current?.getAttribute('href') ?? '';
401
+ linkRemove.hidden = !current;
402
+ linkBox.hidden = false;
403
+ // Under its button, inside the editor.
404
+ const button = buttons.get('link');
405
+ const rootBox = root.getBoundingClientRect();
406
+ const anchor = button?.getBoundingClientRect();
407
+ if (anchor) {
408
+ linkBox.style.top = `${anchor.bottom - rootBox.top + 6}px`;
409
+ linkBox.style.left = `${Math.max(0, Math.min(anchor.left - rootBox.left, rootBox.width - linkBox.offsetWidth))}px`;
410
+ }
411
+ if (!reduced()) keep(gsap.fromTo(linkBox, { opacity: 0, scale: 0.92, y: -6, filter: 'blur(6px)' }, { opacity: 1, scale: 1, y: 0, filter: 'blur(0px)', duration: 0.28, ease: EASE.pop, transformOrigin: '0% 0%', clearProps: 'opacity,transform,filter' }));
412
+ linkInput.focus();
413
+ linkInput.select();
414
+ };
415
+ const closeLink = (focusBack = true) => {
416
+ if (linkBox.hidden) return;
417
+ const finish = () => {
418
+ linkBox.hidden = true;
419
+ gsap.set(linkBox, { clearProps: 'opacity,transform,filter' });
420
+ };
421
+ if (reduced()) finish();
422
+ else keep(gsap.to(linkBox, { opacity: 0, scale: 0.95, duration: 0.16, ease: 'power1.in', onComplete: finish }));
423
+ if (focusBack) {
424
+ content.focus({ preventScroll: true });
425
+ if (savedRange) {
426
+ const selection = window.getSelection();
427
+ selection?.removeAllRanges();
428
+ selection?.addRange(savedRange);
429
+ }
430
+ }
431
+ };
432
+ const applyLink = () => {
433
+ const href = linkInput.value.trim();
434
+ if (href && !SAFE_URL.test(href) && !/^[\w.-]+\.[a-z]{2,}/i.test(href)) {
435
+ keep(shake(linkInput));
436
+ status.textContent = 'La dirección no es válida';
437
+ return;
438
+ }
439
+ const url = href && !SAFE_URL.test(href) ? `https://${href}` : href;
440
+ closeLink();
441
+ if (!url) return;
442
+ run('createLink', url);
443
+ // External links open apart and safely.
444
+ if (/^https?:/i.test(url)) {
445
+ for (const anchor of content.querySelectorAll<HTMLAnchorElement>('a[href]')) {
446
+ if (anchor.getAttribute('href') !== url) continue;
447
+ anchor.target = '_blank';
448
+ anchor.rel = 'noopener noreferrer';
449
+ }
450
+ }
451
+ status.textContent = 'Enlace aplicado';
452
+ paint(true);
453
+ };
454
+ const removeLink = () => {
455
+ closeLink();
456
+ run('unlink');
457
+ status.textContent = 'Enlace quitado';
458
+ paint(true);
459
+ };
460
+
461
+ const exec = (command: RichCommand, value?: string) => {
462
+ if (options.readOnly) return;
463
+ if (command !== 'link' && !selectionInside()) content.focus({ preventScroll: true });
464
+ switch (command) {
465
+ case 'bold':
466
+ case 'italic':
467
+ case 'underline':
468
+ run(command);
469
+ break;
470
+ case 'strike':
471
+ run('strikeThrough');
472
+ break;
473
+ case 'h2':
474
+ case 'h3':
475
+ toggleBlock(command);
476
+ break;
477
+ case 'quote':
478
+ toggleBlock('blockquote');
479
+ break;
480
+ case 'ul':
481
+ run('insertUnorderedList');
482
+ break;
483
+ case 'ol':
484
+ run('insertOrderedList');
485
+ break;
486
+ case 'code':
487
+ toggleCode();
488
+ break;
489
+ case 'link':
490
+ if (value !== undefined) {
491
+ linkInput.value = value;
492
+ savedRange = selectionInside()?.cloneRange() ?? null;
493
+ applyLink();
494
+ } else openLink();
495
+ return;
496
+ case 'clear':
497
+ run('removeFormat');
498
+ run('formatBlock', '<p>');
499
+ break;
500
+ case 'undo':
501
+ case 'redo':
502
+ run(command);
503
+ break;
504
+ }
505
+ paintToolbar();
506
+ paint(true);
507
+ };
508
+
509
+ // ── Toolbar: pointer and keyboard ────────────────────────────────────────
510
+ // The editor keeps its selection while a button is pressed.
511
+ scope.on(toolbar, 'mousedown', (event) => {
512
+ if ((event.target as Element).closest('button')) event.preventDefault();
513
+ });
514
+ scope.on(toolbar, 'click', (event) => {
515
+ const button = (event.target as Element | null)?.closest<HTMLButtonElement>('button[data-command]');
516
+ if (!button) return;
517
+ keep(press(button));
518
+ exec(button.dataset.command as RichCommand);
519
+ });
520
+ scope.on(toolbar, 'keydown', (event) => {
521
+ const key = (event as KeyboardEvent).key;
522
+ const list = [...buttons.values()];
523
+ const at = list.indexOf(document.activeElement as HTMLButtonElement);
524
+ const moves: Record<string, number> = { ArrowRight: at + 1, ArrowLeft: at - 1, Home: 0, End: list.length - 1 };
525
+ if (!(key in moves) || at < 0) return;
526
+ event.preventDefault();
527
+ const next = list[(moves[key]! + list.length) % list.length]!;
528
+ for (const button of list) button.tabIndex = button === next ? 0 : -1;
529
+ next.focus();
530
+ });
531
+
532
+ // ── Writing ──────────────────────────────────────────────────────────────
533
+ scope.on(content, 'keydown', (event) => {
534
+ const key = event as KeyboardEvent;
535
+ if (!(key.ctrlKey || key.metaKey) || key.altKey) return;
536
+ const letter = key.key.toLowerCase();
537
+ const shortcuts: Record<string, RichCommand> = { b: 'bold', i: 'italic', u: 'underline', k: 'link' };
538
+ let command: RichCommand | undefined = shortcuts[letter];
539
+ if (key.shiftKey && letter === 'x') command = 'strike';
540
+ if (!command || key.shiftKey !== (command === 'strike')) return;
541
+ key.preventDefault();
542
+ const button = buttons.get(command);
543
+ if (button) keep(press(button));
544
+ exec(command);
545
+ });
546
+
547
+ /** Markdown at the start of a block ("# ", "- ", "1. ", "> ") and inline (**bold**, `code`). */
548
+ const autoformat = (typed: string) => {
549
+ const range = selectionInside();
550
+ if (!range || !range.collapsed) return;
551
+ const node = range.startContainer;
552
+ if (typed === ' ') {
553
+ const current = block(node);
554
+ if (!current || current.tagName === 'LI' || current.tagName === 'PRE') return;
555
+ // The text from the start of the block to the caret.
556
+ const before = document.createRange();
557
+ before.selectNodeContents(current);
558
+ before.setEnd(range.startContainer, range.startOffset);
559
+ const text = before.toString().replace(new RegExp(ZWSP, 'g'), '');
560
+ const rules: Array<[RegExp, () => void]> = [
561
+ [/^#\s$/, () => run('formatBlock', '<h2>')],
562
+ [/^##\s$/, () => run('formatBlock', '<h3>')],
563
+ [/^[-*]\s$/, () => run('insertUnorderedList')],
564
+ [/^1[.)]\s$/, () => run('insertOrderedList')],
565
+ [/^>\s$/, () => run('formatBlock', '<blockquote>')],
566
+ ];
567
+ for (const [pattern, apply] of rules) {
568
+ if (!pattern.test(text)) continue;
569
+ // The marker goes; the format stays.
570
+ before.deleteContents();
571
+ apply();
572
+ current.normalize();
573
+ return true;
574
+ }
575
+ return;
576
+ }
577
+ if ((typed === '*' || typed === '`') && node.nodeType === Node.TEXT_NODE) {
578
+ const text = (node.textContent ?? '').slice(0, range.startOffset);
579
+ const match = typed === '*' ? /\*\*([^*]+)\*\*$/.exec(text) : /`([^`]+)`$/.exec(text);
580
+ if (!match) return;
581
+ const start = range.startOffset - match[0].length;
582
+ const span = document.createRange();
583
+ span.setStart(node, start);
584
+ span.setEnd(node, range.startOffset);
585
+ span.deleteContents();
586
+ const element = document.createElement(typed === '*' ? 'strong' : 'code');
587
+ element.textContent = match[1]!;
588
+ span.insertNode(element);
589
+ // The caret goes after it, outside of it.
590
+ const after = document.createTextNode(ZWSP);
591
+ element.after(after);
592
+ placeCaret(after, 1);
593
+ // A soft highlight says what just changed.
594
+ if (!reduced()) {
595
+ element.setAttribute('data-mk-rte-fresh', '');
596
+ scope.timeout(() => element.removeAttribute('data-mk-rte-fresh'), 1000);
597
+ }
598
+ return true;
599
+ }
600
+ return;
601
+ };
602
+
603
+ scope.on(content, 'input', (event) => {
604
+ const input = event as InputEvent;
605
+ if (markdown && input.inputType === 'insertText' && input.data && autoformat(input.data.slice(-1))) {
606
+ const fresh = selectionInside() ? block(selectionInside()!.startContainer) : null;
607
+ if (fresh && !reduced()) keep(gsap.fromTo(fresh, { opacity: 0.4, filter: 'blur(4px)' }, { opacity: 1, filter: 'blur(0px)', duration: 0.3, ease: 'power1.out', clearProps: 'opacity,filter' }));
608
+ status.textContent = 'Formato aplicado';
609
+ }
610
+ // An emptied area keeps a paragraph to write in.
611
+ if (!content.firstElementChild) content.innerHTML = '<p><br></p>';
612
+ paint(true);
613
+ paintToolbar();
614
+ });
615
+
616
+ scope.on(content, 'paste', (event) => {
617
+ if (options.readOnly) return;
618
+ const clipboard = (event as ClipboardEvent).clipboardData;
619
+ if (!clipboard) return;
620
+ event.preventDefault();
621
+ const html = clipboard.getData('text/html');
622
+ const clean = html ? sanitizeHtml(html) : textToHtml(clipboard.getData('text/plain'));
623
+ if (!run('insertHTML', clean)) {
624
+ // No insertHTML: put it in by hand.
625
+ const range = selectionInside();
626
+ if (range) {
627
+ range.deleteContents();
628
+ const holder = document.createElement('div');
629
+ holder.innerHTML = clean;
630
+ const fragment = document.createDocumentFragment();
631
+ fragment.append(...holder.childNodes);
632
+ range.insertNode(fragment);
633
+ }
634
+ }
635
+ paint(true);
636
+ });
637
+
638
+ scope.on(linkInput, 'keydown', (event) => {
639
+ const key = (event as KeyboardEvent).key;
640
+ if (key === 'Enter') {
641
+ event.preventDefault();
642
+ applyLink();
643
+ } else if (key === 'Escape') {
644
+ event.preventDefault();
645
+ closeLink();
646
+ }
647
+ });
648
+ scope.on(linkApply, 'click', applyLink);
649
+ scope.on(linkRemove, 'click', removeLink);
650
+ scope.on(document, 'pointerdown', (event) => {
651
+ if (!linkBox.hidden && !linkBox.contains(event.target as Node) && !buttons.get('link')?.contains(event.target as Node)) closeLink(false);
652
+ });
653
+ scope.on(content, 'focus', () => root.setAttribute('data-mk-focused', ''));
654
+ scope.on(content, 'blur', () => root.removeAttribute('data-mk-focused'));
655
+
656
+ return {
657
+ setValue(html) {
658
+ if (scope.disposed) return;
659
+ setContent(html);
660
+ paint(false);
661
+ },
662
+ focus: () => content.focus(),
663
+ exec,
664
+ get value() {
665
+ return cleanValue();
666
+ },
667
+ get text() {
668
+ return textValue();
669
+ },
670
+ element: content,
671
+ destroy: () => scope.dispose(),
672
+ };
673
+ }