@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,440 @@
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 { walk, type TreeNode } from '../../core/tree';
6
+ import { press } from '../../motion/micro';
7
+ import { markText } from '../../core/mark';
8
+ import { createTreeView } from '../tree-view/engine';
9
+
10
+ /*
11
+ * JSON viewer: any JSON value as a tree (tree-view underneath): keys, values
12
+ * coloured by type (strings, numbers, booleans, null; colours with a swatch),
13
+ * objects and lists summed up ("4 claves", "3 elementos") and opened by levels
14
+ * with the tree's own motion (height .32 s, children out of a blur, the
15
+ * chevron turning). Each row has a copy button that turns into a check with a
16
+ * pop; Ctrl/⌘+C copies the focused value. The search marks keys and values and
17
+ * opens what holds them. setData() keeps what was open and flashes the values
18
+ * that changed (and the keys that are new) in the accent for a moment. The
19
+ * selected row's path ($.user.roles[0]) shows below, ready to copy.
20
+ */
21
+
22
+ export type JsonType = 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null';
23
+
24
+ export interface JsonViewerOptions {
25
+ /** The value; without it, the JSON written inside the root (as text or in a <script type="application/json">). */
26
+ data?: unknown;
27
+ /** Levels open at the start: 1 shows the top keys closed, 2 opens them… (default 1; Infinity opens all). */
28
+ expandDepth?: number;
29
+ /** A row for the value itself with this name; without it the top keys are the first rows. */
30
+ rootName?: string;
31
+ /** A search field: true, or its placeholder (default false). */
32
+ search?: boolean | string;
33
+ /** The bar with expand, collapse and copy (default true). */
34
+ toolbar?: boolean;
35
+ /** Keys in alphabetical order (default false: as they come). */
36
+ sortKeys?: boolean;
37
+ /** Accessible name (default 'Datos JSON'). */
38
+ label?: string;
39
+ onSelect?: (path: string, value: unknown) => void;
40
+ onCopy?: (text: string, path: string) => void;
41
+ }
42
+
43
+ export interface JsonViewerInstance extends Instance {
44
+ setData(data: unknown): void;
45
+ expandAll(): void;
46
+ collapseAll(): void;
47
+ /** Open the rows down to this path ($.user.roles) and show it. */
48
+ reveal(path: string): void;
49
+ filter(query: string): void;
50
+ /** Copy a value (default: all of it) as text. */
51
+ copy(path?: string): Promise<boolean>;
52
+ readonly data: unknown;
53
+ }
54
+
55
+ interface JsonMeta {
56
+ key: string | number | null;
57
+ value: unknown;
58
+ type: JsonType;
59
+ path: string;
60
+ /** Keys or items, for objects and lists. */
61
+ size: number;
62
+ }
63
+
64
+ const COPY = '<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg>';
65
+ const CHECK = '<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
66
+ const SVG = (paths: string) => `<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
67
+ const EXPAND = SVG('<path d="M7 9l5-5 5 5M7 15l5 5 5-5"/>');
68
+ const COLLAPSE = SVG('<path d="M7 4l5 5 5-5M7 20l5-5 5 5"/>');
69
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
70
+ const COLOR = /^(#(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})|(?:rgb|hsl|oklch|oklab)a?\([^)]*\))$/i;
71
+
72
+ export function typeOfJson(value: unknown): JsonType {
73
+ if (value === null || value === undefined) return 'null';
74
+ if (Array.isArray(value)) return 'array';
75
+ if (typeof value === 'object') return 'object';
76
+ if (typeof value === 'number' || typeof value === 'bigint') return 'number';
77
+ if (typeof value === 'boolean') return 'boolean';
78
+ return 'string';
79
+ }
80
+
81
+ /** $.user, $.items[0], $["con espacios"]. */
82
+ export const jsonPath = (parent: string, key: string | number) =>
83
+ typeof key === 'number' ? `${parent}[${key}]` : IDENTIFIER.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
84
+
85
+ /** How a value reads in its row: strings quoted, the rest as JSON. */
86
+ const shown = (meta: JsonMeta) =>
87
+ meta.type === 'string' ? JSON.stringify(String(meta.value)) : meta.type === 'null' ? 'null' : typeof meta.value === 'bigint' ? meta.value.toString() : String(meta.value);
88
+ const plural = (n: number, one: string, many: string) => `${n} ${n === 1 ? one : many}`;
89
+ const summary = (meta: JsonMeta) => (meta.type === 'array' ? (meta.size ? plural(meta.size, 'elemento', 'elementos') : 'vacía') : meta.size ? plural(meta.size, 'clave', 'claves') : 'vacío');
90
+ /** What copying a value gives: a string as it is, anything else as indented JSON. */
91
+ const textOf = (value: unknown) =>
92
+ typeof value === 'string' ? value : JSON.stringify(value, (_key, item: unknown) => (typeof item === 'bigint' ? item.toString() : item), 2) ?? String(value);
93
+
94
+ async function writeClipboard(text: string): Promise<boolean> {
95
+ try {
96
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
97
+ await navigator.clipboard.writeText(text);
98
+ return true;
99
+ }
100
+ } catch {
101
+ // Not allowed (no focus, no permission): the old way below.
102
+ }
103
+ try {
104
+ const area = document.createElement('textarea');
105
+ area.value = text;
106
+ area.setAttribute('readonly', '');
107
+ area.style.cssText = 'position:fixed;top:0;left:0;opacity:0;pointer-events:none';
108
+ document.body.append(area);
109
+ area.select();
110
+ const done = typeof document.execCommand === 'function' && document.execCommand('copy');
111
+ area.remove();
112
+ return Boolean(done);
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ export function createJsonViewer(root: HTMLElement | null, options: JsonViewerOptions = {}): JsonViewerInstance {
119
+ if (!root) return inert({ setData() {}, expandAll() {}, collapseAll() {}, reveal() {}, filter() {}, copy: () => Promise.resolve(false), data: undefined });
120
+ registerMotion();
121
+ const scope = createScope();
122
+ // JSON written inside the root stays there, out of sight.
123
+ const authored = root.querySelector('script[type="application/json"]')?.textContent ?? root.textContent ?? '';
124
+ let data: unknown = options.data;
125
+ // JSON text from an attribute (data-mk-data through autoInit) is read as JSON.
126
+ if (typeof data === 'string' && /^\s*[[{]/.test(data)) {
127
+ try {
128
+ data = JSON.parse(data);
129
+ } catch {
130
+ // Not JSON after all: shown as the string it is.
131
+ }
132
+ }
133
+ if (data === undefined) {
134
+ try {
135
+ data = authored.trim() ? JSON.parse(authored) : null;
136
+ } catch {
137
+ data = null;
138
+ }
139
+ }
140
+ const holder = document.createElement('div');
141
+ holder.hidden = true;
142
+ holder.append(...root.childNodes);
143
+ const depthOpen = options.expandDepth ?? 1;
144
+ const effects = new Set<gsap.core.Animation>();
145
+ const keep = (animation: gsap.core.Animation | null) => {
146
+ if (!animation) return;
147
+ effects.add(animation);
148
+ // Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
149
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
150
+ };
151
+
152
+ // ── The value as tree nodes ──────────────────────────────────────────────
153
+ const metas = new Map<string, JsonMeta>();
154
+ const depths = new Map<string, number>();
155
+ const build = (value: unknown, key: string | number | null, path: string, depth: number): TreeNode => {
156
+ const type = typeOfJson(value);
157
+ let children: TreeNode[] | undefined;
158
+ if (type === 'object') {
159
+ const entries = Object.entries(value as Record<string, unknown>).filter(([, item]) => item !== undefined && typeof item !== 'function');
160
+ if (options.sortKeys) entries.sort(([a], [b]) => a.localeCompare(b));
161
+ children = entries.map(([name, item]) => build(item, name, jsonPath(path, name), depth + 1));
162
+ } else if (type === 'array') children = (value as unknown[]).map((item, i) => build(item, i, jsonPath(path, i), depth + 1));
163
+ const meta: JsonMeta = { key, value, type, path, size: children?.length ?? 0 };
164
+ metas.set(path, meta);
165
+ depths.set(path, depth);
166
+ return { id: path, label: key === null ? '' : String(key), children, data: meta };
167
+ };
168
+ const nodesOf = (value: unknown): TreeNode[] => {
169
+ metas.clear();
170
+ depths.clear();
171
+ const type = typeOfJson(value);
172
+ if (options.rootName !== undefined || (type !== 'object' && type !== 'array')) return [build(value, options.rootName ?? 'valor', '$', 0)];
173
+ const top = build(value, null, '$', 0);
174
+ return top.children ?? [];
175
+ };
176
+ /** Leaves as path → text, to tell what changed. */
177
+ const leaves = () => {
178
+ const values = new Map<string, string>();
179
+ for (const [path, meta] of metas) if (meta.type !== 'object' && meta.type !== 'array') values.set(path, shown(meta));
180
+ return values;
181
+ };
182
+ let nodes = nodesOf(data);
183
+ const openAtStart = () => {
184
+ const ids: string[] = [];
185
+ walk(nodes, (node) => {
186
+ if (node.children?.length && (depths.get(node.id) ?? 0) < depthOpen) ids.push(node.id);
187
+ });
188
+ return ids;
189
+ };
190
+
191
+ // ── The parts ────────────────────────────────────────────────────────────
192
+ const status = document.createElement('span');
193
+ status.className = 'mk-sr-only';
194
+ status.setAttribute('role', 'status');
195
+ let bar: HTMLElement | null = null;
196
+ let searchInput: HTMLInputElement | null = null;
197
+ const barButton = (name: string, label: string, icon: string) => {
198
+ const button = document.createElement('button');
199
+ button.type = 'button';
200
+ button.dataset.mkJvTool = name;
201
+ button.setAttribute('aria-label', label);
202
+ button.title = label;
203
+ button.innerHTML = icon;
204
+ return button;
205
+ };
206
+ const copyAll = barButton('copy', 'Copiar JSON', COPY);
207
+ if (options.toolbar !== false || options.search) {
208
+ bar = document.createElement('div');
209
+ bar.dataset.mkJvBar = '';
210
+ if (options.search) {
211
+ searchInput = document.createElement('input');
212
+ searchInput.type = 'search';
213
+ searchInput.dataset.mkJvSearch = '';
214
+ searchInput.placeholder = typeof options.search === 'string' ? options.search : 'Buscar clave o valor…';
215
+ searchInput.setAttribute('aria-label', 'Buscar en el JSON');
216
+ bar.append(searchInput);
217
+ }
218
+ if (options.toolbar !== false) {
219
+ const tools = document.createElement('div');
220
+ tools.dataset.mkJvTools = '';
221
+ tools.append(barButton('expand', 'Expandir todo', EXPAND), barButton('collapse', 'Contraer todo', COLLAPSE), copyAll);
222
+ bar.append(tools);
223
+ }
224
+ }
225
+ const host = document.createElement('div');
226
+ host.className = 'mk-tree-view';
227
+ host.dataset.mkJvTree = '';
228
+ const foot = document.createElement('div');
229
+ foot.dataset.mkJvFoot = '';
230
+ foot.hidden = true;
231
+ const pathText = document.createElement('code');
232
+ pathText.dataset.mkJvPath = '';
233
+ const copyPath = barButton('copy-path', 'Copiar ruta', COPY);
234
+ foot.append(pathText, copyPath);
235
+ if (bar) root.append(bar);
236
+ root.append(host, foot, status);
237
+ scope.add(() => {
238
+ for (const effect of effects) effect.kill();
239
+ effects.clear();
240
+ bar?.remove();
241
+ host.remove();
242
+ foot.remove();
243
+ status.remove();
244
+ root.append(...holder.childNodes);
245
+ });
246
+ scope.attr(root, 'data-mk-ready', '');
247
+
248
+ // ── Copying, with a check that pops ──────────────────────────────────────
249
+ const copied = (button: HTMLElement | null) => {
250
+ if (!button) return;
251
+ button.innerHTML = CHECK;
252
+ button.setAttribute('data-mk-copied', '');
253
+ if (!reduced()) keep(gsap.fromTo(button.firstElementChild, { scale: 0.3, rotation: -25 }, { scale: 1, rotation: 0, duration: 0.42, ease: EASE.pop, transformOrigin: '50% 50%', clearProps: 'transform' }));
254
+ scope.timeout(() => {
255
+ button.innerHTML = COPY;
256
+ button.removeAttribute('data-mk-copied');
257
+ }, 1200);
258
+ };
259
+ const copyValue = async (path: string, button: HTMLElement | null) => {
260
+ const meta = metas.get(path);
261
+ const value = path === '$' ? data : meta?.value;
262
+ if (path !== '$' && !meta) return false;
263
+ const text = textOf(value);
264
+ keep(press(button));
265
+ const done = await writeClipboard(text);
266
+ if (scope.disposed) return done;
267
+ status.textContent = done ? `Copiado ${path === '$' ? 'el JSON' : path}` : 'No se pudo copiar';
268
+ if (done) {
269
+ copied(button);
270
+ options.onCopy?.(text, path);
271
+ root.dispatchEvent(new CustomEvent('mk:copy', { bubbles: true, detail: { text, path } }));
272
+ }
273
+ return done;
274
+ };
275
+
276
+ // ── Rows ─────────────────────────────────────────────────────────────────
277
+ const renderLabel = (node: TreeNode, element: HTMLElement, query: string) => {
278
+ const meta = node.data as JsonMeta;
279
+ element.dataset.type = meta.type;
280
+ if (meta.key !== null) {
281
+ const key = document.createElement('span');
282
+ key.dataset.mkJvKey = typeof meta.key === 'number' ? 'index' : 'name';
283
+ key.append(markText(String(meta.key), query));
284
+ const colon = document.createElement('span');
285
+ colon.dataset.mkJvColon = '';
286
+ colon.textContent = ':';
287
+ element.append(key, colon);
288
+ }
289
+ if (meta.type === 'object' || meta.type === 'array') {
290
+ const brackets = document.createElement('span');
291
+ brackets.dataset.mkJvBrackets = '';
292
+ brackets.textContent = meta.type === 'array' ? '[ ]' : '{ }';
293
+ const count = document.createElement('span');
294
+ count.dataset.mkJvSummary = '';
295
+ count.textContent = summary(meta);
296
+ element.append(brackets, count);
297
+ } else {
298
+ const value = document.createElement('span');
299
+ value.dataset.mkJvValue = meta.type;
300
+ const text = shown(meta);
301
+ value.title = meta.type === 'string' ? String(meta.value) : text;
302
+ if (meta.type === 'string' && COLOR.test(String(meta.value).trim())) {
303
+ const swatch = document.createElement('span');
304
+ swatch.dataset.mkJvSwatch = '';
305
+ swatch.setAttribute('aria-hidden', 'true');
306
+ swatch.style.background = String(meta.value).trim();
307
+ value.append(swatch);
308
+ }
309
+ value.append(markText(text, query));
310
+ element.append(value);
311
+ }
312
+ const copy = document.createElement('button');
313
+ copy.type = 'button';
314
+ copy.tabIndex = -1;
315
+ copy.dataset.mkJvCopy = '';
316
+ copy.setAttribute('aria-label', `Copiar ${meta.path}`);
317
+ copy.title = 'Copiar';
318
+ copy.innerHTML = COPY;
319
+ // The row does not select or open under the button.
320
+ copy.addEventListener('click', (event) => {
321
+ event.stopPropagation();
322
+ void copyValue(meta.path, copy);
323
+ });
324
+ copy.addEventListener('dblclick', (event) => event.stopPropagation());
325
+ element.append(copy);
326
+ };
327
+
328
+ const tree = createTreeView(host, {
329
+ nodes,
330
+ expanded: openAtStart(),
331
+ label: options.label ?? 'Datos JSON',
332
+ renderLabel,
333
+ searchText: (node) => {
334
+ const meta = node.data as JsonMeta;
335
+ return `${meta.key ?? ''} ${meta.type === 'object' || meta.type === 'array' ? '' : shown(meta)}`;
336
+ },
337
+ onSelect: (node) => {
338
+ const meta = node.data as JsonMeta;
339
+ foot.hidden = false;
340
+ pathText.textContent = meta.path;
341
+ options.onSelect?.(meta.path, meta.value);
342
+ root.dispatchEvent(new CustomEvent('mk:select', { bubbles: true, detail: { path: meta.path, value: meta.value } }));
343
+ },
344
+ });
345
+ // Listeners on the viewer hear its own events, not those of the tree inside.
346
+ for (const type of ['mk:select', 'mk:expand', 'mk:check']) scope.on(host, type, (event) => event.stopPropagation());
347
+ scope.add(() => tree.destroy());
348
+
349
+ const itemOf = (path: string) => [...tree.element.querySelectorAll<HTMLElement>('[role="treeitem"]')].find((item) => item.dataset.id === path) ?? null;
350
+
351
+ scope.on(tree.element, 'keydown', (event) => {
352
+ const key = event as KeyboardEvent;
353
+ if (!(key.ctrlKey || key.metaKey) || key.key.toLowerCase() !== 'c' || window.getSelection?.()?.toString()) return;
354
+ const item = (key.target as Element | null)?.closest<HTMLElement>('[role="treeitem"]');
355
+ if (!item) return;
356
+ key.preventDefault();
357
+ void copyValue(item.dataset.id!, item.querySelector<HTMLElement>(':scope > [data-mk-tree-row] [data-mk-jv-copy]'));
358
+ });
359
+ if (searchInput) {
360
+ const input = searchInput;
361
+ scope.on(input, 'input', () => tree.filter(input.value));
362
+ scope.on(input, 'keydown', (event) => {
363
+ if ((event as KeyboardEvent).key !== 'ArrowDown') return;
364
+ event.preventDefault();
365
+ tree.element.querySelector<HTMLElement>('[role="treeitem"]:not([hidden])')?.focus();
366
+ });
367
+ }
368
+ if (bar) {
369
+ scope.on(bar, 'click', (event) => {
370
+ const button = (event.target as Element | null)?.closest<HTMLElement>('[data-mk-jv-tool]');
371
+ if (!button) return;
372
+ const name = button.dataset.mkJvTool;
373
+ if (name === 'expand' || name === 'collapse') {
374
+ keep(press(button));
375
+ if (name === 'expand') tree.expandAll();
376
+ else tree.collapseAll();
377
+ status.textContent = name === 'expand' ? 'Todo expandido' : 'Todo contraído';
378
+ if (name === 'expand' && !reduced()) {
379
+ const rows = [...tree.element.querySelectorAll<HTMLElement>('[role="treeitem"]')].filter((item) => !item.closest('[role="group"][hidden]')).slice(0, 40);
380
+ keep(gsap.fromTo(rows, { opacity: 0, x: -6 }, { opacity: 1, x: 0, duration: 0.3, stagger: 0.012, ease: 'power2.out', clearProps: 'opacity,transform' }));
381
+ }
382
+ } else if (name === 'copy') void copyValue('$', button);
383
+ });
384
+ }
385
+ scope.on(copyPath, 'click', async () => {
386
+ const text = pathText.textContent ?? '';
387
+ keep(press(copyPath));
388
+ if (await writeClipboard(text)) {
389
+ copied(copyPath);
390
+ status.textContent = `Ruta copiada: ${text}`;
391
+ }
392
+ });
393
+
394
+ return {
395
+ setData(next) {
396
+ if (scope.disposed) return;
397
+ const before = leaves();
398
+ const known = new Set(metas.keys());
399
+ data = next;
400
+ nodes = nodesOf(next);
401
+ tree.setNodes(nodes);
402
+ if (!foot.hidden && !metas.has(pathText.textContent ?? '')) foot.hidden = true;
403
+ // What changed flashes for a moment; what is new comes in out of a blur.
404
+ const after = leaves();
405
+ let changes = 0;
406
+ for (const [path, text] of after) {
407
+ const item = itemOf(path);
408
+ const value = item?.querySelector<HTMLElement>(':scope > [data-mk-tree-row] [data-mk-jv-value]');
409
+ if (!value) continue;
410
+ if (before.has(path) && before.get(path) !== text) {
411
+ changes++;
412
+ value.setAttribute('data-mk-jv-changed', '');
413
+ }
414
+ }
415
+ const added = [...metas.keys()].filter((path) => !known.has(path));
416
+ for (const path of added) itemOf(path)?.querySelector<HTMLElement>(':scope > [data-mk-tree-row]')?.setAttribute('data-mk-jv-added', '');
417
+ if (changes || added.length) status.textContent = [changes ? plural(changes, 'valor cambió', 'valores cambiaron') : '', added.length ? plural(added.length, 'clave nueva', 'claves nuevas') : ''].filter(Boolean).join(', ');
418
+ },
419
+ expandAll: () => tree.expandAll(),
420
+ collapseAll: () => tree.collapseAll(),
421
+ reveal(path) {
422
+ tree.reveal(path);
423
+ tree.select(path);
424
+ const meta = metas.get(path);
425
+ if (meta) {
426
+ foot.hidden = false;
427
+ pathText.textContent = meta.path;
428
+ }
429
+ },
430
+ filter(query) {
431
+ if (searchInput) searchInput.value = query;
432
+ tree.filter(query);
433
+ },
434
+ copy: (path = '$') => copyValue(path, path === '$' ? copyAll : (itemOf(path)?.querySelector<HTMLElement>(':scope > [data-mk-tree-row] [data-mk-jv-copy]') ?? null)),
435
+ get data() {
436
+ return data;
437
+ },
438
+ destroy: () => scope.dispose(),
439
+ };
440
+ }