@adukiorg/anza 0.2.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 (349) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/README.md +215 -0
  3. package/bin/anza.js +63 -0
  4. package/bin/create.js +150 -0
  5. package/importmap.json +72 -0
  6. package/package.json +100 -0
  7. package/src/core/animations/index.js +55 -0
  8. package/src/core/animations/play.js +111 -0
  9. package/src/core/animations/registry.js +54 -0
  10. package/src/core/animations/scroll.js +50 -0
  11. package/src/core/animations/tokens.js +58 -0
  12. package/src/core/animations/usage.md +301 -0
  13. package/src/core/animations/waapi.js +86 -0
  14. package/src/core/api/cache.js +120 -0
  15. package/src/core/api/caches/glob.js +24 -0
  16. package/src/core/api/caches/index.js +118 -0
  17. package/src/core/api/events/index.js +75 -0
  18. package/src/core/api/fetch.js +99 -0
  19. package/src/core/api/index.js +158 -0
  20. package/src/core/api/pipeline.js +98 -0
  21. package/src/core/api/plan.md +209 -0
  22. package/src/core/api/prefixes/index.js +66 -0
  23. package/src/core/api/retry.js +69 -0
  24. package/src/core/api/stream.js +127 -0
  25. package/src/core/api/upload.js +180 -0
  26. package/src/core/api/usage.md +206 -0
  27. package/src/core/events/bus.js +38 -0
  28. package/src/core/events/delegate.js +79 -0
  29. package/src/core/events/index.js +26 -0
  30. package/src/core/events/listen.js +50 -0
  31. package/src/core/events/missing.md +103 -0
  32. package/src/core/events/once.js +49 -0
  33. package/src/core/events/plan.md +177 -0
  34. package/src/core/events/types/index.js +34 -0
  35. package/src/core/events/usage.md +107 -0
  36. package/src/core/offline/bridge.js +51 -0
  37. package/src/core/offline/clock.js +100 -0
  38. package/src/core/offline/connectivity.js +116 -0
  39. package/src/core/offline/index.js +41 -0
  40. package/src/core/offline/missing.md +89 -0
  41. package/src/core/offline/plan.md +143 -0
  42. package/src/core/offline/queue.js +168 -0
  43. package/src/core/offline/state.js +18 -0
  44. package/src/core/offline/sync.js +106 -0
  45. package/src/core/offline/usage.md +273 -0
  46. package/src/core/platform/guard.js +104 -0
  47. package/src/core/platform/index.js +42 -0
  48. package/src/core/platform/missing.md +119 -0
  49. package/src/core/platform/platform.d.ts +88 -0
  50. package/src/core/platform/polyfills/anchor.js +79 -0
  51. package/src/core/platform/polyfills/navigation.js +142 -0
  52. package/src/core/platform/polyfills/popover.js +142 -0
  53. package/src/core/platform/polyfills/scheduler.js +194 -0
  54. package/src/core/platform/polyfills/shadow.js +35 -0
  55. package/src/core/platform/polyfills/urlpattern.js +119 -0
  56. package/src/core/platform/supports.js +186 -0
  57. package/src/core/platform/usage.md +287 -0
  58. package/src/core/router/cache.js +95 -0
  59. package/src/core/router/container.js +146 -0
  60. package/src/core/router/handler.js +52 -0
  61. package/src/core/router/history.js +120 -0
  62. package/src/core/router/index.js +158 -0
  63. package/src/core/router/intercept.js +376 -0
  64. package/src/core/router/match.js +145 -0
  65. package/src/core/router/missing.md +716 -0
  66. package/src/core/router/outlet.js +139 -0
  67. package/src/core/router/plan.md +370 -0
  68. package/src/core/router/sync/index.js +16 -0
  69. package/src/core/router/sync/tab.js +115 -0
  70. package/src/core/router/sync/transport.js +139 -0
  71. package/src/core/router/transitions.js +59 -0
  72. package/src/core/router/usage.md +773 -0
  73. package/src/core/security/crypto.js +159 -0
  74. package/src/core/security/index.js +49 -0
  75. package/src/core/security/missing.md +97 -0
  76. package/src/core/security/permissions.js +64 -0
  77. package/src/core/security/sanitize.js +100 -0
  78. package/src/core/security/usage.md +283 -0
  79. package/src/core/state/derived.js +117 -0
  80. package/src/core/state/index.js +23 -0
  81. package/src/core/state/missing.md +165 -0
  82. package/src/core/state/persist.js +284 -0
  83. package/src/core/state/store.js +308 -0
  84. package/src/core/state/sync.js +46 -0
  85. package/src/core/state/usage.md +440 -0
  86. package/src/core/storage/cache.js +83 -0
  87. package/src/core/storage/idb.js +196 -0
  88. package/src/core/storage/index.js +373 -0
  89. package/src/core/storage/lru.js +107 -0
  90. package/src/core/storage/missing.md +165 -0
  91. package/src/core/storage/opfs.js +190 -0
  92. package/src/core/storage/plan.md +69 -0
  93. package/src/core/storage/quota.js +69 -0
  94. package/src/core/storage/usage.md +226 -0
  95. package/src/core/ui/base.js +50 -0
  96. package/src/core/ui/define/container.js +82 -0
  97. package/src/core/ui/define/define.js +12 -0
  98. package/src/core/ui/define/element.js +390 -0
  99. package/src/core/ui/define/index.js +9 -0
  100. package/src/core/ui/define/orchestrator.js +105 -0
  101. package/src/core/ui/define/proxy.js +644 -0
  102. package/src/core/ui/define/state.js +6 -0
  103. package/src/core/ui/define/utils.js +134 -0
  104. package/src/core/ui/implementation.md +170 -0
  105. package/src/core/ui/index.js +41 -0
  106. package/src/core/ui/observe.js +117 -0
  107. package/src/core/ui/plan.md +510 -0
  108. package/src/core/ui/schedule.js +60 -0
  109. package/src/core/ui/template.js +37 -0
  110. package/src/core/ui/transitions.js +37 -0
  111. package/src/core/ui/ui.types.md +890 -0
  112. package/src/core/ui/usage.md +1124 -0
  113. package/src/core/ui/watch.md +346 -0
  114. package/src/core/workers/broadcast.js +138 -0
  115. package/src/core/workers/dedicated.js +153 -0
  116. package/src/core/workers/index.js +169 -0
  117. package/src/core/workers/locks.js +160 -0
  118. package/src/core/workers/offscreen.js +166 -0
  119. package/src/core/workers/plan.md +381 -0
  120. package/src/core/workers/pool.js +267 -0
  121. package/src/core/workers/shared.js +137 -0
  122. package/src/core/workers/usage.md +622 -0
  123. package/src/elements/base.js +12 -0
  124. package/src/elements/data/card/index.html +9 -0
  125. package/src/elements/data/card/index.js +19 -0
  126. package/src/elements/data/card/index.tags.json +1 -0
  127. package/src/elements/data/card/style.css +46 -0
  128. package/src/elements/data/chart/index.html +1 -0
  129. package/src/elements/data/chart/index.js +143 -0
  130. package/src/elements/data/chart/index.tags.json +1 -0
  131. package/src/elements/data/chart/style.css +13 -0
  132. package/src/elements/data/list/index.html +3 -0
  133. package/src/elements/data/list/index.js +19 -0
  134. package/src/elements/data/list/index.tags.json +1 -0
  135. package/src/elements/data/list/style.css +39 -0
  136. package/src/elements/data/stat/index.html +9 -0
  137. package/src/elements/data/stat/index.js +19 -0
  138. package/src/elements/data/stat/index.tags.json +1 -0
  139. package/src/elements/data/stat/style.css +50 -0
  140. package/src/elements/data/table/index.html +1 -0
  141. package/src/elements/data/table/index.js +16 -0
  142. package/src/elements/data/table/index.tags.json +1 -0
  143. package/src/elements/data/table/style.css +50 -0
  144. package/src/elements/feedback/alert/index.html +11 -0
  145. package/src/elements/feedback/alert/index.js +28 -0
  146. package/src/elements/feedback/alert/index.tags.json +1 -0
  147. package/src/elements/feedback/alert/style.css +75 -0
  148. package/src/elements/feedback/empty/index.html +13 -0
  149. package/src/elements/feedback/empty/index.js +34 -0
  150. package/src/elements/feedback/empty/index.tags.json +1 -0
  151. package/src/elements/feedback/empty/style.css +45 -0
  152. package/src/elements/feedback/progress/index.html +7 -0
  153. package/src/elements/feedback/progress/index.js +46 -0
  154. package/src/elements/feedback/progress/index.tags.json +1 -0
  155. package/src/elements/feedback/progress/style.css +36 -0
  156. package/src/elements/feedback/skeleton/index.html +1 -0
  157. package/src/elements/feedback/skeleton/index.js +78 -0
  158. package/src/elements/feedback/skeleton/index.tags.json +1 -0
  159. package/src/elements/feedback/skeleton/style.css +28 -0
  160. package/src/elements/feedback/toast/index.html +3 -0
  161. package/src/elements/feedback/toast/index.js +65 -0
  162. package/src/elements/feedback/toast/index.tags.json +1 -0
  163. package/src/elements/feedback/toast/style.css +36 -0
  164. package/src/elements/forms/checkbox/index.html +7 -0
  165. package/src/elements/forms/checkbox/index.js +104 -0
  166. package/src/elements/forms/checkbox/index.tags.json +1 -0
  167. package/src/elements/forms/checkbox/style.css +86 -0
  168. package/src/elements/forms/field/index.html +13 -0
  169. package/src/elements/forms/field/index.js +42 -0
  170. package/src/elements/forms/field/index.tags.json +1 -0
  171. package/src/elements/forms/field/style.css +42 -0
  172. package/src/elements/forms/form/index.html +3 -0
  173. package/src/elements/forms/form/index.js +122 -0
  174. package/src/elements/forms/form/index.tags.json +1 -0
  175. package/src/elements/forms/form/style.css +11 -0
  176. package/src/elements/forms/input/index.html +4 -0
  177. package/src/elements/forms/input/index.js +103 -0
  178. package/src/elements/forms/input/index.tags.json +1 -0
  179. package/src/elements/forms/input/style.css +39 -0
  180. package/src/elements/forms/radio/index.html +4 -0
  181. package/src/elements/forms/radio/index.js +109 -0
  182. package/src/elements/forms/radio/index.tags.json +1 -0
  183. package/src/elements/forms/radio/style.css +65 -0
  184. package/src/elements/forms/select/index.html +9 -0
  185. package/src/elements/forms/select/index.js +114 -0
  186. package/src/elements/forms/select/index.tags.json +1 -0
  187. package/src/elements/forms/select/style.css +95 -0
  188. package/src/elements/forms/textarea/index.html +4 -0
  189. package/src/elements/forms/textarea/index.js +115 -0
  190. package/src/elements/forms/textarea/index.tags.json +1 -0
  191. package/src/elements/forms/textarea/style.css +46 -0
  192. package/src/elements/forms/toggle/index.html +4 -0
  193. package/src/elements/forms/toggle/index.js +89 -0
  194. package/src/elements/forms/toggle/index.tags.json +1 -0
  195. package/src/elements/forms/toggle/style.css +63 -0
  196. package/src/elements/forms/upload/index.html +13 -0
  197. package/src/elements/forms/upload/index.js +120 -0
  198. package/src/elements/forms/upload/index.tags.json +1 -0
  199. package/src/elements/forms/upload/style.css +61 -0
  200. package/src/elements/index.js +71 -0
  201. package/src/elements/layout/app/index.html +7 -0
  202. package/src/elements/layout/app/index.js +16 -0
  203. package/src/elements/layout/app/index.tags.json +1 -0
  204. package/src/elements/layout/app/style.css +41 -0
  205. package/src/elements/layout/grid/index.html +3 -0
  206. package/src/elements/layout/grid/index.js +41 -0
  207. package/src/elements/layout/grid/index.tags.json +1 -0
  208. package/src/elements/layout/grid/style.css +12 -0
  209. package/src/elements/layout/header/index.html +8 -0
  210. package/src/elements/layout/header/index.js +16 -0
  211. package/src/elements/layout/header/index.tags.json +1 -0
  212. package/src/elements/layout/header/style.css +28 -0
  213. package/src/elements/layout/scroll/index.html +3 -0
  214. package/src/elements/layout/scroll/index.js +19 -0
  215. package/src/elements/layout/scroll/index.tags.json +1 -0
  216. package/src/elements/layout/scroll/style.css +24 -0
  217. package/src/elements/layout/sidebar/index.html +3 -0
  218. package/src/elements/layout/sidebar/index.js +24 -0
  219. package/src/elements/layout/sidebar/index.tags.json +1 -0
  220. package/src/elements/layout/sidebar/style.css +30 -0
  221. package/src/elements/layout/split/index.html +3 -0
  222. package/src/elements/layout/split/index.js +18 -0
  223. package/src/elements/layout/split/index.tags.json +1 -0
  224. package/src/elements/layout/split/style.css +28 -0
  225. package/src/elements/layout/stack/index.html +3 -0
  226. package/src/elements/layout/stack/index.js +31 -0
  227. package/src/elements/layout/stack/index.tags.json +1 -0
  228. package/src/elements/layout/stack/style.css +15 -0
  229. package/src/elements/layout/surface/index.html +3 -0
  230. package/src/elements/layout/surface/index.js +19 -0
  231. package/src/elements/layout/surface/index.tags.json +1 -0
  232. package/src/elements/layout/surface/style.css +29 -0
  233. package/src/elements/navigation/breadcrumb/index.html +5 -0
  234. package/src/elements/navigation/breadcrumb/index.js +16 -0
  235. package/src/elements/navigation/breadcrumb/index.tags.json +1 -0
  236. package/src/elements/navigation/breadcrumb/style.css +36 -0
  237. package/src/elements/navigation/nav/index.html +3 -0
  238. package/src/elements/navigation/nav/index.js +24 -0
  239. package/src/elements/navigation/nav/index.tags.json +1 -0
  240. package/src/elements/navigation/nav/style.css +38 -0
  241. package/src/elements/navigation/pagination/index.html +3 -0
  242. package/src/elements/navigation/pagination/index.js +94 -0
  243. package/src/elements/navigation/pagination/index.tags.json +1 -0
  244. package/src/elements/navigation/pagination/style.css +39 -0
  245. package/src/elements/navigation/steps/index.html +6 -0
  246. package/src/elements/navigation/steps/index.js +64 -0
  247. package/src/elements/navigation/steps/index.tags.json +1 -0
  248. package/src/elements/navigation/steps/style.css +78 -0
  249. package/src/elements/navigation/tabs/index.html +6 -0
  250. package/src/elements/navigation/tabs/index.js +132 -0
  251. package/src/elements/navigation/tabs/index.tags.json +1 -0
  252. package/src/elements/navigation/tabs/style.css +52 -0
  253. package/src/elements/overlay/dialog/index.html +5 -0
  254. package/src/elements/overlay/dialog/index.js +57 -0
  255. package/src/elements/overlay/dialog/index.tags.json +1 -0
  256. package/src/elements/overlay/dialog/style.css +31 -0
  257. package/src/elements/overlay/drawer/index.html +3 -0
  258. package/src/elements/overlay/drawer/index.js +56 -0
  259. package/src/elements/overlay/drawer/index.tags.json +1 -0
  260. package/src/elements/overlay/drawer/style.css +48 -0
  261. package/src/elements/overlay/menu/index.html +3 -0
  262. package/src/elements/overlay/menu/index.js +107 -0
  263. package/src/elements/overlay/menu/index.tags.json +1 -0
  264. package/src/elements/overlay/menu/style.css +43 -0
  265. package/src/elements/overlay/popover/index.html +3 -0
  266. package/src/elements/overlay/popover/index.js +44 -0
  267. package/src/elements/overlay/popover/index.tags.json +1 -0
  268. package/src/elements/overlay/popover/style.css +21 -0
  269. package/src/elements/overlay/sheet/index.html +8 -0
  270. package/src/elements/overlay/sheet/index.js +105 -0
  271. package/src/elements/overlay/sheet/index.tags.json +1 -0
  272. package/src/elements/overlay/sheet/style.css +64 -0
  273. package/src/elements/overlay/tooltip/index.html +6 -0
  274. package/src/elements/overlay/tooltip/index.js +16 -0
  275. package/src/elements/overlay/tooltip/index.tags.json +1 -0
  276. package/src/elements/overlay/tooltip/style.css +41 -0
  277. package/src/elements/primitives/avatar/index.html +2 -0
  278. package/src/elements/primitives/avatar/index.js +79 -0
  279. package/src/elements/primitives/avatar/index.tags.json +1 -0
  280. package/src/elements/primitives/avatar/style.css +36 -0
  281. package/src/elements/primitives/badge/index.html +3 -0
  282. package/src/elements/primitives/badge/index.js +20 -0
  283. package/src/elements/primitives/badge/index.tags.json +1 -0
  284. package/src/elements/primitives/badge/style.css +67 -0
  285. package/src/elements/primitives/button/index.html +3 -0
  286. package/src/elements/primitives/button/index.js +61 -0
  287. package/src/elements/primitives/button/index.tags.json +1 -0
  288. package/src/elements/primitives/button/style.css +66 -0
  289. package/src/elements/primitives/divider/index.html +1 -0
  290. package/src/elements/primitives/divider/index.js +43 -0
  291. package/src/elements/primitives/divider/index.tags.json +1 -0
  292. package/src/elements/primitives/divider/style.css +39 -0
  293. package/src/elements/primitives/icon/index.html +3 -0
  294. package/src/elements/primitives/icon/index.js +66 -0
  295. package/src/elements/primitives/icon/index.tags.json +1 -0
  296. package/src/elements/primitives/icon/style.css +20 -0
  297. package/src/elements/primitives/link/index.html +3 -0
  298. package/src/elements/primitives/link/index.js +129 -0
  299. package/src/elements/primitives/link/index.tags.json +1 -0
  300. package/src/elements/primitives/link/style.css +40 -0
  301. package/src/elements/primitives/spinner/index.html +1 -0
  302. package/src/elements/primitives/spinner/index.js +62 -0
  303. package/src/elements/primitives/spinner/index.tags.json +1 -0
  304. package/src/elements/primitives/spinner/style.css +20 -0
  305. package/src/elements/primitives/text/index.html +1 -0
  306. package/src/elements/primitives/text/index.js +79 -0
  307. package/src/elements/primitives/text/index.tags.json +1 -0
  308. package/src/elements/primitives/text/style.css +25 -0
  309. package/src/index.js +23 -0
  310. package/src/styles/base.css +66 -0
  311. package/src/styles/index.css +10 -0
  312. package/src/styles/layers.css +9 -0
  313. package/src/styles/reset.css +66 -0
  314. package/src/sw/activate.js +51 -0
  315. package/src/sw/expire.js +47 -0
  316. package/src/sw/index.js +28 -0
  317. package/src/sw/install.js +35 -0
  318. package/src/sw/push.js +58 -0
  319. package/src/sw/queue.js +60 -0
  320. package/src/sw/routes.js +71 -0
  321. package/src/sw/strategies.js +247 -0
  322. package/src/sw/sync.js +80 -0
  323. package/src/tokens/index.css +26 -0
  324. package/src/tokens/primitives/colors.css +54 -0
  325. package/src/tokens/primitives/motion.css +34 -0
  326. package/src/tokens/primitives/radius.css +16 -0
  327. package/src/tokens/primitives/shadow.css +34 -0
  328. package/src/tokens/primitives/spacing.css +27 -0
  329. package/src/tokens/primitives/typography.css +46 -0
  330. package/src/tokens/primitives/zindex.css +18 -0
  331. package/src/tokens/registered/colors.css +133 -0
  332. package/src/tokens/registered/dimensions.css +31 -0
  333. package/src/tokens/semantic/components.css +125 -0
  334. package/src/tokens/semantic/contrast.css +33 -0
  335. package/src/tokens/semantic/dark.css +61 -0
  336. package/src/tokens/semantic/light.css +64 -0
  337. package/types/core/animations/index.d.ts +52 -0
  338. package/types/core/api/index.d.ts +68 -0
  339. package/types/core/events/index.d.ts +50 -0
  340. package/types/core/offline/index.d.ts +68 -0
  341. package/types/core/platform/index.d.ts +60 -0
  342. package/types/core/router/index.d.ts +203 -0
  343. package/types/core/security/index.d.ts +33 -0
  344. package/types/core/state/index.d.ts +68 -0
  345. package/types/core/storage/index.d.ts +40 -0
  346. package/types/core/ui/index.d.ts +446 -0
  347. package/types/core/workers/index.d.ts +221 -0
  348. package/types/elements/index.d.ts +150 -0
  349. package/types/index.d.ts +18 -0
