snice 4.13.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/dist/cdn/accordion/snice-accordion.js +1 -1
  2. package/dist/cdn/accordion/snice-accordion.min.js +1 -1
  3. package/dist/cdn/alert/snice-alert.js +1 -1
  4. package/dist/cdn/alert/snice-alert.min.js +1 -1
  5. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  6. package/dist/cdn/app-tiles/snice-app-tiles.min.js +1 -1
  7. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  8. package/dist/cdn/audio-recorder/snice-audio-recorder.min.js +1 -1
  9. package/dist/cdn/avatar/snice-avatar.js +1 -1
  10. package/dist/cdn/avatar/snice-avatar.min.js +1 -1
  11. package/dist/cdn/badge/snice-badge.js +1 -1
  12. package/dist/cdn/badge/snice-badge.min.js +1 -1
  13. package/dist/cdn/banner/snice-banner.js +1 -1
  14. package/dist/cdn/banner/snice-banner.min.js +1 -1
  15. package/dist/cdn/book/snice-book.js +1 -1
  16. package/dist/cdn/book/snice-book.min.js +1 -1
  17. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  18. package/dist/cdn/breadcrumbs/snice-breadcrumbs.min.js +1 -1
  19. package/dist/cdn/button/snice-button.js +1 -1
  20. package/dist/cdn/button/snice-button.min.js +1 -1
  21. package/dist/cdn/calendar/snice-calendar.js +1 -1
  22. package/dist/cdn/calendar/snice-calendar.min.js +1 -1
  23. package/dist/cdn/camera/snice-camera.js +1 -1
  24. package/dist/cdn/camera/snice-camera.min.js +1 -1
  25. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  26. package/dist/cdn/camera-annotate/snice-camera-annotate.min.js +1 -1
  27. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  28. package/dist/cdn/candlestick/snice-candlestick.min.js +1 -1
  29. package/dist/cdn/card/snice-card.js +1 -1
  30. package/dist/cdn/card/snice-card.min.js +1 -1
  31. package/dist/cdn/carousel/snice-carousel.js +1 -1
  32. package/dist/cdn/carousel/snice-carousel.min.js +1 -1
  33. package/dist/cdn/chart/snice-chart.js +1 -1
  34. package/dist/cdn/chart/snice-chart.min.js +1 -1
  35. package/dist/cdn/chat/snice-chat.js +1 -1
  36. package/dist/cdn/chat/snice-chat.min.js +1 -1
  37. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  38. package/dist/cdn/checkbox/snice-checkbox.min.js +1 -1
  39. package/dist/cdn/chip/snice-chip.js +1 -1
  40. package/dist/cdn/chip/snice-chip.min.js +1 -1
  41. package/dist/cdn/code-block/snice-code-block.js +2 -2
  42. package/dist/cdn/code-block/snice-code-block.js.map +1 -1
  43. package/dist/cdn/code-block/snice-code-block.min.js +2 -2
  44. package/dist/cdn/code-block/snice-code-block.min.js.map +1 -1
  45. package/dist/cdn/color-display/snice-color-display.js +1 -1
  46. package/dist/cdn/color-display/snice-color-display.min.js +1 -1
  47. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  48. package/dist/cdn/color-picker/snice-color-picker.min.js +1 -1
  49. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  50. package/dist/cdn/command-palette/snice-command-palette.min.js +1 -1
  51. package/dist/cdn/comments/snice-comments.js +1 -1
  52. package/dist/cdn/comments/snice-comments.min.js +1 -1
  53. package/dist/cdn/countdown/snice-countdown.js +1 -1
  54. package/dist/cdn/countdown/snice-countdown.min.js +1 -1
  55. package/dist/cdn/cropper/snice-cropper.js +1 -1
  56. package/dist/cdn/cropper/snice-cropper.min.js +1 -1
  57. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  58. package/dist/cdn/date-picker/snice-date-picker.min.js +1 -1
  59. package/dist/cdn/diff/snice-diff.js +1 -1
  60. package/dist/cdn/diff/snice-diff.min.js +1 -1
  61. package/dist/cdn/divider/snice-divider.js +1 -1
  62. package/dist/cdn/divider/snice-divider.min.js +1 -1
  63. package/dist/cdn/doc/snice-doc.js +1 -1
  64. package/dist/cdn/doc/snice-doc.min.js +1 -1
  65. package/dist/cdn/draw/snice-draw.js +1 -1
  66. package/dist/cdn/draw/snice-draw.min.js +1 -1
  67. package/dist/cdn/drawer/snice-drawer.js +1 -1
  68. package/dist/cdn/drawer/snice-drawer.min.js +1 -1
  69. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  70. package/dist/cdn/empty-state/snice-empty-state.min.js +1 -1
  71. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  72. package/dist/cdn/file-gallery/snice-file-gallery.min.js +1 -1
  73. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  74. package/dist/cdn/file-upload/snice-file-upload.min.js +1 -1
  75. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  76. package/dist/cdn/flip-card/snice-flip-card.min.js +1 -1
  77. package/dist/cdn/flow/snice-flow.js +1 -1
  78. package/dist/cdn/flow/snice-flow.min.js +1 -1
  79. package/dist/cdn/funnel/snice-funnel.js +1 -1
  80. package/dist/cdn/funnel/snice-funnel.min.js +1 -1
  81. package/dist/cdn/gantt/snice-gantt.js +1 -1
  82. package/dist/cdn/gantt/snice-gantt.min.js +1 -1
  83. package/dist/cdn/gauge/snice-gauge.js +1 -1
  84. package/dist/cdn/gauge/snice-gauge.min.js +1 -1
  85. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  86. package/dist/cdn/heatmap/snice-heatmap.min.js +1 -1
  87. package/dist/cdn/image/snice-image.js +1 -1
  88. package/dist/cdn/image/snice-image.min.js +1 -1
  89. package/dist/cdn/input/snice-input.js +1 -1
  90. package/dist/cdn/input/snice-input.min.js +1 -1
  91. package/dist/cdn/kanban/snice-kanban.js +1 -1
  92. package/dist/cdn/kanban/snice-kanban.min.js +1 -1
  93. package/dist/cdn/kpi/snice-kpi.js +1 -1
  94. package/dist/cdn/kpi/snice-kpi.min.js +1 -1
  95. package/dist/cdn/layout/snice-layout.js +1 -1
  96. package/dist/cdn/layout/snice-layout.min.js +1 -1
  97. package/dist/cdn/link/snice-link.js +1 -1
  98. package/dist/cdn/link/snice-link.min.js +1 -1
  99. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  100. package/dist/cdn/link-preview/snice-link-preview.min.js +1 -1
  101. package/dist/cdn/list/snice-list.js +1 -1
  102. package/dist/cdn/list/snice-list.min.js +1 -1
  103. package/dist/cdn/location/snice-location.js +1 -1
  104. package/dist/cdn/location/snice-location.min.js +1 -1
  105. package/dist/cdn/login/snice-login.js +1 -1
  106. package/dist/cdn/login/snice-login.min.js +1 -1
  107. package/dist/cdn/map/snice-map.js +1 -1
  108. package/dist/cdn/map/snice-map.min.js +1 -1
  109. package/dist/cdn/markdown/snice-markdown.js +1 -1
  110. package/dist/cdn/markdown/snice-markdown.min.js +1 -1
  111. package/dist/cdn/masonry/snice-masonry.js +1 -1
  112. package/dist/cdn/masonry/snice-masonry.min.js +1 -1
  113. package/dist/cdn/menu/snice-menu.js +1 -1
  114. package/dist/cdn/menu/snice-menu.min.js +1 -1
  115. package/dist/cdn/modal/snice-modal.js +1 -1
  116. package/dist/cdn/modal/snice-modal.min.js +1 -1
  117. package/dist/cdn/music-player/snice-music-player.js +1 -1
  118. package/dist/cdn/music-player/snice-music-player.min.js +1 -1
  119. package/dist/cdn/nav/snice-nav.js +1 -1
  120. package/dist/cdn/nav/snice-nav.min.js +1 -1
  121. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  122. package/dist/cdn/network-graph/snice-network-graph.min.js +1 -1
  123. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  124. package/dist/cdn/notification-center/snice-notification-center.min.js +1 -1
  125. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  126. package/dist/cdn/org-chart/snice-org-chart.min.js +1 -1
  127. package/dist/cdn/pagination/snice-pagination.js +1 -1
  128. package/dist/cdn/pagination/snice-pagination.min.js +1 -1
  129. package/dist/cdn/paint/snice-paint.js +1 -1
  130. package/dist/cdn/paint/snice-paint.min.js +1 -1
  131. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  132. package/dist/cdn/pdf-viewer/snice-pdf-viewer.min.js +1 -1
  133. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  134. package/dist/cdn/podcast-player/snice-podcast-player.min.js +1 -1
  135. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  136. package/dist/cdn/pricing-table/snice-pricing-table.min.js +1 -1
  137. package/dist/cdn/progress/snice-progress.js +1 -1
  138. package/dist/cdn/progress/snice-progress.min.js +1 -1
  139. package/dist/cdn/qr-code/README.md +2 -2
  140. package/dist/cdn/qr-code/snice-qr-code.js +149 -20
  141. package/dist/cdn/qr-code/snice-qr-code.js.map +1 -1
  142. package/dist/cdn/qr-code/snice-qr-code.min.js +3 -3
  143. package/dist/cdn/qr-code/snice-qr-code.min.js.map +1 -1
  144. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  145. package/dist/cdn/qr-reader/snice-qr-reader.min.js +1 -1
  146. package/dist/cdn/radio/snice-radio.js +1 -1
  147. package/dist/cdn/radio/snice-radio.min.js +1 -1
  148. package/dist/cdn/rating/snice-rating.js +1 -1
  149. package/dist/cdn/rating/snice-rating.min.js +1 -1
  150. package/dist/cdn/recipe/snice-recipe.js +1 -1
  151. package/dist/cdn/recipe/snice-recipe.min.js +1 -1
  152. package/dist/cdn/runtime/snice-runtime.esm.js +4 -4
  153. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  154. package/dist/cdn/runtime/snice-runtime.esm.min.js +3 -3
  155. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  156. package/dist/cdn/runtime/snice-runtime.js +4 -4
  157. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  158. package/dist/cdn/runtime/snice-runtime.min.js +3 -3
  159. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  160. package/dist/cdn/sankey/snice-sankey.js +1 -1
  161. package/dist/cdn/sankey/snice-sankey.min.js +1 -1
  162. package/dist/cdn/select/snice-select.js +1 -1
  163. package/dist/cdn/select/snice-select.min.js +1 -1
  164. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  165. package/dist/cdn/skeleton/snice-skeleton.min.js +1 -1
  166. package/dist/cdn/slider/snice-slider.js +2 -2
  167. package/dist/cdn/slider/snice-slider.js.map +1 -1
  168. package/dist/cdn/slider/snice-slider.min.js +5 -5
  169. package/dist/cdn/slider/snice-slider.min.js.map +1 -1
  170. package/dist/cdn/sortable/snice-sortable.js +1 -1
  171. package/dist/cdn/sortable/snice-sortable.min.js +1 -1
  172. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  173. package/dist/cdn/sparkline/snice-sparkline.min.js +1 -1
  174. package/dist/cdn/spinner/snice-spinner.js +1 -1
  175. package/dist/cdn/spinner/snice-spinner.min.js +1 -1
  176. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  177. package/dist/cdn/split-pane/snice-split-pane.min.js +1 -1
  178. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  179. package/dist/cdn/spotlight/snice-spotlight.min.js +1 -1
  180. package/dist/cdn/spreadsheet/snice-spreadsheet.js +1 -1
  181. package/dist/cdn/spreadsheet/snice-spreadsheet.min.js +1 -1
  182. package/dist/cdn/stepper/snice-stepper.js +1 -1
  183. package/dist/cdn/stepper/snice-stepper.min.js +1 -1
  184. package/dist/cdn/switch/snice-switch.js +1 -1
  185. package/dist/cdn/switch/snice-switch.min.js +1 -1
  186. package/dist/cdn/table/snice-table.js +1 -1
  187. package/dist/cdn/table/snice-table.min.js +1 -1
  188. package/dist/cdn/tabs/snice-tabs.js +1 -1
  189. package/dist/cdn/tabs/snice-tabs.min.js +1 -1
  190. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  191. package/dist/cdn/tag-input/snice-tag-input.min.js +1 -1
  192. package/dist/cdn/terminal/snice-terminal.js +1 -1
  193. package/dist/cdn/terminal/snice-terminal.min.js +1 -1
  194. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  195. package/dist/cdn/testimonial/snice-testimonial.min.js +1 -1
  196. package/dist/cdn/textarea/snice-textarea.js +1 -1
  197. package/dist/cdn/textarea/snice-textarea.min.js +1 -1
  198. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  199. package/dist/cdn/time-range-picker/snice-time-range-picker.min.js +1 -1
  200. package/dist/cdn/timeline/snice-timeline.js +1 -1
  201. package/dist/cdn/timeline/snice-timeline.min.js +1 -1
  202. package/dist/cdn/timer/snice-timer.js +1 -1
  203. package/dist/cdn/timer/snice-timer.min.js +1 -1
  204. package/dist/cdn/toast/snice-toast.js +1 -1
  205. package/dist/cdn/toast/snice-toast.min.js +1 -1
  206. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  207. package/dist/cdn/tooltip/snice-tooltip.min.js +1 -1
  208. package/dist/cdn/tree/snice-tree.js +1 -1
  209. package/dist/cdn/tree/snice-tree.min.js +1 -1
  210. package/dist/cdn/treemap/snice-treemap.js +1 -1
  211. package/dist/cdn/treemap/snice-treemap.min.js +1 -1
  212. package/dist/cdn/video-player/snice-video-player.js +1 -1
  213. package/dist/cdn/video-player/snice-video-player.min.js +1 -1
  214. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  215. package/dist/cdn/virtual-scroller/snice-virtual-scroller.min.js +1 -1
  216. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  217. package/dist/cdn/waterfall/snice-waterfall.min.js +1 -1
  218. package/dist/cdn/weather/snice-weather.js +1 -1
  219. package/dist/cdn/weather/snice-weather.min.js +1 -1
  220. package/dist/components/code-block/snice-code-block.js +1 -1
  221. package/dist/components/code-block/snice-code-block.js.map +1 -1
  222. package/dist/components/qr-code/qrcode.d.ts +1 -0
  223. package/dist/components/qr-code/qrcode.js +16 -8
  224. package/dist/components/qr-code/qrcode.js.map +1 -1
  225. package/dist/components/qr-code/snice-qr-code.d.ts +5 -2
  226. package/dist/components/qr-code/snice-qr-code.js +132 -11
  227. package/dist/components/qr-code/snice-qr-code.js.map +1 -1
  228. package/dist/components/qr-code/snice-qr-code.types.d.ts +3 -2
  229. package/dist/components/slider/snice-slider.js +1 -1
  230. package/dist/components/slider/snice-slider.js.map +1 -1
  231. package/dist/index.cjs +2 -2
  232. package/dist/index.esm.js +2 -2
  233. package/dist/index.iife.js +2 -2
  234. package/dist/symbols.cjs +1 -1
  235. package/dist/symbols.esm.js +1 -1
  236. package/dist/transitions.cjs +1 -1
  237. package/dist/transitions.esm.js +1 -1
  238. package/dist/types/request-options.d.ts +1 -1
  239. package/docs/ai/api.md +1 -1
  240. package/docs/ai/components/code-block.md +1 -1
  241. package/docs/ai/decorators.md +2 -2
  242. package/docs/ai/patterns.md +17 -6
  243. package/docs/code-block.md +1 -3
  244. package/docs/controllers.md +98 -391
  245. package/docs/elements.md +131 -117
  246. package/docs/events.md +74 -83
  247. package/docs/fetcher.md +64 -76
  248. package/docs/observe.md +13 -33
  249. package/docs/placards.md +6 -16
  250. package/docs/request-response.md +171 -693
  251. package/docs/routing.md +67 -136
  252. package/package.json +1 -1
  253. package/docs/migration-v2-to-v3.md +0 -569
