@yewdiel21/motion-kit 0.9.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. package/README.md +72 -0
  2. package/dist/auto/index.d.ts +72 -3
  3. package/dist/auto/index.js +139 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-BFC27F2G.js → chunk-6P4P7NU6.js} +2 -218
  6. package/dist/chunk-6P4P7NU6.js.map +1 -0
  7. package/dist/chunk-CU6SXW3E.js +20334 -0
  8. package/dist/chunk-CU6SXW3E.js.map +1 -0
  9. package/dist/{chunk-XDWBVFHQ.js → chunk-EY4BJKC4.js} +1203 -29
  10. package/dist/chunk-EY4BJKC4.js.map +1 -0
  11. package/dist/chunk-QP4QJOEN.js +72 -0
  12. package/dist/chunk-QP4QJOEN.js.map +1 -0
  13. package/dist/css/components/activity-feed.css +240 -0
  14. package/dist/css/components/attachment.css +266 -0
  15. package/dist/css/components/breadcrumb.css +110 -0
  16. package/dist/css/components/calendar.css +159 -0
  17. package/dist/css/components/chart-tooltip.css +168 -0
  18. package/dist/css/components/chart.css +296 -0
  19. package/dist/css/components/code-block.css +264 -0
  20. package/dist/css/components/color-area.css +82 -0
  21. package/dist/css/components/color-field.css +67 -0
  22. package/dist/css/components/color-picker.css +131 -0
  23. package/dist/css/components/color-slider.css +113 -0
  24. package/dist/css/components/command-search.css +252 -0
  25. package/dist/css/components/context-menu.css +23 -0
  26. package/dist/css/components/credit-card.css +288 -0
  27. package/dist/css/components/data-table.css +591 -0
  28. package/dist/css/components/date-range-picker.css +168 -0
  29. package/dist/css/components/date-time-picker.css +178 -0
  30. package/dist/css/components/drawer.css +58 -0
  31. package/dist/css/components/editable-table.css +201 -0
  32. package/dist/css/components/empty-state.css +61 -0
  33. package/dist/css/components/file-explorer.css +633 -0
  34. package/dist/css/components/filter-builder.css +232 -0
  35. package/dist/css/components/hold-confirm.css +87 -0
  36. package/dist/css/components/hover-card.css +20 -0
  37. package/dist/css/components/image-preview.css +195 -0
  38. package/dist/css/components/input-otp.css +10 -0
  39. package/dist/css/components/json-viewer.css +272 -0
  40. package/dist/css/components/like-button.css +78 -0
  41. package/dist/css/components/link-preview.css +275 -0
  42. package/dist/css/components/loading-button.css +77 -0
  43. package/dist/css/components/multi-select.css +267 -0
  44. package/dist/css/components/notification-bell.css +127 -0
  45. package/dist/css/components/pagination.css +11 -0
  46. package/dist/css/components/password-strength.css +183 -0
  47. package/dist/css/components/popover.css +25 -0
  48. package/dist/css/components/progress.css +140 -0
  49. package/dist/css/components/quantity.css +16 -0
  50. package/dist/css/components/questionnaire.css +217 -0
  51. package/dist/css/components/range-calendar.css +186 -0
  52. package/dist/css/components/range-slider.css +192 -0
  53. package/dist/css/components/resizable-panel.css +243 -0
  54. package/dist/css/components/rich-text-editor.css +284 -0
  55. package/dist/css/components/scratch-card.css +75 -0
  56. package/dist/css/components/skeleton.css +108 -0
  57. package/dist/css/components/slot-picker.css +126 -0
  58. package/dist/css/components/split-view.css +19 -0
  59. package/dist/css/components/star-rating.css +80 -0
  60. package/dist/css/components/stat-card.css +164 -0
  61. package/dist/css/components/studio-dock.css +35 -0
  62. package/dist/css/components/swipe-actions.css +105 -0
  63. package/dist/css/components/tag-input.css +244 -0
  64. package/dist/css/components/time-field.css +88 -0
  65. package/dist/css/components/timeline.css +219 -0
  66. package/dist/css/components/tooltip.css +79 -0
  67. package/dist/css/components/trailer.css +37 -0
  68. package/dist/css/components/tree-view.css +210 -0
  69. package/dist/css/components/virtual-list.css +195 -0
  70. package/dist/css/controls.css +65 -0
  71. package/dist/css/tokens.css +185 -181
  72. package/dist/elements/index.d.ts +742 -5
  73. package/dist/elements/index.js +1527 -8
  74. package/dist/elements/index.js.map +1 -1
  75. package/dist/{engine-GDGwQnJE.d.ts → engine-Cn6IhE3z.d.ts} +1 -1
  76. package/dist/engine-QECQnGz1.d.ts +33 -0
  77. package/dist/index.css +10113 -181
  78. package/dist/index.d.ts +89 -5
  79. package/dist/index.js +285 -9
  80. package/dist/index.js.map +1 -1
  81. package/dist/react/index.d.ts +678 -23
  82. package/dist/react/index.js +2389 -7
  83. package/dist/react/index.js.map +1 -1
  84. package/dist/{reduced-B98f8LPs.d.ts → reduced-BBUusld6.d.ts} +206 -2
  85. package/dist/reveal-DEvp6U6T.d.ts +2381 -0
  86. package/dist/{store-pHiUvxP8.d.ts → store-DjkTBX7f.d.ts} +2 -2
  87. package/package.json +1 -1
  88. package/registry.json +583 -104
  89. package/skills/motion-kit/SKILL.md +209 -189
  90. package/src/auto/auto-init.ts +136 -0
  91. package/src/components/activity-feed/README.md +56 -0
  92. package/src/components/activity-feed/activity-feed.css +239 -0
  93. package/src/components/activity-feed/element.ts +36 -0
  94. package/src/components/activity-feed/engine.ts +351 -0
  95. package/src/components/activity-feed/react.tsx +32 -0
  96. package/src/components/attachment/README.md +27 -0
  97. package/src/components/attachment/attachment.css +265 -0
  98. package/src/components/attachment/element.ts +50 -0
  99. package/src/components/attachment/engine.ts +469 -0
  100. package/src/components/attachment/react.tsx +65 -0
  101. package/src/components/breadcrumb/README.md +38 -0
  102. package/src/components/breadcrumb/breadcrumb.css +109 -0
  103. package/src/components/breadcrumb/element.ts +27 -0
  104. package/src/components/breadcrumb/engine.ts +249 -0
  105. package/src/components/breadcrumb/react.tsx +30 -0
  106. package/src/components/calendar/README.md +82 -0
  107. package/src/components/calendar/calendar.css +158 -0
  108. package/src/components/calendar/element.ts +27 -0
  109. package/src/components/calendar/engine.ts +412 -0
  110. package/src/components/calendar/react.tsx +69 -0
  111. package/src/components/chart/README.md +24 -0
  112. package/src/components/chart/chart.css +295 -0
  113. package/src/components/chart/element.ts +41 -0
  114. package/src/components/chart/engine.ts +811 -0
  115. package/src/components/chart/react.tsx +50 -0
  116. package/src/components/chart-tooltip/README.md +53 -0
  117. package/src/components/chart-tooltip/chart-tooltip.css +167 -0
  118. package/src/components/chart-tooltip/element.ts +37 -0
  119. package/src/components/chart-tooltip/engine.ts +442 -0
  120. package/src/components/chart-tooltip/react.tsx +47 -0
  121. package/src/components/code-block/README.md +19 -0
  122. package/src/components/code-block/code-block.css +263 -0
  123. package/src/components/code-block/element.ts +34 -0
  124. package/src/components/code-block/engine.ts +254 -0
  125. package/src/components/code-block/highlight.ts +244 -0
  126. package/src/components/code-block/react.tsx +94 -0
  127. package/src/components/color-area/README.md +50 -0
  128. package/src/components/color-area/color-area.css +81 -0
  129. package/src/components/color-area/element.ts +26 -0
  130. package/src/components/color-area/engine.ts +246 -0
  131. package/src/components/color-area/react.tsx +42 -0
  132. package/src/components/color-field/README.md +43 -0
  133. package/src/components/color-field/color-field.css +66 -0
  134. package/src/components/color-field/element.ts +26 -0
  135. package/src/components/color-field/engine.ts +174 -0
  136. package/src/components/color-field/react.tsx +42 -0
  137. package/src/components/color-picker/README.md +45 -0
  138. package/src/components/color-picker/color-picker.css +130 -0
  139. package/src/components/color-picker/element.ts +45 -0
  140. package/src/components/color-picker/engine.ts +314 -0
  141. package/src/components/color-picker/react.tsx +47 -0
  142. package/src/components/color-slider/README.md +55 -0
  143. package/src/components/color-slider/color-slider.css +112 -0
  144. package/src/components/color-slider/element.ts +26 -0
  145. package/src/components/color-slider/engine.ts +234 -0
  146. package/src/components/color-slider/react.tsx +45 -0
  147. package/src/components/command-search/README.md +15 -0
  148. package/src/components/command-search/command-search.css +251 -0
  149. package/src/components/command-search/element.ts +36 -0
  150. package/src/components/command-search/engine.ts +356 -0
  151. package/src/components/command-search/match.ts +53 -0
  152. package/src/components/command-search/react.tsx +66 -0
  153. package/src/components/context-menu/README.md +31 -0
  154. package/src/components/context-menu/context-menu.css +22 -0
  155. package/src/components/context-menu/element.ts +40 -0
  156. package/src/components/context-menu/engine.ts +153 -0
  157. package/src/components/context-menu/react.tsx +57 -0
  158. package/src/components/credit-card/README.md +20 -0
  159. package/src/components/credit-card/card.ts +77 -0
  160. package/src/components/credit-card/credit-card.css +287 -0
  161. package/src/components/credit-card/element.ts +33 -0
  162. package/src/components/credit-card/engine.ts +410 -0
  163. package/src/components/credit-card/react.tsx +36 -0
  164. package/src/components/data-table/README.md +83 -0
  165. package/src/components/data-table/data-table.css +590 -0
  166. package/src/components/data-table/element.ts +54 -0
  167. package/src/components/data-table/engine.ts +1059 -0
  168. package/src/components/data-table/react.tsx +94 -0
  169. package/src/components/date-range-picker/README.md +51 -0
  170. package/src/components/date-range-picker/date-range-picker.css +167 -0
  171. package/src/components/date-range-picker/element.ts +52 -0
  172. package/src/components/date-range-picker/engine.ts +352 -0
  173. package/src/components/date-range-picker/react.tsx +57 -0
  174. package/src/components/date-time-picker/README.md +48 -0
  175. package/src/components/date-time-picker/date-time-picker.css +177 -0
  176. package/src/components/date-time-picker/element.ts +47 -0
  177. package/src/components/date-time-picker/engine.ts +350 -0
  178. package/src/components/date-time-picker/react.tsx +54 -0
  179. package/src/components/dialog/README.md +103 -102
  180. package/src/components/dialog/element.ts +123 -118
  181. package/src/components/dialog/engine.ts +449 -432
  182. package/src/components/drawer/README.md +28 -0
  183. package/src/components/drawer/drawer.css +57 -0
  184. package/src/components/drawer/element.ts +21 -0
  185. package/src/components/drawer/engine.ts +56 -0
  186. package/src/components/drawer/react.tsx +85 -0
  187. package/src/components/editable-table/README.md +67 -0
  188. package/src/components/editable-table/editable-table.css +200 -0
  189. package/src/components/editable-table/element.ts +45 -0
  190. package/src/components/editable-table/engine.ts +775 -0
  191. package/src/components/editable-table/react.tsx +79 -0
  192. package/src/components/empty-state/README.md +41 -0
  193. package/src/components/empty-state/element.ts +23 -0
  194. package/src/components/empty-state/empty-state.css +60 -0
  195. package/src/components/empty-state/engine.ts +232 -0
  196. package/src/components/empty-state/react.tsx +34 -0
  197. package/src/components/file-explorer/README.md +67 -0
  198. package/src/components/file-explorer/element.ts +39 -0
  199. package/src/components/file-explorer/engine.ts +1307 -0
  200. package/src/components/file-explorer/file-explorer.css +632 -0
  201. package/src/components/file-explorer/react.tsx +77 -0
  202. package/src/components/filter-builder/README.md +47 -0
  203. package/src/components/filter-builder/element.ts +34 -0
  204. package/src/components/filter-builder/engine.ts +365 -0
  205. package/src/components/filter-builder/filter-builder.css +231 -0
  206. package/src/components/filter-builder/react.tsx +36 -0
  207. package/src/components/hold-confirm/README.md +13 -0
  208. package/src/components/hold-confirm/element.ts +24 -0
  209. package/src/components/hold-confirm/engine.ts +216 -0
  210. package/src/components/hold-confirm/hold-confirm.css +86 -0
  211. package/src/components/hold-confirm/react.tsx +28 -0
  212. package/src/components/hover-card/README.md +20 -0
  213. package/src/components/hover-card/element.ts +8 -0
  214. package/src/components/hover-card/engine.ts +131 -0
  215. package/src/components/hover-card/hover-card.css +19 -0
  216. package/src/components/hover-card/react.tsx +19 -0
  217. package/src/components/image-preview/README.md +16 -0
  218. package/src/components/image-preview/element.ts +26 -0
  219. package/src/components/image-preview/engine.ts +325 -0
  220. package/src/components/image-preview/image-preview.css +194 -0
  221. package/src/components/image-preview/react.tsx +43 -0
  222. package/src/components/input-otp/README.md +18 -0
  223. package/src/components/input-otp/element.ts +14 -0
  224. package/src/components/input-otp/engine.ts +106 -0
  225. package/src/components/input-otp/input-otp.css +9 -0
  226. package/src/components/input-otp/react.tsx +21 -0
  227. package/src/components/json-viewer/README.md +56 -0
  228. package/src/components/json-viewer/element.ts +37 -0
  229. package/src/components/json-viewer/engine.ts +440 -0
  230. package/src/components/json-viewer/json-viewer.css +271 -0
  231. package/src/components/json-viewer/react.tsx +56 -0
  232. package/src/components/like-button/README.md +13 -0
  233. package/src/components/like-button/element.ts +47 -0
  234. package/src/components/like-button/engine.ts +145 -0
  235. package/src/components/like-button/like-button.css +77 -0
  236. package/src/components/like-button/react.tsx +39 -0
  237. package/src/components/link-preview/README.md +19 -0
  238. package/src/components/link-preview/element.ts +29 -0
  239. package/src/components/link-preview/engine.ts +192 -0
  240. package/src/components/link-preview/link-preview.css +274 -0
  241. package/src/components/link-preview/react.tsx +101 -0
  242. package/src/components/loading-button/README.md +45 -0
  243. package/src/components/loading-button/element.ts +34 -0
  244. package/src/components/loading-button/engine.ts +201 -0
  245. package/src/components/loading-button/loading-button.css +76 -0
  246. package/src/components/loading-button/react.tsx +43 -0
  247. package/src/components/multi-select/README.md +48 -0
  248. package/src/components/multi-select/element.ts +28 -0
  249. package/src/components/multi-select/engine.ts +294 -0
  250. package/src/components/multi-select/multi-select.css +266 -0
  251. package/src/components/multi-select/react.tsx +39 -0
  252. package/src/components/notification-bell/README.md +14 -0
  253. package/src/components/notification-bell/element.ts +48 -0
  254. package/src/components/notification-bell/engine.ts +202 -0
  255. package/src/components/notification-bell/notification-bell.css +126 -0
  256. package/src/components/notification-bell/react.tsx +57 -0
  257. package/src/components/pagination/README.md +17 -0
  258. package/src/components/pagination/element.ts +14 -0
  259. package/src/components/pagination/engine.ts +102 -0
  260. package/src/components/pagination/pagination.css +10 -0
  261. package/src/components/pagination/react.tsx +22 -0
  262. package/src/components/password-strength/README.md +17 -0
  263. package/src/components/password-strength/element.ts +22 -0
  264. package/src/components/password-strength/engine.ts +212 -0
  265. package/src/components/password-strength/password-strength.css +182 -0
  266. package/src/components/password-strength/react.tsx +36 -0
  267. package/src/components/password-strength/score.ts +62 -0
  268. package/src/components/popover/README.md +44 -0
  269. package/src/components/popover/element.ts +31 -0
  270. package/src/components/popover/engine.ts +114 -0
  271. package/src/components/popover/popover.css +24 -0
  272. package/src/components/popover/react.tsx +50 -0
  273. package/src/components/progress/README.md +17 -0
  274. package/src/components/progress/element.ts +30 -0
  275. package/src/components/progress/engine.ts +173 -0
  276. package/src/components/progress/progress.css +139 -0
  277. package/src/components/progress/react.tsx +35 -0
  278. package/src/components/quantity/README.md +20 -0
  279. package/src/components/quantity/element.ts +14 -0
  280. package/src/components/quantity/engine.ts +85 -0
  281. package/src/components/quantity/quantity.css +15 -0
  282. package/src/components/quantity/react.tsx +21 -0
  283. package/src/components/questionnaire/README.md +32 -0
  284. package/src/components/questionnaire/element.ts +118 -0
  285. package/src/components/questionnaire/engine.ts +255 -0
  286. package/src/components/questionnaire/questionnaire.css +216 -0
  287. package/src/components/questionnaire/react.tsx +140 -0
  288. package/src/components/range-calendar/README.md +58 -0
  289. package/src/components/range-calendar/element.ts +42 -0
  290. package/src/components/range-calendar/engine.ts +390 -0
  291. package/src/components/range-calendar/range-calendar.css +185 -0
  292. package/src/components/range-calendar/react.tsx +56 -0
  293. package/src/components/range-slider/README.md +23 -0
  294. package/src/components/range-slider/element.ts +53 -0
  295. package/src/components/range-slider/engine.ts +316 -0
  296. package/src/components/range-slider/range-slider.css +191 -0
  297. package/src/components/range-slider/react.tsx +66 -0
  298. package/src/components/resizable-panel/README.md +61 -0
  299. package/src/components/resizable-panel/element.ts +36 -0
  300. package/src/components/resizable-panel/engine.ts +551 -0
  301. package/src/components/resizable-panel/react.tsx +74 -0
  302. package/src/components/resizable-panel/resizable-panel.css +242 -0
  303. package/src/components/rich-text-editor/README.md +57 -0
  304. package/src/components/rich-text-editor/element.ts +34 -0
  305. package/src/components/rich-text-editor/engine.ts +673 -0
  306. package/src/components/rich-text-editor/react.tsx +60 -0
  307. package/src/components/rich-text-editor/rich-text-editor.css +283 -0
  308. package/src/components/scratch-card/README.md +15 -0
  309. package/src/components/scratch-card/element.ts +30 -0
  310. package/src/components/scratch-card/engine.ts +257 -0
  311. package/src/components/scratch-card/react.tsx +40 -0
  312. package/src/components/scratch-card/scratch-card.css +74 -0
  313. package/src/components/skeleton/README.md +15 -0
  314. package/src/components/skeleton/element.ts +32 -0
  315. package/src/components/skeleton/engine.ts +104 -0
  316. package/src/components/skeleton/react.tsx +41 -0
  317. package/src/components/skeleton/skeleton.css +107 -0
  318. package/src/components/slot-picker/README.md +68 -0
  319. package/src/components/slot-picker/element.ts +27 -0
  320. package/src/components/slot-picker/engine.ts +412 -0
  321. package/src/components/slot-picker/react.tsx +76 -0
  322. package/src/components/slot-picker/slot-picker.css +125 -0
  323. package/src/components/split-view/README.md +50 -0
  324. package/src/components/split-view/element.ts +34 -0
  325. package/src/components/split-view/engine.ts +114 -0
  326. package/src/components/split-view/react.tsx +63 -0
  327. package/src/components/split-view/split-view.css +18 -0
  328. package/src/components/star-rating/README.md +14 -0
  329. package/src/components/star-rating/element.ts +26 -0
  330. package/src/components/star-rating/engine.ts +217 -0
  331. package/src/components/star-rating/react.tsx +33 -0
  332. package/src/components/star-rating/star-rating.css +79 -0
  333. package/src/components/stat-card/README.md +42 -0
  334. package/src/components/stat-card/element.ts +40 -0
  335. package/src/components/stat-card/engine.ts +283 -0
  336. package/src/components/stat-card/react.tsx +36 -0
  337. package/src/components/stat-card/stat-card.css +163 -0
  338. package/src/components/studio-dock/README.md +44 -0
  339. package/src/components/studio-dock/element.ts +11 -0
  340. package/src/components/studio-dock/engine.ts +212 -0
  341. package/src/components/studio-dock/react.tsx +23 -0
  342. package/src/components/studio-dock/studio-dock.css +34 -0
  343. package/src/components/swipe-actions/README.md +19 -0
  344. package/src/components/swipe-actions/element.ts +26 -0
  345. package/src/components/swipe-actions/engine.ts +260 -0
  346. package/src/components/swipe-actions/react.tsx +80 -0
  347. package/src/components/swipe-actions/swipe-actions.css +104 -0
  348. package/src/components/tag-input/README.md +42 -0
  349. package/src/components/tag-input/element.ts +39 -0
  350. package/src/components/tag-input/engine.ts +313 -0
  351. package/src/components/tag-input/react.tsx +54 -0
  352. package/src/components/tag-input/tag-input.css +243 -0
  353. package/src/components/time-field/README.md +54 -0
  354. package/src/components/time-field/element.ts +38 -0
  355. package/src/components/time-field/engine.ts +110 -0
  356. package/src/components/time-field/react.tsx +45 -0
  357. package/src/components/time-field/time-field.css +87 -0
  358. package/src/components/timeline/README.md +57 -0
  359. package/src/components/timeline/element.ts +31 -0
  360. package/src/components/timeline/engine.ts +300 -0
  361. package/src/components/timeline/react.tsx +59 -0
  362. package/src/components/timeline/timeline.css +218 -0
  363. package/src/components/tooltip/README.md +46 -0
  364. package/src/components/tooltip/element.ts +22 -0
  365. package/src/components/tooltip/engine.ts +288 -0
  366. package/src/components/tooltip/react.tsx +30 -0
  367. package/src/components/tooltip/tooltip.css +78 -0
  368. package/src/components/trailer/README.md +28 -0
  369. package/src/components/trailer/element.ts +13 -0
  370. package/src/components/trailer/engine.ts +189 -0
  371. package/src/components/trailer/react.tsx +16 -0
  372. package/src/components/trailer/trailer.css +36 -0
  373. package/src/components/tree-view/README.md +61 -0
  374. package/src/components/tree-view/element.ts +32 -0
  375. package/src/components/tree-view/engine.ts +569 -0
  376. package/src/components/tree-view/react.tsx +78 -0
  377. package/src/components/tree-view/tree-view.css +209 -0
  378. package/src/components/virtual-list/README.md +81 -0
  379. package/src/components/virtual-list/element.ts +42 -0
  380. package/src/components/virtual-list/engine.ts +678 -0
  381. package/src/components/virtual-list/react.tsx +101 -0
  382. package/src/components/virtual-list/virtual-list.css +194 -0
  383. package/src/core/chips.ts +196 -0
  384. package/src/core/color.ts +304 -0
  385. package/src/core/date-range.ts +67 -0
  386. package/src/core/dates.ts +108 -0
  387. package/src/core/files.ts +107 -0
  388. package/src/core/filter.ts +229 -0
  389. package/src/core/gsap.ts +80 -65
  390. package/src/core/listbox.ts +332 -0
  391. package/src/core/mark.ts +29 -0
  392. package/src/core/position.ts +89 -0
  393. package/src/core/relative-time.ts +77 -0
  394. package/src/core/segment-field.ts +373 -0
  395. package/src/core/segments.ts +153 -0
  396. package/src/core/tree.ts +116 -0
  397. package/src/core/virtual.ts +79 -0
  398. package/src/css/controls/README.md +14 -0
  399. package/src/css/controls/status.css +63 -0
  400. package/src/css/tokens.css +186 -182
  401. package/src/elements/index.ts +171 -0
  402. package/src/index.ts +97 -0
  403. package/src/motion/micro.ts +248 -0
  404. package/src/motion/sheet.ts +88 -65
  405. package/src/motion/surface.ts +9 -0
  406. package/src/react/index.ts +78 -0
  407. package/src/react/use-action-button.ts +6 -0
  408. package/src/react/use-editorial-reveal.ts +7 -0
  409. package/src/react/use-film-hero.ts +6 -0
  410. package/src/react/use-font-cycle.ts +7 -0
  411. package/src/react/use-image-zoom.ts +5 -0
  412. package/src/react/use-joined-cta.ts +5 -0
  413. package/src/react/use-local-clock.ts +5 -0
  414. package/src/react/use-scroll-dock.ts +5 -0
  415. package/src/react/use-sticker.ts +5 -0
  416. package/src/react/use-studio-dock.ts +8 -0
  417. package/src/react/use-thumbnail-nav.ts +7 -0
  418. package/src/react/use-trailer.ts +6 -0
  419. package/src/react/use-weight-hover.ts +5 -0
  420. package/src/recipes/action-button/README.md +31 -0
  421. package/src/recipes/action-button/action-button.css +113 -0
  422. package/src/recipes/action-button/create-action-button.ts +56 -0
  423. package/src/recipes/editorial-reveal/README.md +35 -0
  424. package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
  425. package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
  426. package/src/recipes/film-hero/README.md +23 -0
  427. package/src/recipes/film-hero/create-film-hero.ts +65 -0
  428. package/src/recipes/film-hero/film-hero.css +29 -0
  429. package/src/recipes/font-cycle/README.md +11 -0
  430. package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
  431. package/src/recipes/font-cycle/font-cycle.css +36 -0
  432. package/src/recipes/image-zoom/README.md +16 -0
  433. package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
  434. package/src/recipes/image-zoom/image-zoom.css +7 -0
  435. package/src/recipes/joined-cta/README.md +13 -0
  436. package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
  437. package/src/recipes/joined-cta/joined-cta.css +17 -0
  438. package/src/recipes/local-clock/README.md +31 -0
  439. package/src/recipes/local-clock/create-local-clock.ts +61 -0
  440. package/src/recipes/scroll-dock/README.md +46 -0
  441. package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
  442. package/src/recipes/scroll-dock/scroll-dock.css +70 -0
  443. package/src/recipes/sticker/README.md +59 -0
  444. package/src/recipes/sticker/create-sticker.ts +490 -0
  445. package/src/recipes/sticker/sticker.css +112 -0
  446. package/src/recipes/thumbnail-nav/README.md +16 -0
  447. package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
  448. package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
  449. package/src/recipes/weight-hover/README.md +37 -0
  450. package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
  451. package/src/recipes/weight-hover/weight-hover.css +7 -0
  452. package/dist/chunk-BFC27F2G.js.map +0 -1
  453. package/dist/chunk-J6OCFNGU.js +0 -177
  454. package/dist/chunk-J6OCFNGU.js.map +0 -1
  455. package/dist/chunk-JQMJQSXD.js +0 -2374
  456. package/dist/chunk-JQMJQSXD.js.map +0 -1
  457. package/dist/chunk-XDWBVFHQ.js.map +0 -1
  458. package/dist/reveal-C0hCDcB_.d.ts +0 -415
