mates 0.0.21 → 0.1.0-beta.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 (416) hide show
  1. package/README.md +1598 -257
  2. package/dist/Directives/$.d.ts +371 -0
  3. package/dist/Directives/$.d.ts.map +1 -0
  4. package/dist/Directives/animatedConditional.d.ts +67 -0
  5. package/dist/Directives/animatedConditional.d.ts.map +1 -0
  6. package/dist/Directives/animationClasses.d.ts +50 -0
  7. package/dist/Directives/animationClasses.d.ts.map +1 -0
  8. package/dist/Directives/attrDirective.d.ts +69 -0
  9. package/dist/Directives/attrDirective.d.ts.map +1 -0
  10. package/dist/Directives/classesDirective.d.ts +79 -0
  11. package/dist/Directives/classesDirective.d.ts.map +1 -0
  12. package/dist/Directives/eleHook.d.ts +84 -0
  13. package/dist/Directives/eleHook.d.ts.map +1 -0
  14. package/dist/Directives/eventBinding.d.ts +29 -0
  15. package/dist/Directives/eventBinding.d.ts.map +1 -0
  16. package/dist/Directives/htmlHook.d.ts +101 -0
  17. package/dist/Directives/htmlHook.d.ts.map +1 -0
  18. package/dist/Directives/index.d.ts +18 -0
  19. package/dist/Directives/index.d.ts.map +1 -0
  20. package/dist/Directives/intersectDirective.d.ts +67 -0
  21. package/dist/Directives/intersectDirective.d.ts.map +1 -0
  22. package/dist/Directives/lazyLoadDirective.d.ts +66 -0
  23. package/dist/Directives/lazyLoadDirective.d.ts.map +1 -0
  24. package/dist/Directives/lifecycleDirectives.d.ts +56 -0
  25. package/dist/Directives/lifecycleDirectives.d.ts.map +1 -0
  26. package/dist/Directives/masonryGrid.d.ts +114 -0
  27. package/dist/Directives/masonryGrid.d.ts.map +1 -0
  28. package/dist/Directives/memoTemplate.d.ts +94 -0
  29. package/dist/Directives/memoTemplate.d.ts.map +1 -0
  30. package/dist/Directives/onDirective.d.ts +71 -0
  31. package/dist/Directives/onDirective.d.ts.map +1 -0
  32. package/dist/Directives/onParentDirective.d.ts +34 -0
  33. package/dist/Directives/onParentDirective.d.ts.map +1 -0
  34. package/dist/Directives/renderSwitch.d.ts +55 -0
  35. package/dist/Directives/renderSwitch.d.ts.map +1 -0
  36. package/dist/Directives/resolveAttrValue.d.ts +35 -0
  37. package/dist/Directives/resolveAttrValue.d.ts.map +1 -0
  38. package/dist/Directives/styleDirective.d.ts +81 -0
  39. package/dist/Directives/styleDirective.d.ts.map +1 -0
  40. package/dist/Directives/timerDirective.d.ts +52 -0
  41. package/dist/Directives/timerDirective.d.ts.map +1 -0
  42. package/dist/Directives/virtualHelpers.d.ts +10 -0
  43. package/dist/Directives/virtualHelpers.d.ts.map +1 -0
  44. package/dist/Fetch/ErrorTypes.d.ts +74 -0
  45. package/dist/Fetch/ErrorTypes.d.ts.map +1 -0
  46. package/dist/Fetch/Fetch.d.ts +168 -0
  47. package/dist/Fetch/Fetch.d.ts.map +1 -0
  48. package/dist/Fetch/index.d.ts +3 -0
  49. package/dist/Fetch/index.d.ts.map +1 -0
  50. package/dist/Mutables/Extended Atoms/cacheAtom.d.ts +156 -0
  51. package/dist/Mutables/Extended Atoms/cacheAtom.d.ts.map +1 -0
  52. package/dist/Mutables/Extended Atoms/index.d.ts +4 -0
  53. package/dist/Mutables/Extended Atoms/index.d.ts.map +1 -0
  54. package/dist/Mutables/Extended Atoms/paginationAtom.d.ts +32 -0
  55. package/dist/Mutables/Extended Atoms/paginationAtom.d.ts.map +1 -0
  56. package/dist/Mutables/Extended Atoms/themeAtom.d.ts +59 -0
  57. package/dist/Mutables/Extended Atoms/themeAtom.d.ts.map +1 -0
  58. package/dist/Mutables/atom/atom.d.ts +148 -0
  59. package/dist/Mutables/atom/atom.d.ts.map +1 -0
  60. package/dist/Mutables/atom/createTimedAtom.d.ts +43 -0
  61. package/dist/Mutables/atom/createTimedAtom.d.ts.map +1 -0
  62. package/dist/Mutables/atom/debouncedAtom.d.ts +37 -0
  63. package/dist/Mutables/atom/debouncedAtom.d.ts.map +1 -0
  64. package/dist/Mutables/atom/index.d.ts +10 -0
  65. package/dist/Mutables/atom/index.d.ts.map +1 -0
  66. package/dist/Mutables/atom/mapAtom.d.ts +105 -0
  67. package/dist/Mutables/atom/mapAtom.d.ts.map +1 -0
  68. package/dist/Mutables/atom/setAtom.d.ts +107 -0
  69. package/dist/Mutables/atom/setAtom.d.ts.map +1 -0
  70. package/dist/Mutables/atom/storageAtom.d.ts +44 -0
  71. package/dist/Mutables/atom/storageAtom.d.ts.map +1 -0
  72. package/dist/Mutables/atom/throttledAtom.d.ts +37 -0
  73. package/dist/Mutables/atom/throttledAtom.d.ts.map +1 -0
  74. package/dist/Mutables/atom/titleAtom.d.ts +20 -0
  75. package/dist/Mutables/atom/titleAtom.d.ts.map +1 -0
  76. package/dist/Mutables/effect/effect.d.ts +31 -0
  77. package/dist/Mutables/effect/effect.d.ts.map +1 -0
  78. package/dist/Mutables/effect/index.d.ts +2 -0
  79. package/dist/Mutables/effect/index.d.ts.map +1 -0
  80. package/dist/Mutables/events/events.d.ts +123 -0
  81. package/dist/Mutables/events/events.d.ts.map +1 -0
  82. package/dist/Mutables/events/index.d.ts +2 -0
  83. package/dist/Mutables/events/index.d.ts.map +1 -0
  84. package/dist/Mutables/events/xTabEvent.d.ts +61 -0
  85. package/dist/Mutables/events/xTabEvent.d.ts.map +1 -0
  86. package/dist/Mutables/form/formAtom.d.ts +56 -0
  87. package/dist/Mutables/form/formAtom.d.ts.map +1 -0
  88. package/dist/Mutables/form/index.d.ts +5 -0
  89. package/dist/Mutables/form/index.d.ts.map +1 -0
  90. package/dist/Mutables/form/transformers.d.ts +4 -0
  91. package/dist/Mutables/form/transformers.d.ts.map +1 -0
  92. package/dist/Mutables/form/validateAll.d.ts +40 -0
  93. package/dist/Mutables/form/validateAll.d.ts.map +1 -0
  94. package/dist/Mutables/form/validators.d.ts +61 -0
  95. package/dist/Mutables/form/validators.d.ts.map +1 -0
  96. package/dist/Mutables/index.d.ts +13 -0
  97. package/dist/Mutables/index.d.ts.map +1 -0
  98. package/dist/Mutables/memo/index.d.ts +2 -0
  99. package/dist/Mutables/memo/index.d.ts.map +1 -0
  100. package/dist/Mutables/memo/memo.d.ts +47 -0
  101. package/dist/Mutables/memo/memo.d.ts.map +1 -0
  102. package/dist/Mutables/reactiveRunner/index.d.ts +4 -0
  103. package/dist/Mutables/reactiveRunner/index.d.ts.map +1 -0
  104. package/dist/Mutables/reactiveRunner/reactiveRunner.d.ts +56 -0
  105. package/dist/Mutables/reactiveRunner/reactiveRunner.d.ts.map +1 -0
  106. package/dist/Mutables/reactiveRunner/trackAndSubscribe.d.ts +6 -0
  107. package/dist/Mutables/reactiveRunner/trackAndSubscribe.d.ts.map +1 -0
  108. package/dist/Mutables/reactiveRunner/validateReactiveFunction.d.ts +6 -0
  109. package/dist/Mutables/reactiveRunner/validateReactiveFunction.d.ts.map +1 -0
  110. package/dist/Mutables/ref/index.d.ts +2 -0
  111. package/dist/Mutables/ref/index.d.ts.map +1 -0
  112. package/dist/Mutables/ref/ref.d.ts +74 -0
  113. package/dist/Mutables/ref/ref.d.ts.map +1 -0
  114. package/dist/Mutables/scope/index.d.ts +2 -0
  115. package/dist/Mutables/scope/index.d.ts.map +1 -0
  116. package/dist/Mutables/scope/scope.d.ts +60 -0
  117. package/dist/Mutables/scope/scope.d.ts.map +1 -0
  118. package/dist/Mutables/store/index.d.ts +2 -0
  119. package/dist/Mutables/store/index.d.ts.map +1 -0
  120. package/dist/Mutables/store/store.d.ts +73 -0
  121. package/dist/Mutables/store/store.d.ts.map +1 -0
  122. package/dist/Mutables/useState/index.d.ts +2 -0
  123. package/dist/Mutables/useState/index.d.ts.map +1 -0
  124. package/dist/Mutables/useState/useState.d.ts +54 -0
  125. package/dist/Mutables/useState/useState.d.ts.map +1 -0
  126. package/dist/Mutables/useStore/hostContext.d.ts +13 -0
  127. package/dist/Mutables/useStore/hostContext.d.ts.map +1 -0
  128. package/dist/Mutables/useStore/index.d.ts +9 -0
  129. package/dist/Mutables/useStore/index.d.ts.map +1 -0
  130. package/dist/Mutables/useStore/lifecycle.d.ts +94 -0
  131. package/dist/Mutables/useStore/lifecycle.d.ts.map +1 -0
  132. package/dist/Mutables/useStore/onError.d.ts +21 -0
  133. package/dist/Mutables/useStore/onError.d.ts.map +1 -0
  134. package/dist/Mutables/useStore/onReferenceChange.d.ts +2 -0
  135. package/dist/Mutables/useStore/onReferenceChange.d.ts.map +1 -0
  136. package/dist/Mutables/useStore/safetyCheck.d.ts +2 -0
  137. package/dist/Mutables/useStore/safetyCheck.d.ts.map +1 -0
  138. package/dist/Mutables/useStore/setter.d.ts +52 -0
  139. package/dist/Mutables/useStore/setter.d.ts.map +1 -0
  140. package/dist/Mutables/useStore/subscription.d.ts +50 -0
  141. package/dist/Mutables/useStore/subscription.d.ts.map +1 -0
  142. package/dist/Mutables/useStore/useContext.d.ts +3 -0
  143. package/dist/Mutables/useStore/useContext.d.ts.map +1 -0
  144. package/dist/Mutables/useStore/useStore.d.ts +7 -0
  145. package/dist/Mutables/useStore/useStore.d.ts.map +1 -0
  146. package/dist/Router/Router.d.ts +54 -0
  147. package/dist/Router/Router.d.ts.map +1 -0
  148. package/dist/Router/animatedRouter.d.ts +48 -0
  149. package/dist/Router/animatedRouter.d.ts.map +1 -0
  150. package/dist/Router/buildPath.d.ts +20 -0
  151. package/dist/Router/buildPath.d.ts.map +1 -0
  152. package/dist/Router/hashAtom.d.ts +12 -0
  153. package/dist/Router/hashAtom.d.ts.map +1 -0
  154. package/dist/Router/index.d.ts +11 -0
  155. package/dist/Router/index.d.ts.map +1 -0
  156. package/dist/Router/isPathMatching.d.ts +12 -0
  157. package/dist/Router/isPathMatching.d.ts.map +1 -0
  158. package/dist/Router/location.d.ts +74 -0
  159. package/dist/Router/location.d.ts.map +1 -0
  160. package/dist/Router/navigateTo.d.ts +31 -0
  161. package/dist/Router/navigateTo.d.ts.map +1 -0
  162. package/dist/Router/navigationLock.d.ts +50 -0
  163. package/dist/Router/navigationLock.d.ts.map +1 -0
  164. package/dist/Router/pathAtom.d.ts +42 -0
  165. package/dist/Router/pathAtom.d.ts.map +1 -0
  166. package/dist/Router/pathResolver.d.ts +9 -0
  167. package/dist/Router/pathResolver.d.ts.map +1 -0
  168. package/dist/Router/qsAtom.d.ts +12 -0
  169. package/dist/Router/qsAtom.d.ts.map +1 -0
  170. package/dist/Template/index.d.ts +6 -0
  171. package/dist/Template/index.d.ts.map +1 -0
  172. package/dist/Template/scheduler.d.ts +59 -0
  173. package/dist/Template/scheduler.d.ts.map +1 -0
  174. package/dist/Template/x-x.d.ts +60 -0
  175. package/dist/Template/x-x.d.ts.map +1 -0
  176. package/dist/Template/x-x.types.d.ts +91 -0
  177. package/dist/Template/x-x.types.d.ts.map +1 -0
  178. package/dist/Template/x-x.utils.d.ts +15 -0
  179. package/dist/Template/x-x.utils.d.ts.map +1 -0
  180. package/dist/Template/x.d.ts +5 -0
  181. package/dist/Template/x.d.ts.map +1 -0
  182. package/dist/{lib → Template}/xProvider.d.ts.map +1 -1
  183. package/dist/Timers/createManagedTimer.d.ts +12 -0
  184. package/dist/Timers/createManagedTimer.d.ts.map +1 -0
  185. package/dist/Timers/index.d.ts +4 -0
  186. package/dist/Timers/index.d.ts.map +1 -0
  187. package/dist/Timers/interval.d.ts +2 -0
  188. package/dist/Timers/interval.d.ts.map +1 -0
  189. package/dist/Timers/timeout.d.ts +2 -0
  190. package/dist/Timers/timeout.d.ts.map +1 -0
  191. package/dist/TrackState/componentStatus.d.ts +105 -0
  192. package/dist/TrackState/componentStatus.d.ts.map +1 -0
  193. package/dist/TrackState/index.d.ts +3 -0
  194. package/dist/TrackState/index.d.ts.map +1 -0
  195. package/dist/TrackState/readTracking.d.ts +20 -0
  196. package/dist/TrackState/readTracking.d.ts.map +1 -0
  197. package/dist/Utils/Context.d.ts.map +1 -0
  198. package/dist/Utils/Date/__tests__/shared.d.ts +13 -0
  199. package/dist/Utils/Date/__tests__/shared.d.ts.map +1 -0
  200. package/dist/Utils/Date/calendar.d.ts +30 -0
  201. package/dist/Utils/Date/calendar.d.ts.map +1 -0
  202. package/dist/Utils/Date/core.d.ts +29 -0
  203. package/dist/Utils/Date/core.d.ts.map +1 -0
  204. package/dist/Utils/Date/date.d.ts +60 -0
  205. package/dist/Utils/Date/date.d.ts.map +1 -0
  206. package/dist/Utils/Date/index.d.ts +3 -0
  207. package/dist/Utils/Date/index.d.ts.map +1 -0
  208. package/dist/Utils/deepClone.d.ts +7 -0
  209. package/dist/Utils/deepClone.d.ts.map +1 -0
  210. package/dist/Utils/deepFreeze.d.ts +2 -0
  211. package/dist/Utils/deepFreeze.d.ts.map +1 -0
  212. package/dist/Utils/getAllProps.d.ts +2 -0
  213. package/dist/Utils/getAllProps.d.ts.map +1 -0
  214. package/dist/Utils/helpers.d.ts +13 -0
  215. package/dist/Utils/helpers.d.ts.map +1 -0
  216. package/dist/Utils/index.d.ts +13 -0
  217. package/dist/Utils/index.d.ts.map +1 -0
  218. package/dist/Utils/is.d.ts +12 -0
  219. package/dist/Utils/is.d.ts.map +1 -0
  220. package/dist/Utils/isDefinedAsGetter.d.ts +2 -0
  221. package/dist/Utils/isDefinedAsGetter.d.ts.map +1 -0
  222. package/dist/Utils/logger.d.ts +22 -0
  223. package/dist/Utils/logger.d.ts.map +1 -0
  224. package/dist/Utils/notificationManager.d.ts +67 -0
  225. package/dist/Utils/notificationManager.d.ts.map +1 -0
  226. package/dist/Utils/registerCleanup.d.ts +7 -0
  227. package/dist/Utils/registerCleanup.d.ts.map +1 -0
  228. package/dist/Utils/svgIcon.d.ts +95 -0
  229. package/dist/Utils/svgIcon.d.ts.map +1 -0
  230. package/dist/Utils/uuid.d.ts +7 -0
  231. package/dist/Utils/uuid.d.ts.map +1 -0
  232. package/dist/ZeroPromise/index.d.ts +2 -0
  233. package/dist/ZeroPromise/index.d.ts.map +1 -0
  234. package/dist/ZeroPromise/zero.d.ts +23 -0
  235. package/dist/ZeroPromise/zero.d.ts.map +1 -0
  236. package/dist/actions/FetchAction.d.ts +2 -0
  237. package/dist/actions/FetchAction.d.ts.map +1 -0
  238. package/dist/actions/__benches__/asyncAction.bench.d.ts +24 -0
  239. package/dist/actions/__benches__/asyncAction.bench.d.ts.map +1 -0
  240. package/dist/actions/action.d.ts +14 -0
  241. package/dist/actions/action.d.ts.map +1 -0
  242. package/dist/actions/animatePresets.d.ts +38 -0
  243. package/dist/actions/animatePresets.d.ts.map +1 -0
  244. package/dist/actions/animateSwap.d.ts +79 -0
  245. package/dist/actions/animateSwap.d.ts.map +1 -0
  246. package/dist/actions/animateTypes.d.ts +103 -0
  247. package/dist/actions/animateTypes.d.ts.map +1 -0
  248. package/dist/actions/asyncAction.d.ts +4 -0
  249. package/dist/actions/asyncAction.d.ts.map +1 -0
  250. package/dist/actions/asyncActionCache.d.ts +16 -0
  251. package/dist/actions/asyncActionCache.d.ts.map +1 -0
  252. package/dist/actions/asyncActionPolling.d.ts +14 -0
  253. package/dist/actions/asyncActionPolling.d.ts.map +1 -0
  254. package/dist/actions/asyncActionTypes.d.ts +74 -0
  255. package/dist/actions/asyncActionTypes.d.ts.map +1 -0
  256. package/dist/actions/createTimingFunction.d.ts +23 -0
  257. package/dist/actions/createTimingFunction.d.ts.map +1 -0
  258. package/dist/actions/debounce.d.ts +10 -0
  259. package/dist/actions/debounce.d.ts.map +1 -0
  260. package/dist/actions/index.d.ts +12 -0
  261. package/dist/actions/index.d.ts.map +1 -0
  262. package/dist/actions/paginatedAsyncAction.d.ts +121 -0
  263. package/dist/actions/paginatedAsyncAction.d.ts.map +1 -0
  264. package/dist/actions/taskAction.d.ts +105 -0
  265. package/dist/actions/taskAction.d.ts.map +1 -0
  266. package/dist/actions/throttle.d.ts +9 -0
  267. package/dist/actions/throttle.d.ts.map +1 -0
  268. package/dist/css-in-js/cl.d.ts +37 -0
  269. package/dist/css-in-js/cl.d.ts.map +1 -0
  270. package/dist/css-in-js/index.d.ts +7 -0
  271. package/dist/css-in-js/index.d.ts.map +1 -0
  272. package/dist/css-in-js/sanitize.d.ts +31 -0
  273. package/dist/css-in-js/sanitize.d.ts.map +1 -0
  274. package/dist/css-in-js/serialize.d.ts +98 -0
  275. package/dist/css-in-js/serialize.d.ts.map +1 -0
  276. package/dist/css-in-js/stylesheet.bench.d.ts +2 -0
  277. package/dist/css-in-js/stylesheet.bench.d.ts.map +1 -0
  278. package/dist/css-in-js/stylesheet.d.ts +188 -0
  279. package/dist/css-in-js/stylesheet.d.ts.map +1 -0
  280. package/dist/css-in-js/theme.d.ts +105 -0
  281. package/dist/css-in-js/theme.d.ts.map +1 -0
  282. package/dist/devtools/devtoolsHooks.d.ts +101 -0
  283. package/dist/devtools/devtoolsHooks.d.ts.map +1 -0
  284. package/dist/devtools/index.d.ts +2 -0
  285. package/dist/devtools/index.d.ts.map +1 -0
  286. package/dist/dist/styles.css +1 -0
  287. package/dist/index.d.ts +5439 -977
  288. package/dist/index.d.ts.map +1 -0
  289. package/dist/index.esm.js +132 -16
  290. package/dist/index.esm.js.map +1 -1
  291. package/dist/index.js +135 -18
  292. package/dist/index.js.map +1 -1
  293. package/dist/on/hooks.d.ts +319 -0
  294. package/dist/on/hooks.d.ts.map +1 -0
  295. package/dist/on/index.d.ts +3 -0
  296. package/dist/on/index.d.ts.map +1 -0
  297. package/dist/on/on.d.ts +16 -0
  298. package/dist/on/on.d.ts.map +1 -0
  299. package/dist/portals/dialog.d.ts +56 -0
  300. package/dist/portals/dialog.d.ts.map +1 -0
  301. package/dist/portals/index.d.ts +9 -0
  302. package/dist/portals/index.d.ts.map +1 -0
  303. package/dist/portals/popup.d.ts +37 -0
  304. package/dist/portals/popup.d.ts.map +1 -0
  305. package/dist/portals/portal.d.ts +57 -0
  306. package/dist/portals/portal.d.ts.map +1 -0
  307. package/dist/portals/tip.d.ts +38 -0
  308. package/dist/portals/tip.d.ts.map +1 -0
  309. package/dist/socket/index.d.ts +2 -0
  310. package/dist/socket/index.d.ts.map +1 -0
  311. package/dist/socket/ws.d.ts +83 -0
  312. package/dist/socket/ws.d.ts.map +1 -0
  313. package/dist/ssr/index.d.ts +3 -0
  314. package/dist/ssr/index.d.ts.map +1 -0
  315. package/dist/ssr/ssr-bridge.d.ts +89 -0
  316. package/dist/ssr/ssr-bridge.d.ts.map +1 -0
  317. package/dist/ssr/ssrFlag.d.ts +38 -0
  318. package/dist/ssr/ssrFlag.d.ts.map +1 -0
  319. package/dist/virtualizer/LitVirtualizer.d.ts +62 -0
  320. package/dist/virtualizer/LitVirtualizer.d.ts.map +1 -0
  321. package/dist/virtualizer/ScrollerController.d.ts +54 -0
  322. package/dist/virtualizer/ScrollerController.d.ts.map +1 -0
  323. package/dist/virtualizer/Virtualizer.d.ts +214 -0
  324. package/dist/virtualizer/Virtualizer.d.ts.map +1 -0
  325. package/dist/virtualizer/events.d.ts +27 -0
  326. package/dist/virtualizer/events.d.ts.map +1 -0
  327. package/dist/virtualizer/index.d.ts +2 -0
  328. package/dist/virtualizer/index.d.ts.map +1 -0
  329. package/dist/virtualizer/layouts/flexWrap.d.ts +65 -0
  330. package/dist/virtualizer/layouts/flexWrap.d.ts.map +1 -0
  331. package/dist/virtualizer/layouts/flow.d.ts +123 -0
  332. package/dist/virtualizer/layouts/flow.d.ts.map +1 -0
  333. package/dist/virtualizer/layouts/grid.d.ts +25 -0
  334. package/dist/virtualizer/layouts/grid.d.ts.map +1 -0
  335. package/dist/virtualizer/layouts/masonry.d.ts +37 -0
  336. package/dist/virtualizer/layouts/masonry.d.ts.map +1 -0
  337. package/dist/virtualizer/layouts/shared/BaseLayout.d.ts +204 -0
  338. package/dist/virtualizer/layouts/shared/BaseLayout.d.ts.map +1 -0
  339. package/dist/virtualizer/layouts/shared/GridBaseLayout.d.ts +41 -0
  340. package/dist/virtualizer/layouts/shared/GridBaseLayout.d.ts.map +1 -0
  341. package/dist/virtualizer/layouts/shared/Layout.d.ts +138 -0
  342. package/dist/virtualizer/layouts/shared/Layout.d.ts.map +1 -0
  343. package/dist/virtualizer/layouts/shared/SizeCache.d.ts +19 -0
  344. package/dist/virtualizer/layouts/shared/SizeCache.d.ts.map +1 -0
  345. package/dist/virtualizer/layouts/shared/SizeGapPaddingBaseLayout.d.ts +57 -0
  346. package/dist/virtualizer/layouts/shared/SizeGapPaddingBaseLayout.d.ts.map +1 -0
  347. package/dist/virtualizer/lit-virtualizer.d.ts +23 -0
  348. package/dist/virtualizer/lit-virtualizer.d.ts.map +1 -0
  349. package/dist/virtualizer/mates-adapter.d.ts +201 -0
  350. package/dist/virtualizer/mates-adapter.d.ts.map +1 -0
  351. package/dist/virtualizer/support/method-interception.d.ts +33 -0
  352. package/dist/virtualizer/support/method-interception.d.ts.map +1 -0
  353. package/dist/virtualizer/support/resize-observer-errors.d.ts +53 -0
  354. package/dist/virtualizer/support/resize-observer-errors.d.ts.map +1 -0
  355. package/dist/virtualizer/virtualize.d.ts +52 -0
  356. package/dist/virtualizer/virtualize.d.ts.map +1 -0
  357. package/package.json +25 -6
  358. package/dist/examples/src/Counter/Counter.d.ts +0 -2
  359. package/dist/examples/src/Counter/Counter.d.ts.map +0 -1
  360. package/dist/examples/src/Counter/CounterWithAtoms.d.ts +0 -2
  361. package/dist/examples/src/Counter/CounterWithAtoms.d.ts.map +0 -1
  362. package/dist/examples/src/Counter/index.d.ts +0 -3
  363. package/dist/examples/src/Counter/index.d.ts.map +0 -1
  364. package/dist/examples/src/Scope Examples/MultiScopeDemo.d.ts +0 -2
  365. package/dist/examples/src/Scope Examples/MultiScopeDemo.d.ts.map +0 -1
  366. package/dist/examples/src/Scope Examples/ScopeDemo.d.ts +0 -2
  367. package/dist/examples/src/Scope Examples/ScopeDemo.d.ts.map +0 -1
  368. package/dist/examples/src/Scope Examples/SimpleScopeExample.d.ts +0 -2
  369. package/dist/examples/src/Scope Examples/SimpleScopeExample.d.ts.map +0 -1
  370. package/dist/examples/src/Scope Examples/index.d.ts +0 -4
  371. package/dist/examples/src/Scope Examples/index.d.ts.map +0 -1
  372. package/dist/examples/src/Scopes Test/A.d.ts +0 -3
  373. package/dist/examples/src/Scopes Test/A.d.ts.map +0 -1
  374. package/dist/examples/src/ThemeContext.ts/ThemeDemo.d.ts +0 -4
  375. package/dist/examples/src/ThemeContext.ts/ThemeDemo.d.ts.map +0 -1
  376. package/dist/examples/src/Todo/Todo.d.ts +0 -2
  377. package/dist/examples/src/Todo/Todo.d.ts.map +0 -1
  378. package/dist/examples/src/Todo/TodoWithAtoms.d.ts +0 -2
  379. package/dist/examples/src/Todo/TodoWithAtoms.d.ts.map +0 -1
  380. package/dist/examples/src/Todo/index.d.ts +0 -3
  381. package/dist/examples/src/Todo/index.d.ts.map +0 -1
  382. package/dist/examples/src/main.d.ts +0 -3
  383. package/dist/examples/src/main.d.ts.map +0 -1
  384. package/dist/examples/vite.config.d.ts +0 -3
  385. package/dist/examples/vite.config.d.ts.map +0 -1
  386. package/dist/lib/Context.d.ts.map +0 -1
  387. package/dist/lib/atom.d.ts +0 -45
  388. package/dist/lib/atom.d.ts.map +0 -1
  389. package/dist/lib/bubbles.d.ts +0 -2
  390. package/dist/lib/bubbles.d.ts.map +0 -1
  391. package/dist/lib/compute.d.ts +0 -9
  392. package/dist/lib/compute.d.ts.map +0 -1
  393. package/dist/lib/index.d.ts +0 -31
  394. package/dist/lib/index.d.ts.map +0 -1
  395. package/dist/lib/molecule.d.ts +0 -16
  396. package/dist/lib/molecule.d.ts.map +0 -1
  397. package/dist/lib/pathResolver.d.ts +0 -9
  398. package/dist/lib/pathResolver.d.ts.map +0 -1
  399. package/dist/lib/scope.d.ts +0 -14
  400. package/dist/lib/scope.d.ts.map +0 -1
  401. package/dist/lib/store.d.ts +0 -2
  402. package/dist/lib/store.d.ts.map +0 -1
  403. package/dist/lib/styleDirectives.d.ts +0 -14
  404. package/dist/lib/styleDirectives.d.ts.map +0 -1
  405. package/dist/lib/units.d.ts +0 -12
  406. package/dist/lib/units.d.ts.map +0 -1
  407. package/dist/lib/useRefEffect.d.ts +0 -2
  408. package/dist/lib/useRefEffect.d.ts.map +0 -1
  409. package/dist/lib/useStore.d.ts +0 -26
  410. package/dist/lib/useStore.d.ts.map +0 -1
  411. package/dist/lib/view.d.ts +0 -8
  412. package/dist/lib/view.d.ts.map +0 -1
  413. package/dist/lib/x-view.d.ts +0 -40
  414. package/dist/lib/x-view.d.ts.map +0 -1
  415. /package/dist/{lib → Template}/xProvider.d.ts +0 -0
  416. /package/dist/{lib → Utils}/Context.d.ts +0 -0