package/docs/routing.md CHANGED
@@ -32,14 +32,14 @@ const { page, initialize, navigate } = router;
32
32
  ### Router Options
33
33
 
34
34
  ```typescript
35
- interface RouterOptions<T = any> {
35
+ interface RouterOptions {
36
36
  target: string; // Target element selector
37
37
  type: 'hash' | 'pushstate'; // Routing type
38
38
  window?: Window; // Override window object (for testing)
39
39
  document?: Document; // Override document object (for testing)
40
40
  transition?: Transition; // Global transition config
41
41
  layout?: string; // Default layout for all pages
42
- context?: T; // Router context object (shared state)
42
+ context?: any; // Router context object (shared state)
43
43
  fetcher?: Fetcher; // Optional fetch middleware (see docs/fetcher.md)
44
44
  }
45
45
  ```
@@ -76,7 +76,8 @@ const { page, initialize } = Router({
76
76
  ### Basic Page
77
77
 
78
78
  ```typescript
79
- import { page, render, html, styles, css } from 'snice';
79
+ import { render, html, styles, css } from 'snice';
80
+ import { page } from './router'; // page comes from Router(), not from 'snice'
80
81
 
81
82
  @page({ tag: 'home-page', routes: ['/'] })
82
83
  class HomePage extends HTMLElement {
@@ -120,7 +121,8 @@ class HomePage extends HTMLElement {
120
121
  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.
121
122
 
122
123
  ```typescript
123
- import { page, context, render, html, Context } from 'snice';
124
+ import { context, render, html, Context } from 'snice';
125
+ import { page } from './router';
124
126
 
125
127
  @page({ tag: 'profile-page', routes: ['/profile'] })
126
128
  class ProfilePage extends HTMLElement {
@@ -208,7 +210,7 @@ interface Context {
208
210
  application: AppContext; // Your router context (e.g., { user, theme, config })
209
211
  navigation: {
210
212
  placards: Placard[]; // All page placards
211
- route: string; // Current route name
213
+ route: string; // Current path (e.g. '/users/123')
212
214
  params: Record<string, string>; // Route parameters
213
215
  };
214
216
  fetch: typeof globalThis.fetch; // Fetch function with middleware support
@@ -273,12 +275,13 @@ class SettingsPage extends HTMLElement {
273
275
  ### @page Decorator Options
274
276
 
275
277
  ```typescript
276
- interface PageOptions<T = any> {
278
+ interface PageOptions {
277
279
  tag: string; // Custom element tag name
278
280
  routes: string[]; // Route patterns
279
281
  transition?: Transition; // Page-specific transition
280
- guards?: Guard<T> | Guard<T>[]; // Route guards
281
- placard?: Placard<T>; // Page metadata
282
+ guards?: Guard | Guard[]; // Route guards
283
+ layout?: string | false; // Layout tag, or false to disable
284
+ placard?: Placard | ((ctx: AppContext) => Placard); // Page metadata
282
285
  }
283
286
  ```
284
287
 
@@ -455,25 +458,20 @@ class CommentPage extends HTMLElement {
455
458
 
456
459
  ### Query Parameters
457
460
 
458
- Query parameters are not automatically parsed but can be accessed via URL:
461
+ Define query parameters directly in the route pattern they are extracted as route params automatically:
459
462
 
460
463
  ```typescript
461
464
  @page({
462
465
  tag: 'search-page',
463
- routes: ['/search']
466
+ routes: ['/search?q=:query']
464
467
  })
465
468
  class SearchPage extends HTMLElement {
466
469
  @property()
467
470
  query = '';
468
471
 
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');
472
+ @context()
473
+ handleContext(ctx: Context) {
474
+ this.query = ctx.navigation.params.query || '';
477
475
  }
478
476
 
479
477
  @render()
@@ -481,7 +479,6 @@ class SearchPage extends HTMLElement {
481
479
  return html`
482
480
  <div>
483
481
  <h1>Search Results for: ${this.query}</h1>
484
- <p>Page: ${this.page}</p>
485
482
  </div>
486
483
  `;
487
484
  }
@@ -493,7 +490,7 @@ class SearchPage extends HTMLElement {
493
490
  ### Global Transitions
494
491
 
495
492
  ```typescript
496
- import { fadeTransition } from 'snice';
493
+ import { fadeTransition } from 'snice/transitions';
497
494
 
498
495
  const router = Router({
499
496
  target: '#app',
@@ -505,7 +502,7 @@ const router = Router({
505
502
  ### Page-Specific Transitions
506
503
 
507
504
  ```typescript
508
- import { slideTransition } from 'snice';
505
+ import { slideTransition } from 'snice/transitions';
509
506
 
510
507
  @page({
511
508
  tag: 'about-page',
@@ -526,50 +523,36 @@ class AboutPage extends HTMLElement {
526
523
  import {
527
524
  fadeTransition,
528
525
  slideTransition,
529
- slideLeftTransition,
530
526
  slideRightTransition,
531
527
  slideUpTransition,
532
528
  slideDownTransition,
533
- scaleTransition
534
- } from 'snice';
529
+ scaleTransition,
530
+ rotateTransition,
531
+ flipTransition,
532
+ zoomTransition,
533
+ noneTransition
534
+ } from 'snice/transitions';
535
535
  ```
536
536
 
537
537
  ### Custom Transitions
538
538
 
539
+ Transitions use inline CSS property strings for `out` (leaving) and `in` (entering) states:
540
+
539
541
  ```typescript
540
- import { Transition } from 'snice';
542
+ import { Transition } from 'snice/transitions';
541
543
 
542
544
  const customTransition: Transition = {
543
545
  name: 'custom',
544
- duration: 500,
545
- enterClass: 'page-enter',
546
- enterActiveClass: 'page-enter-active',
547
- leaveClass: 'page-leave',
548
- leaveActiveClass: 'page-leave-active'
546
+ outDuration: 300,
547
+ inDuration: 300,
548
+ out: 'opacity: 0; transform: translateY(-20px)',
549
+ in: 'opacity: 1; transform: translateY(0)',
550
+ mode: 'sequential' // or 'simultaneous'
549
551
  };
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
552
  ```
572
553
 
554
+ The `out` string is the end state of the leaving page. The `in` string is the end state of the entering page (which starts invisible and transitions to this state).
555
+
573
556
  ## Route Guards
574
557
 
575
558
  Guards protect routes and can redirect unauthorized access:
@@ -579,7 +562,7 @@ Guards protect routes and can redirect unauthorized access:
579
562
  ```typescript
580
563
  import { Guard } from 'snice';
581
564
 
582
- const isAuthenticated: Guard<AppContext> = (ctx) => {
565
+ const isAuthenticated: Guard<AppContext> = (ctx, params) => {
583
566
  return ctx.getUser() !== null;
584
567
  };
585
568
 
@@ -599,7 +582,7 @@ class DashboardPage extends HTMLElement {
599
582
  ### Multiple Guards
600
583
 
601
584
  ```typescript
602
- const hasAdminRole: Guard<AppContext> = (ctx) => {
585
+ const hasAdminRole: Guard<AppContext> = (ctx, params) => {
603
586
  const user = ctx.getUser();
604
587
  return user?.role === 'admin';
605
588
  };
@@ -619,33 +602,26 @@ class AdminPage extends HTMLElement {
619
602
 
620
603
  ### Guard with Redirect
621
604
 
605
+ When a guard returns `false`, the router renders the `/403` page (if registered) or a default 403 message. Guards can also trigger side effects like redirects:
606
+
622
607
  ```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);
608
+ const isAuthenticated: Guard<AppContext> = (ctx, params) => {
609
+ if (!ctx.getUser()) {
610
+ window.location.hash = '#/login';
611
+ return false;
631
612
  }
632
-
633
- return isAuth;
613
+ return true;
634
614
  };
635
615
  ```
636
616
 
637
- ### Async Guards
617
+ ### Permission Guard
618
+
619
+ Guards are synchronous — pre-load permissions into context before navigating:
638
620
 
639
621
  ```typescript
640
- const checkPermission: Guard<AppContext> = async (ctx) => {
622
+ const hasAdminAccess: Guard<AppContext> = (ctx, params) => {
641
623
  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');
624
+ return user?.role === 'admin';
649
625
  };
650
626
  ```
651
627
 
@@ -781,7 +757,7 @@ const router = Router({
781
757
  @page({
782
758
  tag: 'fullscreen-page',
783
759
  routes: ['/fullscreen'],
784
- layout: null // Disable layout for this page
760
+ layout: false // Disable layout for this page
785
761
  })
786
762
  class FullscreenPage extends HTMLElement {
787
763
  @render()
@@ -985,38 +961,12 @@ class NotFoundPage extends HTMLElement {
985
961
  @render()
986
962
  renderContent() {
987
963
  return html`
988
- <div class="not-found">
964
+ <div>
989
965
  <h1>404 - Page Not Found</h1>
990
- <p>The page you're looking for doesn't exist.</p>
991
966
  <a href="#/">Go Home</a>
992
967
  </div>
993
968
  `;
994
969
  }
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
970
  }
1021
971
  ```
1022
972
 
@@ -1029,11 +979,6 @@ class AppContext {
1029
979
 
1030
980
  setUser(user: User | null) {
1031
981
  this.user = user;
1032
-
1033
- // Redirect if logged out
1034
- if (!user && window.location.hash.includes('/dashboard')) {
1035
- window.location.hash = '#/login';
1036
- }
1037
982
  }
1038
983
 
1039
984
  getUser() {
@@ -1045,8 +990,8 @@ class AppContext {
1045
990
  }
1046
991
  }
1047
992
 
1048
- // Auth guard
1049
- const isAuthenticated: Guard<AppContext> = (ctx) => {
993
+ // Auth guard — redirect to login if not authenticated
994
+ const isAuthenticated: Guard<AppContext> = (ctx, params) => {
1050
995
  if (!ctx.isAuthenticated()) {
1051
996
  window.location.hash = '#/login';
1052
997
  return false;
@@ -1126,37 +1071,24 @@ class LoginPage extends HTMLElement {
1126
1071
  }
1127
1072
  ```
1128
1073
 
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
1074
 
1142
1075
  ## Router API Reference
1143
1076
 
1144
1077
  ### Router()
1145
1078
 
1146
1079
  ```typescript
1147
- function Router<T = any>(options: RouterOptions<T>): {
1148
- page: PropertyDecorator;
1149
- navigate: (path: string) => void;
1080
+ function Router(options: RouterOptions): {
1081
+ page: (pageOptions: PageOptions) => ClassDecorator;
1150
1082
  initialize: () => void;
1151
- getCurrentRoute: () => string;
1152
- getRouteParams: () => Record<string, string>;
1083
+ navigate: (path: string) => Promise<void>;
1084
+ register: (route: string, tag: string, transition?: Transition, guards?: Guard | Guard[]) => void;
1153
1085
  }
1154
1086
  ```
1155
1087
 
1156
1088
  ### navigate()
1157
1089
 
1158
1090
  ```typescript
1159
- navigate(path: string): void
1091
+ navigate(path: string): Promise<void>
1160
1092
  ```
1161
1093
 
1162
1094
  Navigates to the specified path. Uses hash (#) or pushstate depending on router type.
@@ -1169,18 +1101,17 @@ initialize(): void
1169
1101
 
1170
1102
  Initializes the router and starts listening for route changes. Must be called after all pages are defined.
1171
1103
 
1172
- ### getCurrentRoute()
1173
-
1174
- ```typescript
1175
- getCurrentRoute(): string
1176
- ```
1177
-
1178
- Returns the current route path.
1179
-
1180
- ### getRouteParams()
1104
+ ### register()
1181
1105
 
1182
1106
  ```typescript
1183
- getRouteParams(): Record<string, string>
1107
+ register(
1108
+ route: string,
1109
+ tag: string,
1110
+ transition?: Transition,
1111
+ guards?: Guard | Guard[],
1112
+ layout?: string | false,
1113
+ placard?: Placard | ((ctx: AppContext) => Placard)
1114
+ ): void
1184
1115
  ```
1185
1116
 
1186
- Returns current route parameters as an object.
1117
+ Manually register a route without using the `@page` decorator.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snice",
3
- "version": "4.13.0",
3
+ "version": "4.14.0",
4
4
  "type": "module",
5
5
  "description": "Imperative TypeScript framework for building vanilla web components with decorators, differential rendering, routing, and controllers. No virtual DOM, no build complexity.",
6
6
  "main": "dist/index.cjs",