snice 2.5.4 → 3.1.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 (323) hide show
  1. package/README.md +501 -882
  2. package/bin/templates/base/src/components/counter-button.ts +13 -26
  3. package/bin/templates/base/src/controllers/counter-controller.ts +3 -3
  4. package/dist/components/accordion/snice-accordion-item.d.ts +4 -5
  5. package/dist/components/accordion/snice-accordion-item.js +37 -39
  6. package/dist/components/accordion/snice-accordion-item.js.map +1 -1
  7. package/dist/components/accordion/snice-accordion.d.ts +5 -11
  8. package/dist/components/accordion/snice-accordion.js +51 -52
  9. package/dist/components/accordion/snice-accordion.js.map +1 -1
  10. package/dist/components/alert/snice-alert.d.ts +2 -6
  11. package/dist/components/alert/snice-alert.js +41 -56
  12. package/dist/components/alert/snice-alert.js.map +1 -1
  13. package/dist/components/avatar/snice-avatar.d.ts +2 -6
  14. package/dist/components/avatar/snice-avatar.js +64 -71
  15. package/dist/components/avatar/snice-avatar.js.map +1 -1
  16. package/dist/components/badge/snice-badge.d.ts +2 -3
  17. package/dist/components/badge/snice-badge.js +22 -23
  18. package/dist/components/badge/snice-badge.js.map +1 -1
  19. package/dist/components/breadcrumbs/snice-breadcrumbs.d.ts +5 -12
  20. package/dist/components/breadcrumbs/snice-breadcrumbs.js +88 -89
  21. package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -1
  22. package/dist/components/button/snice-button.d.ts +3 -7
  23. package/dist/components/button/snice-button.js +37 -58
  24. package/dist/components/button/snice-button.js.map +1 -1
  25. package/dist/components/card/snice-card.d.ts +5 -8
  26. package/dist/components/card/snice-card.js +71 -56
  27. package/dist/components/card/snice-card.js.map +1 -1
  28. package/dist/components/checkbox/snice-checkbox.d.ts +4 -13
  29. package/dist/components/checkbox/snice-checkbox.js +66 -137
  30. package/dist/components/checkbox/snice-checkbox.js.map +1 -1
  31. package/dist/components/chip/snice-chip.d.ts +5 -11
  32. package/dist/components/chip/snice-chip.js +44 -47
  33. package/dist/components/chip/snice-chip.js.map +1 -1
  34. package/dist/components/date-picker/snice-date-picker.d.ts +11 -11
  35. package/dist/components/date-picker/snice-date-picker.js +134 -133
  36. package/dist/components/date-picker/snice-date-picker.js.map +1 -1
  37. package/dist/components/divider/snice-divider.d.ts +2 -4
  38. package/dist/components/divider/snice-divider.js +14 -22
  39. package/dist/components/divider/snice-divider.js.map +1 -1
  40. package/dist/components/drawer/snice-drawer.d.ts +4 -4
  41. package/dist/components/drawer/snice-drawer.js +25 -19
  42. package/dist/components/drawer/snice-drawer.js.map +1 -1
  43. package/dist/components/input/snice-input.d.ts +8 -6
  44. package/dist/components/input/snice-input.js +122 -105
  45. package/dist/components/input/snice-input.js.map +1 -1
  46. package/dist/components/layout/snice-layout-blog.d.ts +4 -4
  47. package/dist/components/layout/snice-layout-blog.js +21 -19
  48. package/dist/components/layout/snice-layout-blog.js.map +1 -1
  49. package/dist/components/layout/snice-layout-card.d.ts +2 -2
  50. package/dist/components/layout/snice-layout-card.js +16 -9
  51. package/dist/components/layout/snice-layout-card.js.map +1 -1
  52. package/dist/components/layout/snice-layout-centered.d.ts +2 -2
  53. package/dist/components/layout/snice-layout-centered.js +14 -7
  54. package/dist/components/layout/snice-layout-centered.js.map +1 -1
  55. package/dist/components/layout/snice-layout-dashboard.d.ts +5 -5
  56. package/dist/components/layout/snice-layout-dashboard.js +38 -30
  57. package/dist/components/layout/snice-layout-dashboard.js.map +1 -1
  58. package/dist/components/layout/snice-layout-fullscreen.d.ts +2 -2
  59. package/dist/components/layout/snice-layout-fullscreen.js +17 -10
  60. package/dist/components/layout/snice-layout-fullscreen.js.map +1 -1
  61. package/dist/components/layout/snice-layout-landing.d.ts +4 -4
  62. package/dist/components/layout/snice-layout-landing.js +21 -19
  63. package/dist/components/layout/snice-layout-landing.js.map +1 -1
  64. package/dist/components/layout/snice-layout-minimal.d.ts +2 -2
  65. package/dist/components/layout/snice-layout-minimal.js +17 -6
  66. package/dist/components/layout/snice-layout-minimal.js.map +1 -1
  67. package/dist/components/layout/snice-layout-sidebar.d.ts +5 -4
  68. package/dist/components/layout/snice-layout-sidebar.js +42 -20
  69. package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
  70. package/dist/components/layout/snice-layout-split.d.ts +2 -2
  71. package/dist/components/layout/snice-layout-split.js +14 -7
  72. package/dist/components/layout/snice-layout-split.js.map +1 -1
  73. package/dist/components/layout/snice-layout.d.ts +4 -4
  74. package/dist/components/layout/snice-layout.js +16 -10
  75. package/dist/components/layout/snice-layout.js.map +1 -1
  76. package/dist/components/login/snice-login.d.ts +6 -11
  77. package/dist/components/login/snice-login.js +97 -71
  78. package/dist/components/login/snice-login.js.map +1 -1
  79. package/dist/components/modal/snice-modal.d.ts +5 -9
  80. package/dist/components/modal/snice-modal.js +47 -78
  81. package/dist/components/modal/snice-modal.js.map +1 -1
  82. package/dist/components/nav/snice-nav.d.ts +13 -7
  83. package/dist/components/nav/snice-nav.js +191 -100
  84. package/dist/components/nav/snice-nav.js.map +1 -1
  85. package/dist/components/nav/snice-nav.types.d.ts +3 -3
  86. package/dist/components/pagination/snice-pagination.d.ts +6 -7
  87. package/dist/components/pagination/snice-pagination.js +94 -81
  88. package/dist/components/pagination/snice-pagination.js.map +1 -1
  89. package/dist/components/progress/snice-progress.d.ts +2 -7
  90. package/dist/components/progress/snice-progress.js +41 -98
  91. package/dist/components/progress/snice-progress.js.map +1 -1
  92. package/dist/components/radio/snice-radio.d.ts +4 -4
  93. package/dist/components/radio/snice-radio.js +52 -44
  94. package/dist/components/radio/snice-radio.js.map +1 -1
  95. package/dist/components/select/snice-option.d.ts +2 -1
  96. package/dist/components/select/snice-option.js +12 -5
  97. package/dist/components/select/snice-option.js.map +1 -1
  98. package/dist/components/select/snice-select.d.ts +9 -21
  99. package/dist/components/select/snice-select.js +98 -170
  100. package/dist/components/select/snice-select.js.map +1 -1
  101. package/dist/components/skeleton/snice-skeleton.d.ts +2 -6
  102. package/dist/components/skeleton/snice-skeleton.js +18 -49
  103. package/dist/components/skeleton/snice-skeleton.js.map +1 -1
  104. package/dist/components/snice-cell-BLFVdxPp.js +4 -0
  105. package/dist/components/snice-cell-BLFVdxPp.js.map +1 -0
  106. package/dist/components/switch/snice-switch.d.ts +2 -2
  107. package/dist/components/switch/snice-switch.js +38 -26
  108. package/dist/components/switch/snice-switch.js.map +1 -1
  109. package/dist/components/table/snice-cell-actions.d.ts +24 -0
  110. package/dist/components/table/snice-cell-actions.js +149 -0
  111. package/dist/components/table/snice-cell-actions.js.map +1 -0
  112. package/dist/components/table/snice-cell-boolean.d.ts +2 -2
  113. package/dist/components/table/snice-cell-boolean.js +13 -7
  114. package/dist/components/table/snice-cell-boolean.js.map +1 -1
  115. package/dist/components/table/snice-cell-color.d.ts +18 -0
  116. package/dist/components/table/snice-cell-color.js +149 -0
  117. package/dist/components/table/snice-cell-color.js.map +1 -0
  118. package/dist/components/table/snice-cell-currency.d.ts +24 -0
  119. package/dist/components/table/snice-cell-currency.js +235 -0
  120. package/dist/components/table/snice-cell-currency.js.map +1 -0
  121. package/dist/components/table/snice-cell-date.d.ts +2 -2
  122. package/dist/components/table/snice-cell-date.js +14 -8
  123. package/dist/components/table/snice-cell-date.js.map +1 -1
  124. package/dist/components/table/snice-cell-duration.d.ts +2 -2
  125. package/dist/components/table/snice-cell-duration.js +12 -6
  126. package/dist/components/table/snice-cell-duration.js.map +1 -1
  127. package/dist/components/table/snice-cell-email.d.ts +15 -0
  128. package/dist/components/table/snice-cell-email.js +125 -0
  129. package/dist/components/table/snice-cell-email.js.map +1 -0
  130. package/dist/components/table/snice-cell-filesize.d.ts +2 -2
  131. package/dist/components/table/snice-cell-filesize.js +12 -6
  132. package/dist/components/table/snice-cell-filesize.js.map +1 -1
  133. package/dist/components/table/snice-cell-image.d.ts +20 -0
  134. package/dist/components/table/snice-cell-image.js +162 -0
  135. package/dist/components/table/snice-cell-image.js.map +1 -0
  136. package/dist/components/table/snice-cell-json.d.ts +20 -0
  137. package/dist/components/table/snice-cell-json.js +186 -0
  138. package/dist/components/table/snice-cell-json.js.map +1 -0
  139. package/dist/components/table/snice-cell-link.d.ts +17 -0
  140. package/dist/components/table/snice-cell-link.js +142 -0
  141. package/dist/components/table/snice-cell-link.js.map +1 -0
  142. package/dist/components/table/snice-cell-location.d.ts +19 -0
  143. package/dist/components/table/snice-cell-location.js +185 -0
  144. package/dist/components/table/snice-cell-location.js.map +1 -0
  145. package/dist/components/table/snice-cell-number.d.ts +2 -2
  146. package/dist/components/table/snice-cell-number.js +12 -6
  147. package/dist/components/table/snice-cell-number.js.map +1 -1
  148. package/dist/components/table/snice-cell-percentage.d.ts +22 -0
  149. package/dist/components/table/snice-cell-percentage.js +208 -0
  150. package/dist/components/table/snice-cell-percentage.js.map +1 -0
  151. package/dist/components/table/snice-cell-phone.d.ts +18 -0
  152. package/dist/components/table/snice-cell-phone.js +153 -0
  153. package/dist/components/table/snice-cell-phone.js.map +1 -0
  154. package/dist/components/table/snice-cell-progress.d.ts +2 -2
  155. package/dist/components/table/snice-cell-progress.js +12 -6
  156. package/dist/components/table/snice-cell-progress.js.map +1 -1
  157. package/dist/components/table/snice-cell-rating.d.ts +2 -2
  158. package/dist/components/table/snice-cell-rating.js +12 -6
  159. package/dist/components/table/snice-cell-rating.js.map +1 -1
  160. package/dist/components/table/snice-cell-sparkline.d.ts +2 -2
  161. package/dist/components/table/snice-cell-sparkline.js +13 -7
  162. package/dist/components/table/snice-cell-sparkline.js.map +1 -1
  163. package/dist/components/table/snice-cell-status.d.ts +17 -0
  164. package/dist/components/table/snice-cell-status.js +144 -0
  165. package/dist/components/table/snice-cell-status.js.map +1 -0
  166. package/dist/components/table/snice-cell-tag.d.ts +16 -0
  167. package/dist/components/table/snice-cell-tag.js +131 -0
  168. package/dist/components/table/snice-cell-tag.js.map +1 -0
  169. package/dist/components/table/snice-cell-text.d.ts +2 -2
  170. package/dist/components/table/snice-cell-text.js +14 -8
  171. package/dist/components/table/snice-cell-text.js.map +1 -1
  172. package/dist/components/table/snice-cell.d.ts +2 -2
  173. package/dist/components/table/snice-cell.js +12 -6
  174. package/dist/components/table/snice-cell.js.map +1 -1
  175. package/dist/components/table/snice-column.d.ts +1 -1
  176. package/dist/components/table/snice-column.js +6 -3
  177. package/dist/components/table/snice-column.js.map +1 -1
  178. package/dist/components/table/snice-header.d.ts +5 -5
  179. package/dist/components/table/snice-header.js +60 -50
  180. package/dist/components/table/snice-header.js.map +1 -1
  181. package/dist/components/table/snice-progress.d.ts +2 -2
  182. package/dist/components/table/snice-progress.js +18 -11
  183. package/dist/components/table/snice-progress.js.map +1 -1
  184. package/dist/components/table/snice-rating.d.ts +2 -2
  185. package/dist/components/table/snice-rating.js +15 -8
  186. package/dist/components/table/snice-rating.js.map +1 -1
  187. package/dist/components/table/snice-row.d.ts +17 -6
  188. package/dist/components/table/snice-row.js +95 -44
  189. package/dist/components/table/snice-row.js.map +1 -1
  190. package/dist/components/table/snice-table.d.ts +18 -10
  191. package/dist/components/table/snice-table.js +355 -173
  192. package/dist/components/table/snice-table.js.map +1 -1
  193. package/dist/components/table/snice-table.types.d.ts +101 -2
  194. package/dist/components/tabs/snice-tab-panel.d.ts +2 -2
  195. package/dist/components/tabs/snice-tab-panel.js +12 -6
  196. package/dist/components/tabs/snice-tab-panel.js.map +1 -1
  197. package/dist/components/tabs/snice-tab.d.ts +6 -5
  198. package/dist/components/tabs/snice-tab.js +36 -19
  199. package/dist/components/tabs/snice-tab.js.map +1 -1
  200. package/dist/components/tabs/snice-tabs.d.ts +5 -5
  201. package/dist/components/tabs/snice-tabs.js +38 -28
  202. package/dist/components/tabs/snice-tabs.js.map +1 -1
  203. package/dist/components/toast/snice-toast-container.d.ts +7 -7
  204. package/dist/components/toast/snice-toast-container.js +19 -12
  205. package/dist/components/toast/snice-toast-container.js.map +1 -1
  206. package/dist/components/toast/snice-toast.d.ts +3 -15
  207. package/dist/components/toast/snice-toast.js +49 -108
  208. package/dist/components/toast/snice-toast.js.map +1 -1
  209. package/dist/components/tooltip/snice-tooltip.d.ts +2 -2
  210. package/dist/components/tooltip/snice-tooltip.js +14 -7
  211. package/dist/components/tooltip/snice-tooltip.js.map +1 -1
  212. package/dist/context.d.ts +44 -0
  213. package/dist/element-ready.d.ts +40 -0
  214. package/dist/{types/element.d.ts → element.d.ts} +2 -8
  215. package/dist/{types/events.d.ts → events.d.ts} +0 -4
  216. package/dist/index.cjs +2589 -605
  217. package/dist/index.cjs.map +1 -1
  218. package/dist/index.d.ts +21 -0
  219. package/dist/index.esm.js +2568 -604
  220. package/dist/index.esm.js.map +1 -1
  221. package/dist/index.iife.js +2589 -605
  222. package/dist/index.iife.js.map +1 -1
  223. package/dist/method-decorators.d.ts +121 -0
  224. package/dist/on.d.ts +59 -0
  225. package/dist/parts.d.ts +159 -0
  226. package/dist/render-debug.d.ts +27 -0
  227. package/dist/render-tracker.d.ts +14 -0
  228. package/dist/render.d.ts +96 -0
  229. package/dist/symbols.cjs +163 -0
  230. package/dist/symbols.cjs.map +1 -1
  231. package/dist/{types/symbols.d.ts → symbols.d.ts} +22 -0
  232. package/dist/symbols.esm.js +27 -3
  233. package/dist/symbols.esm.js.map +1 -1
  234. package/dist/template.d.ts +100 -0
  235. package/dist/transitions.cjs +219 -0
  236. package/dist/transitions.esm.js +2 -2
  237. package/dist/types/context.d.ts +48 -0
  238. package/dist/types/element-options.d.ts +26 -0
  239. package/dist/types/index.d.ts +25 -9
  240. package/dist/types/nav-context.d.ts +19 -0
  241. package/dist/types/{types/on-options.d.ts → on-options.d.ts} +2 -0
  242. package/dist/types/{types/placard.d.ts → placard.d.ts} +0 -1
  243. package/docs/ai/README.md +17 -0
  244. package/docs/ai/api.md +175 -0
  245. package/docs/ai/architecture.md +160 -0
  246. package/docs/ai/components/accordion.md +174 -0
  247. package/docs/ai/components/alert.md +77 -0
  248. package/docs/ai/components/avatar.md +61 -0
  249. package/docs/ai/components/badge.md +69 -0
  250. package/docs/ai/components/breadcrumbs.md +74 -0
  251. package/docs/ai/components/button.md +75 -0
  252. package/docs/ai/components/card.md +61 -0
  253. package/docs/ai/components/checkbox.md +74 -0
  254. package/docs/ai/components/chip.md +73 -0
  255. package/docs/ai/components/date-picker.md +75 -0
  256. package/docs/ai/components/divider.md +66 -0
  257. package/docs/ai/components/drawer.md +80 -0
  258. package/docs/ai/components/input.md +111 -0
  259. package/docs/ai/components/login.md +109 -0
  260. package/docs/ai/components/modal.md +67 -0
  261. package/docs/ai/components/nav.md +76 -0
  262. package/docs/ai/components/pagination.md +55 -0
  263. package/docs/ai/components/progress.md +72 -0
  264. package/docs/ai/components/radio.md +79 -0
  265. package/docs/ai/components/select.md +92 -0
  266. package/docs/ai/components/skeleton.md +57 -0
  267. package/docs/ai/components/switch.md +53 -0
  268. package/docs/ai/components/table.md +227 -0
  269. package/docs/ai/components/tabs.md +83 -0
  270. package/docs/ai/components/toast.md +140 -0
  271. package/docs/ai/components/tooltip.md +146 -0
  272. package/docs/ai/patterns.md +244 -0
  273. package/docs/components/accordion.md +558 -0
  274. package/docs/components/drawer.md +602 -0
  275. package/docs/components/modal.md +558 -0
  276. package/docs/components/nav.md +239 -0
  277. package/docs/components/pagination.md +289 -0
  278. package/docs/components/select.md +599 -0
  279. package/docs/components/switch.md +354 -0
  280. package/docs/components/tabs.md +546 -0
  281. package/docs/components/toast.md +506 -0
  282. package/docs/components/tooltip.md +523 -0
  283. package/docs/controllers.md +744 -0
  284. package/docs/elements.md +855 -0
  285. package/docs/events.md +807 -0
  286. package/docs/migration-v2-to-v3.md +569 -0
  287. package/docs/observe.md +588 -0
  288. package/docs/placards.md +401 -0
  289. package/docs/request-response.md +852 -0
  290. package/docs/routing.md +1186 -0
  291. package/package.json +10 -11
  292. package/dist/components/snice-cell-C9N6yGxQ.js +0 -4
  293. package/dist/components/snice-cell-C9N6yGxQ.js.map +0 -1
  294. package/dist/types/types/index.d.ts +0 -23
  295. /package/dist/{types/controller.d.ts → controller.d.ts} +0 -0
  296. /package/dist/{types/global.d.ts → global.d.ts} +0 -0
  297. /package/dist/{types/observe.d.ts → observe.d.ts} +0 -0
  298. /package/dist/{types/request-response.d.ts → request-response.d.ts} +0 -0
  299. /package/dist/{types/router.d.ts → router.d.ts} +0 -0
  300. /package/dist/{types/testing.d.ts → testing.d.ts} +0 -0
  301. /package/dist/{types/transitions.d.ts → transitions.d.ts} +0 -0
  302. /package/dist/types/{types/adopted-options.d.ts → adopted-options.d.ts} +0 -0
  303. /package/dist/types/{types/app-context.d.ts → app-context.d.ts} +0 -0
  304. /package/dist/types/{types/dispatch-options.d.ts → dispatch-options.d.ts} +0 -0
  305. /package/dist/types/{types/guard.d.ts → guard.d.ts} +0 -0
  306. /package/dist/types/{types/i-controller.d.ts → i-controller.d.ts} +0 -0
  307. /package/dist/types/{types/moved-options.d.ts → moved-options.d.ts} +0 -0
  308. /package/dist/types/{types/observe-options.d.ts → observe-options.d.ts} +0 -0
  309. /package/dist/types/{types/page-options.d.ts → page-options.d.ts} +0 -0
  310. /package/dist/types/{types/part-options.d.ts → part-options.d.ts} +0 -0
  311. /package/dist/types/{types/property-converter.d.ts → property-converter.d.ts} +0 -0
  312. /package/dist/types/{types/property-options.d.ts → property-options.d.ts} +0 -0
  313. /package/dist/types/{types/query-options.d.ts → query-options.d.ts} +0 -0
  314. /package/dist/types/{types/request-options.d.ts → request-options.d.ts} +0 -0
  315. /package/dist/types/{types/respond-options.d.ts → respond-options.d.ts} +0 -0
  316. /package/dist/types/{types/route-params.d.ts → route-params.d.ts} +0 -0
  317. /package/dist/types/{types/router-instance.d.ts → router-instance.d.ts} +0 -0
  318. /package/dist/types/{types/router-options.d.ts → router-options.d.ts} +0 -0
  319. /package/dist/types/{types/simple-array.d.ts → simple-array.d.ts} +0 -0
  320. /package/dist/types/{types/snice-element.d.ts → snice-element.d.ts} +0 -0
  321. /package/dist/types/{types/snice-global.d.ts → snice-global.d.ts} +0 -0
  322. /package/dist/types/{types/transition.d.ts → transition.d.ts} +0 -0
  323. /package/dist/{types/utils.d.ts → utils.d.ts} +0 -0