package/README.md CHANGED
@@ -1,412 +1,1753 @@
1
- # Mates
1
+ # MATES
2
+
3
+ **MATES** is a lightweight, TypeScript-first front-end framework built on [lit-html](https://lit.dev/docs/libraries/standalone-templates/) to help you build large scale complex web applications that focuses on Developer Experience.
4
+
5
+ What if we had a framework that is just perfect? A framework that's faster than react and without the need of a compiler. A Framework that's super type safe. A framework that cherishes capabilities of Javascript. Mates a Beautiful, simple, Perfect Javascript Framework with **no virtual DOM**, **no compiler step**, but with lots of **goodness**. You can install Mates and run using a build tool like Vite or Bun or just use CDN to load Mates at runtime.
6
+
7
+ MATES is based on an architecture: **M**utable State, **A**ctions, **T**emplates, **E**vents and **S**etups. State is mutable, Actions are trackable functions, Templates are functions that return html template strings, setups are run once before the component is mounted to set up some state or some business logic or effects.
8
+
9
+ Mates is about 50Kb gzipped, but it comes with a Router, really good state management system (you will not need anything else), Date Utils with timezone support (no need for extra library), Fetch and web socket Utils (no axios or socket.io needed), Animation Utils, Form Validation Utils, Virtualisation for lists or tables, built in CSS-in-Js library, portals, tooltips, snackbars, popups (so it'll help you create popups with no effort), UUID utils to generate random ID, Actions, Events, custom hook support, Memoisation, Zero promises (cancellable and reusable promises), built in support for pagination and lazy loading, with tons of built in hooks that are very useful, utilites to handle local storage across all tabs, a Task Manager to handle lots of async tasks in parallel or one by one and many more.
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [Why Mates?](#why-mates)
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Components](#components)
19
+ - [The Two-Layer Model](#the-two-layer-model)
20
+ - [Rendering Components](#rendering-components)
21
+ - [Props](#props)
22
+ - [Children / Slots](#children--slots)
23
+ - [State Management](#state-management)
24
+ - [atom — reactive primitive](#atom--reactive-primitive)
25
+ - [iAtom — immutable signal](#iatom--immutable-signal)
26
+ - [effect — reactive side effect](#effect--reactive-side-effect)
27
+ - [memo — derived / computed value](#memo--derived--computed-value)
28
+ - [useState — local object state](#usestate--local-object-state)
29
+ - [store — module-level shared state](#store--module-level-shared-state)
30
+ - [setAtom — reactive Set](#setatom--reactive-set)
31
+ - [mapAtom — reactive Map](#mapatom--reactive-map)
32
+ - [lsAtom / ssAtom — persistent state](#lsatom--ssatom--persistent-state)
33
+ - [Scopes — Shared State Without Prop Drilling](#scopes--shared-state-without-prop-drilling)
34
+ - [Lifecycle Hooks](#lifecycle-hooks)
35
+ - [DOM & Window Hooks](#dom--window-hooks)
36
+ - [Async Actions](#async-actions)
37
+ - [asyncAction](#asyncaction)
38
+ - [action — synchronous action](#action--synchronous-action)
39
+ - [paginatedAsyncAction](#paginatedasyncaction)
40
+ - [taskAction](#taskaction)
41
+ - [HTTP Client](#http-client)
42
+ - [Routing](#routing)
43
+ - [CSS-in-JS & Theming](#css-in-js--theming)
44
+ - [Directives](#directives)
45
+ - [Portals, Dialogs & Tooltips](#portals-dialogs--tooltips)
46
+ - [Animations](#animations)
47
+ - [WebSocket](#websocket)
48
+ - [Virtualization](#virtualization)
49
+ - [DevTools](#devtools)
50
+ - [TypeScript](#typescript)
51
+ - [Features at a Glance](#features-at-a-glance)
52
+ - [How Mates Compares](#how-mates-compares)
53
+ - [License](#license)
54
+
55
+ ---
56
+
57
+ ## Why Mates?
58
+
59
+ Most frameworks ask you to learn a new mental model — JSX compilers, reactive transforms, or a shadow DOM abstraction. Mates takes a different approach:
60
+
61
+ - **No compiler, no JSX** — just tagged template literals from `lit-html`. Works in any TypeScript project out of the box.
62
+ - **No virtual DOM** — `lit-html` patches only the parts of the DOM that actually changed. Updates are surgical, not diffed from scratch.]
63
+ - **Predictable two-layer components** — the outer function (setup) runs once. The inner function (render) runs on every update. Clear separation between initialization and rendering.
64
+ - **Batteries included** — routing, async state machines, CSS-in-JS, WebSocket, virtualization, portals, and animations are all first-party and designed to work together.
65
+ - **Very Capable** it's shipped with lots of utitlies to help you avoid installing a third party library just for few of it's functions.
66
+ - **TypeScript-first** — every API is fully typed from the ground up.
67
+
68
+ ---
69
+
70
+ ## Installation
2
71
 
3
- **Mates** is a lightweight framework that focuses on developer experience. It lets you build great web applications easily with it's awesome state management system. It's typescript First Framework. supports typescript all the way.
72
+ ```bash
73
+ npm install mates
74
+ ```
4
75
 
5
- ## 🚀 Features (MATES)
76
+ Mates requires `lit-html` as a peer dependency (automatically installed):
6
77
 
7
- - **M**utable State: Mutate state directly through setters to update components (views)
8
- - **A**ctions: Function utilities to help with business logic
9
- - **T**emplates: Template functions that return **lit-html** result
10
- - **E**vents: Event Utilites for event-driven development
11
- - **S**etup functions: These are functions to intialise state needed for the view
78
+ ```bash
79
+ npm install mates lit-html
80
+ ```
12
81
 
13
- ## 📦 Installation
82
+ ---
14
83
 
15
- ```bash
84
+ ## Quick Start
16
85
 
17
- npm install mates
86
+ ```typescript
87
+ import { atom, html, renderX, x } from "mates";
18
88
 
19
- # or
89
+ // A simple counter component
90
+ const Counter = () => {
91
+ const count = atom(0);
20
92
 
21
- yarn add mates
93
+ return () => html`
94
+ <button @click=${() => count.set((n) => n - 1)}>−</button>
95
+ <span>${count()}</span>
96
+ <button @click=${() => count.set((n) => n + 1)}>+</button>
97
+ `;
98
+ };
22
99
 
100
+ // Mount to the DOM
101
+ renderX(Counter, document.getElementById("app")!);
23
102
  ```
24
103
 
25
- ## 🧠 Core Concepts
104
+ ---
105
+
106
+ ## Components
26
107
 
27
- ### 👁 Views: Components of Mates
108
+ ### The Two-Layer Model
28
109
 
29
- Views are similar to components in react. They are closure functions with outer function aka **Setup** function (the S from the MATES), and inner function is called **Template** function (the T from MATES) which returns html template string.
110
+ Every Mates component is a **closure** with two layers:
111
+
112
+ | Layer | Runs | Purpose |
113
+ |-------|------|---------|
114
+ | **Outer function** | Once on mount | Create atoms, set up subscriptions, register lifecycle hooks |
115
+ | **Inner function** | Every render | Read reactive values and return a `TemplateResult` |
30
116
 
31
117
  ```typescript
32
- import { renderView, setter, html } from "mates";
118
+ import { atom, html } from "mates";
119
+ import type { Props } from "mates";
33
120
 
34
- const CounterView = (props) => {
35
- // setup function: initialise state
36
- let count = 0;
37
- const incr = setter(() => count++);
121
+ const Counter = (propsFn: Props<{ label: string }>) => {
122
+ // ── Outer: runs once ────────────────────────────────────
123
+ const count = atom(0);
38
124
 
39
- // template function: returns html template strings.
40
- return () => html` <div>
41
- <h1>Count: ${count}</h1>
42
- <button @click=${incr}>Increment</button>
43
- </div>`;
125
+ // ── Inner: runs on every re-render ──────────────────────
126
+ return () => html`
127
+ <p>${propsFn().label}: ${count()}</p>
128
+ <button @click=${() => count.set((n) => n + 1)}>Increment</button>
129
+ `;
44
130
  };
45
-
46
- // Render the view in any html element by passing it's id
47
- renderView(CounterView, "app");
48
131
  ```
49
132
 
50
- **count** is a **local let variable** that holds value. which can only be changed froma setter function.
51
- **incr** is a **setter** function, that when called, updates the view. it has to be non-async.
133
+ > **Rule of thumb:** atoms, effects, hooks, and subscriptions always go in the **outer** function, you can also make api calls..etc you can be assumed that this code is only executed once. template function always returns html temlate strings. Modern IDEs have a built in formatting support for the html template strings.
52
134
 
53
- ### Scopes
135
+ ---
54
136
 
55
- Mates introduces a new feature called Scopes. an amazing way to share state with child views (components). using Scopes, child components can access parent's state directly without using props.
137
+ ### Rendering Components
56
138
 
57
- #### Formatting Support:
139
+ Use `x()` (also exported as `view`) to embed a component inside a template. Use `renderX()` to mount a component into a real DOM element.
58
140
 
59
- you can install lit-html plugin on your IDE like vs code or cursor for proper formatting for your template strings.
141
+ **x** function lets you mount components in another component. you can also use **view** for the same puprose. **x** takes in a component.
60
142
 
61
- ### props()
143
+ ```typescript
144
+ import { html, renderX, x } from "mates";
145
+
146
+ // Embed in another component's template
147
+ const App = () => () => html`
148
+ <main>
149
+ <h1>My App</h1>
150
+ ${x(Counter, { label: "Clicks" })}
151
+ </main>
152
+ `;
62
153
 
63
- In Mates, props can be passed to child views as object. props is a funciton and not an object but it's passed as object to child component. the view will have to call props() to get the projects object.
154
+ ```
155
+
156
+ ---
157
+
158
+ ### plain state and settter
159
+
160
+ You don't need to create atoms to handle state for your application. The state can be anything, it can be a plain primitives or objects or sets or maps or something else, the reason we use atoms because they are trackable and you can derive computed values from them. But you can also use plain JS objects as state and you can setter functions to notify the component that something is changed and that it needs to update itself. Here is an example of the same counter without using atoms.
64
161
 
65
162
  ```typescript
66
- const CounterView = () => {
163
+ import { setter, html } from "mates";
164
+ import type { Props } from "mates";
165
+
166
+ const Counter = (propsFn: Props<{ label: string }>) => {
67
167
  let count = 0;
68
- let incr = setter(() => count++);
168
+ const incr = setter(()=>count++);
169
+ // ── Inner: runs every time the incr is called or parent component is re-rendered.
69
170
  return () => html`
70
- <div>count is: ${count}</div>
71
- <div>${view(ChildView, { count })}</div>
171
+ <p>${propsFn().label}: ${count}</p>
172
+ <button @click=${incr}>Increment</button>
72
173
  `;
73
174
  };
74
- const ChildView = (props: Props<{ count: number }>) => {
75
- return html`parent count is : ${props().count}`;
175
+ ```
176
+
177
+ ### PropsFn
178
+
179
+ Props are passed as the second argument to `x()` and arrive inside the component as a **function** — `propsFn()`. Calling it inside the **inner** function ensures you always get the latest values when the parent re-renders.
180
+
181
+ ```typescript
182
+ import { html, x, renderX } from "mates";
183
+ import type { Props } from "mates";
184
+
185
+ const Greeting = (propsFn: Props<{ name: string; color?: string }>) => {
186
+ // ✅ Read props in the inner function — always up-to-date
187
+ return () => html`
188
+ <p style="color: ${propsFn().color ?? "black"}">
189
+ Hello, ${propsFn().name}!
190
+ </p>
191
+ `;
192
+ };
193
+
194
+ // Props update automatically when the parent re-renders
195
+ const App = () => {
196
+ const name = atom("World");
197
+ return () => html`
198
+ ${x(Greeting, { name: name(), color: "royalblue" })}
199
+ <button @click=${() => name.set("Mates")}>Change Name</button>
200
+ `;
76
201
  };
77
202
  ```
78
203
 
79
- **note** please don't destructure props into local variables in the outer function, as it breaks connection from the parent object. but you can do this in the inner function as inner function gets executed everytime the parent view is updated. so you will have always the latest value.
204
+ > ⚠️ Never destructure props in the outer function (`const { name } = propsFn()`). The value would be captured at mount time and never update.
80
205
 
81
- ### ⚛️ Atoms: Simple Reactive State
206
+ ---
82
207
 
83
- Atoms hold mutable values. they can hold any Javascript value like primitive or objects or maps or sets.etc They store a single value that can be read, set (replaced with new value), or updated (if it's an object). They support typescript fully.
208
+ ### Children / Slots
84
209
 
85
- ```typescript
210
+ Pass children using the `<x-view>` element and render them inside the component with a standard `<slot>` element:
86
211
 
87
- import { atom } from "mates";
212
+ ```typescript
213
+ // Parent passes children
214
+ const App = () => () => html`
215
+ <x-view .view=${Card} .props=${{title: "Hello"}}>
216
+ <p>This is child content</p>
217
+ </x-view>
218
+ `;
219
+
220
+ // Component renders children via <slot>
221
+ const Card = (propsFn: Props<{ title: string }>) => () => html`
222
+ <div class="card">
223
+ <h2>${propsFn().title}</h2>
224
+ <slot></slot>
225
+ </div>
226
+ `;
227
+ ```
88
228
 
89
- // Create an atom with initial value
90
- const username = atom("guest");
229
+ ---
91
230
 
92
- // Read the value
93
- console.log(username()); // "guest"
94
- // or
95
- console.log(username.get()); // "guest"
231
+ ## State Management
96
232
 
97
- // set the value
98
- username.set("alice");
233
+ ### `atom` reactive primitive
99
234
 
100
- // you can also Use setter function
101
- username.set((val) => val.toUpperCase());
235
+ `atom` is the core reactive primitive. It holds any JavaScript value. Reading an atom inside the **inner** function (or an `effect`) registers a dependency — that subscriber re-runs whenever the atom changes.
102
236
 
103
- // you can also update the value if it's of object type
104
- const address = atom({ street: "" });
237
+ ```typescript
238
+ import { atom } from "mates";
239
+
240
+ // Create
241
+ const username = atom("guest");
242
+ const count = atom(0);
243
+ const user = atom<{ name: string; age: number } | null>(null);
244
+
245
+ // Read
246
+ username(); // "guest" — registers reactive dependency
247
+ username.get(); // "guest" — alias, same behavior
248
+ username.val; // "guest" — non-reactive (snapshot, no tracking)
249
+
250
+ // Write
251
+ username.set("alice"); // replace
252
+ count.set((n) => n + 1); // updater function
253
+ user.set({ name: "Alice", age: 30 });
254
+
255
+ // Mutate objects in-place
256
+ const profile = atom({ name: "Alice", score: 0 });
257
+ profile.update((p) => { p.score++; }); // mutation, triggers update
258
+ ```
105
259
 
106
- // you are updating just the street value in an object.
107
- address.update((s) => (s.street = "newstreet"));
260
+ **Atoms created inside a component's outer function** are scoped to that component they are created on mount and automatically garbage-collected when the component unmounts.
108
261
 
109
- // supports maps or sets:
110
- const nums = atom(new Map());
111
- // modify the map through update.
112
- nums.update(m=>m.set(1, "one"));
262
+ **Atoms created at module level** are global — they persist for the lifetime of the application and can be shared between any component.
113
263
 
114
- const nums = atom(new Set([1,2,3]);
115
- nums.update(s=>s.add(4));
116
- // get the value
117
- nums(); // Set(4) [1,2,3,4]
264
+ ```typescript
265
+ // Global state — shared across all components
266
+ export const currentUser = atom<User | null>(null);
267
+ export const cartCount = atom(0);
118
268
  ```
119
269
 
120
- ### Counter app using atoms
270
+ ---
271
+
272
+ ### `iAtom` — immutable signal
273
+
274
+ `iAtom` (also exported as `signal`) behaves like `atom` but **deep-freezes every value** after each `set()`. Accidental mutations throw in strict mode, making state flows easier to reason about. There is no `.update()` method — you always replace the whole value.
121
275
 
122
276
  ```typescript
123
- const CounterView = (props) => {
124
- // setup function: initialise state
125
- const count = atom(0);
126
- const incr = count.set(count() + 1);
277
+ import { iAtom } from "mates";
127
278
 
128
- // template function: returns html template strings.
129
- return () => html` <div>
130
- <h1>Count: ${count}</h1>
131
- <button @click=${incr}>Increment</button>
132
- </div>`;
133
- };
279
+ const theme = iAtom<"light" | "dark">("light");
280
+
281
+ theme.set("dark");
282
+ theme(); // "dark"
283
+
284
+ const config = iAtom({ debug: false, version: 1 });
285
+ config.set({ debug: true, version: 2 }); // must replace entirely
134
286
  ```
135
287
 
136
- ### Units: Independent Object-Based State
288
+ ---
137
289
 
138
- Units are perfect for managing object-based state with methods and building store utilities.
139
- Units have the following pieces
290
+ ### `effect` reactive side effect
140
291
 
141
- - Data: any type of javascript value
142
- - Setters: methods whose name start with (\_)
143
- - Getters: methods that returns data without changing it
144
- - Actions: async or non-async methods that call getters or setters for getting, setting data.
292
+ An effect runs **immediately** and automatically re-runs whenever any atom it reads during execution changes. Return a cleanup function to run before the next execution and on disposal.
145
293
 
146
294
  ```typescript
147
- import { unit } from "mates";
295
+ import { atom, effect } from "mates";
148
296
 
149
- // Create a users unit
150
- const todoList = unit({
151
- users: [],
152
- isLoading = false,
153
- // setter
154
- _setIsLoading(value) {
155
- this.isLoading = value;
156
- },
157
- // setter
158
- _setUsers(newUsers) {
159
- this.users = newUsers;
160
- },
161
- // async action that calls setters to set state
162
- async loadUsers() {
163
- this._setIsLoading(true);
164
- this._setUsers(await fetch("/users").then((d) => d.json()));
165
- this._setIsLoading(false);
166
- },
167
- getUsersCount() {
168
- return this.users.length;
169
- },
297
+ const count = atom(0);
298
+ const label = atom("counter");
299
+
300
+ // Runs immediately, then again whenever count or label changes
301
+ const stop = effect(() => {
302
+ document.title = `${label()}: ${count()}`;
303
+
304
+ // Optional: return a cleanup function
305
+ return () => {
306
+ document.title = "App";
307
+ };
170
308
  });
309
+
310
+ count.set(5); // document.title → "counter: 5"
311
+ label.set("score"); // document.title → "score: 5"
312
+
313
+ stop(); // dispose — cleanup runs, no more re-runs
171
314
  ```
172
315
 
173
- ### 📊 Getters: Computed Values
316
+ When used inside a component's outer function, effects are automatically disposed when the component unmounts.
317
+
318
+ ---
319
+
320
+ ### `memo` — derived / computed value
174
321
 
175
- Getters create computed values that only recalculate when their dependencies change.
322
+ `memo` creates a derived atom that stays in sync with its dependencies. It only recomputes when a dependency changes. The result is itself a readable atom.
176
323
 
177
324
  ```typescript
178
- import { atom, getter } from "mates";
325
+ import { atom, memo } from "mates";
179
326
 
180
- const firstName = atom("John");
327
+ const firstName = atom("Alice");
328
+ const lastName = atom("Smith");
181
329
 
182
- const lastName = atom("Doe");
330
+ const fullName = memo(() => `${firstName()} ${lastName()}`);
183
331
 
184
- const fullName = getter(() => {
185
- return `${firstName()} ${lastName()}`;
186
- });
332
+ fullName(); // "Alice Smith"
333
+ firstName.set("Bob");
334
+ fullName(); // "Bob Smith" — recomputed automatically
335
+ ```
336
+
337
+ ---
338
+
339
+ ### `useState` — local object state
340
+
341
+ `useState` is designed for local component state expressed as a plain object with data properties and setter methods. Every method call triggers a re-render.
342
+
343
+ ```typescript
344
+ import { html, renderX, useState } from "mates";
345
+
346
+ const Counter = () => {
347
+ const [state] = useState({
348
+ count: 0,
349
+ step: 1,
350
+ incr() { this.count += this.step; },
351
+ decr() { this.count -= this.step; },
352
+ reset() { this.count = 0; },
353
+ setStep(n: number) { this.step = n; },
354
+ });
355
+
356
+ return () => html`
357
+ <div>
358
+ <button @click=${state.decr}>−</button>
359
+ <span>${state.count}</span>
360
+ <button @click=${state.incr}>+</button>
361
+ <button @click=${state.reset}>Reset</button>
362
+ <label>
363
+ Step:
364
+ <input
365
+ type="number"
366
+ .value=${String(state.step)}
367
+ @input=${(e: InputEvent) =>
368
+ state.setStep(Number((e.target as HTMLInputElement).value))}
369
+ />
370
+ </label>
371
+ </div>
372
+ `;
373
+ };
374
+ ```
375
+
376
+ > Async methods inside `useState` are automatically wrapped with `asyncAction`, giving you `.data`, `.isLoading`, `.error`, and `.status` atoms for free on async operations.
377
+
378
+ ---
379
+
380
+ ### `store` — module-level shared state
187
381
 
188
- console.log(fullName()); // "John Doe"
382
+ `store` creates a **global reactive container** from a plain object. Define it at module level and share it across the entire application. Inside a component, call the store to get a `[state, update]` tuple.
189
383
 
190
- // Only recalculates when dependencies change
384
+ ```typescript
385
+ import { store, html, x, renderX } from "mates";
386
+
387
+ // Define once at module level
388
+ const counterStore = store({
389
+ count: 0,
390
+ step: 1,
391
+ incr() { this.count += this.step; },
392
+ decr() { this.count -= this.step; },
393
+ get doubled() { return this.count * 2; },
394
+ });
191
395
 
192
- firstName.set("Jane");
396
+ // Consume in any component
397
+ const Counter = () => {
398
+ const [state, update] = counterStore();
193
399
 
194
- console.log(fullName()); // "Jane Doe"
400
+ return () => html`
401
+ <button @click=${state.decr}>−</button>
402
+ <span>${state.count} (doubled: ${state.doubled})</span>
403
+ <button @click=${state.incr}>+</button>
404
+ <button @click=${() => update((s) => { s.count = 0; })}>Reset</button>
405
+ `;
406
+ };
195
407
  ```
196
408
 
197
- ### 🧬 Molecules: group atoms or units or getters into one molecule
409
+ The `update` function is for direct external mutations. State methods (`incr`, `decr`) trigger re-renders automatically.
410
+
411
+ ---
412
+
413
+ ### `setAtom` — reactive Set
414
+
415
+ A reactive wrapper around JavaScript's `Set`. All mutations trigger component updates.
198
416
 
199
417
  ```typescript
200
- import { molecule, atom } from "mates";
418
+ import { setAtom } from "mates";
201
419
 
202
- class UserStore {
203
- name = atom("Guest");
420
+ const selectedIds = setAtom<number>([1, 2]);
204
421
 
205
- isLoggedIn = atom(false);
422
+ // Mutations — trigger re-renders
423
+ selectedIds.add(3);
424
+ selectedIds.delete(1);
425
+ selectedIds.clear();
206
426
 
207
- login(username) {
208
- this.name.set(username);
209
- this.isLoggedIn.set(true);
210
- }
427
+ // Reads — track as reactive dependencies
428
+ selectedIds.size; // 2
429
+ selectedIds.has(2); // true
430
+ selectedIds.values(); // IterableIterator
431
+ selectedIds.forEach((v) => {}); // iterate
432
+ ```
211
433
 
212
- logout() {
213
- this.name.set("Guest");
214
- this.isLoggedIn.set(false);
215
- }
216
- }
434
+ ---
217
435
 
218
- // Create a molecule from the class
436
+ ### `mapAtom` reactive Map
219
437
 
220
- const userStore = molecule(UserStore);
438
+ A reactive wrapper around JavaScript's `Map`. All mutations trigger component updates.
221
439
 
222
- // Use the molecule
440
+ ```typescript
441
+ import { mapAtom } from "mates";
223
442
 
224
- console.log(userStore().name()); // "Guest"
443
+ const cache = mapAtom<string, number>([["apples", 3]]);
225
444
 
226
- userStore().login("Alice");
445
+ // Mutations
446
+ cache.set("bananas", 5);
447
+ cache.delete("apples");
448
+ cache.clear();
227
449
 
228
- console.log(userStore().isLoggedIn()); // true
450
+ // Reads
451
+ cache.size; // 1
452
+ cache.get("bananas"); // 5
453
+ cache.has("bananas"); // true
454
+ cache.entries(); // IterableIterator<[string, number]>
229
455
  ```
230
456
 
231
- ### 🔄 XProvider: Context Management
457
+ ---
232
458
 
233
- XProvider allows you to provide and consume context across your application.
459
+ ### `lsAtom` / `ssAtom` persistent state
460
+
461
+ `lsAtom` persists to `localStorage`; `ssAtom` persists to `sessionStorage`. Both auto-hydrate from storage on first read and automatically sync writes back.
234
462
 
235
463
  ```typescript
236
- import { html } from "lit-html";
464
+ import { lsAtom, ssAtom } from "mates";
237
465
 
238
- import { view, useContext } from "mates";
466
+ // Persisted across page reloads
467
+ const theme = lsAtom<"light" | "dark">("light");
468
+ const sidebar = lsAtom<boolean>(true);
239
469
 
240
- // Create a context class
470
+ // Session-only cleared when the tab closes
471
+ const draftText = ssAtom<string>("");
241
472
 
242
- class ThemeContext {
243
- theme = "light";
473
+ theme.set("dark"); // saved to localStorage["mates"]
474
+ theme(); // "dark" — even after a page reload
475
+ ```
244
476
 
245
- toggleTheme() {
246
- this.theme = this.theme === "light" ? "dark" : "light";
247
- }
248
- }
477
+ `lsAtom` also syncs across browser tabs via the `storage` event.
249
478
 
250
- // Provider component
479
+ ---
251
480
 
252
- const ThemeProvider = view(
253
- (props) => {
254
- const themeContext = new ThemeContext();
481
+ ## Scopes Shared State Without Prop Drilling
255
482
 
256
- return () => html`
257
- <x-provider .value=${themeContext}> ${props().children} </x-provider>
258
- `;
259
- },
483
+ Scopes let any **descendant** component access state from a parent without threading props through every layer in between. Define a scope as a class, initialize it in the parent with `useScope`, and read it anywhere below with `getParentScope`.
260
484
 
261
- { children: [] }
262
- );
485
+ ```typescript
486
+ import { atom, effect, getParentScope, html, onMount, repeat, useScope, x } from "mates";
487
+ import type { Props } from "mates";
488
+
489
+ // 1. Define the scope as a class
490
+ class CartScope {
491
+ items = atom<string[]>([]);
492
+ loading = atom(false);
263
493
 
264
- // Consumer component
494
+ add(item: string) {
495
+ this.items.update((list) => { list.push(item); });
496
+ }
497
+ remove(item: string) {
498
+ this.items.update((list) => {
499
+ const i = list.indexOf(item);
500
+ if (i !== -1) list.splice(i, 1);
501
+ });
502
+ }
265
503
 
266
- const ThemedButton = view(() => {
267
- // Get context instance
504
+ // setup() runs before the component mounts
505
+ setup() {
506
+ effect(() => {
507
+ console.log("Cart has", this.items().length, "items");
508
+ });
509
+ }
510
+ }
268
511
 
269
- const theme = useContext(ThemeContext);
512
+ // 2. Parent creates the scope
513
+ const Cart = () => {
514
+ const { items } = useScope(CartScope);
270
515
 
271
516
  return () => html`
272
- <button class="${theme.theme}-theme" @click=${() => theme.toggleTheme()}>
273
- Toggle Theme (Current: ${theme.theme})
274
- </button>
517
+ <div>
518
+ <p>${items().length} item(s) in cart</p>
519
+ ${x(ProductList)}
520
+ ${x(CartSummary)}
521
+ </div>
275
522
  `;
276
- }, {});
523
+ };
524
+
525
+ // 3. Any descendant reads the scope — no props needed
526
+ const ProductList = () => {
527
+ const { add } = getParentScope(CartScope);
528
+
529
+ return () => html`
530
+ <ul>
531
+ <li><button @click=${() => add("Apple")}>Add Apple</button></li>
532
+ <li><button @click=${() => add("Banana")}>Add Banana</button></li>
533
+ </ul>
534
+ `;
535
+ };
536
+
537
+ const CartSummary = () => {
538
+ const { items, remove } = getParentScope(CartScope);
539
+
540
+ return () => html`
541
+ <ul>
542
+ ${repeat(
543
+ items(),
544
+ (item) => item,
545
+ (item) => html`
546
+ <li>${item} <button @click=${() => remove(item)}>✕</button></li>
547
+ `,
548
+ )}
549
+ </ul>
550
+ `;
551
+ };
552
+ ```
553
+
554
+ ### `setup()` — scope initialization
555
+
556
+ Add a `setup()` method to a scope class to run initialization logic (effects, timers, subscriptions, lifecycle hooks) before the host component mounts:
557
+
558
+ ```typescript
559
+ class TimerScope {
560
+ seconds = atom(0);
561
+
562
+ setup() {
563
+ // Lifecycle hooks work inside setup()
564
+ onMount(() => {
565
+ console.log("timer started");
566
+ });
567
+
568
+ onInterval(() => {
569
+ this.seconds.set((n) => n + 1);
570
+ }, 1000);
571
+
572
+ onCleanup(() => {
573
+ console.log("timer stopped");
574
+ });
575
+
576
+ // Effects are auto-disposed with the component
577
+ effect(() => {
578
+ document.title = `${this.seconds()}s elapsed`;
579
+ });
580
+ }
581
+ }
277
582
  ```
278
583
 
279
- ## 🎮 Complete Example
584
+ ---
585
+
586
+ ## Lifecycle Hooks
280
587
 
281
- Here's a complete todo list example that showcases Mates' features:
588
+ Lifecycle hooks must be called in the component's **outer function** (or in a scope's `setup()` method). They are automatically cleaned up when the component unmounts.
282
589
 
283
590
  ```typescript
284
- import { html } from "lit-html";
591
+ import { atom, html, onMount, onCleanup, onPaint } from "mates";
285
592
 
286
- import { view, bubble, atom } from "mates";
593
+ const Timer = () => {
594
+ const seconds = atom(0);
287
595
 
288
- // Create state with a bubble
596
+ // Runs after the component's first render
597
+ onMount(() => {
598
+ const id = setInterval(() => seconds.set((n) => n + 1), 1000);
289
599
 
290
- const todos = bubble((setter) => {
291
- let items = [];
600
+ // Return a cleanup function — called on unmount
601
+ return () => clearInterval(id);
602
+ });
292
603
 
293
- let newTodoText = "";
604
+ // Runs after every browser paint (double-RAF)
605
+ onPaint(() => {
606
+ console.log("painted");
607
+ });
294
608
 
295
- const setNewTodoText = setter((text) => {
296
- newTodoText = text;
609
+ // Explicit cleanup equivalent to returning a fn from onMount
610
+ onCleanup(() => {
611
+ console.log("component removed");
297
612
  });
298
613
 
299
- const addTodo = setter(() => {
300
- if (newTodoText.trim()) {
301
- items.push({ text: newTodoText, completed: false });
614
+ return () => html`<p>Elapsed: ${seconds()}s</p>`;
615
+ };
616
+ ```
617
+
618
+ | Hook | Timing | Notes |
619
+ |------|--------|-------|
620
+ | `onMount(fn)` | After first render | Return a fn to run on cleanup |
621
+ | `onCleanup(fn)` | On unmount | Equivalent to `onMount` cleanup return |
622
+ | `onPaint(fn)` | After browser paint | Double RAF — element is measured |
623
+ | `onAllMount(fn)` | After all children mount | Safe for cross-component reads |
624
+ | `onError(fn)` | On component error | Receives the Error object |
625
+
626
+ ---
627
+
628
+ ## DOM & Window Hooks
302
629
 
303
- newTodoText = "";
630
+ These hooks attach listeners **scoped to the component**. They automatically detach when the component unmounts.
631
+
632
+ ```typescript
633
+ import {
634
+ onDOMReady,
635
+ onKeyDown,
636
+ onWindowResize,
637
+ onVisibilityChange,
638
+ onInterval,
639
+ onTimeout,
640
+ onNavigate,
641
+ onClickAway,
642
+ onFileDrop,
643
+ onScroll,
644
+ onResize,
645
+ onStorageChange,
646
+ onOnline,
647
+ onOffline,
648
+ } from "mates";
649
+
650
+ const SearchBar = () => {
651
+ const query = atom("");
652
+
653
+ // Fires on every keydown anywhere on the page
654
+ onKeyDown((e) => {
655
+ if (e.key === "/" && !e.target.matches("input")) {
656
+ e.preventDefault();
657
+ inputRef.el?.focus();
304
658
  }
305
659
  });
306
660
 
307
- const toggleTodo = setter((index) => {
308
- items[index].completed = !items[index].completed;
661
+ // Fires when the browser window is resized
662
+ onWindowResize((e) => {
663
+ console.log("resized", window.innerWidth);
309
664
  });
310
665
 
311
- const deleteTodo = setter((index) => {
312
- items.splice(index, 1);
666
+ // Fires when the tab is hidden or shown
667
+ onVisibilityChange((hidden) => {
668
+ if (hidden) pauseSearch();
313
669
  });
314
670
 
315
- return () => ({
316
- items,
671
+ // setInterval auto-cleared on unmount
672
+ onInterval(() => {
673
+ refreshResults();
674
+ }, 30_000);
317
675
 
318
- newTodoText,
676
+ // setTimeout — auto-cleared on unmount
677
+ onTimeout(() => {
678
+ showTip();
679
+ }, 2_000);
680
+
681
+ // Fires on every pathAtom change (client-side navigation)
682
+ onNavigate((path) => {
683
+ console.log("navigated to", path);
684
+ });
319
685
 
320
- setNewTodoText,
686
+ // Fires when a click happens outside the component's host element
687
+ onClickAway(() => {
688
+ closeDropdown();
689
+ });
690
+
691
+ // Fires when files are dragged + dropped onto the window
692
+ onFileDrop((files) => {
693
+ handleUpload(files);
694
+ });
321
695
 
322
- addTodo,
696
+ // Network status
697
+ onOnline(() => syncPendingChanges());
698
+ onOffline(() => showOfflineBanner());
699
+
700
+ return () => html`...`;
701
+ };
702
+ ```
703
+
704
+ | Hook | Trigger |
705
+ |------|---------|
706
+ | `onWindow(event, fn)` | Any `window` event |
707
+ | `onWindowScroll(fn)` | Window scroll |
708
+ | `onWindowResize(fn)` | Window resize |
709
+ | `onKeyDown(fn)` | Global `keydown` |
710
+ | `onKeyUp(fn)` | Global `keyup` |
711
+ | `onVisibilityChange(fn)` | Tab show/hide, receives `hidden: boolean` |
712
+ | `onClickAway(fn)` | Click outside host element |
713
+ | `onFileDrop(fn)` | Window file drop |
714
+ | `onScroll(fn, target?)` | Scroll on window or a specific element |
715
+ | `onResize(fn, target?)` | `ResizeObserver` on host element or a specific element |
716
+ | `onNavigate(fn)` | Path changes |
717
+ | `onInterval(fn, ms)` | Repeating timer |
718
+ | `onTimeout(fn, ms)` | One-shot timer |
719
+ | `onOnline(fn)` | Browser goes online |
720
+ | `onOffline(fn)` | Browser goes offline |
721
+ | `onStorageChange(fn)` | Cross-tab `localStorage` changes |
722
+ | `onPaste(fn)` | Clipboard paste |
723
+ | `onCopy(fn)` | Clipboard copy |
724
+ | `onCut(fn)` | Clipboard cut |
725
+ | `onSelectionChange(fn)` | Text selection changes |
726
+ | `onSocket(fn, sockets)` | WebSocket messages (see WebSocket section) |
727
+ | `onUpdate(fn)` | Every re-render of the component |
728
+
729
+ ---
730
+
731
+ ## Async Actions
732
+
733
+ ### `asyncAction`
734
+
735
+ `asyncAction` wraps an async function and gives it a complete **loading / error / data state machine** with atoms, automatic cancellation of stale calls, caching, polling, and interceptors — all built in.
736
+
737
+ ```typescript
738
+ import { asyncAction, html, x, renderX } from "mates";
739
+ import type { Props } from "mates";
323
740
 
324
- toggleTodo,
741
+ const fetchUser = asyncAction(async (id: number) => {
742
+ const res = await fetch(`/api/users/${id}`);
743
+ if (!res.ok) throw new Error("Not found");
744
+ return res.json() as Promise<{ id: number; name: string; email: string }>;
745
+ });
325
746
 
326
- deleteTodo,
747
+ const UserCard = (propsFn: Props<{ userId: number }>) => {
748
+ // Load data when the component mounts
749
+ onMount(() => {
750
+ fetchUser(propsFn().userId);
327
751
  });
752
+
753
+ return () => html`
754
+ <div class="card">
755
+ ${fetchUser.isLoading()
756
+ ? html`<p>Loading…</p>`
757
+ : fetchUser.error()
758
+ ? html`<p class="error">${fetchUser.error()!.message}</p>`
759
+ : fetchUser.data()
760
+ ? html`
761
+ <h2>${fetchUser.data()!.name}</h2>
762
+ <p>${fetchUser.data()!.email}</p>
763
+ `
764
+ : html`<p>No user loaded</p>`}
765
+ <button @click=${() => fetchUser(propsFn().userId)}>Reload</button>
766
+ </div>
767
+ `;
768
+ };
769
+ ```
770
+
771
+ **State atoms on every `asyncAction`:**
772
+
773
+ | Atom | Type | Description |
774
+ |------|------|-------------|
775
+ | `.data` | `AtomType<T \| null>` | The resolved value |
776
+ | `.error` | `AtomType<Error \| null>` | The rejection error |
777
+ | `.isLoading` | `AtomType<boolean>` | `true` while in flight |
778
+ | `.status` | `AtomType<"init" \| "loading" \| "success" \| "error">` | Full state machine status |
779
+
780
+ **Methods on every `asyncAction`:**
781
+
782
+ | Method | Description |
783
+ |--------|-------------|
784
+ | `.cancel()` | Abort the current in-flight call |
785
+ | `.cache(...args)` | Call and cache result by args (LRU, configurable size + TTL) |
786
+ | `.clearCache(...args)` | Evict specific cached entries |
787
+ | `.startPolling(...args)` | Begin polling the function on a fixed interval |
788
+ | `.stopPolling()` | Stop polling |
789
+ | `.subscribe(fn)` | Subscribe to completion events |
790
+ | `.interceptBefore(fn)` | Middleware — transform args before the function runs |
791
+ | `.interceptAfter(fn)` | Transform the resolved value before it hits `.data` |
792
+
793
+ **Options:**
794
+
795
+ ```typescript
796
+ const fetchData = asyncAction(myFn, {
797
+ cacheLimit: 20, // Max LRU entries (default: 10)
798
+ cacheDuration: 60_000, // Cache TTL in ms (default: infinite)
799
+ pollInterval: 5_000, // Polling interval in ms (default: 5000)
328
800
  });
801
+ ```
329
802
 
330
- // Create a view for the todo app
803
+ **Stale-call cancellation is automatic.** If you call the action a second time before the first resolves, the first call is aborted and its result is discarded. Only the latest call's data is ever written to `.data`.
331
804
 
332
- const TodoApp = view(() => {
333
- return () => {
334
- const {
335
- items,
805
+ ---
336
806
 
337
- newTodoText,
807
+ ### `action` — synchronous action
338
808
 
339
- setNewTodoText,
809
+ `action` wraps a synchronous function with subscriber notifications, `interceptBefore`/`interceptAfter` middleware, and hot-swappable implementation.
340
810
 
341
- addTodo,
811
+ ```typescript
812
+ import { action } from "mates";
342
813
 
343
- toggleTodo,
814
+ const addToCart = action((productId: string, quantity: number) => {
815
+ cart.update((c) => { c[productId] = (c[productId] ?? 0) + quantity; });
816
+ return { productId, quantity };
817
+ });
344
818
 
345
- deleteTodo,
346
- } = todos();
819
+ // Subscribe to every call
820
+ addToCart.__subscribe((result) => {
821
+ console.log("Added:", result);
822
+ analytics.track("add_to_cart", result);
823
+ });
347
824
 
348
- return html`
349
- <div class="todo-app">
350
- <h1>Todo List</h1>
351
-
352
- <div class="add-todo">
353
- <input
354
- value=${newTodoText}
355
- @input=${(e) => setNewTodoText(e.target.value)}
356
- @keypress=${(e) => e.key === "Enter" && addTodo()}
357
- placeholder="Add new todo"
358
- />
359
-
360
- <button @click=${addTodo}>Add</button>
361
- </div>
362
-
363
- <ul class="todo-list">
364
- ${items.map(
365
- (item, index) => html`
366
- <li class=${item.completed ? "completed" : ""}>
367
- <input
368
- type="checkbox"
369
- .checked=${item.completed}
370
- @change=${() => toggleTodo(index)}
371
- />
372
-
373
- <span>${item.text}</span>
374
-
375
- <button @click=${() => deleteTodo(index)}>Delete</button>
376
- </li>
377
- `
378
- )}
379
- </ul>
380
-
381
- <div class="todo-stats">
382
- <p>${items.filter((item) => !item.completed).length} items left</p>
383
- </div>
825
+ // Add middleware
826
+ addToCart.interceptBefore((next, productId, quantity) => {
827
+ if (quantity < 1) throw new Error("Quantity must be positive");
828
+ return next(productId, quantity);
829
+ });
830
+
831
+ addToCart("prod_123", 2);
832
+ ```
833
+
834
+ ---
835
+
836
+ ### `paginatedAsyncAction`
837
+
838
+ `paginatedAsyncAction` extends `asyncAction` with a built-in `page` atom and a `next()` helper.
839
+
840
+ ```typescript
841
+ import { paginatedAsyncAction } from "mates";
842
+
843
+ const fetchPosts = paginatedAsyncAction(async () => {
844
+ const page = fetchPosts.page();
845
+ const res = await fetch(`/api/posts?page=${page}&limit=10`);
846
+ return res.json() as Promise<Post[]>;
847
+ });
848
+
849
+ // Load first page
850
+ fetchPosts();
851
+
852
+ // Load next page
853
+ fetchPosts.next();
854
+
855
+ // Jump to page
856
+ fetchPosts.page.set(3);
857
+ fetchPosts();
858
+ ```
859
+
860
+ Extra atoms: `fetchPosts.page` (`AtomType<number>`), `fetchPosts.totalPages` (`AtomType<number>`).
861
+
862
+ ---
863
+
864
+ ### `taskAction`
865
+
866
+ `taskAction` queues async tasks and runs them one at a time, tracking the running status of each individual task.
867
+
868
+ ```typescript
869
+ import { taskAction } from "mates";
870
+
871
+ const processFile = taskAction(async (file: File) => {
872
+ const result = await uploadFile(file);
873
+ return result.url;
874
+ });
875
+
876
+ // Queue multiple files — they run serially
877
+ processFile(file1);
878
+ processFile(file2);
879
+ processFile(file3);
880
+
881
+ // Check overall status
882
+ processFile.status(); // "loading" | "success" | "error" | "init"
883
+ processFile.data(); // result of the last completed task
884
+ ```
885
+
886
+ ---
887
+
888
+ ## HTTP Client
889
+
890
+ Mates includes a first-party HTTP client with interceptors, URL template substitution, automatic JSON serialization, SSR support, and `asyncAction` integration.
891
+
892
+ ### Basic usage
893
+
894
+ ```typescript
895
+ import { Fetch, Get, Post, Put, Patch, Delete } from "mates";
896
+
897
+ // GET with query params
898
+ const users = await Get({ url: "/api/users", params: { page: 1, limit: 10 } });
899
+
900
+ // POST with a JSON body
901
+ const created = await Post({
902
+ url: "/api/users",
903
+ body: { name: "Alice", email: "alice@example.com" },
904
+ });
905
+
906
+ // URL template substitution — :id is replaced, extra params become query string
907
+ const user = await Get({ url: "/api/users/:id", params: { id: 42, include: "posts" } });
908
+ // → GET /api/users/42?include=posts
909
+
910
+ // Shorthand — pass just a URL string
911
+ const data = await Fetch("/api/health");
912
+ ```
913
+
914
+ ### FetchClient — per-instance configuration
915
+
916
+ Create a dedicated client for each API with a shared base config and per-instance interceptors:
917
+
918
+ ```typescript
919
+ import { FetchClient } from "mates";
920
+
921
+ const api = new FetchClient({
922
+ host: "https://api.example.com",
923
+ headers: { "Accept": "application/json" },
924
+ });
925
+
926
+ // Add auth to every request
927
+ api.interceptBefore((url, opts) => ({
928
+ url,
929
+ options: {
930
+ ...opts,
931
+ headers: { ...opts.headers, Authorization: `Bearer ${getToken()}` },
932
+ },
933
+ }));
934
+
935
+ // Log every error
936
+ api.interceptError((error) => {
937
+ logger.error(error);
938
+ });
939
+
940
+ const users = await api.Get({ url: "/users" });
941
+ const me = await api.Get({ url: "/users/me" });
942
+ ```
943
+
944
+ ### `fetchAction` / HTTP action shortcuts
945
+
946
+ Combine the HTTP client with `asyncAction` in one line:
947
+
948
+ ```typescript
949
+ import { getAction, postAction, deleteAction } from "mates";
950
+
951
+ // getAction creates an asyncAction that calls GET
952
+ const loadUsers = getAction({ url: "/api/users" });
953
+ loadUsers();
954
+
955
+ // postAction with dynamic body
956
+ const createUser = postAction<User>();
957
+ createUser.interceptBefore((next) => next({ url: "/api/users", body: form() }));
958
+ createUser();
959
+
960
+ // Per-client action
961
+ const api = new FetchClient({ host: "https://api.example.com" });
962
+ const loadProfile = api.getAction({ url: "/users/me" });
963
+ loadProfile();
964
+ ```
965
+
966
+ ---
967
+
968
+ ## Routing
969
+
970
+ ### Navigation
971
+
972
+ ```typescript
973
+ import { navigateTo, pathAtom, qsAtom, hashAtom, location } from "mates";
974
+
975
+ // Push a new history entry
976
+ navigateTo("/about");
977
+
978
+ // Replace current history entry (no back button entry)
979
+ navigateTo("/login", true);
980
+
981
+ // With history state data
982
+ navigateTo("/checkout", false, { step: 2 });
983
+
984
+ // Read current location reactively
985
+ pathAtom(); // "/about"
986
+ qsAtom(); // { q: "search term" } — parsed query string
987
+ hashAtom(); // "#section-2"
988
+ location.pathname; // "/about"
989
+
990
+ // Update query string (replaces history state)
991
+ qsAtom.set({ q: "mates framework", page: 2 });
992
+
993
+ // Navigation lock — prevents navigation (e.g. unsaved changes)
994
+ lockNavigation();
995
+ unlockNavigation();
996
+ navigationLocked(); // true/false — reactive
997
+ ```
998
+
999
+ ### `Router` — declarative route table
1000
+
1001
+ ```typescript
1002
+ import { Router, navigateTo, html, renderX } from "mates";
1003
+
1004
+ // Define components
1005
+ const HomePage = () => () => html`<h1>Home</h1>`;
1006
+ const AboutPage = () => () => html`<h1>About</h1>`;
1007
+ const UserPage = (p: Props<{}>) => () => html`<h1>User</h1>`;
1008
+ const NotFound = () => () => html`<h1>404 — Not Found</h1>`;
1009
+
1010
+ // Create the router
1011
+ const appRouter = Router([
1012
+ { path: "/", component: HomePage },
1013
+ { path: "/about", component: AboutPage },
1014
+ { path: "/users/:id", component: UserPage },
1015
+ // Lazy-loaded route — code-split automatically
1016
+ { path: "/dashboard", component: async () => import("./Dashboard") },
1017
+ ], NotFound);
1018
+
1019
+ // Mount
1020
+ const App = () => () => html`
1021
+ <nav>
1022
+ <a @click=${() => navigateTo("/")}>Home</a>
1023
+ <a @click=${() => navigateTo("/about")}>About</a>
1024
+ </nav>
1025
+ <main>${appRouter()}</main>
1026
+ `;
1027
+
1028
+ renderX(App, document.getElementById("app")!);
1029
+ ```
1030
+
1031
+ ### `route` — inline conditional routing
1032
+
1033
+ For simpler scenarios, use `route` directly in a template:
1034
+
1035
+ ```typescript
1036
+ import { route, navigateTo, html } from "mates";
1037
+
1038
+ const App = () => () => html`
1039
+ ${route("/", { view: HomePage })}
1040
+ ${route("/about", { view: AboutPage })}
1041
+ ${route("/users/:id", { view: UserPage })}
1042
+ `;
1043
+ ```
1044
+
1045
+ ### `animatedRouter` — page transitions
1046
+
1047
+ ```typescript
1048
+ import { animatedRouter, fadeInPreset, fadeOutPreset } from "mates";
1049
+
1050
+ const router = animatedRouter(routes, {
1051
+ enter: fadeInPreset,
1052
+ exit: fadeOutPreset,
1053
+ scrollToTop: true,
1054
+ });
1055
+ ```
1056
+
1057
+ ### `isPathMatching` — pattern testing
1058
+
1059
+ ```typescript
1060
+ import { isPathMatching } from "mates";
1061
+
1062
+ isPathMatching("/"); // true when path is exactly "/"
1063
+ isPathMatching("/users/:id"); // true for "/users/42", "/users/abc", etc.
1064
+ isPathMatching("/posts/:id"); // false when on "/users/42"
1065
+ ```
1066
+
1067
+ ### `buildPath` — fill URL templates
1068
+
1069
+ ```typescript
1070
+ import { buildPath } from "mates";
1071
+
1072
+ buildPath("/users/:id/posts/:postId", { id: 42, postId: 7, highlight: true });
1073
+ // → "/users/42/posts/7?highlight=true"
1074
+ ```
1075
+
1076
+ ---
1077
+
1078
+ ## CSS-in-JS & Theming
1079
+
1080
+ ### Scoped stylesheets with `stylesheet`
1081
+
1082
+ `stylesheet()` creates a **scoped** CSS-in-JS instance. Each call generates unique class names so styles from different components never collide. Call it at module level, then call `mount()` inside the component.
1083
+
1084
+ ```typescript
1085
+ import { stylesheet, html, renderX } from "mates";
1086
+
1087
+ // Module-level — created once
1088
+ const { css, mount, keyframes } = stylesheet();
1089
+
1090
+ // Define an animation
1091
+ const spin = keyframes("spin", {
1092
+ from: { transform: "rotate(0deg)" },
1093
+ to: { transform: "rotate(360deg)" },
1094
+ });
1095
+
1096
+ // Define styles
1097
+ const cl = css({
1098
+ card: {
1099
+ display: "flex",
1100
+ flexDirection: "column",
1101
+ padding: "1.5rem",
1102
+ borderRadius: "12px",
1103
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
1104
+ "&:hover": { boxShadow: "0 4px 16px rgba(0,0,0,0.15)" },
1105
+ "md": { padding: "2rem" }, // breakpoint shorthand
1106
+ },
1107
+ title: { fontSize: "1.25rem", fontWeight: "600" },
1108
+ loader: { animation: `${spin} 1s linear infinite` },
1109
+ });
1110
+
1111
+ const Card = () => {
1112
+ mount(); // injects styles when component mounts, removes on unmount
1113
+
1114
+ return () => html`
1115
+ <div class="${cl.card}">
1116
+ <h2 class="${cl.title}">Hello</h2>
1117
+ </div>
1118
+ `;
1119
+ };
1120
+ ```
1121
+
1122
+ **Supported nested keys inside a block:**
1123
+
1124
+ | Key pattern | Compiled to |
1125
+ |-------------|-------------|
1126
+ | `"&:hover"` | `.block:hover { … }` |
1127
+ | `"&::before"` | `.block::before { … }` |
1128
+ | `"&[disabled]"` | `.block[disabled] { … }` |
1129
+ | `"sm"`, `"md"`, `"lg"`, `"xl"`, `"2xl"` | `@media (min-width: …) { … }` |
1130
+ | `"@media (…)"` | `@media (…) { … }` |
1131
+
1132
+ Configure custom breakpoints globally:
1133
+
1134
+ ```typescript
1135
+ import { configureCSS } from "mates";
1136
+
1137
+ configureCSS({ breakpoints: { tablet: "900px", desktop: "1200px" } });
1138
+ ```
1139
+
1140
+ ### Global styles with `globalCSS`
1141
+
1142
+ ```typescript
1143
+ import { globalCSS } from "mates";
1144
+
1145
+ // Singleton — injected once into <head>
1146
+ const g = globalCSS({
1147
+ body: { margin: "0", fontFamily: "'Inter', sans-serif" },
1148
+ "*, *::before, *::after": { boxSizing: "border-box" },
1149
+ a: { color: "inherit", textDecoration: "none" },
1150
+ });
1151
+ ```
1152
+
1153
+ ### Design tokens and theming with `globalTheme`
1154
+
1155
+ ```typescript
1156
+ import { globalTheme } from "mates";
1157
+
1158
+ const { cssVars, themeAtom } = globalTheme({
1159
+ light: {
1160
+ primary: "#3b82f6",
1161
+ background: "#ffffff",
1162
+ surface: "#f8fafc",
1163
+ text: "#111827",
1164
+ border: "#e5e7eb",
1165
+ },
1166
+ dark: {
1167
+ primary: "#60a5fa",
1168
+ background: "#0f172a",
1169
+ surface: "#1e293b",
1170
+ text: "#f1f5f9",
1171
+ border: "#334155",
1172
+ },
1173
+ });
1174
+
1175
+ // cssVars maps token names to CSS custom property strings
1176
+ // cssVars.primary → "--primary" cssVars.background → "--background"
1177
+
1178
+ // Use in stylesheet
1179
+ const cl = css({
1180
+ button: {
1181
+ backgroundColor: `var(${cssVars.primary})`,
1182
+ color: `var(${cssVars.background})`,
1183
+ },
1184
+ });
1185
+
1186
+ // Switch theme reactively
1187
+ themeAtom.set("dark"); // adds data-theme="dark" to <html>
1188
+ themeAtom.set("auto"); // uses OS preference via prefers-color-scheme
1189
+ themeAtom.set("light"); // explicit light
1190
+ ```
1191
+
1192
+ `globalTheme` injects:
1193
+ - `:root { ... }` — first theme as base variables
1194
+ - `[data-theme="name"] { ... }` — each theme explicitly
1195
+ - `@media (prefers-color-scheme: dark) { :root:not([data-theme]) { ... } }` — OS auto mode
1196
+
1197
+ ### `cl` helper — conditional class names
1198
+
1199
+ ```typescript
1200
+ import { cl } from "mates";
1201
+
1202
+ // Merge conditional class names into a single string
1203
+ const classes = cl(
1204
+ styles.btn,
1205
+ isActive && styles.active,
1206
+ isLoading && styles.loading,
1207
+ );
1208
+ ```
1209
+
1210
+ ---
1211
+
1212
+ ## Directives
1213
+
1214
+ Directives are attribute and child-position bindings for `lit-html` templates.
1215
+
1216
+ ### DOM manipulation directives
1217
+
1218
+ ```typescript
1219
+ import { attr, style, classes } from "mates";
1220
+
1221
+ html`
1222
+ <!-- Set / remove attributes declaratively -->
1223
+ <div ${attr({ id: "main", "aria-label": "content", disabled: isDisabled })}>
1224
+
1225
+ <!-- Apply inline styles -->
1226
+ <div ${style({ color: "red", display: isVisible ? "block" : "none" })}>
1227
+
1228
+ <!-- Conditional class management -->
1229
+ <button ${classes([
1230
+ "btn",
1231
+ [isPrimary, "btn-primary", "btn-secondary"], // ternary tuple
1232
+ [isLoading, "btn-loading"], // conditional tuple
1233
+ isDisabled && "btn-disabled", // falsy-safe expression
1234
+ ])}>
1235
+ `
1236
+ ```
1237
+
1238
+ ### Lifecycle directives
1239
+
1240
+ ```typescript
1241
+ import { onConnect, onDisconnect, onUpdate, onIntersect, onVisible, lazyLoad } from "mates";
1242
+
1243
+ html`
1244
+ <!-- React to element entering/leaving the DOM -->
1245
+ <div ${onConnect((el) => console.log("connected", el))}>
1246
+ <div ${onDisconnect(() => cleanup())}>
1247
+
1248
+ <!-- Re-run on every re-render of this node -->
1249
+ <canvas ${onUpdate((el) => drawChart(el))}>
1250
+
1251
+ <!-- IntersectionObserver -->
1252
+ <section ${onIntersect({
1253
+ onVisible: (el) => el.classList.add("visible"),
1254
+ onHidden: (el) => el.classList.remove("visible"),
1255
+ rootMargin: "0px 0px -100px 0px",
1256
+ })}>
1257
+
1258
+ <!-- Lazy load when scrolled into view -->
1259
+ <img ${lazyLoad((el) => {
1260
+ (el as HTMLImageElement).src = el.dataset.src!;
1261
+ })} data-src="/images/photo.jpg" />
1262
+ `
1263
+ ```
1264
+
1265
+ ### Rendering helpers
1266
+
1267
+ ```typescript
1268
+ import { renderSwitch, animatedIf } from "mates";
1269
+
1270
+ // Render first matching case
1271
+ html`${renderSwitch([
1272
+ [status() === "loading", html`<spinner-el></spinner-el>`],
1273
+ [status() === "error", html`<error-msg .msg=${error()}></error-msg>`],
1274
+ [status() === "success", html`<user-card .user=${data()}></user-card>`],
1275
+ html`<p>Idle</p>`, // default fallback
1276
+ ])}`
1277
+
1278
+ // Conditional rendering with animated transitions
1279
+ html`${animatedIf(
1280
+ isOpen(),
1281
+ () => html`<div class="panel">...</div>`,
1282
+ undefined,
1283
+ { enter: fadeInPreset, exit: fadeOutPreset },
1284
+ )}`
1285
+ ```
1286
+
1287
+ ### Event directives
1288
+
1289
+ ```typescript
1290
+ import { on } from "mates";
1291
+
1292
+ html`
1293
+ <!-- Declarative event map — cleaned up automatically -->
1294
+ <div ${on({ click: handleClick, mouseover: handleHover })}>
1295
+ `
1296
+ ```
1297
+
1298
+ ### `eleHook` / `htmlHook` — custom directives
1299
+
1300
+ Build your own reusable directives using the low-level hooks:
1301
+
1302
+ ```typescript
1303
+ import { eleHook, htmlHook } from "mates";
1304
+
1305
+ // eleHook — bound to a real element
1306
+ const autoFocus = eleHook(($) => {
1307
+ ($.el as HTMLElement).focus();
1308
+
1309
+ return {
1310
+ onCleanup() { /* cleanup */ },
1311
+ };
1312
+ });
1313
+
1314
+ html`<input ${autoFocus()} />`
1315
+
1316
+ // htmlHook — for inline content slots (no element required)
1317
+ const liveTime = htmlHook((render) => {
1318
+ const tick = () => render(html`<span>${new Date().toLocaleTimeString()}</span>`);
1319
+ tick();
1320
+ const id = setInterval(tick, 1000);
1321
+ return { onCleanup: () => clearInterval(id) };
1322
+ });
1323
+
1324
+ html`<p>Current time: ${liveTime()}</p>`
1325
+ ```
1326
+
1327
+ ### `$` — fluent DOM chain
1328
+
1329
+ Imperatively manipulate elements inside hooks and lifecycle callbacks:
1330
+
1331
+ ```typescript
1332
+ import { $ } from "mates";
1333
+
1334
+ $(el)
1335
+ .attr({ "data-active": "true", "aria-expanded": "false" })
1336
+ .style({ color: "red", transform: `translateX(${offset}px)` })
1337
+ .classes(["base", [isActive, "active"], [isError, "error", "ok"]])
1338
+ .on("click", handleClick)
1339
+ .focus()
1340
+ .scroll({ top: 0, behavior: "smooth" });
1341
+ ```
1342
+
1343
+ ---
1344
+
1345
+ ## Portals, Dialogs & Tooltips
1346
+
1347
+ Render content **outside** the normal DOM tree — useful for modals, toasts, context menus, and tooltips that must escape `overflow: hidden` or `transform` stacking contexts.
1348
+
1349
+ ### `portal` — fixed-position overlay
1350
+
1351
+ ```typescript
1352
+ import { portal, html } from "mates";
1353
+
1354
+ html`
1355
+ ${isToastVisible() && portal(
1356
+ html`<div class="toast">Saved ✓</div>`,
1357
+ { style: { bottom: "16px", right: "16px", position: "fixed" } },
1358
+ )}
1359
+ `
1360
+ ```
1361
+
1362
+ ### `dialog` — modal with backdrop
1363
+
1364
+ ```typescript
1365
+ import { dialog, html } from "mates";
1366
+
1367
+ html`
1368
+ ${isOpen() && dialog(
1369
+ html`
1370
+ <div class="modal">
1371
+ <h2>Confirm Delete</h2>
1372
+ <p>This action cannot be undone.</p>
1373
+ <button @click=${() => isOpen.set(false)}>Cancel</button>
1374
+ <button @click=${doDelete}>Delete</button>
384
1375
  </div>
385
- `;
1376
+ `,
1377
+ {
1378
+ onBackdropClick: () => isOpen.set(false),
1379
+ style: { backdropColor: "rgba(0,0,0,0.6)" },
1380
+ dialogStyle: { borderRadius: "16px", padding: "2rem", maxWidth: "480px" },
1381
+ },
1382
+ )}
1383
+ `
1384
+ ```
1385
+
1386
+ `dialog` automatically prevents body scroll while open and restores it exactly on close, even with nested dialogs.
1387
+
1388
+ ### `tooltip` / `tip`
1389
+
1390
+ ```typescript
1391
+ import { tooltip, html } from "mates";
1392
+
1393
+ html`
1394
+ <!-- Plain string tip -->
1395
+ <button ${tooltip("Save changes (Ctrl+S)")}>Save</button>
1396
+
1397
+ <!-- Rich HTML tip -->
1398
+ <button ${tooltip(html`Press <kbd>Ctrl</kbd> + <kbd>S</kbd>`)}>Save</button>
1399
+
1400
+ <!-- Custom style -->
1401
+ <span ${tooltip("Long description", { maxWidth: "280px", placement: "bottom" })}>
1402
+ Hover me
1403
+ </span>
1404
+ `
1405
+ ```
1406
+
1407
+ Tooltips auto-position above/below based on available viewport space. They use a singleton overlay element on `<body>` — no portal proliferation.
1408
+
1409
+ ---
1410
+
1411
+ ## Animations
1412
+
1413
+ Mates provides a first-party animation system built on the Web Animations API (WAAPI).
1414
+
1415
+ ```typescript
1416
+ import {
1417
+ animate,
1418
+ animateDirective,
1419
+ fadeInPreset,
1420
+ fadeOutPreset,
1421
+ slideInPreset,
1422
+ slideOutPreset,
1423
+ scaleInPreset,
1424
+ bouncePreset,
1425
+ springInPreset,
1426
+ withStaggerPreset,
1427
+ } from "mates";
1428
+
1429
+ // Imperative — animate an element directly
1430
+ animate(el, fadeInPreset);
1431
+ animate(el, { keyframes: [{ opacity: 0 }, { opacity: 1 }], duration: 300 });
1432
+
1433
+ // Directive — declaratively apply to a template element
1434
+ html`
1435
+ <div ${animateDirective({ enter: fadeInPreset, exit: fadeOutPreset })}>
1436
+ Content
1437
+ </div>
1438
+ `
1439
+
1440
+ // Stagger children
1441
+ html`
1442
+ <ul ${animateDirective(withStaggerPreset(fadeInPreset, { stagger: 50 }))}>
1443
+ ${items.map((i) => html`<li>${i}</li>`)}
1444
+ </ul>
1445
+ `
1446
+ ```
1447
+
1448
+ **Built-in animation presets:**
1449
+
1450
+ | Preset | Effect |
1451
+ |--------|--------|
1452
+ | `fadeInPreset` / `fadeOutPreset` | Opacity fade |
1453
+ | `slideInPreset` / `slideOutPreset` | Slide from edge |
1454
+ | `scaleInPreset` / `scaleOutPreset` | Scale from center |
1455
+ | `blurInPreset` / `blurOutPreset` | Blur + fade |
1456
+ | `flipInPreset` / `flipOutPreset` | 3D flip |
1457
+ | `bouncePreset` | Elastic bounce |
1458
+ | `pulsePreset` | Heartbeat pulse |
1459
+ | `shakePreset` | Horizontal shake |
1460
+ | `spinPreset` | Full rotation |
1461
+ | `springInPreset` | Spring physics enter |
1462
+ | `withStaggerPreset` | Wrap any preset with stagger |
1463
+
1464
+ ---
1465
+
1466
+ ## WebSocket
1467
+
1468
+ ```typescript
1469
+ import { ws, onSocket, html } from "mates";
1470
+
1471
+ type ChatMessage = { user: string; text: string; ts: number };
1472
+
1473
+ const Chat = () => {
1474
+ const messages = atom<ChatMessage[]>([]);
1475
+ const input = atom("");
1476
+
1477
+ // Create connection — auto-reconnects, auto-cleanup on unmount
1478
+ const socket = ws<ChatMessage>("wss://api.example.com/chat", {
1479
+ reconnect: true,
1480
+ reconnectDelay: 1_000,
1481
+ reconnectMaxDelay: 30_000,
1482
+ auth: () => ({ token: authToken() }), // refreshed on every reconnect
1483
+ });
1484
+
1485
+ // Subscribe to incoming messages
1486
+ onSocket((msg) => {
1487
+ messages.update((list) => { list.push(msg); });
1488
+ }, [socket]);
1489
+
1490
+ const send = () => {
1491
+ socket.send({ user: "me", text: input(), ts: Date.now() });
1492
+ input.set("");
386
1493
  };
387
- }, {});
388
1494
 
389
- // Mount the app
1495
+ return () => html`
1496
+ <div class="chat">
1497
+ <p>Status: ${socket.status()}</p>
1498
+ <ul>
1499
+ ${messages().map((m) => html`<li><b>${m.user}:</b> ${m.text}</li>`)}
1500
+ </ul>
1501
+ <input .value=${input()} @input=${(e: InputEvent) =>
1502
+ input.set((e.target as HTMLInputElement).value)} />
1503
+ <button @click=${send}>Send</button>
1504
+ </div>
1505
+ `;
1506
+ };
1507
+ ```
1508
+
1509
+ | Option | Default | Description |
1510
+ |--------|---------|-------------|
1511
+ | `reconnect` | `true` | Auto-reconnect on close/error |
1512
+ | `reconnectDelay` | `1000` | Initial delay in ms (doubles each attempt) |
1513
+ | `reconnectMaxDelay` | `30000` | Exponential backoff cap |
1514
+ | `reconnectMaxAttempts` | `Infinity` | Max attempts before giving up |
1515
+ | `auth` | — | `() => Record<string, string>` — query params added on connect |
1516
+ | `autoConnect` | `true` | Set to `false` to defer until `.connect()` is called manually |
1517
+
1518
+ ---
390
1519
 
391
- document.body.appendChild(TodoApp);
1520
+ ## Virtualization
1521
+
1522
+ Render large lists and grids efficiently by only mounting visible items:
1523
+
1524
+ ```typescript
1525
+ import { virtualList, virtualGrid, virtualMasonry, masonryGrid } from "mates";
1526
+
1527
+ // Virtualized list — only renders visible rows
1528
+ html`${virtualList({
1529
+ items: bigArray,
1530
+ itemHeight: 60,
1531
+ renderItem: (item, index) => html`
1532
+ <div class="row">${index}: ${item.name}</div>
1533
+ `,
1534
+ })}`
1535
+
1536
+ // Virtualized grid — only renders visible cells
1537
+ html`${virtualGrid({
1538
+ items: products,
1539
+ columns: 4,
1540
+ rowHeight: 240,
1541
+ renderItem: (product) => html`
1542
+ <div class="product-card">
1543
+ <img src=${product.image} />
1544
+ <p>${product.name}</p>
1545
+ </div>
1546
+ `,
1547
+ })}`
1548
+
1549
+ // Masonry layout (non-virtualized)
1550
+ html`${masonryGrid({
1551
+ items: photos,
1552
+ columns: 3,
1553
+ gap: 16,
1554
+ renderItem: (photo) => html`<img src=${photo.url} />`,
1555
+ })}`
1556
+ ```
1557
+
1558
+ ---
1559
+
1560
+ ## DevTools
1561
+
1562
+ Mates DevTools is a companion browser extension. When installed, it provides component tree inspection, atom state history, time-travel debugging, and re-render tracking — all without any code changes.
1563
+
1564
+ To wire up custom DevTools integration at runtime:
1565
+
1566
+ ```typescript
1567
+ import { installDevToolsHooks, isDevToolsInstalled } from "mates";
1568
+
1569
+ if (!isDevToolsInstalled()) {
1570
+ installDevToolsHooks({
1571
+ onAtomCreate: (atom) => { /* ... */ },
1572
+ onAtomSet: (atom, prev, next) => { /* ... */ },
1573
+ onRender: (component) => { /* ... */ },
1574
+ });
1575
+ }
392
1576
  ```
393
1577
 
394
- ## 🔄 Why Mates?
1578
+ ---
395
1579
 
396
- Mates gives you the power and simplicity of React hooks without the React! As a complete framework, it's perfect for:
1580
+ ## TypeScript
397
1581
 
398
- - Building lightweight web apps without other heavy frameworks
1582
+ All APIs are fully typed. The most common types you'll import:
399
1583
 
400
- - Adding reactivity to existing applications
1584
+ ```typescript
1585
+ import type {
1586
+ Props, // propsFn type: () => T
1587
+ Component, // outer fn → inner fn (closure component)
1588
+ TemplateFn, // fn → TemplateResult
1589
+ AtomType, // atom return type: AtomType<T>
1590
+ IAtomType, // iAtom return type
1591
+ AsyncActionReturnType, // asyncAction return type
1592
+ ActionReturnType, // action return type
1593
+ CSSBlock, // css-in-js block type
1594
+ CSSRulesInput, // full css() rules object type
1595
+ WsConfig, // WebSocket config
1596
+ WsConnection, // WebSocket connection handle
1597
+ } from "mates";
1598
+ ```
1599
+
1600
+ **Typed component example:**
1601
+
1602
+ ```typescript
1603
+ import type { Props, Component } from "mates";
1604
+
1605
+ interface ButtonProps {
1606
+ label: string;
1607
+ onClick: () => void;
1608
+ variant?: "primary" | "ghost" | "danger";
1609
+ disabled?: boolean;
1610
+ }
401
1611
 
402
- - Creating reusable, reactive components
1612
+ const Button: Component<ButtonProps> = (propsFn) => {
1613
+ return () => {
1614
+ const { label, onClick, variant = "primary", disabled = false } = propsFn();
1615
+ return html`
1616
+ <button
1617
+ class="btn btn--${variant}"
1618
+ ?disabled=${disabled}
1619
+ @click=${onClick}
1620
+ >${label}</button>
1621
+ `;
1622
+ };
1623
+ };
1624
+ ```
403
1625
 
404
- - Prototyping ideas quickly
1626
+ ---
1627
+
1628
+ ## Features at a Glance
1629
+
1630
+ | Category | Feature |
1631
+ |----------|---------|
1632
+ | **Components** | Two-layer closure model, props as function, slot/children support |
1633
+ | **Reactivity** | `atom`, `iAtom`, `effect`, `memo`, `store`, reactive Map/Set |
1634
+ | **Local state** | `useState` with auto-async wrapping |
1635
+ | **Shared state** | Scope classes with `useScope` / `getParentScope` — no prop drilling |
1636
+ | **Persistence** | `lsAtom` (localStorage), `ssAtom` (sessionStorage), cross-tab sync |
1637
+ | **Async** | `asyncAction` with loading/error/data atoms, cancellation, polling, LRU cache |
1638
+ | **Pagination** | `paginatedAsyncAction` with built-in page atom and `next()` |
1639
+ | **Task queue** | `taskAction` for serial async work |
1640
+ | **HTTP** | `FetchClient` with interceptors, URL templates, JSON auto-serialization, SSR support |
1641
+ | **Routing** | `Router`, `route`, `navigateTo`, `pathAtom`, `qsAtom`, animated transitions |
1642
+ | **Navigation lock** | Built-in unsaved-changes guard |
1643
+ | **CSS-in-JS** | Scoped `stylesheet`, `globalCSS`, `keyframes` — zero runtime for static styles |
1644
+ | **Theming** | `globalTheme` with CSS custom properties, dark mode, OS auto-mode |
1645
+ | **Directives** | `attr`, `style`, `classes`, `on`, `onIntersect`, `lazyLoad`, `animatedIf`, etc. |
1646
+ | **Hooks** | `onMount`, `onPaint`, `onCleanup`, `onKeyDown`, `onInterval`, `onNavigate`, … |
1647
+ | **Portals** | `portal`, `dialog`, `tooltip` — escape DOM stacking contexts |
1648
+ | **Animations** | WAAPI wrapper, `animateDirective`, 15+ presets, stagger |
1649
+ | **WebSocket** | `ws()` with reconnect, exponential backoff, auth refresh, `onSocket` hook |
1650
+ | **Virtualization** | `virtualList`, `virtualGrid`, `virtualMasonry`, `masonryGrid` |
1651
+ | **DevTools** | Installable hook bridge for browser extension integration |
1652
+ | **SSR** | `isSSR`, `setSSRMode`, handler registry for zero-network server rendering |
1653
+ | **TypeScript** | Full type coverage, generics throughout, strict-mode safe |
1654
+ | **lit-html** | Full re-export of `html`, `svg`, `render`, `repeat`, `classMap`, `styleMap`, `when`, `cache`, `live`, `keyed`, `guard`, `until`, and more |
1655
+
1656
+ ---
1657
+
1658
+ ## How Mates Compares
1659
+
1660
+ ### vs React
1661
+
1662
+ | | Mates | React |
1663
+ |---|---|---|
1664
+ | **Rendering** | `lit-html` patches the real DOM directly — no virtual DOM, no diffing tree | VDOM diff on every render, reconciler determines what to patch |
1665
+ | **Component model** | Two-layer closure — outer (setup) runs once, inner (render) runs on change | Function components re-run entirely on every render |
1666
+ | **Reactivity** | Fine-grained atoms — only components that read a changed atom re-run | Renders propagate top-down via props and context unless `memo`/`useMemo` are used |
1667
+ | **State** | `atom` (module or component-scoped), `useState`, `store` | `useState`, `useReducer`, `useContext`, external libraries |
1668
+ | **Side effects** | `effect(fn)` is reactive — re-runs when dependencies change | `useEffect` with manual dependency arrays |
1669
+ | **Computed values** | `memo(fn)` — auto-tracked dependencies | `useMemo(fn, deps)` — manual dependency arrays |
1670
+ | **Shared state** | Scopes (class-based, `useScope`/`getParentScope`) | Context API + `useContext` or external store (Zustand, Redux) |
1671
+ | **Compiler** | None — plain TypeScript, standard ESM | JSX transform required |
1672
+ | **Bundle size** | ~50 KB gzipped (framework + lit-html) | ~45 KB gzipped (React + ReactDOM) |
1673
+ | **HTTP** | Built-in `FetchClient` + `asyncAction` | Third-party (Axios, TanStack Query, SWR) |
1674
+ | **Routing** | Built-in `Router` | Third-party (React Router, TanStack Router) |
1675
+ | **CSS** | Built-in `stylesheet` + `globalTheme` | Third-party (Emotion, styled-components, CSS Modules) |
1676
+ | **Animation** | Built-in WAAPI presets + `animateDirective` | Third-party (Framer Motion, react-spring) |
1677
+ | **WebSocket** | Built-in `ws()` with reconnect | Third-party |
1678
+ | **Virtualization** | Built-in `virtualList`, `virtualGrid`, `virtualMasonry` | Third-party (react-window, TanStack Virtual) |
1679
+
1680
+ **Key difference:** In React, calling `setState` schedules a re-render of the whole component tree from that node down. In Mates, changing an atom only re-runs the specific inner functions and effects that read that atom — everything else stays untouched.
1681
+
1682
+ ---
1683
+
1684
+ ### vs Vue 3
1685
+
1686
+ | | Mates | Vue 3 |
1687
+ |---|---|---|
1688
+ | **Templates** | Tagged template literals — standard JavaScript, no compiler | `.vue` SFC files or JSX with Vite compiler |
1689
+ | **Reactivity** | `atom` — explicit, function-call reads | `ref`/`reactive` — Proxy-based, transparent reads |
1690
+ | **Components** | Plain closure functions | Options API or `setup()` function + `<template>` |
1691
+ | **Scoped styles** | `stylesheet()` — scoped class names | `<style scoped>` — attribute-based scoping |
1692
+ | **Router** | Built-in | Official but separate (`vue-router`) |
1693
+ | **State management** | `atom`, `store`, `scope` built-in | Official but separate (`pinia`) |
1694
+ | **Compiler** | None required | Required for SFC and template directives |
1695
+
1696
+ ---
1697
+
1698
+ ### vs Svelte
1699
+
1700
+ | | Mates | Svelte |
1701
+ |---|---|---|
1702
+ | **Build step** | None — TypeScript only | Required Svelte compiler |
1703
+ | **Reactivity** | Explicit `atom` calls | Compile-time `$:` labels / `$state` runes |
1704
+ | **Component files** | Plain `.ts` files | `.svelte` files with `<script>`, `<template>`, `<style>` sections |
1705
+ | **Bundle size** | Consistent ~50 KB | Per-component compiled output — small for tiny apps, grows with app size |
1706
+ | **TypeScript** | Native — no extra config | Requires `lang="ts"` + type-checking config |
1707
+ | **Animation** | Built-in WAAPI presets | Built-in `transition:`, `animate:` directives (compile-time) |
1708
+
1709
+ ---
1710
+
1711
+ ### vs SolidJS
1712
+
1713
+ | | Mates | SolidJS |
1714
+ |---|---|---|
1715
+ | **Rendering** | `lit-html` patches — no VDOM | Compiled fine-grained DOM updates |
1716
+ | **Reactivity** | `atom` — explicit function call to read | `createSignal`, `createEffect` — runs at compile time |
1717
+ | **Compiler** | None | JSX transform + reactivity transform required |
1718
+ | **Component re-runs** | Inner function re-runs on change | Components run once — JSX expressions are reactive subscriptions |
1719
+ | **Ecosystem** | Self-contained (router, HTTP, CSS, WS all built-in) | Growing ecosystem, mostly third-party |
1720
+ | **Learning curve** | Low — plain TypeScript + tagged templates | Moderate — must understand compiled reactive graph |
1721
+
1722
+ ---
1723
+
1724
+ ### vs Preact / Inferno
1725
+
1726
+ Both are drop-in React replacements with VDOM. Mates takes a fundamentally different approach (lit-html + fine-grained atoms) and ships a complete feature set out of the box rather than relying on the React ecosystem for routing, state, animations, and HTTP.
1727
+
1728
+ ---
1729
+
1730
+ ### Summary: When to choose Mates
1731
+
1732
+ ✅ You want **no compiler magic** — just TypeScript and a build tool you already use
1733
+ ✅ You want **everything in one package** — HTTP, routing, CSS, WS, animations, virtualization
1734
+ ✅ You want **fine-grained reactivity** without a framework-specific compiler
1735
+ ✅ You're building a **single-page application** with complex state and async flows
1736
+ ✅ You want **predictable performance** — only what changed is ever re-rendered
1737
+ ✅ You prefer **explicit over implicit** — reads and writes are always function calls
1738
+
1739
+ ---
1740
+
1741
+ ## IDE Support
1742
+
1743
+ Install the **Lit** extension for VS Code / Cursor ([marketplace](https://marketplace.visualstudio.com/items?itemName=lit.lit-plugin)) to get:
405
1744
 
406
- ## 📚 Learn More
1745
+ - Syntax highlighting inside `html\`...\`` template literals
1746
+ - Attribute and property completions on HTML elements
1747
+ - Inline TypeScript errors in templates
407
1748
 
408
- Check out our [examples](https://github.com/yourusername/mates/tree/main/examples) to see more usage patterns and advanced framework features.
1749
+ ---
409
1750
 
410
- ## 📄 License
1751
+ ## License
411
1752
 
412
- MIT
1753
+ MIT