@@ -0,0 +1,1124 @@
1
+ # Native UI Usage Guide
2
+
3
+ The Native UI layer defines custom elements with Shadow DOM, lifecycle-owned cleanup, cached template anchors, delegated events, scoped mutation watching, observer wrappers, scheduler helpers, View Transitions, and cached template fragments.
4
+
5
+ Status: this guide documents the implemented public UI contract: `ui.define`, `ui.element`, `ui.container`, props, `reflect`, `methods`, `refs`, `tags`, delegated `on`, `watch`, `ui.schedule`, `ui.scheduleFrame`, `ui.yield`, `ui.observe`, `ui.transition`, `ui.template`, form-associated elements, generated `.tags.json` descriptors, generated `routes.json`, and TypeScript component typing.
6
+
7
+ Import from the UI entry point:
8
+
9
+ ```javascript
10
+ import { ui } from '@adukiorg/anza/ui';
11
+ ```
12
+
13
+ ## 1. Choosing an API
14
+
15
+ | Need | Use |
16
+ | --- | --- |
17
+ | Declarative component | `ui.element` |
18
+ | Router-owned layout slot | `ui.container` |
19
+ | Existing custom element class | `ui.define` |
20
+ | Stable template anchor | `refs` |
21
+ | Cached selector lookup | `tags` |
22
+ | Delegated event binding | `on` |
23
+ | Shadow-root mutation watching | `watch` |
24
+ | Raw platform observer | `ui.observe` |
25
+ | Deferred or priority work | `ui.schedule` |
26
+ | Frame-synced DOM work | `ui.scheduleFrame` |
27
+ | Cooperative loop pause | `ui.yield` |
28
+ | Visible DOM swap | `ui.transition` |
29
+ | Reusable fragment | `ui.template` |
30
+
31
+ ## 2. Component Shape
32
+
33
+ Use `ui.element` for components, pages, and primitives.
34
+
35
+ ```javascript
36
+ ui.element('ui-counter', {
37
+ template: './index.html',
38
+ style: './style.css',
39
+
40
+ props: {
41
+ count: { type: Number, default: 0 },
42
+ disabled: { type: Boolean, default: false, state: true },
43
+ label: { type: String, default: 'Count' }
44
+ },
45
+
46
+ mount({ el, refs, on }) {
47
+ refs.label.textContent = el.label;
48
+
49
+ on.click('button', () => {
50
+ if (el.disabled) return;
51
+ el.count++;
52
+ });
53
+ },
54
+
55
+ update({ el, name, val, prev, refs }) {
56
+ if (name === 'count') {
57
+ refs.value.textContent = String(val);
58
+ refs.value.dataset.prev = String(prev);
59
+ }
60
+
61
+ if (name === 'disabled') {
62
+ refs.button.disabled = val;
63
+ }
64
+ },
65
+
66
+ unmount({ refs }) {
67
+ refs.value.textContent = '';
68
+ }
69
+ }, import.meta.url);
70
+ ```
71
+
72
+ Always pass `import.meta.url` as the third argument when `template` or `style` is a relative path. If a relative `template`/`style` is given without a base, the element logs an error (the resource would otherwise silently fail to load).
73
+
74
+ ### Prop shorthand
75
+
76
+ Props accept a shorthand: a literal default whose type is inferred. Use the full
77
+ object form only when you need `state`, `reflect`, or an explicit `type`.
78
+
79
+ ```javascript
80
+ ui.element('ui-counter', {
81
+ props: {
82
+ count: 0, // -> { type: Number, default: 0 }
83
+ label: 'Count', // -> { type: String, default: 'Count' }
84
+ open: false, // -> { type: Boolean, default: false }
85
+ selected: { type: Boolean, default: false, state: true } // full form
86
+ }
87
+ });
88
+ ```
89
+
90
+ ### Splitting a component across files
91
+
92
+ Because the toolchain resolves the full import graph, keep `index.js` small and
93
+ import lifecycle/logic from sibling modules with plain ESM — the imported files
94
+ are emitted into `dist/` automatically. (No special spec field is needed.)
95
+
96
+ ```javascript
97
+ // counter/logic.js
98
+ export function mount({ el, on }) {
99
+ on.click('button', () => el.count++);
100
+ }
101
+ export function update({ name, val, refs }) {
102
+ if (name === 'count') refs.value.textContent = String(val);
103
+ }
104
+
105
+ // counter/index.js
106
+ import { ui } from '../../core/ui/index.js';
107
+ import { mount, update } from './logic.js';
108
+
109
+ ui.element('ui-counter', {
110
+ template: './index.html',
111
+ style: './style.css',
112
+ props: { count: 0 },
113
+ mount,
114
+ update
115
+ }, import.meta.url);
116
+ ```
117
+
118
+ Component methods can be installed on the generated element prototype.
119
+
120
+ ```javascript
121
+ ui.element('ui-counter', {
122
+ props: {
123
+ count: { type: Number, default: 0 }
124
+ },
125
+
126
+ methods: {
127
+ increment() {
128
+ this.count++;
129
+ }
130
+ },
131
+
132
+ mount({ el, on }) {
133
+ on.click('button', () => el.increment());
134
+ }
135
+ });
136
+ ```
137
+
138
+ Rules:
139
+
140
+ - Methods run with `this` set to the host element.
141
+ - Methods are installed before `customElements.define`.
142
+ - Reserved lifecycle names such as `connectedCallback`, `disconnectedCallback`, `attributeChangedCallback`, `mount`, and `unmount` are skipped with a warning.
143
+ - Methods are prototype methods, not per-instance bound functions.
144
+
145
+ ## 3. Component Files
146
+
147
+ Keep component files together.
148
+
149
+ ```text
150
+ src/elements/primitives/button/
151
+ index.js
152
+ index.html
153
+ index.tags.json
154
+ style.css
155
+ ```
156
+
157
+ `index.js` registers the element.
158
+
159
+ ```javascript
160
+ import { ui } from '../../../core/ui/index.js';
161
+
162
+ ui.element('ui-button', {
163
+ template: './index.html',
164
+ style: './style.css',
165
+ props: {
166
+ disabled: { type: Boolean, default: false, state: true }
167
+ }
168
+ }, import.meta.url);
169
+ ```
170
+
171
+ `index.html` contains the shadow template.
172
+
173
+ ```html
174
+ <button ref="button" id="button" type="button">
175
+ <slot></slot>
176
+ </button>
177
+ <span ref="status" hidden></span>
178
+ ```
179
+
180
+ `style.css` contains component-scoped styles adopted into the shadow root.
181
+
182
+ ```css
183
+ :host {
184
+ display: inline-block;
185
+ }
186
+
187
+ button:disabled {
188
+ opacity: 0.5;
189
+ }
190
+ ```
191
+
192
+ ## 4. Lifecycle Context
193
+
194
+ `mount` receives a stable context for the current connection lifecycle.
195
+
196
+ ```javascript
197
+ mount({
198
+ el,
199
+ ctrl,
200
+ tags,
201
+ on,
202
+ refs,
203
+ watch,
204
+ internals
205
+ }) {}
206
+ ```
207
+
208
+ `update` receives the same helpers plus the changed property.
209
+
210
+ ```javascript
211
+ update({
212
+ el,
213
+ ctrl,
214
+ tags,
215
+ on,
216
+ refs,
217
+ watch,
218
+ internals,
219
+ name,
220
+ val,
221
+ prev,
222
+ old
223
+ }) {}
224
+ ```
225
+
226
+ `unmount` receives cleanup-friendly helpers.
227
+
228
+ ```javascript
229
+ unmount({
230
+ el,
231
+ tags,
232
+ refs,
233
+ watch,
234
+ internals
235
+ }) {}
236
+ ```
237
+
238
+ Rules:
239
+
240
+ - `el` is the host custom element.
241
+ - `ctrl` is an `AbortController` created on connect and aborted on disconnect.
242
+ - `refs`, `tags`, `on`, and `watch` are scoped to the component shadow root.
243
+ - `internals` is available when `form: true` is set.
244
+ - Do not store lifecycle context globally.
245
+
246
+ ## 5. Props
247
+
248
+ Props define reflected element properties and observed attributes.
249
+
250
+ ```javascript
251
+ props: {
252
+ open: { type: Boolean, default: false, state: true },
253
+ count: { type: Number, default: 0 },
254
+ label: { type: String, default: 'Untitled' },
255
+ cache: { type: String, default: '', reflect: false }
256
+ }
257
+ ```
258
+
259
+ Supported types:
260
+
261
+ - `Boolean`: present attribute is `true`; missing attribute is `false`.
262
+ - `Number`: attribute value is cast with `Number(...)`.
263
+ - `String`: attribute value is used as text.
264
+
265
+ Boolean example:
266
+
267
+ ```javascript
268
+ el.open = true;
269
+ el.hasAttribute('open'); // true
270
+
271
+ el.open = false;
272
+ el.hasAttribute('open'); // false
273
+ ```
274
+
275
+ Number and string example:
276
+
277
+ ```javascript
278
+ el.count = 3;
279
+ el.getAttribute('count'); // "3"
280
+
281
+ el.label = 'Ready';
282
+ el.getAttribute('label'); // "Ready"
283
+ ```
284
+
285
+ Use `state: true` to mirror truthy values into `ElementInternals.states` when supported.
286
+
287
+ ```javascript
288
+ props: {
289
+ selected: { type: Boolean, default: false, state: true }
290
+ }
291
+ ```
292
+
293
+ Reflection defaults to enabled. Set `reflect: false` when a property should stay internal.
294
+
295
+ ```javascript
296
+ props: {
297
+ label: { type: String, default: '', reflect: true },
298
+ token: { type: String, default: '', reflect: false }
299
+ }
300
+ ```
301
+
302
+ Rules:
303
+
304
+ - `reflect: true` writes property changes to attributes.
305
+ - `reflect: false` keeps property writes off attributes.
306
+ - Attribute changes still update observed properties.
307
+
308
+ ## 6. Updates
309
+
310
+ Updates are batched after initialization.
311
+
312
+ ```javascript
313
+ update({ name, val, prev, refs }) {
314
+ if (name === 'label') {
315
+ refs.label.textContent = val;
316
+ }
317
+
318
+ if (name === 'count') {
319
+ refs.count.textContent = String(val);
320
+ refs.count.dataset.prev = String(prev);
321
+ }
322
+ }
323
+ ```
324
+
325
+ By default, updates flush in a microtask. If the update is visual and should wait for a frame, mark the update function:
326
+
327
+ ```javascript
328
+ function update(ctx) {
329
+ ctx.refs.panel.hidden = !ctx.el.open;
330
+ }
331
+
332
+ update.visual = true;
333
+
334
+ ui.element('ui-panel', {
335
+ props: {
336
+ open: { type: Boolean, default: false }
337
+ },
338
+ update
339
+ });
340
+ ```
341
+
342
+ Rules:
343
+
344
+ - Keep `update` idempotent.
345
+ - Branch on `name`.
346
+ - Use `val` for the new value.
347
+ - Use `prev` or `old` for the previous value.
348
+ - Prefer property writes, `textContent`, attributes, and class lists.
349
+
350
+ ## 7. Templates
351
+
352
+ External HTML templates are fetched once per component registration and cloned per instance.
353
+
354
+ ```javascript
355
+ ui.element('ui-card', {
356
+ template: './index.html',
357
+ style: './style.css'
358
+ }, import.meta.url);
359
+ ```
360
+
361
+ Inline templates are also supported.
362
+
363
+ ```javascript
364
+ ui.element('ui-dot', {
365
+ template: '<span ref="dot" part="dot"></span>',
366
+ style: ':host { display: inline-block; }'
367
+ });
368
+ ```
369
+
370
+ Rules:
371
+
372
+ - Use external files for reusable elements.
373
+ - Use inline templates only for tiny internal elements.
374
+ - Assign dynamic data through DOM APIs after mount.
375
+ - Avoid `innerHTML` for user data.
376
+
377
+ ## 8. Refs
378
+
379
+ Use `ref="name"` for stable anchors that component code needs often.
380
+
381
+ ```html
382
+ <button ref="button" type="button"></button>
383
+ <span ref="label"></span>
384
+ ```
385
+
386
+ Access refs in lifecycle hooks:
387
+
388
+ ```javascript
389
+ mount({ refs }) {
390
+ refs.button.disabled = false;
391
+ refs.label.textContent = 'Ready';
392
+ }
393
+ ```
394
+
395
+ Rules:
396
+
397
+ - `refs.name` is a direct element reference.
398
+ - Missing refs are `undefined`.
399
+ - Duplicate refs warn and the first match wins.
400
+ - Refs are frozen for the current mount lifecycle.
401
+ - Use refs for stable anchors, not repeated dynamic list items.
402
+
403
+ ## 9. Tags
404
+
405
+ `tags` is a cached selector helper scoped to the shadow root.
406
+
407
+ ```javascript
408
+ mount({ tags }) {
409
+ const button = tags.one('button');
410
+ const items = tags.all('[data-item]');
411
+
412
+ tags.each('[data-item]', (item, index) => {
413
+ item.dataset.index = String(index);
414
+ });
415
+ }
416
+ ```
417
+
418
+ Methods:
419
+
420
+ ```javascript
421
+ tags.one(selector) // Element | null
422
+ tags.all(selector) // Element[]
423
+ tags.each(selector, fn) // void
424
+ tags.has(selector) // boolean
425
+ tags.clear() // void
426
+ ```
427
+
428
+ Rules:
429
+
430
+ - Use `refs` when an element has a stable name.
431
+ - Use `tags` for selector-based access.
432
+ - `tags.one` and `tags.all` have separate caches.
433
+ - The cache clears when direct shadow children change.
434
+ - Call `tags.clear()` after structural changes that the automatic invalidation cannot see.
435
+
436
+ ## 10. Delegated Events
437
+
438
+ `on` binds delegated event handlers to the shadow root and cleans up with `ctrl.signal`.
439
+
440
+ ```javascript
441
+ mount({ on }) {
442
+ on.click('button', (event, button) => {
443
+ button.classList.add('active');
444
+ });
445
+
446
+ on.input('input[type="search"]', (event, input) => {
447
+ filter(input.value);
448
+ });
449
+
450
+ on.submit('form', (event, form) => {
451
+ event.preventDefault();
452
+ save(new FormData(form));
453
+ });
454
+
455
+ on['nav:change']('[data-tab]', (event, tab) => {
456
+ activate(tab.dataset.tab);
457
+ });
458
+ }
459
+ ```
460
+
461
+ Handler shape:
462
+
463
+ ```javascript
464
+ (event, matchedElement) => void
465
+ ```
466
+
467
+ Supported call shapes:
468
+
469
+ ```javascript
470
+ on.click(selector, handler)
471
+ on.click(selector, handler, ctrl.signal)
472
+ on.click(selector, handler, { signal: ctrl.signal, passive: false })
473
+ on.click(selector, handler, { once: true })
474
+ on.click.once(selector, handler)
475
+ on.click.once(selector, handler, ctrl.signal)
476
+ ```
477
+
478
+ Rules:
479
+
480
+ - Events are delegated, so dynamically added matching children work.
481
+ - The matched element is found with `closest(selector)`.
482
+ - Invalid selectors warn and do not throw.
483
+ - Handlers are passive by default unless `passive: false` is passed.
484
+ - Use `passive: false` when calling `preventDefault()`.
485
+ - The returned disposer removes only that binding.
486
+
487
+ Example with a disposer:
488
+
489
+ ```javascript
490
+ mount({ on }) {
491
+ const stop = on.click('.remove', remove);
492
+ stop();
493
+ }
494
+ ```
495
+
496
+ ## 11. Raw Events
497
+
498
+ Use raw platform events when delegation is not the right shape.
499
+
500
+ ```javascript
501
+ mount({ refs, ctrl }) {
502
+ refs.scroller.addEventListener('scroll', onScroll, {
503
+ signal: ctrl.signal,
504
+ passive: true
505
+ });
506
+
507
+ refs.video.addEventListener('loadedmetadata', onLoad, {
508
+ signal: ctrl.signal
509
+ });
510
+ }
511
+ ```
512
+
513
+ Use raw events for:
514
+
515
+ - `scroll`
516
+ - media events
517
+ - global `window` or `document` events
518
+ - non-bubbling events that delegation cannot catch cleanly
519
+
520
+ Always pass `ctrl.signal` or another abort signal.
521
+
522
+ ## 12. Watch
523
+
524
+ `watch` observes mutations inside the component shadow root.
525
+
526
+ ```javascript
527
+ mount({ refs, watch }) {
528
+ watch.attr(refs.button, 'disabled', (attr, next, prev, button) => {
529
+ button.dataset.wasDisabled = String(prev !== null);
530
+ });
531
+ }
532
+ ```
533
+
534
+ Targets can be selector strings or direct elements.
535
+
536
+ ```javascript
537
+ watch.attr('button', 'disabled', handler);
538
+ watch.attr(refs.button, 'disabled', handler);
539
+ ```
540
+
541
+ Every watch call returns a disposer.
542
+
543
+ ```javascript
544
+ const stop = watch.text(refs.status, sync);
545
+ stop();
546
+ ```
547
+
548
+ The final argument can be an `AbortSignal` or an options object.
549
+
550
+ ```javascript
551
+ watch.attr(refs.button, 'disabled', handler, ctrl.signal);
552
+ watch.attr(refs.button, 'disabled', handler, { signal: ctrl.signal, once: true });
553
+ watch.attr.once(refs.button, 'disabled', handler);
554
+ ```
555
+
556
+ ## 13. Attribute Watch
557
+
558
+ Use `watch.attr` for one, many, or all attributes.
559
+
560
+ ```javascript
561
+ watch.attr('button', 'disabled', (attr, next, prev, button) => {});
562
+ watch.attr('.card', ['aria-expanded', 'data-state'], handler);
563
+ watch.attr('.card', '*', handler);
564
+ watch.attr.once('details', 'open', handler);
565
+ ```
566
+
567
+ Handler shape:
568
+
569
+ ```javascript
570
+ (attrName, newValue, oldValue, element) => void
571
+ ```
572
+
573
+ Notes:
574
+
575
+ - `newValue` is read with `element.getAttribute(attrName)`.
576
+ - Removed attributes produce `null`.
577
+ - Boolean attributes usually produce `''` when present and `null` when absent.
578
+
579
+ ## 14. Children Watch
580
+
581
+ Use `watch.kids` for child additions and removals.
582
+
583
+ ```javascript
584
+ watch.kids('ul', ({ added, removed }, list) => {
585
+ for (const node of added) {
586
+ if (node.nodeType === Node.ELEMENT_NODE) {
587
+ node.classList.add('new');
588
+ }
589
+ }
590
+ });
591
+ ```
592
+
593
+ Direct target:
594
+
595
+ ```javascript
596
+ watch.kids(refs.list, handler);
597
+ ```
598
+
599
+ Deep child watching:
600
+
601
+ ```javascript
602
+ watch.kids('section', { deep: true }, handler);
603
+ watch.kids.once('section', { deep: true }, handler);
604
+ ```
605
+
606
+ Handler shape:
607
+
608
+ ```javascript
609
+ ({ added, removed }, element) => void
610
+ ```
611
+
612
+ `added` and `removed` are arrays.
613
+
614
+ ## 15. Text Watch
615
+
616
+ Use `watch.text` for text-node changes inside a target.
617
+
618
+ ```javascript
619
+ watch.text(refs.counter, (next, prev, counter) => {
620
+ counter.dataset.changed = String(next !== prev);
621
+ });
622
+ ```
623
+
624
+ Handler shape:
625
+
626
+ ```javascript
627
+ (newText, oldText, element) => void
628
+ ```
629
+
630
+ Rules:
631
+
632
+ - `newText` is the current `textContent`.
633
+ - `oldText` comes from the mutation record.
634
+ - `element` can be `null` if the text node no longer has a parent.
635
+
636
+ ## 16. Tree Watch
637
+
638
+ Use `watch.tree` only when you need raw mutation records.
639
+
640
+ ```javascript
641
+ watch.tree(refs.editor, (records, editor) => {
642
+ for (const record of records) {
643
+ if (record.type === 'childList') syncOutline();
644
+ if (record.type === 'attributes') syncToolbar();
645
+ }
646
+ });
647
+ ```
648
+
649
+ Handler shape:
650
+
651
+ ```javascript
652
+ (records, element) => void
653
+ ```
654
+
655
+ Prefer `watch.attr`, `watch.kids`, or `watch.text` when the mutation type is known.
656
+
657
+ ## 17. Containers
658
+
659
+ Use `ui.container` for router-owned layout slots.
660
+
661
+ ```javascript
662
+ ui.container('ui-main', {
663
+ template: './index.html',
664
+ style: './style.css',
665
+
666
+ mount({ el, on }) {
667
+ on.click('[data-close]', () => {
668
+ el.dispatchEvent(new CustomEvent('close', {
669
+ bubbles: true,
670
+ composed: true
671
+ }));
672
+ });
673
+ }
674
+ }, import.meta.url);
675
+ ```
676
+
677
+ Containers inherit the same lifecycle helpers as elements and register themselves with the router. The runtime adds `swapView(newElement, options)`.
678
+
679
+ ```javascript
680
+ await container.swapView(page, {
681
+ direction: 'push'
682
+ });
683
+ ```
684
+
685
+ Rules:
686
+
687
+ - Use containers for layout outlets.
688
+ - A container name is read from its `name` attribute or the tag name.
689
+ - Only one container with the same name can be mounted at a time.
690
+ - `swapView` uses element-scoped View Transitions when available, then document View Transitions, then direct replacement.
691
+
692
+ ## 18. Declarative Routes
693
+
694
+ `ui.element` can register a route when `url` is provided.
695
+
696
+ ```javascript
697
+ ui.element('page-profile', {
698
+ url: '/members/:member',
699
+ container: 'main',
700
+ props: {
701
+ member: { type: String, default: '' }
702
+ },
703
+ template: './profile.html',
704
+
705
+ mount({ el }) {
706
+ loadMember(el.member);
707
+ }
708
+ }, import.meta.url);
709
+ ```
710
+
711
+ When the router finds the route, the UI orchestrator creates the page element and swaps it into the named container.
712
+
713
+ ## 19. Form-Associated Elements
714
+
715
+ Set `form: true` to attach `ElementInternals`.
716
+
717
+ ```javascript
718
+ ui.element('ui-field', {
719
+ form: true,
720
+ template: './index.html',
721
+ style: './style.css',
722
+
723
+ props: {
724
+ value: { type: String, default: '' },
725
+ required: { type: Boolean, default: false }
726
+ },
727
+
728
+ mount({ el, refs, internals, on }) {
729
+ internals.setFormValue(el.value);
730
+
731
+ on.input('input', (_event, input) => {
732
+ el.value = input.value;
733
+ internals.setFormValue(el.value);
734
+ }, { passive: false });
735
+ },
736
+
737
+ update({ el, name, val, internals, refs }) {
738
+ if (name === 'value') {
739
+ refs.input.value = val;
740
+ internals.setFormValue(val);
741
+ }
742
+
743
+ if (name === 'required') {
744
+ internals.setValidity(
745
+ val && !el.value ? { valueMissing: true } : {},
746
+ val && !el.value ? 'Required' : ''
747
+ );
748
+ }
749
+ },
750
+
751
+ associated(form) {
752
+ this.dataset.form = form?.id || '';
753
+ },
754
+
755
+ disabled(value) {
756
+ this.toggleAttribute('disabled', value);
757
+ },
758
+
759
+ reset() {
760
+ this.value = '';
761
+ },
762
+
763
+ restore(state) {
764
+ this.value = String(state ?? '');
765
+ }
766
+ }, import.meta.url);
767
+ ```
768
+
769
+ Rules:
770
+
771
+ - Use `internals.setFormValue(value)` to participate in form submission.
772
+ - Use `internals.setValidity(flags, message)` for validation.
773
+ - Use `internals.form` when the element must interact with its owner form.
774
+ - Use `associated`, `disabled`, `reset`, and `restore` for form lifecycle hooks.
775
+
776
+ ### Form Hook Mapping
777
+
778
+ The spec hook names map to the platform `ElementInternals` callbacks:
779
+
780
+ | Spec Hook | Platform Callback | Purpose |
781
+ | --- | --- | --- |
782
+ | `associated(form)` | `formAssociatedCallback` | Called when element is associated with a `<form>` |
783
+ | `disabled(value)` | `formDisabledCallback` | Called when the form is disabled |
784
+ | `reset()` | `formResetCallback` | Called when the form is reset |
785
+ | `restore(state, mode)` | `formStateRestoreCallback` | Called when browser restores form state |
786
+
787
+ These hooks are installed on the element prototype and called automatically by the browser.
788
+
789
+ ## 20. `ui.define`
790
+
791
+ Use `ui.define` for a hand-written custom element class.
792
+
793
+ ```javascript
794
+ import { BaseElement, ui } from '@adukiorg/anza/ui';
795
+
796
+ class NativeClock extends BaseElement {
797
+ mount() {
798
+ this.timer = setInterval(() => {
799
+ this.textContent = new Date().toLocaleTimeString();
800
+ }, 1000);
801
+
802
+ this.ctrl.signal.addEventListener('abort', () => {
803
+ clearInterval(this.timer);
804
+ }, { once: true });
805
+ }
806
+ }
807
+
808
+ ui.define('native-clock', NativeClock);
809
+ ```
810
+
811
+ `BaseElement` creates `this.ctrl` in `connectedCallback` and aborts it in `disconnectedCallback`.
812
+
813
+ ## 21. Scheduling
814
+
815
+ Use scheduler helpers for expensive work that should not block interaction.
816
+
817
+ ```javascript
818
+ await ui.schedule(() => {
819
+ buildSecondaryIndex();
820
+ });
821
+ ```
822
+
823
+ Priorities:
824
+
825
+ ```javascript
826
+ await ui.schedule(work, 'user-blocking');
827
+ await ui.schedule(work, 'user-visible');
828
+ await ui.schedule(work, 'background');
829
+ ```
830
+
831
+ Use `scheduleFrame` for DOM writes that should happen in a frame.
832
+
833
+ ```javascript
834
+ await ui.scheduleFrame(() => {
835
+ refs.panel.hidden = false;
836
+ });
837
+ ```
838
+
839
+ Use `ui.yield()` inside long loops.
840
+
841
+ ```javascript
842
+ for (let i = 0; i < rows.length; i++) {
843
+ renderRow(rows[i]);
844
+ if (i % 50 === 0) await ui.yield();
845
+ }
846
+ ```
847
+
848
+ Fallbacks:
849
+
850
+ - `scheduler.postTask` when available.
851
+ - `requestIdleCallback` for background tasks when available.
852
+ - `setTimeout` for broad browser support.
853
+
854
+ ## 22. Observers
855
+
856
+ `ui.observe` wraps platform observers with disposer functions and optional `AbortSignal` cleanup.
857
+
858
+ ```javascript
859
+ const stopResize = ui.observe.resize(refs.panel, (entries) => {
860
+ syncSize(entries[0].contentRect);
861
+ }, ctrl.signal);
862
+
863
+ const stopVisible = ui.observe.intersection(refs.sentinel, (entries) => {
864
+ if (entries[0].isIntersecting) loadMore();
865
+ }, ctrl.signal, {
866
+ rootMargin: '200px'
867
+ });
868
+
869
+ const stopMutation = ui.observe.mutation(refs.list, (records) => {
870
+ console.log(records.length);
871
+ }, ctrl.signal, {
872
+ childList: true
873
+ });
874
+
875
+ const stopPerf = ui.observe.performance(['longtask'], (list) => {
876
+ report(list.getEntries());
877
+ }, ctrl.signal);
878
+ ```
879
+
880
+ Rules:
881
+
882
+ - Prefer injected `watch` for component shadow-root mutations.
883
+ - Use `ui.observe.*` for lower-level platform observer access.
884
+ - Pass `ctrl.signal` from component code.
885
+ - Call the returned disposer for early cleanup.
886
+
887
+ ## 23. Transitions
888
+
889
+ `ui.transition` wraps document View Transitions and respects reduced motion.
890
+
891
+ ```javascript
892
+ const tx = await ui.transition(() => {
893
+ refs.panel.replaceChildren(next);
894
+ });
895
+
896
+ await tx.finished;
897
+ ```
898
+
899
+ Fallback behavior:
900
+
901
+ - If View Transitions are unsupported, the callback runs directly.
902
+ - If `prefers-reduced-motion: reduce` matches, the callback runs directly.
903
+ - The returned object has `finished`, `updateCallbackDone`, `ready`, and `skipTransition`.
904
+
905
+ For router containers, prefer `swapView`.
906
+
907
+ ## 24. Template Helper
908
+
909
+ `ui.template` creates a cached fragment from a tagged template literal.
910
+
911
+ ```javascript
912
+ const row = ui.template`
913
+ <li class="row">
914
+ <span ref="label"></span>
915
+ </li>
916
+ `;
917
+
918
+ row.querySelector('[ref="label"]').textContent = label;
919
+ refs.list.appendChild(row);
920
+ ```
921
+
922
+ Interpolated values are intentionally ignored.
923
+
924
+ ```javascript
925
+ const bad = ui.template`<span>${label}</span>`;
926
+ ```
927
+
928
+ Use DOM APIs after cloning:
929
+
930
+ ```javascript
931
+ const item = ui.template`<li><span class="label"></span></li>`;
932
+ item.querySelector('.label').textContent = label;
933
+ ```
934
+
935
+ Rules:
936
+
937
+ - Use static markup inside the tagged template.
938
+ - Do not put user data into template strings.
939
+ - Clone first, then assign dynamic data with DOM APIs.
940
+
941
+ ## 25. Template Scanning
942
+
943
+ The Rust tool scans component HTML and emits `.tags.json` descriptors used for refs and tag prewarming.
944
+
945
+ Commands:
946
+
947
+ ```bash
948
+ anza --src src --build
949
+ anza --src src --port 3000
950
+ anza scan --src src
951
+ anza scan --src src --watch
952
+ anza build --src src --dist dist
953
+ anza dev --src src --port 3000
954
+ ```
955
+
956
+ Descriptor shape:
957
+
958
+ ```json
959
+ {
960
+ "version": 1,
961
+ "refs": ["button", "status"],
962
+ "ids": ["button"],
963
+ "classes": ["btn", "status"],
964
+ "tags": ["button", "span"],
965
+ "compound": ["button.btn", "span.status"],
966
+ "attrs": ["type", "hidden"],
967
+ "refTypes": {
968
+ "button": "HTMLButtonElement",
969
+ "status": "HTMLSpanElement"
970
+ }
971
+ }
972
+ ```
973
+
974
+ Rules:
975
+
976
+ - Descriptors are optional at runtime.
977
+ - If a descriptor is missing, refs are discovered by scanning `[ref]`.
978
+ - Malformed descriptor fields are ignored safely.
979
+ - Keep descriptors committed when generated for library components.
980
+ - Regenerate descriptors after template structure changes.
981
+
982
+ The toolchain also scans `ui.element` specs and emits `routes.json` for declarative routes.
983
+
984
+ ```json
985
+ {
986
+ "version": 1,
987
+ "routes": [
988
+ {
989
+ "tag": "page-member",
990
+ "path": "/members/:member",
991
+ "container": "main",
992
+ "params": ["member"]
993
+ }
994
+ ]
995
+ }
996
+ ```
997
+
998
+ ## 26. Typing Components
999
+
1000
+ The package exports TypeScript declarations for strict component contexts.
1001
+
1002
+ ```typescript
1003
+ import { ui, type MountContext, type UpdateContext } from '@adukiorg/anza/ui';
1004
+
1005
+ interface Refs {
1006
+ button: HTMLButtonElement;
1007
+ status: HTMLSpanElement;
1008
+ }
1009
+
1010
+ const props = {
1011
+ count: { type: Number, default: 0 },
1012
+ open: { type: Boolean, default: false },
1013
+ label: { type: String, default: '' }
1014
+ };
1015
+
1016
+ ui.element<'ui-typed', typeof props, Refs>('ui-typed', {
1017
+ props,
1018
+
1019
+ mount({ el, refs, tags, on, watch }) {
1020
+ el.count.toFixed();
1021
+ refs.button.disabled = true;
1022
+
1023
+ const input = tags.one<HTMLInputElement>('input');
1024
+ input?.value.trim();
1025
+
1026
+ on.click<HTMLButtonElement>('button', (event, button) => {
1027
+ event.clientX.toFixed();
1028
+ button.disabled = false;
1029
+ });
1030
+
1031
+ watch.attr(refs.button, 'disabled', (_attr, next, prev, button) => {
1032
+ button.disabled = next !== null;
1033
+ });
1034
+ },
1035
+
1036
+ update(ctx) {
1037
+ if (ctx.name === 'count') {
1038
+ ctx.val.toFixed();
1039
+ }
1040
+
1041
+ if (ctx.name === 'label') {
1042
+ ctx.val.trim();
1043
+ }
1044
+ }
1045
+ });
1046
+
1047
+ declare const mount: MountContext<typeof props, Refs>;
1048
+ mount.refs.button.disabled = false;
1049
+
1050
+ declare const update: UpdateContext<typeof props, Refs>;
1051
+ if (update.name === 'open') {
1052
+ update.val.valueOf();
1053
+ }
1054
+ ```
1055
+
1056
+ For deeper type details, see `src/core/ui/ui.types.md`.
1057
+
1058
+ ## 27. Security
1059
+
1060
+ - Avoid `innerHTML` for dynamic content.
1061
+ - Use `textContent` for text.
1062
+ - Use properties, attributes, and class lists for state.
1063
+ - Validate tag names before creating dynamic elements.
1064
+ - Dispatch outward with composed custom events when the event must cross shadow boundaries.
1065
+ - Treat slotted content as external input.
1066
+
1067
+ Safe custom event:
1068
+
1069
+ ```javascript
1070
+ el.dispatchEvent(new CustomEvent('activate', {
1071
+ bubbles: true,
1072
+ composed: true,
1073
+ detail: { value: el.value }
1074
+ }));
1075
+ ```
1076
+
1077
+ ## 28. Performance
1078
+
1079
+ - Prefer `refs` for stable anchors.
1080
+ - Prefer `tags` over repeated `shadowRoot.querySelector`.
1081
+ - Prefer delegated `on` over repeated child listeners.
1082
+ - Prefer `watch` over ad hoc `MutationObserver` inside shadow roots.
1083
+ - Use `ui.schedule` for expensive non-visual work.
1084
+ - Use `ui.scheduleFrame` for frame-synced DOM writes.
1085
+ - Use `ui.yield` in long loops.
1086
+ - Keep update handlers small and branch by `name`.
1087
+ - Clear `tags` after structural changes not covered by direct shadow child invalidation.
1088
+
1089
+ ## 29. Escape Hatches
1090
+
1091
+ Use platform APIs directly when they are clearer.
1092
+
1093
+ ```javascript
1094
+ refs.input.focus();
1095
+ refs.dialog.showModal();
1096
+ refs.video.play();
1097
+ refs.scroller.scrollTo({ top: 0, behavior: 'smooth' });
1098
+ ```
1099
+
1100
+ Rules:
1101
+
1102
+ - Keep cleanup tied to `ctrl.signal`.
1103
+ - Keep DOM writes explicit.
1104
+ - Do not hide heavy work in property setters.
1105
+ - Do not rely on undocumented spec fields.
1106
+
1107
+ ## 30. Checklist
1108
+
1109
+ - Use `ui.element` for components and pages.
1110
+ - Use `ui.container` only for router-owned layout slots.
1111
+ - Use `ui.define` for hand-written element classes.
1112
+ - Keep each component's `index.js`, `index.html`, and `style.css` together.
1113
+ - Pass `import.meta.url` for relative template or style assets.
1114
+ - Prefer `refs` for stable anchors.
1115
+ - Prefer `tags` for cached selector access.
1116
+ - Prefer `on` for delegated shadow-root events.
1117
+ - Use `passive: false` when a delegated handler calls `preventDefault()`.
1118
+ - Prefer `watch` for shadow-root mutations.
1119
+ - Prefer `ui.observe` for raw platform observers.
1120
+ - Use scheduler helpers for heavy or deferred work.
1121
+ - Keep cleanup tied to `ctrl.signal`.
1122
+ - Use `textContent` and properties for dynamic data.
1123
+ - Avoid `innerHTML` for user data.
1124
+ - Dispatch outward with `CustomEvent`.