@@ -0,0 +1,212 @@
1
+ import { gsap, 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 { scorePassword, type PasswordChecks, type PasswordScore } from './score';
6
+
7
+ /*
8
+ * Password strength: a native password field with a show/hide toggle (the eye
9
+ * swaps with a blur), a four-segment meter whose segments light up one after
10
+ * another and change colour by level (error → warning → good → strong), a label
11
+ * that changes with a blur and a list of requirements whose checks draw
12
+ * themselves. A polite status announces the level once typing pauses.
13
+ */
14
+
15
+ export interface PasswordStrengthOptions {
16
+ minLength?: number;
17
+ /** Replace the built-in scoring. */
18
+ score?: (value: string) => PasswordScore;
19
+ /** Below this score the field reports itself invalid (setCustomValidity). */
20
+ requireScore?: number;
21
+ labels?: { show?: string; hide?: string; invalid?: string; checks?: Partial<Record<keyof PasswordChecks, string>> };
22
+ onChange?: (result: PasswordScore) => void;
23
+ }
24
+
25
+ export interface PasswordStrengthInstance extends Instance {
26
+ readonly score: number;
27
+ readonly value: string;
28
+ }
29
+
30
+ const EYE = 'M2.5 12s3.5-6.5 9.5-6.5 9.5 6.5 9.5 6.5-3.5 6.5-9.5 6.5S2.5 12 2.5 12z';
31
+ const PUPIL = 'M12 9.2a2.8 2.8 0 1 0 0 5.6 2.8 2.8 0 0 0 0-5.6z';
32
+ const SLASH = 'M4 4l16 16';
33
+ const CHECK = 'M5 12.5l4.5 4.5L19 7.5';
34
+ const ORDER: Array<keyof PasswordChecks> = ['length', 'lower', 'upper', 'number', 'symbol'];
35
+ let sequence = 0;
36
+
37
+ const icon = (paths: string[], part: string) => {
38
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
39
+ svg.setAttribute('viewBox', '0 0 24 24');
40
+ svg.setAttribute('aria-hidden', 'true');
41
+ svg.dataset.mkPart = part;
42
+ for (const d of paths) {
43
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
44
+ path.setAttribute('d', d);
45
+ path.setAttribute('pathLength', '1');
46
+ svg.append(path);
47
+ }
48
+ return svg;
49
+ };
50
+
51
+ export function createPasswordStrength(root: HTMLElement | null, options: PasswordStrengthOptions = {}): PasswordStrengthInstance {
52
+ if (!root) return inert({ score: 0, value: '' });
53
+ registerMotion();
54
+ const scope = createScope();
55
+ const uid = `mk-password-${++sequence}`;
56
+ const minLength = Number.isFinite(options.minLength) && options.minLength! > 0 ? Math.floor(options.minLength!) : 8;
57
+ const score = options.score ?? ((value: string) => scorePassword(value, minLength));
58
+ const labels = {
59
+ show: 'Mostrar contraseña',
60
+ hide: 'Ocultar contraseña',
61
+ invalid: 'Elige una contraseña más fuerte.',
62
+ ...options.labels,
63
+ checks: { length: `Al menos ${minLength} caracteres`, lower: 'Una minúscula', upper: 'Una mayúscula', number: 'Un número', symbol: 'Un símbolo', ...options.labels?.checks },
64
+ };
65
+ const made: Element[] = [];
66
+ scope.add(() => made.forEach((element) => element.remove()));
67
+
68
+ let input = root.querySelector<HTMLInputElement>('input[type="password"], input[data-mk-password]');
69
+ if (!input) {
70
+ input = document.createElement('input');
71
+ input.autocomplete = 'new-password';
72
+ input.setAttribute('aria-label', 'Contraseña');
73
+ root.append(input);
74
+ made.push(input);
75
+ }
76
+ scope.attr(input, 'type', 'password');
77
+ scope.attr(input, 'data-mk-password', '');
78
+ const field = document.createElement('span');
79
+ field.dataset.mkPasswordField = '';
80
+ input.replaceWith(field);
81
+ field.append(input);
82
+ scope.add(() => field.replaceWith(input!));
83
+ const toggle = document.createElement('button');
84
+ toggle.type = 'button';
85
+ toggle.dataset.mkPasswordToggle = '';
86
+ toggle.setAttribute('aria-pressed', 'false');
87
+ toggle.setAttribute('aria-label', labels.show);
88
+ if (input.id) toggle.setAttribute('aria-controls', input.id);
89
+ const eye = icon([EYE, PUPIL, SLASH], 'eye');
90
+ toggle.append(eye);
91
+ field.append(toggle);
92
+
93
+ const meter = document.createElement('div');
94
+ meter.dataset.mkPasswordMeter = '';
95
+ meter.setAttribute('aria-hidden', 'true');
96
+ const segments = Array.from({ length: 4 }, () => {
97
+ const segment = document.createElement('span');
98
+ segment.append(document.createElement('i'));
99
+ meter.append(segment);
100
+ return segment;
101
+ });
102
+ const label = document.createElement('span');
103
+ label.dataset.mkPasswordLabel = '';
104
+ meter.append(label);
105
+ const list = document.createElement('ul');
106
+ list.dataset.mkPasswordChecks = '';
107
+ list.id = `${uid}-checks`;
108
+ const items = ORDER.map((key) => {
109
+ const item = document.createElement('li');
110
+ item.dataset.mkCheck = key;
111
+ item.append(icon([CHECK], 'check'), labels.checks[key] ?? key);
112
+ list.append(item);
113
+ return item;
114
+ });
115
+ const live = document.createElement('span');
116
+ live.className = 'mk-sr-only';
117
+ live.setAttribute('role', 'status');
118
+ root.append(meter, list, live);
119
+ made.push(meter, list, live);
120
+ scope.attr(input, 'aria-describedby', [input.getAttribute('aria-describedby'), list.id].filter(Boolean).join(' '));
121
+ scope.attr(root, 'data-mk-ready', '');
122
+
123
+ const effects = new Set<gsap.core.Animation>();
124
+ const keep = (animation: gsap.core.Animation | null) => {
125
+ if (!animation) return;
126
+ effects.add(animation);
127
+ // Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
128
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
129
+ };
130
+ let announce: ReturnType<typeof setTimeout> | undefined;
131
+ scope.add(() => {
132
+ clearTimeout(announce);
133
+ for (const effect of effects) effect.kill();
134
+ effects.clear();
135
+ });
136
+
137
+ let result = score(input.value);
138
+ let lit = 0;
139
+ const paint = (animate: boolean) => {
140
+ const previous = lit;
141
+ const level = input!.value ? Math.max(1, result.score) : 0;
142
+ lit = level;
143
+ scope.attr(root, 'data-mk-score', input!.value ? String(result.score) : null);
144
+ const bars = segments.map((segment) => segment.firstElementChild as HTMLElement);
145
+ if (!animate || reduced()) gsap.set(bars, { scaleX: (i: number) => (i < level ? 1 : 0) });
146
+ else if (level > previous) keep(gsap.fromTo(bars.slice(previous, level), { scaleX: 0 }, { scaleX: 1, duration: 0.35, ease: EASE.swatch, stagger: 0.07 }));
147
+ else if (level < previous) keep(gsap.to(bars.slice(level, previous).reverse(), { scaleX: 0, duration: 0.2, ease: 'power2.in', stagger: 0.04 }));
148
+ if (label.textContent !== result.label) {
149
+ if (!animate || reduced()) label.textContent = result.label;
150
+ else {
151
+ gsap.killTweensOf(label);
152
+ keep(
153
+ gsap
154
+ .timeline()
155
+ .to(label, { opacity: 0, filter: 'blur(4px)', y: -3, duration: 0.12 })
156
+ .call(() => {
157
+ label.textContent = result.label;
158
+ })
159
+ .fromTo(label, { opacity: 0, filter: 'blur(4px)', y: 3 }, { opacity: 1, filter: 'blur(0px)', y: 0, duration: 0.25, ease: EASE.pop, clearProps: 'all' }),
160
+ );
161
+ }
162
+ }
163
+ items.forEach((item, i) => {
164
+ const met = result.checks[ORDER[i]!];
165
+ if (item.hasAttribute('data-mk-met') === met) return;
166
+ item.toggleAttribute('data-mk-met', met);
167
+ const path = item.querySelector('path');
168
+ if (met && path && animate && !reduced()) keep(gsap.fromTo(path, { strokeDashoffset: 1 }, { strokeDashoffset: 0, duration: 0.35, ease: 'power2.out' }));
169
+ });
170
+ if (Number.isFinite(options.requireScore)) input!.setCustomValidity(input!.value && result.score < options.requireScore! ? labels.invalid : '');
171
+ };
172
+
173
+ const update = () => {
174
+ result = score(input!.value);
175
+ paint(true);
176
+ options.onChange?.(result);
177
+ root.dispatchEvent(new CustomEvent('mk:strength', { bubbles: true, detail: { score: result.score, label: result.label, met: result.checks } }));
178
+ clearTimeout(announce);
179
+ announce = setTimeout(() => {
180
+ if (!scope.disposed) live.textContent = input!.value ? `Fortaleza: ${result.label}` : '';
181
+ }, 700);
182
+ };
183
+
184
+ scope.on(input, 'input', update);
185
+ scope.on(toggle, 'click', () => {
186
+ const show = input!.type === 'password';
187
+ input!.type = show ? 'text' : 'password';
188
+ toggle.setAttribute('aria-pressed', String(show));
189
+ toggle.setAttribute('aria-label', show ? labels.hide : labels.show);
190
+ root.toggleAttribute('data-mk-visible', show);
191
+ const slash = eye.querySelectorAll('path')[2];
192
+ if (!reduced()) {
193
+ keep(gsap.fromTo(eye, { scale: 0.7, filter: 'blur(3px)' }, { scale: 1, filter: 'blur(0px)', duration: 0.35, ease: EASE.click, clearProps: 'all' }));
194
+ if (slash) keep(gsap.fromTo(slash, { strokeDashoffset: show ? 1 : 0 }, { strokeDashoffset: show ? 0 : 1, duration: 0.3, ease: 'power2.out' }));
195
+ } else if (slash) gsap.set(slash, { strokeDashoffset: show ? 0 : 1 });
196
+ });
197
+ scope.add(() => root.removeAttribute('data-mk-visible'));
198
+ scope.add(() => input!.setCustomValidity(''));
199
+ gsap.set(eye.querySelectorAll('path')[2]!, { strokeDasharray: 1, strokeDashoffset: 1 });
200
+ for (const item of items) gsap.set(item.querySelector('path')!, { strokeDasharray: 1, strokeDashoffset: 0 });
201
+ paint(false);
202
+
203
+ return {
204
+ get score() {
205
+ return result.score;
206
+ },
207
+ get value() {
208
+ return input!.value;
209
+ },
210
+ destroy: () => scope.dispose(),
211
+ };
212
+ }
@@ -0,0 +1,182 @@
1
+ /* password-strength — a Melonmind field (16 px radius, surface-container-high, 3 px focus ring) with an eye toggle,
2
+ four rounded segments that light up by level and a checklist whose checks draw themselves. */
3
+ .mk-password-strength,
4
+ mk-password-strength {
5
+ --mk-password-weak: var(--mk-error);
6
+ --mk-password-fair: #e8a317;
7
+ --mk-password-good: #3c9a5f;
8
+ --mk-password-strong: var(--mk-accent);
9
+ --mk-password-tone: var(--mk-outline);
10
+ display: grid;
11
+ gap: 0.75rem;
12
+ color: var(--mk-text);
13
+ font-family: var(--mk-font);
14
+ }
15
+
16
+ :is(.mk-password-strength, mk-password-strength):is([data-mk-score='0'], [data-mk-score='1']) {
17
+ --mk-password-tone: var(--mk-password-weak);
18
+ }
19
+
20
+ :is(.mk-password-strength, mk-password-strength)[data-mk-score='2'] {
21
+ --mk-password-tone: var(--mk-password-fair);
22
+ }
23
+
24
+ :is(.mk-password-strength, mk-password-strength)[data-mk-score='3'] {
25
+ --mk-password-tone: var(--mk-password-good);
26
+ }
27
+
28
+ :is(.mk-password-strength, mk-password-strength)[data-mk-score='4'] {
29
+ --mk-password-tone: var(--mk-password-strong);
30
+ }
31
+
32
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-field] {
33
+ position: relative;
34
+ display: flex;
35
+ align-items: center;
36
+ }
37
+
38
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password] {
39
+ width: 100%;
40
+ min-height: 3.25rem;
41
+ padding: 0 3.5rem 0 1.1rem;
42
+ border: 0;
43
+ border-radius: 16px;
44
+ outline: none;
45
+ background: var(--mk-surface-4);
46
+ color: var(--mk-text);
47
+ font: inherit;
48
+ font-size: 1rem;
49
+ letter-spacing: 0.02em;
50
+ transition:
51
+ box-shadow 0.5s,
52
+ background-color 0.5s;
53
+ }
54
+
55
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password]:hover {
56
+ background: var(--mk-surface-5);
57
+ }
58
+
59
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password]:focus {
60
+ background: var(--mk-surface-2);
61
+ box-shadow:
62
+ 0 0 0 3px var(--mk-outline-soft),
63
+ 0 0.5px 2.5px rgb(0 0 0 / 0.3);
64
+ }
65
+
66
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-toggle] {
67
+ position: absolute;
68
+ right: 0.4rem;
69
+ display: grid;
70
+ place-items: center;
71
+ width: 2.5rem;
72
+ height: 2.5rem;
73
+ padding: 0;
74
+ border: 0;
75
+ border-radius: 50%;
76
+ background: transparent;
77
+ color: var(--mk-muted);
78
+ cursor: pointer;
79
+ transition:
80
+ background-color 0.2s ease,
81
+ color 0.2s ease;
82
+ }
83
+
84
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-toggle]:hover {
85
+ background: var(--mk-surface-3);
86
+ color: var(--mk-text);
87
+ }
88
+
89
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-toggle]:focus-visible {
90
+ outline: 2px solid var(--mk-accent);
91
+ outline-offset: 2px;
92
+ }
93
+
94
+ :is(.mk-password-strength, mk-password-strength) svg {
95
+ width: 1.25rem;
96
+ height: 1.25rem;
97
+ fill: none;
98
+ stroke: currentColor;
99
+ stroke-width: 1.8;
100
+ stroke-linecap: round;
101
+ stroke-linejoin: round;
102
+ }
103
+
104
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-meter] {
105
+ display: grid;
106
+ grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
107
+ align-items: center;
108
+ gap: 0.35rem;
109
+ }
110
+
111
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-meter] > span:not([data-mk-password-label]) {
112
+ height: 6px;
113
+ overflow: hidden;
114
+ border-radius: 64px;
115
+ background: var(--mk-surface-4);
116
+ }
117
+
118
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-meter] i {
119
+ display: block;
120
+ height: 100%;
121
+ border-radius: inherit;
122
+ background: var(--mk-password-tone);
123
+ transform: scaleX(0);
124
+ transform-origin: left center;
125
+ transition: background-color 0.3s ease;
126
+ }
127
+
128
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-label] {
129
+ min-width: 6.5rem;
130
+ margin-left: 0.4rem;
131
+ color: var(--mk-password-tone);
132
+ font-size: 0.8125rem;
133
+ font-weight: 500;
134
+ text-align: right;
135
+ }
136
+
137
+ :is(.mk-password-strength, mk-password-strength) [data-mk-password-checks] {
138
+ display: grid;
139
+ grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
140
+ gap: 0.3rem 1rem;
141
+ margin: 0;
142
+ padding: 0;
143
+ color: var(--mk-muted);
144
+ font-size: 0.8125rem;
145
+ list-style: none;
146
+ }
147
+
148
+ :is(.mk-password-strength, mk-password-strength) [data-mk-check] {
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 0.4rem;
152
+ transition: color 0.2s ease;
153
+ }
154
+
155
+ :is(.mk-password-strength, mk-password-strength) [data-mk-check] svg {
156
+ flex: none;
157
+ width: 1rem;
158
+ height: 1rem;
159
+ padding: 2px;
160
+ border-radius: 50%;
161
+ background: var(--mk-surface-3);
162
+ stroke: transparent;
163
+ stroke-width: 3;
164
+ transition:
165
+ background-color 0.2s ease,
166
+ stroke 0.2s ease;
167
+ }
168
+
169
+ :is(.mk-password-strength, mk-password-strength) [data-mk-check][data-mk-met] {
170
+ color: var(--mk-text);
171
+ }
172
+
173
+ :is(.mk-password-strength, mk-password-strength) [data-mk-check][data-mk-met] svg {
174
+ background: var(--mk-password-good);
175
+ stroke: #fff;
176
+ }
177
+
178
+ @media (prefers-reduced-motion: reduce) {
179
+ :is(.mk-password-strength, mk-password-strength) :is([data-mk-password], [data-mk-check], [data-mk-check] svg, [data-mk-password-meter] i) {
180
+ transition: none;
181
+ }
182
+ }
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { useRef, type InputHTMLAttributes } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createPasswordStrength, type PasswordStrengthOptions } from './engine';
5
+
6
+ export interface PasswordStrengthProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange'>, Omit<PasswordStrengthOptions, 'onChange'> {
7
+ onStrength?: PasswordStrengthOptions['onChange'];
8
+ /** The text of the field as it changes. */
9
+ onValueChange?: (value: string) => void;
10
+ }
11
+
12
+ /** <PasswordStrength name="password" aria-label="Contraseña" requireScore={3} onStrength={setResult} />. The field stays uncontrolled. */
13
+ export function PasswordStrength({ minLength, score, requireScore, labels, onStrength, onValueChange, className, ...props }: PasswordStrengthProps) {
14
+ const ref = useRef<HTMLDivElement>(null);
15
+ const strength = useLatest(onStrength);
16
+ const value = useLatest(onValueChange);
17
+ const scorer = useLatest(score);
18
+ const custom = score !== undefined;
19
+ const key = JSON.stringify(labels ?? {});
20
+ useIsomorphicLayoutEffect(() => {
21
+ const instance = createPasswordStrength(ref.current, {
22
+ minLength,
23
+ requireScore,
24
+ labels,
25
+ score: custom ? (text) => scorer.current!(text) : undefined,
26
+ onChange: (result) => strength.current?.(result),
27
+ });
28
+ return () => instance.destroy();
29
+ // eslint-disable-next-line react-hooks/exhaustive-deps
30
+ }, [minLength, requireScore, key, custom]);
31
+ return (
32
+ <div ref={ref} className={['mk-password-strength', className].filter(Boolean).join(' ')}>
33
+ <input autoComplete="new-password" aria-label="Contraseña" {...props} type="password" onInput={(event) => value.current?.(event.currentTarget.value)} />
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Password strength (pure): length and variety raise the score, common
3
+ * passwords, runs (abc, 123) and repeats (aaa) lower it. 0–4, a label and the
4
+ * checks met. A heuristic for feedback, not a security guarantee: the server
5
+ * decides what it accepts.
6
+ */
7
+
8
+ export interface PasswordChecks {
9
+ length: boolean;
10
+ lower: boolean;
11
+ upper: boolean;
12
+ number: boolean;
13
+ symbol: boolean;
14
+ }
15
+
16
+ export interface PasswordScore {
17
+ score: 0 | 1 | 2 | 3 | 4;
18
+ label: string;
19
+ checks: PasswordChecks;
20
+ }
21
+
22
+ export const STRENGTH_LABELS = ['Muy débil', 'Débil', 'Aceptable', 'Fuerte', 'Muy fuerte'] as const;
23
+
24
+ const COMMON = new Set(
25
+ 'password password1 123456 1234567 12345678 123456789 1234567890 qwerty qwerty123 abc123 111111 000000 iloveyou admin welcome letmein monkey dragon contraseña contrasena secreto hola123 football baseball sunshine princess'.split(' '),
26
+ );
27
+
28
+ const runs = (value: string) => {
29
+ const text = value.toLowerCase();
30
+ for (let i = 0; i + 2 < text.length; i++) {
31
+ const [a, b, c] = [text.charCodeAt(i), text.charCodeAt(i + 1), text.charCodeAt(i + 2)];
32
+ if ((b - a === 1 && c - b === 1) || (a - b === 1 && b - c === 1)) return true;
33
+ }
34
+ return false;
35
+ };
36
+
37
+ export function scorePassword(value: string, minLength = 8): PasswordScore {
38
+ const text = String(value ?? '');
39
+ const checks: PasswordChecks = {
40
+ length: text.length >= minLength,
41
+ lower: /[a-zà-ÿ]/.test(text),
42
+ upper: /[A-ZÀ-Þ]/.test(text),
43
+ number: /\d/.test(text),
44
+ symbol: /[^\p{L}\p{N}\s]/u.test(text),
45
+ };
46
+ if (!text) return { score: 0, label: '', checks };
47
+ const variety = [checks.lower, checks.upper, checks.number, checks.symbol].filter(Boolean).length;
48
+ let points = 0;
49
+ if (text.length >= minLength) points += 1;
50
+ if (text.length >= minLength + 4) points += 1;
51
+ if (text.length >= minLength + 8) points += 1;
52
+ if (variety >= 3) points += 1;
53
+ if (variety === 4) points += 1;
54
+ // Penalties count after the cap: a long, varied password with a pattern is not the best.
55
+ points = Math.min(4, points);
56
+ if (/(.)\1\1/.test(text)) points -= 1;
57
+ if (runs(text)) points -= 1;
58
+ if (text.length < minLength) points = Math.min(points, 1);
59
+ if (COMMON.has(text.toLowerCase())) points = 0;
60
+ const score = Math.max(0, Math.min(4, points)) as PasswordScore['score'];
61
+ return { score, label: STRENGTH_LABELS[score], checks };
62
+ }
@@ -0,0 +1,44 @@
1
+ # popover
2
+
3
+ Panel flotante interactivo anclado a su botón, sobre la misma superficie que `menu` y `color-picker`: crece desde el botón (transformación de contenedor de `motion/morph.ts`) mientras su contenido entra en cascada como las opciones de un menú (desde 90 % y un blur de 4 px, 40 ms entre piezas), y al cerrar se pliega de vuelta en él. Se cierra con Escape, con un clic fuera o con cualquier `[data-mk-close]` de dentro, y el foco vuelve al botón.
4
+
5
+ Importa también `css/components/dialog.css` (o el CSS completo). La CLI lo copia: la entrada `requires` dialog.
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <mk-popover class="mk-popover">
11
+ <button class="mk-button" data-mk-popover-trigger>Filtros</button>
12
+ <div data-mk-popover-content>
13
+ <label class="mk-field">Estado <select>…</select></label>
14
+ <button class="mk-button mk-button--primary" data-mk-close>Aplicar</button>
15
+ </div>
16
+ </mk-popover>
17
+ ```
18
+
19
+ `[data-mk-popover-content]` pasa a un `<dialog>` que crea el motor y vuelve a su sitio al destruirlo. También puedes escribir directamente `<dialog data-mk-popover-panel>`.
20
+
21
+ ## React
22
+
23
+ ```tsx
24
+ <Popover trigger={<button className="mk-button">Filtros</button>} onOpenChange={setOpen}>
25
+ <FiltersForm />
26
+ </Popover>
27
+ ```
28
+
29
+ Con `open` es controlado. `ref` da `open()` y `close()`.
30
+
31
+ ## Opciones
32
+
33
+ | Opción | Atributo | Por defecto | Qué hace |
34
+ |---|---|---|---|
35
+ | `label` | `label` | el texto del botón | Nombre accesible del panel. |
36
+ | `onOpenChange(open)` | — | — | Al abrirse o cerrarse. |
37
+
38
+ ## API y eventos
39
+
40
+ `open()`, `close()`, `toggle()`, `isOpen`, `panel` (el `<dialog>`), `destroy()`. Evento `mk:open-change` con `{ open }`.
41
+
42
+ ## Estilo
43
+
44
+ Esquinas de 20 px sobre `--mk-surface-1`, tan ancho como su contenido hasta 22rem, con 1rem de relleno. Se estiliza por clase (`.mk-popover-panel`), así la copia que se pliega al cerrar conserva su aspecto. En teléfonos el diálogo anclado se centra. Con movimiento reducido aparece sin crecer.
@@ -0,0 +1,31 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createPopover, type PopoverInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-popover class="mk-popover">
6
+ * <button data-mk-popover-trigger>Filtros</button>
7
+ * <div data-mk-popover-content>…</div>
8
+ * </mk-popover>
9
+ * Emits mk:open-change ({open}).
10
+ */
11
+ export class MkPopoverElement extends MkElement<PopoverInstance> {
12
+ static options: Record<string, OptionType> = { label: 'string' };
13
+
14
+ protected create(options: Record<string, unknown>) {
15
+ return createPopover(this, options);
16
+ }
17
+
18
+ open() {
19
+ this.instance?.open();
20
+ }
21
+
22
+ close() {
23
+ this.instance?.close();
24
+ }
25
+
26
+ get isOpen() {
27
+ return this.instance?.isOpen ?? false;
28
+ }
29
+ }
30
+
31
+ export const definePopover = () => defineElement('mk-popover', MkPopoverElement);
@@ -0,0 +1,114 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+ import { flowInMenu } from '../../motion/entrances';
4
+ import { createDialog } from '../dialog/engine';
5
+
6
+ /*
7
+ * Popover: an interactive panel anchored to its trigger, on the same anchored
8
+ * dialog surface as menu and color-picker. It grows out of the trigger
9
+ * (motion/morph.ts container transform) while its content flows in like a
10
+ * menu's options (from 90 % out of a 4 px blur, 40 ms apart), folds back into
11
+ * the trigger on close, closes with Escape, a click outside or any
12
+ * [data-mk-close] inside, and gives the focus back to the trigger. A
13
+ * dialog[data-mk-popover-panel] is used as it is; [data-mk-popover-content] is
14
+ * moved into a dialog the engine creates and put back on destroy.
15
+ */
16
+
17
+ export interface PopoverOptions {
18
+ /** Accessible name of the panel (default: the trigger's text). */
19
+ label?: string;
20
+ onOpenChange?: (open: boolean) => void;
21
+ }
22
+
23
+ export interface PopoverInstance extends Instance {
24
+ open(): void;
25
+ close(): void;
26
+ toggle(): void;
27
+ readonly isOpen: boolean;
28
+ /** The panel (a <dialog>). */
29
+ readonly panel: HTMLElement | null;
30
+ }
31
+
32
+ let sequence = 0;
33
+
34
+ export function createPopover(root: HTMLElement | null, options: PopoverOptions = {}): PopoverInstance {
35
+ const inertInstance = inert<PopoverInstance>({ open() {}, close() {}, toggle() {}, isOpen: false, panel: null });
36
+ if (!root) return inertInstance;
37
+ let dialog = root.querySelector<HTMLDialogElement>('dialog[data-mk-popover-panel]');
38
+ const content = dialog ? null : root.querySelector<HTMLElement>('[data-mk-popover-content]');
39
+ const trigger =
40
+ root.querySelector<HTMLElement>('[data-mk-popover-trigger]') ??
41
+ [...root.querySelectorAll<HTMLElement>('button, a[href], [role="button"]')].find((element) => !dialog?.contains(element) && !content?.contains(element)) ??
42
+ null;
43
+ if (!trigger || (!dialog && !content)) return inertInstance;
44
+ const scope = createScope();
45
+
46
+ if (!dialog) {
47
+ // The authored content moves into a dialog in its place, and back on destroy.
48
+ const created = document.createElement('dialog');
49
+ const anchor = content!.nextSibling;
50
+ const parent = content!.parentNode!;
51
+ parent.insertBefore(created, content);
52
+ created.append(content!);
53
+ dialog = created;
54
+ scope.add(() => {
55
+ if (content!.isConnected || created.isConnected) parent.insertBefore(content!, created.isConnected ? created : anchor);
56
+ created.remove();
57
+ });
58
+ }
59
+ const panel = dialog;
60
+ // Classes, not only attributes: the copy that folds back into the trigger on close keeps classes only.
61
+ scope.attr(panel, 'class', [panel.getAttribute('class'), 'mk-dialog', 'mk-popover-panel'].filter(Boolean).join(' '));
62
+ if (!panel.id) scope.attr(panel, 'id', `mk-popover-${++sequence}`);
63
+ if (!panel.hasAttribute('aria-label') && !panel.hasAttribute('aria-labelledby')) scope.attr(panel, 'aria-label', options.label ?? trigger.textContent?.trim() ?? 'Panel');
64
+ scope.attr(trigger, 'aria-haspopup', 'dialog');
65
+ scope.attr(trigger, 'aria-expanded', 'false');
66
+ scope.attr(trigger, 'aria-controls', panel.id);
67
+ scope.attr(root, 'data-mk-ready', '');
68
+
69
+ const announce = (open: boolean) => {
70
+ trigger.setAttribute('aria-expanded', String(open));
71
+ options.onOpenChange?.(open);
72
+ root.dispatchEvent(new CustomEvent('mk:open-change', { bubbles: true, detail: { open } }));
73
+ };
74
+ let flow: ReturnType<typeof flowInMenu> = null;
75
+ const surface = createDialog(panel, {
76
+ placement: 'anchor',
77
+ backdrop: false,
78
+ drag: false,
79
+ autoResize: false,
80
+ triggers: false,
81
+ animateContent: false,
82
+ // Like a menu's options: the content flows in while the surface grows out of the trigger.
83
+ onOpen: () => {
84
+ flow?.kill();
85
+ flow = flowInMenu([...panel.children].filter((child) => !child.classList.contains('mk-morph-face')));
86
+ announce(true);
87
+ },
88
+ onClose: () => announce(false),
89
+ });
90
+ scope.add(() => {
91
+ flow?.kill();
92
+ surface.destroy();
93
+ });
94
+
95
+ const open = () => {
96
+ if (!surface.isOpen) surface.open(trigger);
97
+ };
98
+ scope.on(trigger, 'click', (event) => {
99
+ event.preventDefault();
100
+ if (surface.isOpen) surface.close();
101
+ else open();
102
+ });
103
+
104
+ return {
105
+ open,
106
+ close: () => surface.close(),
107
+ toggle: () => (surface.isOpen ? surface.close() : open()),
108
+ get isOpen() {
109
+ return surface.isOpen;
110
+ },
111
+ panel,
112
+ destroy: () => scope.dispose(),
113
+ };
114
+ }