@@ -0,0 +1,1186 @@
1
+ # Routing API Documentation
2
+
3
+ Snice provides a powerful routing system for single-page applications with support for hash and pushstate routing, page transitions, route parameters, guards, and layouts.
4
+
5
+ ## Table of Contents
6
+ - [Router Setup](#router-setup)
7
+ - [Page Components](#page-components)
8
+ - [Route Configuration](#route-configuration)
9
+ - [Navigation](#navigation)
10
+ - [Route Parameters](#route-parameters)
11
+ - [Page Transitions](#page-transitions)
12
+ - [Route Guards](#route-guards)
13
+ - [Layouts](#layouts)
14
+ - [Advanced Patterns](#advanced-patterns)
15
+
16
+ ## Router Setup
17
+
18
+ ### Creating a Router
19
+
20
+ ```typescript
21
+ import { Router } from 'snice';
22
+
23
+ const router = Router({
24
+ target: '#app', // Target element selector
25
+ type: 'hash' // 'hash' or 'pushstate'
26
+ });
27
+
28
+ // Destructure router methods
29
+ const { page, initialize, navigate } = router;
30
+ ```
31
+
32
+ ### Router Options
33
+
34
+ ```typescript
35
+ interface RouterOptions<T = any> {
36
+ target: string; // Target element selector
37
+ type: 'hash' | 'pushstate'; // Routing type
38
+ window?: Window; // Override window object (for testing)
39
+ document?: Document; // Override document object (for testing)
40
+ transition?: Transition; // Global transition config
41
+ layout?: string; // Default layout for all pages
42
+ context?: T; // Router context object (shared state)
43
+ }
44
+ ```
45
+
46
+ ### Router Context
47
+
48
+ The context object provides shared state across all pages and layouts:
49
+
50
+ ```typescript
51
+ // app-context.ts
52
+ class AppContext {
53
+ user: User | null = null;
54
+ theme: 'light' | 'dark' = 'light';
55
+
56
+ setUser(user: User) {
57
+ this.user = user;
58
+ }
59
+
60
+ getUser() {
61
+ return this.user;
62
+ }
63
+ }
64
+
65
+ // main.ts
66
+ const { page, initialize } = Router({
67
+ target: '#app',
68
+ type: 'hash',
69
+ context: new AppContext()
70
+ });
71
+ ```
72
+
73
+ ## Page Components
74
+
75
+ ### Basic Page
76
+
77
+ ```typescript
78
+ import { page, render, html, styles, css } from 'snice';
79
+
80
+ @page({ tag: 'home-page', routes: ['/'] })
81
+ class HomePage extends HTMLElement {
82
+ @render()
83
+ renderContent() {
84
+ return html`
85
+ <div class="home">
86
+ <h1>Welcome Home</h1>
87
+ <nav>
88
+ <a href="#/about">About</a>
89
+ <a href="#/contact">Contact</a>
90
+ </nav>
91
+ </div>
92
+ `;
93
+ }
94
+
95
+ @styles()
96
+ homeStyles() {
97
+ return css`
98
+ .home {
99
+ padding: 20px;
100
+ text-align: center;
101
+ }
102
+
103
+ nav a {
104
+ margin: 0 10px;
105
+ color: blue;
106
+ text-decoration: none;
107
+ }
108
+
109
+ nav a:hover {
110
+ text-decoration: underline;
111
+ }
112
+ `;
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Page with Context
118
+
119
+ The `@context()` decorator is a **method decorator** that receives context updates from the router. The method is called whenever navigation occurs, with a Context object containing application state and navigation data.
120
+
121
+ ```typescript
122
+ import { page, context, render, html, Context } from 'snice';
123
+
124
+ @page({ tag: 'profile-page', routes: ['/profile'] })
125
+ class ProfilePage extends HTMLElement {
126
+ private appContext?: AppContext;
127
+
128
+ @context()
129
+ handleContextUpdate(ctx: Context) {
130
+ // ctx.application is your router context (AppContext)
131
+ this.appContext = ctx.application;
132
+ // ctx.navigation contains { placards, route, params }
133
+ this.requestRender();
134
+ }
135
+
136
+ @render()
137
+ renderContent() {
138
+ const user = this.appContext?.getUser();
139
+
140
+ if (!user) {
141
+ return html`
142
+ <div>
143
+ <p>Please log in to view your profile</p>
144
+ <a href="#/login">Login</a>
145
+ </div>
146
+ `;
147
+ }
148
+
149
+ return html`
150
+ <div class="profile">
151
+ <h1>Profile: ${user.name}</h1>
152
+ <p>Email: ${user.email}</p>
153
+ <button @click=${this.logout}>Logout</button>
154
+ </div>
155
+ `;
156
+ }
157
+
158
+ logout() {
159
+ this.appContext?.setUser(null);
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Context Options
165
+
166
+ The `@context()` decorator accepts optional timing and behavior controls:
167
+
168
+ ```typescript
169
+ @page({ tag: 'dashboard-page', routes: ['/dashboard'] })
170
+ class DashboardPage extends HTMLElement {
171
+ private appContext?: AppContext;
172
+
173
+ // Called immediately on every navigation
174
+ @context()
175
+ handleContext(ctx: Context) {
176
+ this.appContext = ctx.application;
177
+ this.requestRender();
178
+ }
179
+
180
+ // Debounce: Wait 300ms after last update before calling
181
+ @context({ debounce: 300 })
182
+ handleContextDebounced(ctx: Context) {
183
+ // Useful for expensive operations
184
+ this.updateExpensiveCalculation(ctx);
185
+ }
186
+
187
+ // Throttle: Call at most once per 100ms
188
+ @context({ throttle: 100 })
189
+ handleContextThrottled(ctx: Context) {
190
+ // Useful for frequent updates
191
+ this.updateAnimation(ctx);
192
+ }
193
+
194
+ // Once: Call only once, then unregister
195
+ @context({ once: true })
196
+ handleContextOnce(ctx: Context) {
197
+ // Useful for one-time initialization
198
+ this.initializeFromContext(ctx);
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Context Object Structure
204
+
205
+ The Context object passed to `@context()` methods has the following structure:
206
+
207
+ ```typescript
208
+ interface Context<T = any> {
209
+ application: T; // Your router context (e.g., AppContext)
210
+ navigation: {
211
+ placards: Placard[]; // All page placards
212
+ route: string; // Current route name
213
+ params: Record<string, string>; // Route parameters
214
+ };
215
+ update(): void; // Notify all subscribers of changes
216
+ }
217
+ ```
218
+
219
+ **Example:**
220
+
221
+ ```typescript
222
+ @page({ tag: 'user-page', routes: ['/users/:userId'] })
223
+ class UserPage extends HTMLElement {
224
+ private ctx?: Context<AppContext>;
225
+
226
+ @context()
227
+ handleContext(ctx: Context<AppContext>) {
228
+ this.ctx = ctx;
229
+
230
+ // Access application state
231
+ const currentUser = ctx.application.getUser();
232
+
233
+ // Access navigation data
234
+ const userId = ctx.navigation.params.userId;
235
+ const currentRoute = ctx.navigation.route;
236
+ const allPlacards = ctx.navigation.placards;
237
+
238
+ // Use this data
239
+ this.loadUserData(userId, currentUser);
240
+ }
241
+ }
242
+ ```
243
+
244
+ ### Triggering Context Updates
245
+
246
+ When you modify the application context, call `update()` to notify all subscribers:
247
+
248
+ ```typescript
249
+ @page({ tag: 'settings-page', routes: ['/settings'] })
250
+ class SettingsPage extends HTMLElement {
251
+ private ctx?: Context<AppContext>;
252
+
253
+ @context()
254
+ handleContext(ctx: Context<AppContext>) {
255
+ this.ctx = ctx;
256
+ this.requestRender();
257
+ }
258
+
259
+ changeTheme(theme: 'light' | 'dark') {
260
+ // Modify the application context
261
+ this.ctx!.application.theme = theme;
262
+
263
+ // Notify all @context subscribers
264
+ this.ctx!.update();
265
+ }
266
+ }
267
+ ```
268
+
269
+ **Note:** The router automatically calls `update()` during navigation. Only call it manually when changing application state outside of navigation (login, logout, theme changes, etc.).
270
+
271
+ ## Route Configuration
272
+
273
+ ### @page Decorator Options
274
+
275
+ ```typescript
276
+ interface PageOptions<T = any> {
277
+ tag: string; // Custom element tag name
278
+ routes: string[]; // Route patterns
279
+ transition?: Transition; // Page-specific transition
280
+ guards?: Guard<T> | Guard<T>[]; // Route guards
281
+ placard?: Placard<T>; // Page metadata
282
+ }
283
+ ```
284
+
285
+ ### Multiple Routes
286
+
287
+ ```typescript
288
+ @page({
289
+ tag: 'user-page',
290
+ routes: ['/user', '/users', '/profile']
291
+ })
292
+ class UserPage extends HTMLElement {
293
+ @render()
294
+ renderContent() {
295
+ return html`<h1>User Page</h1>`;
296
+ }
297
+ }
298
+ ```
299
+
300
+ ### Route with Parameters
301
+
302
+ ```typescript
303
+ @page({
304
+ tag: 'user-detail-page',
305
+ routes: ['/users/:userId']
306
+ })
307
+ class UserDetailPage extends HTMLElement {
308
+ @property()
309
+ userId = '';
310
+
311
+ @render()
312
+ renderContent() {
313
+ return html`
314
+ <div>
315
+ <h1>User Details</h1>
316
+ <p>Viewing user: ${this.userId}</p>
317
+ </div>
318
+ `;
319
+ }
320
+ }
321
+ ```
322
+
323
+ ### Multiple Parameters
324
+
325
+ ```typescript
326
+ @page({
327
+ tag: 'post-detail-page',
328
+ routes: ['/users/:userId/posts/:postId']
329
+ })
330
+ class PostDetailPage extends HTMLElement {
331
+ @property()
332
+ userId = '';
333
+
334
+ @property()
335
+ postId = '';
336
+
337
+ @render()
338
+ renderContent() {
339
+ return html`
340
+ <h1>Post ${this.postId} by User ${this.userId}</h1>
341
+ `;
342
+ }
343
+ }
344
+ ```
345
+
346
+ ## Navigation
347
+
348
+ ### Hash Navigation
349
+
350
+ ```typescript
351
+ // In templates
352
+ html`<a href="#/about">About</a>`
353
+
354
+ // Programmatic navigation
355
+ navigate('/about');
356
+
357
+ // With parameters
358
+ navigate('/users/123');
359
+ ```
360
+
361
+ ### Pushstate Navigation
362
+
363
+ ```typescript
364
+ // In templates
365
+ html`<a href="/about">About</a>`
366
+
367
+ // Programmatic navigation using the router instance
368
+ const { navigate } = Router({
369
+ target: '#app',
370
+ type: 'pushstate'
371
+ });
372
+
373
+ navigate('/about');
374
+ ```
375
+
376
+ ### Back/Forward Navigation
377
+
378
+ ```typescript
379
+ // Browser back
380
+ window.history.back();
381
+
382
+ // Browser forward
383
+ window.history.forward();
384
+
385
+ // Go back 2 pages
386
+ window.history.go(-2);
387
+ ```
388
+
389
+ ## Route Parameters
390
+
391
+ ### Accessing Parameters
392
+
393
+ Route parameters are automatically mapped to element properties:
394
+
395
+ ```typescript
396
+ @page({
397
+ tag: 'article-page',
398
+ routes: ['/articles/:articleId']
399
+ })
400
+ class ArticlePage extends HTMLElement {
401
+ @property()
402
+ articleId = '';
403
+
404
+ @ready()
405
+ async loadArticle() {
406
+ // articleId is automatically set from URL
407
+ const article = await fetch(`/api/articles/${this.articleId}`);
408
+ this.article = await article.json();
409
+ }
410
+
411
+ @render()
412
+ renderContent() {
413
+ return html`<h1>Article ${this.articleId}</h1>`;
414
+ }
415
+ }
416
+ ```
417
+
418
+ ### Multiple Parameters
419
+
420
+ ```typescript
421
+ @page({
422
+ tag: 'comment-page',
423
+ routes: ['/posts/:postId/comments/:commentId']
424
+ })
425
+ class CommentPage extends HTMLElement {
426
+ @property()
427
+ postId = '';
428
+
429
+ @property()
430
+ commentId = '';
431
+
432
+ @ready()
433
+ async loadData() {
434
+ // Both postId and commentId are set from URL
435
+ const [post, comment] = await Promise.all([
436
+ fetch(`/api/posts/${this.postId}`).then(r => r.json()),
437
+ fetch(`/api/comments/${this.commentId}`).then(r => r.json())
438
+ ]);
439
+
440
+ this.post = post;
441
+ this.comment = comment;
442
+ }
443
+
444
+ @render()
445
+ renderContent() {
446
+ return html`
447
+ <div>
448
+ <h2>Comment on Post ${this.postId}</h2>
449
+ <p>Comment ID: ${this.commentId}</p>
450
+ </div>
451
+ `;
452
+ }
453
+ }
454
+ ```
455
+
456
+ ### Query Parameters
457
+
458
+ Query parameters are not automatically parsed but can be accessed via URL:
459
+
460
+ ```typescript
461
+ @page({
462
+ tag: 'search-page',
463
+ routes: ['/search']
464
+ })
465
+ class SearchPage extends HTMLElement {
466
+ @property()
467
+ query = '';
468
+
469
+ @property()
470
+ page = 1;
471
+
472
+ @ready()
473
+ parseQueryParams() {
474
+ const params = new URLSearchParams(window.location.search);
475
+ this.query = params.get('q') || '';
476
+ this.page = parseInt(params.get('page') || '1');
477
+ }
478
+
479
+ @render()
480
+ renderContent() {
481
+ return html`
482
+ <div>
483
+ <h1>Search Results for: ${this.query}</h1>
484
+ <p>Page: ${this.page}</p>
485
+ </div>
486
+ `;
487
+ }
488
+ }
489
+ ```
490
+
491
+ ## Page Transitions
492
+
493
+ ### Global Transitions
494
+
495
+ ```typescript
496
+ import { fadeTransition } from 'snice';
497
+
498
+ const router = Router({
499
+ target: '#app',
500
+ type: 'hash',
501
+ transition: fadeTransition
502
+ });
503
+ ```
504
+
505
+ ### Page-Specific Transitions
506
+
507
+ ```typescript
508
+ import { slideTransition } from 'snice';
509
+
510
+ @page({
511
+ tag: 'about-page',
512
+ routes: ['/about'],
513
+ transition: slideTransition
514
+ })
515
+ class AboutPage extends HTMLElement {
516
+ @render()
517
+ renderContent() {
518
+ return html`<h1>About</h1>`;
519
+ }
520
+ }
521
+ ```
522
+
523
+ ### Built-in Transitions
524
+
525
+ ```typescript
526
+ import {
527
+ fadeTransition,
528
+ slideTransition,
529
+ slideLeftTransition,
530
+ slideRightTransition,
531
+ slideUpTransition,
532
+ slideDownTransition,
533
+ scaleTransition
534
+ } from 'snice';
535
+ ```
536
+
537
+ ### Custom Transitions
538
+
539
+ ```typescript
540
+ import { Transition } from 'snice';
541
+
542
+ const customTransition: Transition = {
543
+ name: 'custom',
544
+ duration: 500,
545
+ enterClass: 'page-enter',
546
+ enterActiveClass: 'page-enter-active',
547
+ leaveClass: 'page-leave',
548
+ leaveActiveClass: 'page-leave-active'
549
+ };
550
+
551
+ // CSS for custom transition
552
+ /*
553
+ .page-enter {
554
+ opacity: 0;
555
+ transform: translateY(20px);
556
+ }
557
+
558
+ .page-enter-active {
559
+ transition: all 500ms ease-out;
560
+ }
561
+
562
+ .page-leave {
563
+ opacity: 1;
564
+ }
565
+
566
+ .page-leave-active {
567
+ opacity: 0;
568
+ transition: all 500ms ease-in;
569
+ }
570
+ */
571
+ ```
572
+
573
+ ## Route Guards
574
+
575
+ Guards protect routes and can redirect unauthorized access:
576
+
577
+ ### Basic Guard
578
+
579
+ ```typescript
580
+ import { Guard } from 'snice';
581
+
582
+ const isAuthenticated: Guard<AppContext> = (ctx) => {
583
+ return ctx.getUser() !== null;
584
+ };
585
+
586
+ @page({
587
+ tag: 'dashboard-page',
588
+ routes: ['/dashboard'],
589
+ guards: isAuthenticated
590
+ })
591
+ class DashboardPage extends HTMLElement {
592
+ @render()
593
+ renderContent() {
594
+ return html`<h1>Dashboard</h1>`;
595
+ }
596
+ }
597
+ ```
598
+
599
+ ### Multiple Guards
600
+
601
+ ```typescript
602
+ const hasAdminRole: Guard<AppContext> = (ctx) => {
603
+ const user = ctx.getUser();
604
+ return user?.role === 'admin';
605
+ };
606
+
607
+ @page({
608
+ tag: 'admin-page',
609
+ routes: ['/admin'],
610
+ guards: [isAuthenticated, hasAdminRole]
611
+ })
612
+ class AdminPage extends HTMLElement {
613
+ @render()
614
+ renderContent() {
615
+ return html`<h1>Admin Dashboard</h1>`;
616
+ }
617
+ }
618
+ ```
619
+
620
+ ### Guard with Redirect
621
+
622
+ ```typescript
623
+ const requiresAuth: Guard<AppContext> = (ctx) => {
624
+ const isAuth = ctx.getUser() !== null;
625
+
626
+ if (!isAuth) {
627
+ // Redirect to login page
628
+ setTimeout(() => {
629
+ window.location.hash = '#/login';
630
+ }, 0);
631
+ }
632
+
633
+ return isAuth;
634
+ };
635
+ ```
636
+
637
+ ### Async Guards
638
+
639
+ ```typescript
640
+ const checkPermission: Guard<AppContext> = async (ctx) => {
641
+ const user = ctx.getUser();
642
+ if (!user) return false;
643
+
644
+ // Check with API
645
+ const response = await fetch(`/api/permissions/${user.id}`);
646
+ const permissions = await response.json();
647
+
648
+ return permissions.includes('access_dashboard');
649
+ };
650
+ ```
651
+
652
+ ## Layouts
653
+
654
+ Layouts wrap pages with shared UI like headers, footers, and navigation:
655
+
656
+ ### Creating a Layout
657
+
658
+ ```typescript
659
+ import { layout, render, html, styles, css, Layout } from 'snice';
660
+
661
+ @layout('app-shell')
662
+ class AppShell extends HTMLElement implements Layout {
663
+ private placards: Placard[] = [];
664
+ private currentRoute = '';
665
+
666
+ @render()
667
+ renderContent() {
668
+ return html`
669
+ <div class="app-shell">
670
+ <header>
671
+ <h1>My App</h1>
672
+ <nav>
673
+ ${this.placards
674
+ .filter(p => p.show !== false)
675
+ .map(p => html`
676
+ <a
677
+ href="#/${p.name}"
678
+ class="${this.currentRoute === p.name ? 'active' : ''}"
679
+ >
680
+ ${p.icon || ''} ${p.title}
681
+ </a>
682
+ `)}
683
+ </nav>
684
+ </header>
685
+
686
+ <main>
687
+ <slot name="page"></slot>
688
+ </main>
689
+
690
+ <footer>
691
+ <p>&copy; 2024 My App</p>
692
+ </footer>
693
+ </div>
694
+ `;
695
+ }
696
+
697
+ @styles()
698
+ shellStyles() {
699
+ return css`
700
+ .app-shell {
701
+ display: flex;
702
+ flex-direction: column;
703
+ min-height: 100vh;
704
+ }
705
+
706
+ header {
707
+ background: #333;
708
+ color: white;
709
+ padding: 1rem;
710
+ }
711
+
712
+ nav a {
713
+ color: white;
714
+ margin: 0 1rem;
715
+ text-decoration: none;
716
+ }
717
+
718
+ nav a.active {
719
+ font-weight: bold;
720
+ text-decoration: underline;
721
+ }
722
+
723
+ main {
724
+ flex: 1;
725
+ padding: 2rem;
726
+ }
727
+
728
+ footer {
729
+ background: #f0f0f0;
730
+ padding: 1rem;
731
+ text-align: center;
732
+ }
733
+ `;
734
+ }
735
+
736
+ // Called by router when route changes
737
+ update(appContext: any, placards: Placard[], currentRoute: string, routeParams: any) {
738
+ this.placards = placards;
739
+ this.currentRoute = currentRoute;
740
+ // Property changes trigger re-render
741
+ }
742
+ }
743
+ ```
744
+
745
+ ### Using a Layout
746
+
747
+ ```typescript
748
+ const router = Router({
749
+ target: '#app',
750
+ type: 'hash',
751
+ layout: 'app-shell', // Layout tag name
752
+ context: new AppContext()
753
+ });
754
+ ```
755
+
756
+ ### Layout Interface
757
+
758
+ ```typescript
759
+ interface Layout {
760
+ update(
761
+ appContext: any,
762
+ placards: Placard[],
763
+ currentRoute: string,
764
+ routeParams: Record<string, string>
765
+ ): void;
766
+ }
767
+ ```
768
+
769
+ ### Conditional Layout
770
+
771
+ Different pages can use different layouts or no layout:
772
+
773
+ ```typescript
774
+ // Router with default layout
775
+ const router = Router({
776
+ target: '#app',
777
+ layout: 'app-shell'
778
+ });
779
+
780
+ // Page without layout
781
+ @page({
782
+ tag: 'fullscreen-page',
783
+ routes: ['/fullscreen'],
784
+ layout: null // Disable layout for this page
785
+ })
786
+ class FullscreenPage extends HTMLElement {
787
+ @render()
788
+ renderContent() {
789
+ return html`<div>Fullscreen content</div>`;
790
+ }
791
+ }
792
+ ```
793
+
794
+ ## Advanced Patterns
795
+
796
+ ### Lazy Loading Pages
797
+
798
+ ```typescript
799
+ @page({
800
+ tag: 'lazy-page',
801
+ routes: ['/lazy']
802
+ })
803
+ class LazyPage extends HTMLElement {
804
+ @property({ type: Boolean })
805
+ loaded = false;
806
+
807
+ @ready()
808
+ async loadContent() {
809
+ // Simulate loading external content
810
+ await new Promise(resolve => setTimeout(resolve, 1000));
811
+
812
+ // Dynamically import module
813
+ const module = await import('./lazy-content.js');
814
+ module.initialize(this);
815
+
816
+ this.loaded = true;
817
+ }
818
+
819
+ @render()
820
+ renderContent() {
821
+ if (!this.loaded) {
822
+ return html`<div>Loading...</div>`;
823
+ }
824
+
825
+ return html`<div>Loaded content</div>`;
826
+ }
827
+ }
828
+ ```
829
+
830
+ ### Nested Routing
831
+
832
+ ```typescript
833
+ // Parent page with sub-navigation
834
+ @page({
835
+ tag: 'settings-page',
836
+ routes: ['/settings', '/settings/:section']
837
+ })
838
+ class SettingsPage extends HTMLElement {
839
+ @property()
840
+ section = 'general';
841
+
842
+ @render()
843
+ renderContent() {
844
+ return html`
845
+ <div class="settings">
846
+ <nav>
847
+ <a href="#/settings/general">General</a>
848
+ <a href="#/settings/privacy">Privacy</a>
849
+ <a href="#/settings/security">Security</a>
850
+ </nav>
851
+
852
+ <div class="content">
853
+ <case ${this.section}>
854
+ <when value="general">
855
+ <div>General settings</div>
856
+ </when>
857
+ <when value="privacy">
858
+ <div>Privacy settings</div>
859
+ </when>
860
+ <when value="security">
861
+ <div>Security settings</div>
862
+ </when>
863
+ <default>
864
+ <div>Unknown section</div>
865
+ </default>
866
+ </case>
867
+ </div>
868
+ </div>
869
+ `;
870
+ }
871
+ }
872
+ ```
873
+
874
+ ### Route-Based Data Loading
875
+
876
+ ```typescript
877
+ @page({
878
+ tag: 'product-page',
879
+ routes: ['/products/:productId']
880
+ })
881
+ class ProductPage extends HTMLElement {
882
+ @property()
883
+ productId = '';
884
+
885
+ @property()
886
+ product: any = null;
887
+
888
+ @property({ type: Boolean })
889
+ loading = true;
890
+
891
+ @ready()
892
+ loadProduct() {
893
+ this.fetchProduct();
894
+ }
895
+
896
+ @watch('productId')
897
+ onProductIdChange() {
898
+ // Reload when productId changes
899
+ this.fetchProduct();
900
+ }
901
+
902
+ async fetchProduct() {
903
+ this.loading = true;
904
+
905
+ try {
906
+ const response = await fetch(`/api/products/${this.productId}`);
907
+ this.product = await response.json();
908
+ } catch (error) {
909
+ console.error('Failed to load product:', error);
910
+ } finally {
911
+ this.loading = false;
912
+ }
913
+ }
914
+
915
+ @render()
916
+ renderContent() {
917
+ if (this.loading) {
918
+ return html`<div>Loading product...</div>`;
919
+ }
920
+
921
+ if (!this.product) {
922
+ return html`<div>Product not found</div>`;
923
+ }
924
+
925
+ return html`
926
+ <div class="product">
927
+ <h1>${this.product.name}</h1>
928
+ <p>${this.product.description}</p>
929
+ <span class="price">$${this.product.price}</span>
930
+ </div>
931
+ `;
932
+ }
933
+ }
934
+ ```
935
+
936
+ ### Breadcrumb Navigation
937
+
938
+ ```typescript
939
+ @page({
940
+ tag: 'breadcrumb-page',
941
+ routes: ['/categories/:category/products/:productId'],
942
+ placard: {
943
+ name: 'product-detail',
944
+ title: 'Product Details',
945
+ breadcrumbs: ['home', 'categories', 'products', 'product-detail']
946
+ }
947
+ })
948
+ class BreadcrumbPage extends HTMLElement {
949
+ @property()
950
+ category = '';
951
+
952
+ @property()
953
+ productId = '';
954
+
955
+ @render()
956
+ renderContent() {
957
+ return html`
958
+ <nav class="breadcrumbs">
959
+ <a href="#/">Home</a>
960
+ <span>/</span>
961
+ <a href="#/categories">Categories</a>
962
+ <span>/</span>
963
+ <a href="#/categories/${this.category}">
964
+ ${this.category}
965
+ </a>
966
+ <span>/</span>
967
+ <span>${this.productId}</span>
968
+ </nav>
969
+ <div class="content">
970
+ <h1>Product ${this.productId} in ${this.category}</h1>
971
+ </div>
972
+ `;
973
+ }
974
+ }
975
+ ```
976
+
977
+ ### Error Page (404)
978
+
979
+ ```typescript
980
+ @page({
981
+ tag: 'not-found-page',
982
+ routes: ['/404', '*'] // Catch-all route
983
+ })
984
+ class NotFoundPage extends HTMLElement {
985
+ @render()
986
+ renderContent() {
987
+ return html`
988
+ <div class="not-found">
989
+ <h1>404 - Page Not Found</h1>
990
+ <p>The page you're looking for doesn't exist.</p>
991
+ <a href="#/">Go Home</a>
992
+ </div>
993
+ `;
994
+ }
995
+
996
+ @styles()
997
+ errorStyles() {
998
+ return css`
999
+ .not-found {
1000
+ text-align: center;
1001
+ padding: 4rem;
1002
+ }
1003
+
1004
+ h1 {
1005
+ color: #e74c3c;
1006
+ font-size: 3rem;
1007
+ }
1008
+
1009
+ a {
1010
+ display: inline-block;
1011
+ margin-top: 2rem;
1012
+ padding: 0.5rem 2rem;
1013
+ background: #3498db;
1014
+ color: white;
1015
+ text-decoration: none;
1016
+ border-radius: 4px;
1017
+ }
1018
+ `;
1019
+ }
1020
+ }
1021
+ ```
1022
+
1023
+ ### Protected Route Pattern
1024
+
1025
+ ```typescript
1026
+ // Context with auth state
1027
+ class AppContext {
1028
+ private user: User | null = null;
1029
+
1030
+ setUser(user: User | null) {
1031
+ this.user = user;
1032
+
1033
+ // Redirect if logged out
1034
+ if (!user && window.location.hash.includes('/dashboard')) {
1035
+ window.location.hash = '#/login';
1036
+ }
1037
+ }
1038
+
1039
+ getUser() {
1040
+ return this.user;
1041
+ }
1042
+
1043
+ isAuthenticated() {
1044
+ return this.user !== null;
1045
+ }
1046
+ }
1047
+
1048
+ // Auth guard
1049
+ const requireAuth: Guard<AppContext> = (ctx) => {
1050
+ if (!ctx.isAuthenticated()) {
1051
+ window.location.hash = '#/login';
1052
+ return false;
1053
+ }
1054
+ return true;
1055
+ };
1056
+
1057
+ // Protected page
1058
+ @page({
1059
+ tag: 'dashboard-page',
1060
+ routes: ['/dashboard'],
1061
+ guards: requireAuth
1062
+ })
1063
+ class DashboardPage extends HTMLElement {
1064
+ private appContext?: AppContext;
1065
+
1066
+ @context()
1067
+ handleContext(ctx: Context<AppContext>) {
1068
+ this.appContext = ctx.application;
1069
+ this.requestRender();
1070
+ }
1071
+
1072
+ @render()
1073
+ renderContent() {
1074
+ const user = this.appContext?.getUser();
1075
+
1076
+ return html`
1077
+ <div>
1078
+ <h1>Welcome, ${user?.name}!</h1>
1079
+ <p>This is your dashboard</p>
1080
+ </div>
1081
+ `;
1082
+ }
1083
+ }
1084
+
1085
+ // Login page
1086
+ @page({
1087
+ tag: 'login-page',
1088
+ routes: ['/login']
1089
+ })
1090
+ class LoginPage extends HTMLElement {
1091
+ private appContext?: AppContext;
1092
+
1093
+ @context()
1094
+ handleContext(ctx: Context<AppContext>) {
1095
+ this.appContext = ctx.application;
1096
+ }
1097
+
1098
+ @render()
1099
+ renderContent() {
1100
+ return html`
1101
+ <form @submit=${this.handleLogin}>
1102
+ <input type="text" name="username" placeholder="Username" required>
1103
+ <input type="password" name="password" placeholder="Password" required>
1104
+ <button type="submit">Login</button>
1105
+ </form>
1106
+ `;
1107
+ }
1108
+
1109
+ handleLogin(e: Event) {
1110
+ e.preventDefault();
1111
+
1112
+ const form = e.target as HTMLFormElement;
1113
+ const formData = new FormData(form);
1114
+
1115
+ // Simulate login
1116
+ const user = {
1117
+ id: 1,
1118
+ name: formData.get('username') as string
1119
+ };
1120
+
1121
+ this.appContext?.setUser(user);
1122
+
1123
+ // Redirect to dashboard
1124
+ window.location.hash = '#/dashboard';
1125
+ }
1126
+ }
1127
+ ```
1128
+
1129
+ ## Best Practices
1130
+
1131
+ 1. **Use semantic routes**: `/users/123` instead of `/page?id=123`
1132
+ 2. **Leverage route parameters**: Automatically mapped to properties
1133
+ 3. **Use guards for protection**: Keep auth logic separate from pages
1134
+ 4. **Implement transitions**: Smooth user experience between pages
1135
+ 5. **Use layouts efficiently**: Share common UI without duplication
1136
+ 6. **Handle 404s**: Always include a catch-all route
1137
+ 7. **Use context for shared state**: Avoid prop drilling
1138
+ 8. **Lazy load when needed**: Improve initial load time
1139
+ 9. **Type your guards**: Use TypeScript generics for context
1140
+ 10. **Test navigation**: Ensure all routes work correctly
1141
+
1142
+ ## Router API Reference
1143
+
1144
+ ### Router()
1145
+
1146
+ ```typescript
1147
+ function Router<T = any>(options: RouterOptions<T>): {
1148
+ page: PropertyDecorator;
1149
+ navigate: (path: string) => void;
1150
+ initialize: () => void;
1151
+ getCurrentRoute: () => string;
1152
+ getRouteParams: () => Record<string, string>;
1153
+ }
1154
+ ```
1155
+
1156
+ ### navigate()
1157
+
1158
+ ```typescript
1159
+ navigate(path: string): void
1160
+ ```
1161
+
1162
+ Navigates to the specified path. Uses hash (#) or pushstate depending on router type.
1163
+
1164
+ ### initialize()
1165
+
1166
+ ```typescript
1167
+ initialize(): void
1168
+ ```
1169
+
1170
+ Initializes the router and starts listening for route changes. Must be called after all pages are defined.
1171
+
1172
+ ### getCurrentRoute()
1173
+
1174
+ ```typescript
1175
+ getCurrentRoute(): string
1176
+ ```
1177
+
1178
+ Returns the current route path.
1179
+
1180
+ ### getRouteParams()
1181
+
1182
+ ```typescript
1183
+ getRouteParams(): Record<string, string>
1184
+ ```
1185
+
1186
+ Returns current route parameters as an object.