@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,202 @@
1
+ import { gsap, ELASTIC, EASE, registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { createScope } from '../../core/scope';
4
+ import { inert, type Instance } from '../../core/types';
5
+ import { blurIn, blurOut, rollDigits } from '../../motion/micro';
6
+
7
+ /*
8
+ * Notification bell: a bell with a badge on a button. notify() rings it — the
9
+ * bell swings from its top in a damped arc (±18°) with the clapper a beat
10
+ * behind, waves ripple out, the badge pops in elastically (or its number rolls
11
+ * up), and an optional message rises beside it for a few seconds. The button's
12
+ * name carries the count and a polite status announces new ones.
13
+ */
14
+
15
+ export interface NotificationBellOptions {
16
+ count?: number;
17
+ /** Accessible name before the count (default 'Notificaciones'). */
18
+ label?: string;
19
+ /** How long a message stays, in ms (default 3200). */
20
+ messageDuration?: number;
21
+ /** Above this the badge reads n+ (default 99). */
22
+ max?: number;
23
+ onOpen?: () => void;
24
+ }
25
+
26
+ export interface NotificationBellInstance extends Instance {
27
+ /** Ring with a new notification (and show `message`, as text). */
28
+ notify(message?: string): void;
29
+ setCount(count: number): void;
30
+ clear(): void;
31
+ readonly count: number;
32
+ }
33
+
34
+ const SVG = 'http://www.w3.org/2000/svg';
35
+ const BODY = 'M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2H4.5z';
36
+
37
+ export function createNotificationBell(root: HTMLElement | null, options: NotificationBellOptions = {}): NotificationBellInstance {
38
+ const button = root ? ((root.matches('button') ? root : root.querySelector('button')) as HTMLButtonElement | null) : null;
39
+ if (!root || !button) return inert({ notify() {}, setCount() {}, clear() {}, count: 0 });
40
+ registerMotion();
41
+ const scope = createScope();
42
+ const max = Number.isFinite(options.max) && options.max! > 0 ? Math.floor(options.max!) : 99;
43
+ const label = options.label ?? 'Notificaciones';
44
+ const stay = Number.isFinite(options.messageDuration) ? Math.max(0, options.messageDuration!) : 3200;
45
+ let count = Math.max(0, Math.round(Number.isFinite(options.count) ? options.count! : 0));
46
+
47
+ const made: Element[] = [];
48
+ scope.add(() => made.forEach((element) => element.remove()));
49
+ const icon = document.createElementNS(SVG, 'svg');
50
+ icon.setAttribute('viewBox', '0 0 24 24');
51
+ icon.setAttribute('aria-hidden', 'true');
52
+ icon.dataset.mkBellIcon = '';
53
+ const bell = document.createElementNS(SVG, 'g');
54
+ bell.dataset.mkPart = 'bell';
55
+ const body = document.createElementNS(SVG, 'path');
56
+ body.setAttribute('d', BODY);
57
+ const top = document.createElementNS(SVG, 'path');
58
+ top.setAttribute('d', 'M12 3v2');
59
+ bell.append(body, top);
60
+ const clapper = document.createElementNS(SVG, 'path');
61
+ clapper.setAttribute('d', 'M10 20.5a2 2 0 0 0 4 0');
62
+ clapper.dataset.mkPart = 'clapper';
63
+ icon.append(bell, clapper);
64
+ const waves = document.createElement('span');
65
+ waves.dataset.mkBellWaves = '';
66
+ waves.setAttribute('aria-hidden', 'true');
67
+ const badge = document.createElement('span');
68
+ badge.dataset.mkBellBadge = '';
69
+ badge.setAttribute('aria-hidden', 'true');
70
+ button.prepend(waves, icon);
71
+ button.append(badge);
72
+ const bubble = document.createElement('span');
73
+ bubble.dataset.mkBellMessage = '';
74
+ bubble.setAttribute('aria-hidden', 'true');
75
+ bubble.hidden = true;
76
+ const live = document.createElement('span');
77
+ live.className = 'mk-sr-only';
78
+ live.setAttribute('role', 'status');
79
+ (root === button ? button : root).append(bubble, live);
80
+ made.push(waves, icon, badge, bubble, live);
81
+
82
+ scope.attr(button, 'type', button.getAttribute('type') ?? 'button');
83
+ scope.attr(button, 'data-mk-bell', '');
84
+ scope.attr(root, 'data-mk-ready', '');
85
+
86
+ let hideTimer: ReturnType<typeof setTimeout> | undefined;
87
+ const effects = new Set<gsap.core.Animation>();
88
+ const keep = (animation: gsap.core.Animation | null) => {
89
+ if (!animation) return;
90
+ effects.add(animation);
91
+ // Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
92
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
93
+ };
94
+ scope.add(() => {
95
+ clearTimeout(hideTimer);
96
+ for (const effect of effects) effect.kill();
97
+ effects.clear();
98
+ gsap.killTweensOf([bell, clapper, badge, bubble]);
99
+ });
100
+
101
+ const text = (n: number) => (n > max ? `${max}+` : String(n));
102
+ const name = () => (count ? `${label}, ${count} sin leer` : label);
103
+ const paint = () => {
104
+ scope.attr(button, 'aria-label', name());
105
+ badge.hidden = count === 0;
106
+ };
107
+
108
+ const showBadge = (previous: number) => {
109
+ if (count === 0) {
110
+ if (reduced() || previous === 0) {
111
+ badge.hidden = true;
112
+ return;
113
+ }
114
+ keep(gsap.to(badge, { scale: 0, duration: 0.2, ease: 'power2.in', onComplete: () => void (badge.hidden = true), clearProps: 'scale,transform' }));
115
+ return;
116
+ }
117
+ if (previous === 0) {
118
+ badge.textContent = text(count);
119
+ badge.hidden = false;
120
+ if (!reduced()) keep(gsap.fromTo(badge, { scale: 0 }, { scale: 1, duration: 0.8, ease: ELASTIC, clearProps: 'scale,transform' }));
121
+ } else keep(rollDigits(badge, text(count), count >= previous ? 1 : -1, 0.4));
122
+ };
123
+
124
+ const ring = () => {
125
+ if (reduced()) return;
126
+ gsap.killTweensOf([bell, clapper]);
127
+ keep(gsap.fromTo(bell, { rotation: 0 }, { keyframes: { rotation: [18, -14, 10, -6, 3, 0] }, duration: 1, ease: 'power1.out', svgOrigin: '12 4', clearProps: 'rotation,transform' }));
128
+ keep(gsap.fromTo(clapper, { x: 0 }, { keyframes: { x: [0, -2.5, 2, -1.4, 0.8, 0] }, duration: 1, delay: 0.06, ease: 'power1.out', clearProps: 'x,transform' }));
129
+ for (let i = 0; i < 2; i++) {
130
+ const wave = document.createElement('i');
131
+ waves.append(wave);
132
+ keep(gsap.fromTo(wave, { scale: 0.6, opacity: 0.55 }, { scale: 1.9, opacity: 0, duration: 0.8, delay: i * 0.18, ease: 'power2.out', onComplete: () => wave.remove() }));
133
+ }
134
+ };
135
+
136
+ // The message rises on the side with room inside the bell's container (and the window), so a bell at the
137
+ // right edge of a card speaks to its left instead of running over the next thing.
138
+ const place = () => {
139
+ bubble.style.maxWidth = '';
140
+ const box = root.getBoundingClientRect();
141
+ const frame = root.parentElement?.getBoundingClientRect();
142
+ const right = Math.min(frame?.right ?? innerWidth, innerWidth) - box.right;
143
+ const left = box.left - Math.max(frame?.left ?? 0, 0);
144
+ const side = right >= bubble.offsetWidth + 12 || right >= left ? 'right' : 'left';
145
+ bubble.dataset.mkSide = side;
146
+ bubble.style.maxWidth = `min(16rem, ${Math.max(140, (side === 'right' ? right : left) - 12)}px)`;
147
+ };
148
+
149
+ const say = (message: string) => {
150
+ clearTimeout(hideTimer);
151
+ gsap.killTweensOf(bubble);
152
+ bubble.textContent = message;
153
+ bubble.hidden = false;
154
+ place();
155
+ keep(blurIn(bubble, { from: 'pop' }));
156
+ hideTimer = setTimeout(() => {
157
+ if (scope.disposed) return;
158
+ const out = blurOut(bubble, {
159
+ to: 'up',
160
+ onComplete: () => {
161
+ bubble.hidden = true;
162
+ gsap.set(bubble, { clearProps: 'all' });
163
+ },
164
+ });
165
+ keep(out);
166
+ }, stay);
167
+ };
168
+
169
+ const setCount = (next: number, announce = false) => {
170
+ if (scope.disposed || !Number.isFinite(next)) return;
171
+ const previous = count;
172
+ count = Math.max(0, Math.round(next));
173
+ if (count === previous) return;
174
+ paint();
175
+ showBadge(previous);
176
+ if (announce) live.textContent = name();
177
+ root.dispatchEvent(new CustomEvent('mk:count', { bubbles: true, detail: { count } }));
178
+ };
179
+
180
+ scope.on(button, 'click', () => {
181
+ options.onOpen?.();
182
+ root.dispatchEvent(new CustomEvent('mk:open', { bubbles: true, detail: { count } }));
183
+ });
184
+ badge.textContent = text(count);
185
+ paint();
186
+
187
+ return {
188
+ notify(message) {
189
+ if (scope.disposed) return;
190
+ setCount(count + 1, true);
191
+ ring();
192
+ if (message) say(String(message));
193
+ if (message) live.textContent = `${name()}. ${message}`;
194
+ },
195
+ setCount: (next) => setCount(next),
196
+ clear: () => setCount(0),
197
+ get count() {
198
+ return count;
199
+ },
200
+ destroy: () => scope.dispose(),
201
+ };
202
+ }
@@ -0,0 +1,126 @@
1
+ /* notification-bell — a round Melonmind icon button (surface tone) with a bell, a badge in the error tone and a
2
+ message that rises beside it in the inverse glass. */
3
+ .mk-notification-bell,
4
+ mk-notification-bell {
5
+ position: relative;
6
+ display: inline-flex;
7
+ }
8
+
9
+ [data-mk-bell] {
10
+ position: relative;
11
+ display: grid;
12
+ place-items: center;
13
+ width: 3rem;
14
+ height: 3rem;
15
+ padding: 0;
16
+ border: 0;
17
+ border-radius: 50%;
18
+ background: var(--mk-surface-3);
19
+ color: var(--mk-text);
20
+ cursor: pointer;
21
+ transition: background-color 0.2s ease;
22
+ }
23
+
24
+ [data-mk-bell]:hover {
25
+ background: var(--mk-surface-4);
26
+ }
27
+
28
+ [data-mk-bell]:active {
29
+ animation: mk-bell-press 500ms cubic-bezier(0.37, 1.42, 0.37, 1);
30
+ }
31
+
32
+ [data-mk-bell]:focus-visible {
33
+ outline: 2px solid var(--mk-accent);
34
+ outline-offset: 3px;
35
+ }
36
+
37
+ [data-mk-bell] [data-mk-bell-icon] {
38
+ width: 1.5rem;
39
+ height: 1.5rem;
40
+ overflow: visible;
41
+ fill: none;
42
+ stroke: currentColor;
43
+ stroke-width: 1.8;
44
+ stroke-linecap: round;
45
+ stroke-linejoin: round;
46
+ }
47
+
48
+ [data-mk-bell] [data-mk-bell-waves] {
49
+ position: absolute;
50
+ inset: 0;
51
+ display: grid;
52
+ place-items: center;
53
+ pointer-events: none;
54
+ }
55
+
56
+ [data-mk-bell] [data-mk-bell-waves] i {
57
+ position: absolute;
58
+ inset: 0;
59
+ border-radius: 50%;
60
+ box-shadow: inset 0 0 0 2px var(--mk-text);
61
+ }
62
+
63
+ [data-mk-bell] [data-mk-bell-badge] {
64
+ position: absolute;
65
+ top: 0.15rem;
66
+ right: 0.1rem;
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ min-width: 1.25rem;
71
+ height: 1.25rem;
72
+ padding: 0 0.3rem;
73
+ border-radius: 64px;
74
+ background: var(--mk-error);
75
+ box-shadow: 0 0 0 2px var(--mk-bg);
76
+ color: var(--mk-on-error);
77
+ font-size: 0.6875rem;
78
+ font-weight: 600;
79
+ font-variant-numeric: tabular-nums;
80
+ line-height: 1;
81
+ }
82
+
83
+ [data-mk-bell] [data-mk-bell-badge][hidden] {
84
+ display: none;
85
+ }
86
+
87
+ :is(.mk-notification-bell, mk-notification-bell) [data-mk-bell-message] {
88
+ position: absolute;
89
+ top: 50%;
90
+ left: calc(100% + 0.75rem);
91
+ width: max-content;
92
+ max-width: 16rem;
93
+ padding: 0.5rem 0.85rem;
94
+ border-radius: 16px;
95
+ background: var(--mk-glass-inverse);
96
+ box-shadow: var(--mk-glass-bevel);
97
+ backdrop-filter: blur(16px);
98
+ color: var(--mk-on-glass-inverse);
99
+ font-size: 0.8125rem;
100
+ translate: 0 -50%;
101
+ pointer-events: none;
102
+ transform-origin: 0 50%;
103
+ }
104
+
105
+ :is(.mk-notification-bell, mk-notification-bell) [data-mk-bell-message][data-mk-side='left'] {
106
+ right: calc(100% + 0.75rem);
107
+ left: auto;
108
+ transform-origin: 100% 50%;
109
+ }
110
+
111
+ :is(.mk-notification-bell, mk-notification-bell) [data-mk-bell-message][hidden] {
112
+ display: none;
113
+ }
114
+
115
+ @keyframes mk-bell-press {
116
+ 50% {
117
+ transform: scale(0.92);
118
+ }
119
+ }
120
+
121
+ @media (prefers-reduced-motion: reduce) {
122
+ [data-mk-bell] {
123
+ animation: none !important;
124
+ transition: none;
125
+ }
126
+ }
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+ import { useRef, type ButtonHTMLAttributes, type MutableRefObject } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createNotificationBell, type NotificationBellInstance } from './engine';
5
+
6
+ export interface NotificationBellProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
7
+ /** Unread count. A higher count than before rings the bell. */
8
+ count?: number;
9
+ /** The newest message: a new value rings the bell and shows it. */
10
+ message?: string;
11
+ label?: string;
12
+ messageDuration?: number;
13
+ max?: number;
14
+ onOpen?: () => void;
15
+ instanceRef?: MutableRefObject<NotificationBellInstance | null>;
16
+ }
17
+
18
+ /** <NotificationBell count={unread} message={latest} onOpen={openPanel} />. */
19
+ export function NotificationBell({ count = 0, message, label, messageDuration, max, onOpen, instanceRef, className, ...props }: NotificationBellProps) {
20
+ const ref = useRef<HTMLSpanElement>(null);
21
+ const instance = useRef<NotificationBellInstance | null>(null);
22
+ const open = useLatest(onOpen);
23
+ const initial = useRef(count);
24
+ useIsomorphicLayoutEffect(() => {
25
+ const created = createNotificationBell(ref.current, { count: initial.current, label, messageDuration, max, onOpen: () => open.current?.() });
26
+ instance.current = created;
27
+ if (instanceRef) instanceRef.current = created;
28
+ return () => {
29
+ initial.current = created.count;
30
+ created.destroy();
31
+ instance.current = null;
32
+ if (instanceRef) instanceRef.current = null;
33
+ };
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ }, [label, messageDuration, max]);
36
+ const seen = useRef({ count, message });
37
+ useIsomorphicLayoutEffect(() => {
38
+ const bell = instance.current;
39
+ if (!bell) return;
40
+ const before = seen.current;
41
+ seen.current = { count, message };
42
+ const fresh = message !== undefined && message !== before.message;
43
+ if (count > bell.count) {
44
+ // One ring for the newest; the rest of the jump is set at once.
45
+ bell.setCount(count - 1);
46
+ bell.notify(fresh ? message : undefined);
47
+ } else {
48
+ bell.setCount(count);
49
+ if (fresh && message) bell.notify(message);
50
+ }
51
+ }, [count, message]);
52
+ return (
53
+ <span ref={ref} className={['mk-notification-bell', className].filter(Boolean).join(' ')}>
54
+ <button type="button" {...props} />
55
+ </span>
56
+ );
57
+ }
@@ -0,0 +1,17 @@
1
+ # password-strength
2
+
3
+ Campo de contraseña nativo con fortaleza en vivo: botón mostrar/ocultar (el ojo cambia con blur y su barra se dibuja), medidor de cuatro segmentos que se encienden en cascada y cambian de color por nivel (débil → aceptable → fuerte → muy fuerte), etiqueta que cambia con blur y lista de requisitos cuyos ✓ se dibujan. Un estado educado anuncia el nivel cuando dejas de escribir.
4
+
5
+ ```html
6
+ <mk-password-strength class="mk-password-strength">
7
+ <input type="password" name="password" autocomplete="new-password" aria-label="Contraseña" />
8
+ </mk-password-strength>
9
+ ```
10
+
11
+ `defineElements()`, `createPasswordStrength(root, options)` o `<PasswordStrength name="password" requireScore={3} onStrength={setResult} />` (el campo sigue sin control; `onValueChange` da el texto).
12
+
13
+ Opciones: `minLength=8`, `score(value)` (sustituye la puntuación), `requireScore` (por debajo, el campo se marca inválido con `setCustomValidity`), `labels {show, hide, invalid, checks}`, `onChange(result)`. API: `score`, `value`, `destroy()`. Evento `mk:strength` con `{score, label, met}`.
14
+
15
+ Utilidad pura `scorePassword(value, minLength)`: la longitud y la variedad suben la puntuación; contraseñas comunes, secuencias (abc, 123) y repeticiones (aaa) la bajan. Es orientativa: el servidor decide qué acepta.
16
+
17
+ Variables: `--mk-password-weak`, `--mk-password-fair`, `--mk-password-good`, `--mk-password-strong`. Movimiento reducido: los segmentos y la etiqueta cambian sin animación.
@@ -0,0 +1,22 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createPasswordStrength, type PasswordStrengthInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-password-strength class="mk-password-strength" min-length="10">
6
+ * <input type="password" name="password" autocomplete="new-password" aria-label="Contraseña" />
7
+ * </mk-password-strength>
8
+ * Emits mk:strength ({score, label, met}).
9
+ */
10
+ export class MkPasswordStrengthElement extends MkElement<PasswordStrengthInstance> {
11
+ static options: Record<string, OptionType> = { minLength: 'number', requireScore: 'number', labels: 'json' };
12
+
13
+ protected create(options: Record<string, unknown>) {
14
+ return createPasswordStrength(this, options);
15
+ }
16
+
17
+ get score() {
18
+ return this.instance?.score ?? 0;
19
+ }
20
+ }
21
+
22
+ export const definePasswordStrength = () => defineElement('mk-password-strength', MkPasswordStrengthElement);
@@ -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
+ }