snice 2.3.0 → 2.5.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 (75) hide show
  1. package/README.md +76 -4
  2. package/bin/templates/base/src/main.ts +3 -0
  3. package/bin/templates/base/src/pages/about-page.ts +10 -1
  4. package/bin/templates/base/src/pages/home-page.ts +10 -1
  5. package/bin/templates/base/src/router.ts +3 -2
  6. package/dist/components/drawer/snice-drawer.d.ts +4 -0
  7. package/dist/components/drawer/snice-drawer.js +58 -23
  8. package/dist/components/drawer/snice-drawer.js.map +1 -1
  9. package/dist/components/drawer/snice-drawer.types.d.ts +1 -0
  10. package/dist/components/layout/snice-layout-blog.d.ts +10 -1
  11. package/dist/components/layout/snice-layout-blog.js +47 -8
  12. package/dist/components/layout/snice-layout-blog.js.map +1 -1
  13. package/dist/components/layout/snice-layout-dashboard.d.ts +12 -1
  14. package/dist/components/layout/snice-layout-dashboard.js +92 -13
  15. package/dist/components/layout/snice-layout-dashboard.js.map +1 -1
  16. package/dist/components/layout/snice-layout-landing.d.ts +10 -1
  17. package/dist/components/layout/snice-layout-landing.js +47 -8
  18. package/dist/components/layout/snice-layout-landing.js.map +1 -1
  19. package/dist/components/layout/snice-layout-sidebar.d.ts +13 -1
  20. package/dist/components/layout/snice-layout-sidebar.js +69 -28
  21. package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
  22. package/dist/components/layout/snice-layout.d.ts +9 -1
  23. package/dist/components/layout/snice-layout.js +35 -8
  24. package/dist/components/layout/snice-layout.js.map +1 -1
  25. package/dist/components/layout/snice-layout.types.d.ts +2 -1
  26. package/dist/components/nav/snice-nav.d.ts +16 -0
  27. package/dist/components/nav/snice-nav.js +158 -0
  28. package/dist/components/nav/snice-nav.js.map +1 -0
  29. package/dist/components/nav/snice-nav.types.d.ts +11 -0
  30. package/dist/index.cjs +243 -45
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.esm.js +242 -42
  33. package/dist/index.esm.js.map +1 -1
  34. package/dist/index.iife.js +243 -45
  35. package/dist/index.iife.js.map +1 -1
  36. package/dist/symbols.cjs.map +1 -1
  37. package/dist/symbols.esm.js +8 -2
  38. package/dist/symbols.esm.js.map +1 -1
  39. package/dist/transitions.esm.js +1 -1
  40. package/dist/types/controller.d.ts +1 -1
  41. package/dist/types/element.d.ts +70 -3
  42. package/dist/types/events.d.ts +2 -2
  43. package/dist/types/global.d.ts +1 -1
  44. package/dist/types/index.d.ts +3 -2
  45. package/dist/types/observe.d.ts +1 -1
  46. package/dist/types/request-response.d.ts +2 -2
  47. package/dist/types/router.d.ts +2 -2
  48. package/dist/types/symbols.d.ts +5 -0
  49. package/dist/types/testing.d.ts +1 -1
  50. package/dist/types/transitions.d.ts +1 -1
  51. package/dist/types/types/adopted-options.d.ts +4 -0
  52. package/dist/types/types/app-context.d.ts +81 -0
  53. package/dist/types/types/guard.d.ts +19 -0
  54. package/dist/types/types/index.d.ts +23 -17
  55. package/dist/types/types/moved-options.d.ts +4 -0
  56. package/dist/types/types/{PageOptions.d.ts → page-options.d.ts} +13 -4
  57. package/dist/types/types/placard.d.ts +90 -0
  58. package/dist/types/types/{PropertyOptions.d.ts → property-options.d.ts} +2 -2
  59. package/dist/types/types/route-params.d.ts +21 -0
  60. package/dist/types/types/{RouterInstance.d.ts → router-instance.d.ts} +5 -3
  61. package/dist/types/types/{RouterOptions.d.ts → router-options.d.ts} +1 -1
  62. package/package.json +3 -3
  63. /package/dist/types/types/{DispatchOptions.d.ts → dispatch-options.d.ts} +0 -0
  64. /package/dist/types/types/{IController.d.ts → i-controller.d.ts} +0 -0
  65. /package/dist/types/types/{ObserveOptions.d.ts → observe-options.d.ts} +0 -0
  66. /package/dist/types/types/{OnOptions.d.ts → on-options.d.ts} +0 -0
  67. /package/dist/types/types/{PartOptions.d.ts → part-options.d.ts} +0 -0
  68. /package/dist/types/types/{PropertyConverter.d.ts → property-converter.d.ts} +0 -0
  69. /package/dist/types/types/{QueryOptions.d.ts → query-options.d.ts} +0 -0
  70. /package/dist/types/types/{RequestOptions.d.ts → request-options.d.ts} +0 -0
  71. /package/dist/types/types/{RespondOptions.d.ts → respond-options.d.ts} +0 -0
  72. /package/dist/types/types/{SimpleArray.d.ts → simple-array.d.ts} +0 -0
  73. /package/dist/types/types/{SniceElement.d.ts → snice-element.d.ts} +0 -0
  74. /package/dist/types/types/{SniceGlobal.d.ts → snice-global.d.ts} +0 -0
  75. /package/dist/types/types/{Transition.d.ts → transition.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v2.2.3
2
+ * snice v2.4.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -40,6 +40,7 @@ var Snice = (function (exports) {
40
40
  const READY_PROMISE = getSymbol('ready-promise');
41
41
  const READY_RESOLVE = getSymbol('ready-resolve');
42
42
  const CONTROLLER = getSymbol('controller');
43
+ const INITIALIZED = getSymbol('initialized');
43
44
  // Event handler symbols
44
45
  const ON_HANDLERS = getSymbol('on-handlers');
45
46
  // Controller symbols
@@ -65,11 +66,16 @@ var Snice = (function (exports) {
65
66
  // Lifecycle symbols
66
67
  const READY_HANDLERS = getSymbol('ready-handlers');
67
68
  const DISPOSE_HANDLERS = getSymbol('dispose-handlers');
69
+ const MOVED_HANDLERS = getSymbol('moved-handlers');
70
+ const ADOPTED_HANDLERS = getSymbol('adopted-handlers');
68
71
  // Observer symbols
69
72
  const OBSERVERS = getSymbol('observers');
70
73
  // Part symbols
71
74
  const PARTS = getSymbol('parts');
72
75
  const PART_TIMERS = getSymbol('part-timers');
76
+ // Lifecycle callback timers
77
+ const MOVED_TIMERS = getSymbol('moved-timers');
78
+ const ADOPTED_TIMERS = getSymbol('adopted-timers');
73
79
  // Dispatch timing symbols
74
80
  const DISPATCH_TIMERS = getSymbol('dispatch-timers');
75
81
 
@@ -1124,14 +1130,6 @@ var Snice = (function (exports) {
1124
1130
  detail: { name: controllerName, controller: controllerInstance }
1125
1131
  }));
1126
1132
  }
1127
- /**
1128
- * Gets the controller instance attached to an element
1129
- * @param element The element to get the controller from
1130
- * @returns The controller instance or undefined
1131
- */
1132
- function getController(element) {
1133
- return element[CONTROLLER_KEY];
1134
- }
1135
1133
  /**
1136
1134
  * Enable controller support for native HTML elements
1137
1135
  * This sets up a MutationObserver to watch for controller attributes
@@ -1229,17 +1227,6 @@ var Snice = (function (exports) {
1229
1227
  // Store observer reference for cleanup if needed
1230
1228
  globalThis.sniceNativeControllerObserver = observer;
1231
1229
  }
1232
- /**
1233
- * Stop watching for native element controllers
1234
- */
1235
- function cleanupNativeElementControllers() {
1236
- const observer = globalThis.sniceNativeControllerObserver;
1237
- if (observer) {
1238
- observer.disconnect();
1239
- delete globalThis.sniceNativeControllerObserver;
1240
- delete globalThis.sniceNativeControllersInitialized;
1241
- }
1242
- }
1243
1230
 
1244
1231
  /**
1245
1232
  * SimpleArray type for arrays that can be safely reflected to attributes
@@ -1503,6 +1490,24 @@ var Snice = (function (exports) {
1503
1490
  this[READY_RESOLVE] = resolve;
1504
1491
  });
1505
1492
  }
1493
+ // Only run initialization logic once, but re-establish handlers on reconnection
1494
+ if (this[INITIALIZED]) {
1495
+ // Re-establish handlers that get cleaned up on disconnect
1496
+ setupEventHandlers(this, this);
1497
+ setupResponseHandlers(this, this);
1498
+ // Re-establish observers that get cleaned up on disconnect
1499
+ try {
1500
+ setupObservers(this, this);
1501
+ }
1502
+ catch (error) {
1503
+ console.error(`Error setting up observers for ${this.tagName} on reconnection:`, error);
1504
+ }
1505
+ // Call user's connectedCallback
1506
+ if (originalConnectedCallback) {
1507
+ originalConnectedCallback.call(this);
1508
+ }
1509
+ return;
1510
+ }
1506
1511
  try {
1507
1512
  // Initialize properties from attributes before rendering
1508
1513
  const properties = constructor[PROPERTIES];
@@ -1526,8 +1531,6 @@ var Snice = (function (exports) {
1526
1531
  this[PROPERTIES_INITIALIZED] = true;
1527
1532
  // Properties are now stateless and read from DOM attributes only
1528
1533
  // Initial values are not automatically reflected
1529
- // Clean up any existing event handlers first (for reconnection)
1530
- cleanupEventHandlers(this);
1531
1534
  // Create shadow root if it doesn't exist
1532
1535
  if (!this.shadowRoot) {
1533
1536
  this.attachShadow({ mode: 'open' });
@@ -1600,6 +1603,8 @@ var Snice = (function (exports) {
1600
1603
  catch (error) {
1601
1604
  console.error(`Error setting up observers for ${this.tagName}:`, error);
1602
1605
  }
1606
+ // Mark as initialized
1607
+ this[INITIALIZED] = true;
1603
1608
  // NOW call the original user-defined connectedCallback after shadow DOM is set up
1604
1609
  if (originalConnectedCallback) {
1605
1610
  originalConnectedCallback.call(this);
@@ -1721,6 +1726,36 @@ var Snice = (function (exports) {
1721
1726
  }
1722
1727
  }
1723
1728
  };
1729
+ // Add connectedMoveCallback for handling DOM moves
1730
+ constructor.prototype.connectedMoveCallback = async function () {
1731
+ // Call @moved handlers
1732
+ const movedHandlers = constructor[MOVED_HANDLERS];
1733
+ if (movedHandlers) {
1734
+ for (const handler of movedHandlers) {
1735
+ try {
1736
+ await handler.method.call(this);
1737
+ }
1738
+ catch (error) {
1739
+ console.error(`Error in @moved handler ${handler.methodName}:`, error);
1740
+ }
1741
+ }
1742
+ }
1743
+ };
1744
+ // Add adoptedCallback for handling document adoption
1745
+ constructor.prototype.adoptedCallback = async function () {
1746
+ // Call @adopted handlers
1747
+ const adoptedHandlers = constructor[ADOPTED_HANDLERS];
1748
+ if (adoptedHandlers) {
1749
+ for (const handler of adoptedHandlers) {
1750
+ try {
1751
+ await handler.method.call(this);
1752
+ }
1753
+ catch (error) {
1754
+ console.error(`Error in @adopted handler ${handler.methodName}:`, error);
1755
+ }
1756
+ }
1757
+ }
1758
+ };
1724
1759
  }
1725
1760
  function element(tagName) {
1726
1761
  return function (constructor, context) {
@@ -2063,6 +2098,148 @@ var Snice = (function (exports) {
2063
2098
  });
2064
2099
  };
2065
2100
  }
2101
+ /**
2102
+ * Decorator for methods that should run when element is moved within DOM
2103
+ * Supports debounce and throttle options to control execution timing
2104
+ */
2105
+ function moved(options = {}) {
2106
+ return function (originalMethod, context) {
2107
+ const methodName = context.name;
2108
+ context.addInitializer(function () {
2109
+ const constructor = this.constructor;
2110
+ if (!constructor[MOVED_HANDLERS]) {
2111
+ constructor[MOVED_HANDLERS] = [];
2112
+ }
2113
+ constructor[MOVED_HANDLERS].push({
2114
+ methodName,
2115
+ method: originalMethod,
2116
+ options
2117
+ });
2118
+ });
2119
+ // Return wrapped method that handles timing options
2120
+ return function (...args) {
2121
+ // Initialize timers storage if not present
2122
+ if (!this[MOVED_TIMERS]) {
2123
+ this[MOVED_TIMERS] = new Map();
2124
+ }
2125
+ // Get or create timers for this specific method
2126
+ if (!this[MOVED_TIMERS].has(methodName)) {
2127
+ this[MOVED_TIMERS].set(methodName, {
2128
+ throttleTimer: null,
2129
+ debounceTimer: null,
2130
+ lastThrottleCall: 0
2131
+ });
2132
+ }
2133
+ const timers = this[MOVED_TIMERS].get(methodName);
2134
+ // Helper function to execute method
2135
+ const executeMethod = (...methodArgs) => {
2136
+ return originalMethod.apply(this, methodArgs);
2137
+ };
2138
+ const hasDebounce = options.debounce !== undefined && options.debounce > 0;
2139
+ const hasThrottle = options.throttle !== undefined && options.throttle > 0;
2140
+ // Handle timing based on priority: debounce > throttle > immediate
2141
+ switch (true) {
2142
+ case hasDebounce: {
2143
+ clearTimeout(timers.debounceTimer);
2144
+ timers.debounceTimer = setTimeout(() => executeMethod(...args), options.debounce);
2145
+ return undefined;
2146
+ }
2147
+ case hasThrottle: {
2148
+ const throttleMs = options.throttle;
2149
+ const now = Date.now();
2150
+ const canExecuteImmediately = timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= throttleMs;
2151
+ if (canExecuteImmediately) {
2152
+ timers.lastThrottleCall = now;
2153
+ return executeMethod(...args);
2154
+ }
2155
+ const hasScheduledTimer = !!timers.throttleTimer;
2156
+ if (!hasScheduledTimer) {
2157
+ const remainingTime = throttleMs - (now - timers.lastThrottleCall);
2158
+ timers.throttleTimer = setTimeout(() => {
2159
+ timers.throttleTimer = null;
2160
+ timers.lastThrottleCall = Date.now();
2161
+ executeMethod(...args);
2162
+ }, remainingTime);
2163
+ }
2164
+ return undefined;
2165
+ }
2166
+ default:
2167
+ return executeMethod(...args);
2168
+ }
2169
+ };
2170
+ };
2171
+ }
2172
+ /**
2173
+ * Decorator for methods that should run when element is adopted to new document
2174
+ * Supports debounce and throttle options to control execution timing
2175
+ */
2176
+ function adopted(options = {}) {
2177
+ return function (originalMethod, context) {
2178
+ const methodName = context.name;
2179
+ context.addInitializer(function () {
2180
+ const constructor = this.constructor;
2181
+ if (!constructor[ADOPTED_HANDLERS]) {
2182
+ constructor[ADOPTED_HANDLERS] = [];
2183
+ }
2184
+ constructor[ADOPTED_HANDLERS].push({
2185
+ methodName,
2186
+ method: originalMethod,
2187
+ options
2188
+ });
2189
+ });
2190
+ // Return wrapped method that handles timing options
2191
+ return function (...args) {
2192
+ // Initialize timers storage if not present
2193
+ if (!this[ADOPTED_TIMERS]) {
2194
+ this[ADOPTED_TIMERS] = new Map();
2195
+ }
2196
+ // Get or create timers for this specific method
2197
+ if (!this[ADOPTED_TIMERS].has(methodName)) {
2198
+ this[ADOPTED_TIMERS].set(methodName, {
2199
+ throttleTimer: null,
2200
+ debounceTimer: null,
2201
+ lastThrottleCall: 0
2202
+ });
2203
+ }
2204
+ const timers = this[ADOPTED_TIMERS].get(methodName);
2205
+ // Helper function to execute method
2206
+ const executeMethod = (...methodArgs) => {
2207
+ return originalMethod.apply(this, methodArgs);
2208
+ };
2209
+ const hasDebounce = options.debounce !== undefined && options.debounce > 0;
2210
+ const hasThrottle = options.throttle !== undefined && options.throttle > 0;
2211
+ // Handle timing based on priority: debounce > throttle > immediate
2212
+ switch (true) {
2213
+ case hasDebounce: {
2214
+ clearTimeout(timers.debounceTimer);
2215
+ timers.debounceTimer = setTimeout(() => executeMethod(...args), options.debounce);
2216
+ return undefined;
2217
+ }
2218
+ case hasThrottle: {
2219
+ const throttleMs = options.throttle;
2220
+ const now = Date.now();
2221
+ const canExecuteImmediately = timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= throttleMs;
2222
+ if (canExecuteImmediately) {
2223
+ timers.lastThrottleCall = now;
2224
+ return executeMethod(...args);
2225
+ }
2226
+ const hasScheduledTimer = !!timers.throttleTimer;
2227
+ if (!hasScheduledTimer) {
2228
+ const remainingTime = throttleMs - (now - timers.lastThrottleCall);
2229
+ timers.throttleTimer = setTimeout(() => {
2230
+ timers.throttleTimer = null;
2231
+ timers.lastThrottleCall = Date.now();
2232
+ executeMethod(...args);
2233
+ }, remainingTime);
2234
+ }
2235
+ return undefined;
2236
+ }
2237
+ default:
2238
+ return executeMethod(...args);
2239
+ }
2240
+ };
2241
+ };
2242
+ }
2066
2243
  /**
2067
2244
  * Decorator for methods that render specific parts of the template
2068
2245
  * Parts are identified by the 'part' attribute in the HTML template
@@ -2888,6 +3065,7 @@ var Snice = (function (exports) {
2888
3065
  function Router(options) {
2889
3066
  const routes = [];
2890
3067
  let is_sorted = false;
3068
+ let placards = []; // Store collected placards
2891
3069
  let _404; // the 404 page
2892
3070
  let _403; // the 403 forbidden page
2893
3071
  let home; // the home page
@@ -2916,7 +3094,7 @@ var Snice = (function (exports) {
2916
3094
  function page(pageOptions) {
2917
3095
  return function (constructor, context) {
2918
3096
  // Transfer metadata from context to constructor (for new decorators)
2919
- if (context.metadata && context.metadata[PROPERTIES]) {
3097
+ if (context?.metadata && context.metadata[PROPERTIES]) {
2920
3098
  if (!constructor[PROPERTIES]) {
2921
3099
  constructor[PROPERTIES] = new Map();
2922
3100
  }
@@ -2961,8 +3139,8 @@ var Snice = (function (exports) {
2961
3139
  };
2962
3140
  // Define the custom element
2963
3141
  customElements.define(pageOptions.tag, constructor);
2964
- // Register the routes with guards and layout
2965
- pageOptions.routes.forEach(route => register(route, pageOptions.tag, pageOptions.transition, pageOptions.guards, pageOptions.layout));
3142
+ // Register the routes with guards, layout, and placard
3143
+ pageOptions.routes.forEach(route => register(route, pageOptions.tag, pageOptions.transition, pageOptions.guards, pageOptions.layout, pageOptions.placard));
2966
3144
  return constructor;
2967
3145
  };
2968
3146
  }
@@ -2973,8 +3151,8 @@ var Snice = (function (exports) {
2973
3151
  * @example
2974
3152
  * register('/custom-route', 'custom-element');
2975
3153
  */
2976
- function register(route, tag, transition, guards, layout) {
2977
- routes.push({ route: new Route(route), tag, transition, guards, layout });
3154
+ function register(route, tag, transition, guards, layout, placard) {
3155
+ routes.push({ route: new Route(route), tag, transition, guards, layout, placard });
2978
3156
  is_sorted = false;
2979
3157
  if (route === '/404') {
2980
3158
  _404 = tag;
@@ -3025,10 +3203,28 @@ var Snice = (function (exports) {
3025
3203
  routes.sort((a, b) => b.route.spec.length - a.route.spec.length);
3026
3204
  is_sorted = true;
3027
3205
  }
3206
+ // Collect placards from registered routes
3207
+ collectPlacards();
3028
3208
  setupEventListeners();
3029
3209
  const path = getPath();
3030
3210
  navigate(path);
3031
3211
  }
3212
+ function collectPlacards() {
3213
+ placards = routes
3214
+ .filter(route => route.placard)
3215
+ .map(route => {
3216
+ const placard = route.placard;
3217
+ return typeof placard === 'function'
3218
+ ? placard(context)
3219
+ : placard;
3220
+ });
3221
+ }
3222
+ function updateLayout(layoutElement, currentPath, routeParams) {
3223
+ // Check if layout implements the update method
3224
+ if (typeof layoutElement.update === 'function') {
3225
+ layoutElement.update(context, placards, currentPath, routeParams);
3226
+ }
3227
+ }
3032
3228
  function getPath() {
3033
3229
  switch (options.type) {
3034
3230
  case 'hash':
@@ -3037,7 +3233,7 @@ var Snice = (function (exports) {
3037
3233
  return window.location.pathname;
3038
3234
  }
3039
3235
  }
3040
- async function renderForbiddenPage(target) {
3236
+ function renderForbiddenPage(target) {
3041
3237
  let newPageElement;
3042
3238
  const has403Page = !!_403;
3043
3239
  if (has403Page) {
@@ -3055,16 +3251,16 @@ var Snice = (function (exports) {
3055
3251
  currentLayoutName = null;
3056
3252
  currentLayoutTimestamp = null;
3057
3253
  }
3058
- async function checkGuards(guards, params, target) {
3254
+ function checkGuards(guards, params, target) {
3059
3255
  const hasGuards = !!guards;
3060
3256
  if (!hasGuards) {
3061
3257
  return true;
3062
3258
  }
3063
3259
  const guardsArray = Array.isArray(guards) ? guards : [guards];
3064
3260
  for (const guard of guardsArray) {
3065
- const allowed = await guard(context, params);
3261
+ const allowed = guard(context, params);
3066
3262
  if (!allowed) {
3067
- await renderForbiddenPage(target);
3263
+ renderForbiddenPage(target);
3068
3264
  return false;
3069
3265
  }
3070
3266
  }
@@ -3092,14 +3288,14 @@ var Snice = (function (exports) {
3092
3288
  div.innerHTML = /*html*/ `<h1>404</h1><p>Page not found</p>`;
3093
3289
  return { element: div, transition: undefined, layout: undefined };
3094
3290
  }
3095
- async function resolveRoute(path, target) {
3291
+ function resolveRoute(path, target) {
3096
3292
  for (const route of routes) {
3097
3293
  const params = route.route.match(path);
3098
3294
  const isMatch = params !== false;
3099
3295
  if (!isMatch) {
3100
3296
  continue;
3101
3297
  }
3102
- const guardsAllowed = await checkGuards(route.guards, params, target);
3298
+ const guardsAllowed = checkGuards(route.guards, params, target);
3103
3299
  if (!guardsAllowed) {
3104
3300
  return { result: RouteResult.GUARDS_FAILED };
3105
3301
  }
@@ -3107,7 +3303,7 @@ var Snice = (function (exports) {
3107
3303
  newPageElement[ROUTER_CONTEXT] = context;
3108
3304
  const routeParams = params;
3109
3305
  Object.keys(routeParams).forEach(key => newPageElement.setAttribute(key, routeParams[key]));
3110
- return { result: RouteResult.SUCCESS, element: newPageElement, transition: route.transition, layout: route.layout };
3306
+ return { result: RouteResult.SUCCESS, element: newPageElement, transition: route.transition, layout: route.layout, routeParams };
3111
3307
  }
3112
3308
  return { result: RouteResult.NOT_FOUND };
3113
3309
  }
@@ -3144,11 +3340,13 @@ var Snice = (function (exports) {
3144
3340
  currentLayoutTimestamp = null;
3145
3341
  return { element: null, needsNewLayout: true };
3146
3342
  }
3147
- async function renderWithLayout(target, pageElement, transition, layoutElement, needsNewLayout) {
3343
+ async function renderWithLayout(target, pageElement, transition, layoutElement, needsNewLayout, currentPath, routeParams) {
3148
3344
  const currentLayout = layoutElement || getCurrentLayoutElement(target);
3149
3345
  if (!currentLayout) {
3150
3346
  return;
3151
3347
  }
3348
+ // Update layout with current context and placards
3349
+ updateLayout(currentLayout, currentPath, routeParams);
3152
3350
  const oldPageInLayout = currentLayout.querySelector('[slot="page"]');
3153
3351
  const shouldTransition = !!(transition && oldPageInLayout);
3154
3352
  if (shouldTransition) {
@@ -3190,11 +3388,13 @@ var Snice = (function (exports) {
3190
3388
  if (!target) {
3191
3389
  throw new Error(`Target element not found: ${options.target}`);
3192
3390
  }
3391
+ // Collect fresh placards before navigation
3392
+ collectPlacards();
3193
3393
  window.scrollTo(0, 0);
3194
3394
  const isHomePath = (path.trim() === '' || path === '/') && !!home;
3195
3395
  if (isHomePath) {
3196
3396
  const homeRoute = routes.find(r => r.route.match('/'));
3197
- const guardsAllowed = await checkGuards(homeRoute?.guards, {}, target);
3397
+ const guardsAllowed = checkGuards(homeRoute?.guards, {}, target);
3198
3398
  if (!guardsAllowed) {
3199
3399
  return;
3200
3400
  }
@@ -3204,26 +3404,26 @@ var Snice = (function (exports) {
3204
3404
  const finalTransition = transition || options.transition;
3205
3405
  const hasLayout = layoutElement !== null || getCurrentLayoutElement(target) !== null;
3206
3406
  if (hasLayout) {
3207
- await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout);
3407
+ await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout, path, {});
3208
3408
  return;
3209
3409
  }
3210
3410
  await renderDirect(target, element, finalTransition);
3211
3411
  return;
3212
3412
  }
3213
- const routeResult = await resolveRoute(path, target);
3413
+ const routeResult = resolveRoute(path, target);
3214
3414
  const isGuardsFailed = routeResult.result === RouteResult.GUARDS_FAILED;
3215
3415
  if (isGuardsFailed) {
3216
3416
  return;
3217
3417
  }
3218
3418
  const isSuccess = routeResult.result === RouteResult.SUCCESS;
3219
3419
  if (isSuccess) {
3220
- const { element, transition, layout } = routeResult;
3420
+ const { element, transition, layout, routeParams = {} } = routeResult;
3221
3421
  const layoutToUse = determineLayout(layout);
3222
3422
  const { element: layoutElement, needsNewLayout } = setupLayout(layoutToUse);
3223
3423
  const finalTransition = transition || options.transition;
3224
3424
  const hasLayout = layoutElement !== null || getCurrentLayoutElement(target) !== null;
3225
3425
  if (hasLayout) {
3226
- await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout);
3426
+ await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout, path, routeParams);
3227
3427
  return;
3228
3428
  }
3229
3429
  await renderDirect(target, element, finalTransition);
@@ -3235,7 +3435,7 @@ var Snice = (function (exports) {
3235
3435
  const finalTransition = transition || options.transition;
3236
3436
  const hasLayout = layoutElement !== null || getCurrentLayoutElement(target) !== null;
3237
3437
  if (hasLayout) {
3238
- await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout);
3438
+ await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout, path, {});
3239
3439
  return;
3240
3440
  }
3241
3441
  await renderDirect(target, element, finalTransition);
@@ -3254,18 +3454,16 @@ var Snice = (function (exports) {
3254
3454
  exports.IS_CONTROLLER_INSTANCE = IS_CONTROLLER_INSTANCE;
3255
3455
  exports.Router = Router;
3256
3456
  exports.SimpleArray = SimpleArray;
3457
+ exports.adopted = adopted;
3257
3458
  exports.applyElementFunctionality = applyElementFunctionality;
3258
- exports.attachController = attachController;
3259
- exports.cleanupNativeElementControllers = cleanupNativeElementControllers;
3260
3459
  exports.context = context;
3261
3460
  exports.controller = controller;
3262
- exports.detachController = detachController;
3263
3461
  exports.dispatch = dispatch;
3264
3462
  exports.dispose = dispose;
3265
3463
  exports.element = element;
3266
- exports.getController = getController;
3267
3464
  exports.getSymbol = getSymbol;
3268
3465
  exports.layout = layout;
3466
+ exports.moved = moved;
3269
3467
  exports.observe = observe;
3270
3468
  exports.on = on;
3271
3469
  exports.part = part;