@mhmo91/schmancy 0.4.89 → 0.4.90
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.
- package/ai/area.md +264 -89
- package/dist/ai/area.md +264 -89
- package/dist/area.cjs +1 -1
- package/dist/area.js +1 -1
- package/dist/{avatar-CBVbma9p.js → avatar-BNmDgcCL.js} +2 -2
- package/dist/{avatar-CBVbma9p.js.map → avatar-BNmDgcCL.js.map} +1 -1
- package/dist/{avatar-DbGgH5qi.cjs → avatar-enEZduLr.cjs} +2 -2
- package/dist/{avatar-DbGgH5qi.cjs.map → avatar-enEZduLr.cjs.map} +1 -1
- package/dist/badge.cjs +1 -1
- package/dist/badge.js +1 -1
- package/dist/content-drawer.cjs +1 -1
- package/dist/content-drawer.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/dist/nav-drawer.cjs +1 -1
- package/dist/nav-drawer.js +1 -1
- package/dist/{route.component-d8kvuxqB.js → route.component-CGJfnuN5.js} +3 -3
- package/dist/route.component-CGJfnuN5.js.map +1 -0
- package/dist/{route.component-B-uzbNko.cjs → route.component-kP_bl5DX.cjs} +2 -2
- package/dist/route.component-kP_bl5DX.cjs.map +1 -0
- package/dist/teleport.cjs +1 -1
- package/dist/teleport.js +1 -1
- package/package.json +1 -1
- package/dist/route.component-B-uzbNko.cjs.map +0 -1
- package/dist/route.component-d8kvuxqB.js.map +0 -1
package/ai/area.md
CHANGED
|
@@ -22,7 +22,7 @@ The main container that displays routed components.
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
### 2. `<schmancy-route>` - Declarative Routing
|
|
25
|
-
Define routes declaratively with segment matching and guards.
|
|
25
|
+
Define routes declaratively with segment matching and guards. Routes are detected via slot change detection and are evaluated in order.
|
|
26
26
|
|
|
27
27
|
```html
|
|
28
28
|
<!-- Simple route -->
|
|
@@ -44,13 +44,6 @@ Define routes declaratively with segment matching and guards.
|
|
|
44
44
|
component="admin-panel"
|
|
45
45
|
.guard=${() => isAuthenticated()}>
|
|
46
46
|
</schmancy-route>
|
|
47
|
-
|
|
48
|
-
<!-- Route with default component -->
|
|
49
|
-
<schmancy-route
|
|
50
|
-
when="dashboard"
|
|
51
|
-
component="dashboard-main"
|
|
52
|
-
default="dashboard-overview">
|
|
53
|
-
</schmancy-route>
|
|
54
47
|
```
|
|
55
48
|
|
|
56
49
|
## Properties Reference
|
|
@@ -60,53 +53,69 @@ Define routes declaratively with segment matching and guards.
|
|
|
60
53
|
| Property | Type | Description |
|
|
61
54
|
|----------|------|-------------|
|
|
62
55
|
| `name` | `string` | **Required**. Unique identifier for this router outlet. |
|
|
63
|
-
| `default` | `string \| Promise<NodeModule> \| CustomElementConstructor \|
|
|
56
|
+
| `default` | `string \| Promise<NodeModule> \| CustomElementConstructor \| HTMLElement \| LazyComponent` | Default component to display when no route matches or area is empty. |
|
|
64
57
|
|
|
65
58
|
### `<schmancy-route>` Properties
|
|
66
59
|
|
|
67
60
|
| Property | Type | Description |
|
|
68
61
|
|----------|------|-------------|
|
|
69
|
-
| `when` | `string` | **Required**. URL segment
|
|
70
|
-
| `component` | `
|
|
62
|
+
| `when` | `string` | **Required**. URL segment to match OR component tag name for programmatic navigation. |
|
|
63
|
+
| `component` | `RouteComponent` | Component to render when route matches (string, constructor, element, lazy component). |
|
|
71
64
|
| `exact` | `boolean` | Whether route should match exactly (default: false). |
|
|
72
|
-
| `guard` | `() => GuardResult \| Promise<GuardResult>` | Navigation guard function
|
|
65
|
+
| `guard` | `() => GuardResult \| Promise<GuardResult>` | Navigation guard function. |
|
|
66
|
+
|
|
67
|
+
Note: `<schmancy-route>` does NOT have a `default` property - that's only available on `<schmancy-area>`.
|
|
73
68
|
|
|
74
|
-
## URL
|
|
69
|
+
## URL Matching Behavior
|
|
75
70
|
|
|
76
|
-
The
|
|
71
|
+
The routing system uses multiple strategies to match routes:
|
|
72
|
+
|
|
73
|
+
### 1. **URL Segment Matching**
|
|
74
|
+
The URL path is split by '/' and the `when` attribute is checked against each segment in the array:
|
|
77
75
|
|
|
78
76
|
```html
|
|
79
77
|
<!-- Segment name matching -->
|
|
80
|
-
<schmancy-route when="
|
|
81
|
-
<!--
|
|
78
|
+
<schmancy-route when="products" component="product-list"></schmancy-route>
|
|
79
|
+
<!-- URL: /store/products/123 → segments: ['store', 'products', '123'] → MATCHES (products is in array) -->
|
|
80
|
+
<!-- URL: /products → segments: ['products'] → MATCHES -->
|
|
81
|
+
<!-- URL: /home → segments: ['home'] → DOES NOT MATCH -->
|
|
82
|
+
```
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<!-- Matches when routing to 'user-profile' component -->
|
|
84
|
+
### 2. **Component Tag Name Matching**
|
|
85
|
+
For programmatic navigation, the `when` attribute can match the component tag name:
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
<schmancy-route when="
|
|
89
|
-
<!--
|
|
87
|
+
```html
|
|
88
|
+
<schmancy-route when="user-profile" component="user-profile"></schmancy-route>
|
|
89
|
+
<!-- When navigating with: area.push({ component: 'user-profile', area: 'main' }) → MATCHES -->
|
|
90
|
+
```
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<!-- Matches: /products, /store/products, /products/123, etc. -->
|
|
92
|
+
### 3. **JSON-Encoded State in URL**
|
|
93
|
+
When pretty URLs are disabled (default), the router encodes state as JSON in the URL:
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
```javascript
|
|
96
|
+
// URL: /%7B%22main%22%3A%7B%22component%22%3A%22user-profile%22%7D%7D
|
|
97
|
+
// Decoded: {"main":{"component":"user-profile"}}
|
|
98
|
+
// The router extracts this and matches against routes
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
###
|
|
101
|
+
### Matching Priority
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
Routes are evaluated in the order they appear in the DOM. The first matching route wins:
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<schmancy-area name="main">
|
|
107
|
+
<!-- Evaluated first -->
|
|
108
|
+
<schmancy-route when="user" component="user-detail"></schmancy-route>
|
|
109
|
+
<!-- Evaluated second -->
|
|
110
|
+
<schmancy-route when="users" component="user-list"></schmancy-route>
|
|
111
|
+
<!-- Catch-all (empty when) evaluated last -->
|
|
112
|
+
<schmancy-route when="" component="not-found"></schmancy-route>
|
|
113
|
+
</schmancy-area>
|
|
114
|
+
```
|
|
106
115
|
|
|
107
116
|
## Navigation Guards
|
|
108
117
|
|
|
109
|
-
Guards protect routes and can redirect navigation:
|
|
118
|
+
Guards protect routes and can redirect navigation. Guards are executed before component creation for better performance:
|
|
110
119
|
|
|
111
120
|
```typescript
|
|
112
121
|
// Boolean guard - simple allow/deny
|
|
@@ -154,11 +163,13 @@ Guards protect routes and can redirect navigation:
|
|
|
154
163
|
| Return Value | Behavior |
|
|
155
164
|
|--------------|----------|
|
|
156
165
|
| `true` | Allow navigation to proceed |
|
|
157
|
-
| `false` | Block navigation silently |
|
|
158
|
-
| `"/path"` (string) | Redirect to the specified path |
|
|
166
|
+
| `false` | Block navigation silently, falls back to default if specified |
|
|
167
|
+
| `"/path"` (string) | Redirect to the specified path segment |
|
|
159
168
|
| `{redirect: "/path"}` | Explicit redirect object |
|
|
160
169
|
| `Promise<...>` | Async guard, resolved before navigation |
|
|
161
170
|
|
|
171
|
+
**Note**: Guard failures (`false` return) will fall back to the area's `default` component if specified. Redirects trigger a new route lookup based on the redirect path segment.
|
|
172
|
+
|
|
162
173
|
## Nested Routing
|
|
163
174
|
|
|
164
175
|
Create complex nested routing structures:
|
|
@@ -220,28 +231,44 @@ import { area } from '@schmancy/index';
|
|
|
220
231
|
|
|
221
232
|
// Navigation methods
|
|
222
233
|
area.push({
|
|
223
|
-
component: 'user-profile', // Component constructor, string tag name, or
|
|
224
|
-
area: 'main', // Target area name
|
|
225
|
-
state?: { view: 'profile' }, // Optional state object
|
|
226
|
-
params?: { id: '123' }, // Optional query parameters
|
|
227
|
-
props?: { userId: '123' }, // Optional component properties
|
|
228
|
-
historyStrategy
|
|
229
|
-
clearQueryParams?: ['sort'] // Clear specific query params
|
|
234
|
+
component: 'user-profile', // Component constructor, string tag name, element instance, or lazy component
|
|
235
|
+
area: 'main', // Target area name (required)
|
|
236
|
+
state?: { view: 'profile' }, // Optional state object (stored in history)
|
|
237
|
+
params?: { id: '123' }, // Optional query parameters (NOT URL query params - these are component params)
|
|
238
|
+
props?: { userId: '123' }, // Optional component properties (alias for params, applied to component)
|
|
239
|
+
historyStrategy?: 'push', // 'push' | 'replace' | 'pop' | 'silent' (default: 'push')
|
|
240
|
+
clearQueryParams?: ['sort'] // Clear specific URL query params
|
|
230
241
|
});
|
|
231
242
|
|
|
232
|
-
// Remove/clear an area
|
|
233
|
-
area.pop('sidebar'); //
|
|
243
|
+
// Remove/clear an area - sends clearing signal to area
|
|
244
|
+
area.pop('sidebar'); // Clears the area content and updates history
|
|
234
245
|
|
|
235
246
|
// Subscription methods (return RxJS Observables)
|
|
236
|
-
area.on(areaName, skipCurrent?)
|
|
237
|
-
area.all(skipCurrent?)
|
|
238
|
-
area.getState<T>(areaName)
|
|
239
|
-
area.params<T>(areaName)
|
|
240
|
-
area.param<T>(areaName, key)
|
|
241
|
-
area.props<T>(areaName)
|
|
242
|
-
area.prop<T>(areaName, key)
|
|
247
|
+
area.on(areaName, skipCurrent?) // Subscribe to an area's route changes
|
|
248
|
+
area.all(skipCurrent?) // Subscribe to all areas
|
|
249
|
+
area.getState<T>(areaName) // Get typed state from an area
|
|
250
|
+
area.params<T>(areaName) // Get typed params from an area
|
|
251
|
+
area.param<T>(areaName, key) // Get a specific param value
|
|
252
|
+
area.props<T>(areaName) // Get typed props from an area
|
|
253
|
+
area.prop<T>(areaName, key) // Get a specific prop value
|
|
254
|
+
|
|
255
|
+
// Utility methods
|
|
256
|
+
area.hasArea(areaName) // Check if an area exists
|
|
257
|
+
area.getActiveAreas() // Get array of active area names
|
|
258
|
+
area.getRoute(areaName) // Get route synchronously (returns ActiveRoute | undefined)
|
|
259
|
+
|
|
260
|
+
// Configuration
|
|
261
|
+
area.prettyURL = false // Enable pretty URLs (default: false uses JSON encoding)
|
|
262
|
+
area.mode = 'HISTORY' // 'HISTORY' | 'SILENT' (default: 'HISTORY')
|
|
263
|
+
area.enableHistoryMode = true // Enable browser history management (default: true)
|
|
243
264
|
```
|
|
244
265
|
|
|
266
|
+
### Important Distinctions:
|
|
267
|
+
- **`params`**: Component-level parameters that are passed as properties to the component instance
|
|
268
|
+
- **`props`**: Alias for params - both are applied directly to the component element
|
|
269
|
+
- **`state`**: Arbitrary data stored in browser history, accessible via subscriptions
|
|
270
|
+
- **URL Query Parameters**: Managed separately via `clearQueryParams` option
|
|
271
|
+
|
|
245
272
|
## Common Patterns
|
|
246
273
|
|
|
247
274
|
### Basic Navigation
|
|
@@ -302,7 +329,7 @@ area.push({
|
|
|
302
329
|
</schmancy-area>
|
|
303
330
|
```
|
|
304
331
|
|
|
305
|
-
###
|
|
332
|
+
### Clearing Areas with area.pop()
|
|
306
333
|
|
|
307
334
|
```typescript
|
|
308
335
|
// Open a modal or sidebar
|
|
@@ -312,11 +339,14 @@ area.push({
|
|
|
312
339
|
props: { userId: '123' }
|
|
313
340
|
});
|
|
314
341
|
|
|
315
|
-
//
|
|
316
|
-
area.pop('modal'); //
|
|
342
|
+
// Clear the modal - sends clearing signal to area component
|
|
343
|
+
area.pop('modal'); // Component receives null signal and clears content
|
|
317
344
|
|
|
318
345
|
// Clear multiple areas
|
|
319
346
|
['modal', 'sidebar', 'overlay'].forEach(name => area.pop(name));
|
|
347
|
+
|
|
348
|
+
// Note: area.pop() now properly sends a clearing signal through the RxJS pipeline
|
|
349
|
+
// The area component will receive a route with null component and remove its content
|
|
320
350
|
```
|
|
321
351
|
|
|
322
352
|
### Reactive Subscriptions
|
|
@@ -438,12 +468,12 @@ class AppLayout extends LitElement {
|
|
|
438
468
|
|
|
439
469
|
#### Using the `lazy()` helper function
|
|
440
470
|
|
|
441
|
-
Schmancy Area provides a powerful `lazy()` function similar to React.lazy() for optimal code splitting:
|
|
471
|
+
Schmancy Area provides a powerful `lazy()` function similar to React.lazy() for optimal code splitting. Lazy components are resolved to constructors before element creation for better performance:
|
|
442
472
|
|
|
443
473
|
```typescript
|
|
444
474
|
import { lazy } from '@schmancy/area';
|
|
445
475
|
|
|
446
|
-
// Create lazy-loaded components
|
|
476
|
+
// Create lazy-loaded components with default exports
|
|
447
477
|
const LazyDashboard = lazy(() => import('./components/dashboard'));
|
|
448
478
|
const LazyAnalytics = lazy(() => import('./components/analytics'));
|
|
449
479
|
const LazyReports = lazy(() => import('./components/reports'));
|
|
@@ -467,20 +497,23 @@ area.push({
|
|
|
467
497
|
|
|
468
498
|
#### Preloading Components
|
|
469
499
|
|
|
470
|
-
Lazy components support preloading for better perceived performance:
|
|
500
|
+
Lazy components support preloading for better perceived performance. The lazy() function caches both the loading promise and the loaded module:
|
|
471
501
|
|
|
472
502
|
```typescript
|
|
473
503
|
const LazyProfile = lazy(() => import('./profile'));
|
|
474
504
|
|
|
475
505
|
// Preload on hover for instant navigation
|
|
476
506
|
button.addEventListener('mouseenter', () => {
|
|
477
|
-
LazyProfile.preload();
|
|
507
|
+
LazyProfile.preload(); // Starts loading and caches the promise
|
|
478
508
|
});
|
|
479
509
|
|
|
480
|
-
//
|
|
510
|
+
// The component will be instantly available when navigated to
|
|
511
|
+
area.push({ component: LazyProfile, area: 'main' }); // Uses cached module
|
|
512
|
+
|
|
513
|
+
// Preload critical routes after initial page load
|
|
481
514
|
window.addEventListener('load', () => {
|
|
482
515
|
setTimeout(() => {
|
|
483
|
-
LazyProfile.preload();
|
|
516
|
+
LazyProfile.preload(); // Caches for future use
|
|
484
517
|
LazySettings.preload();
|
|
485
518
|
}, 2000);
|
|
486
519
|
});
|
|
@@ -673,6 +706,123 @@ html`
|
|
|
673
706
|
`;
|
|
674
707
|
```
|
|
675
708
|
|
|
709
|
+
## History Management
|
|
710
|
+
|
|
711
|
+
The router manages browser history automatically, storing state in `history.state.schmancyAreas`:
|
|
712
|
+
|
|
713
|
+
### Browser State Structure
|
|
714
|
+
```javascript
|
|
715
|
+
// Browser history.state structure:
|
|
716
|
+
{
|
|
717
|
+
schmancyAreas: {
|
|
718
|
+
main: {
|
|
719
|
+
component: 'user-profile',
|
|
720
|
+
state: { view: 'edit' },
|
|
721
|
+
params: { userId: '123' },
|
|
722
|
+
area: 'main'
|
|
723
|
+
},
|
|
724
|
+
sidebar: {
|
|
725
|
+
component: 'nav-menu',
|
|
726
|
+
area: 'sidebar'
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### URL Encoding Modes
|
|
733
|
+
|
|
734
|
+
#### 1. **JSON Encoding (Default)**
|
|
735
|
+
When `area.prettyURL = false` (default), state is encoded as JSON in the URL:
|
|
736
|
+
```javascript
|
|
737
|
+
// URL: /%7B%22main%22%3A%7B%22component%22%3A%22user-profile%22%7D%7D
|
|
738
|
+
// Decoded: {"main":{"component":"user-profile"}}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
#### 2. **Pretty URLs**
|
|
742
|
+
When `area.prettyURL = true`, creates cleaner URLs:
|
|
743
|
+
```javascript
|
|
744
|
+
area.prettyURL = true;
|
|
745
|
+
// URL: /user-profile?userId=123
|
|
746
|
+
// Component and simple params are included in the URL
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### History Strategies
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
area.push({
|
|
753
|
+
component: 'page',
|
|
754
|
+
area: 'main',
|
|
755
|
+
historyStrategy: 'push' // Options: 'push' | 'replace' | 'pop' | 'silent'
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// 'push': Adds new entry to history (default)
|
|
759
|
+
// 'replace': Replaces current history entry
|
|
760
|
+
// 'pop': Updates during back/forward navigation
|
|
761
|
+
// 'silent': No history update
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
## Internal Behaviors
|
|
765
|
+
|
|
766
|
+
### Component Lifecycle
|
|
767
|
+
|
|
768
|
+
1. **Route Resolution Pipeline**:
|
|
769
|
+
- Route request received (programmatic, URL, or browser navigation)
|
|
770
|
+
- Lazy components resolved to constructors (if applicable)
|
|
771
|
+
- Component identifier extracted for deduplication
|
|
772
|
+
- Route deduplicated using `distinctUntilChanged`
|
|
773
|
+
- HTMLElement created and properties applied
|
|
774
|
+
- Component swapped with animation
|
|
775
|
+
|
|
776
|
+
2. **Component Swapping Animation**:
|
|
777
|
+
- Old component fades out (150ms)
|
|
778
|
+
- Old component removed from DOM
|
|
779
|
+
- New component added to shadow DOM
|
|
780
|
+
- New component fades in (150ms)
|
|
781
|
+
|
|
782
|
+
3. **Deduplication Strategy**:
|
|
783
|
+
Components are deduplicated based on:
|
|
784
|
+
- Component identifier (tag name or constructor name)
|
|
785
|
+
- JSON stringified params
|
|
786
|
+
- JSON stringified state
|
|
787
|
+
|
|
788
|
+
This prevents unnecessary re-renders when navigating to the same route.
|
|
789
|
+
|
|
790
|
+
### RxJS Pipeline Architecture
|
|
791
|
+
|
|
792
|
+
The area component uses a sophisticated RxJS pipeline:
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
// Three navigation sources merged into one stream:
|
|
796
|
+
merge(
|
|
797
|
+
area.request, // Programmatic navigation
|
|
798
|
+
of(location.pathname), // Initial load
|
|
799
|
+
fromEvent('popstate') // Browser back/forward
|
|
800
|
+
)
|
|
801
|
+
.pipe(
|
|
802
|
+
// Resolve lazy components
|
|
803
|
+
switchMap(/* ... */),
|
|
804
|
+
|
|
805
|
+
// Deduplicate identical routes
|
|
806
|
+
distinctUntilChanged(/* ... */),
|
|
807
|
+
|
|
808
|
+
// Share single subscription
|
|
809
|
+
shareReplay(1),
|
|
810
|
+
|
|
811
|
+
// Handle errors gracefully
|
|
812
|
+
catchError(/* ... */),
|
|
813
|
+
|
|
814
|
+
// Cleanup on disconnect
|
|
815
|
+
takeUntil(this.disconnecting)
|
|
816
|
+
)
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### Error Handling
|
|
820
|
+
|
|
821
|
+
- **Lazy Load Failures**: Logged to console, component set to null
|
|
822
|
+
- **Component Creation Failures**: Logged to console, gracefully skipped
|
|
823
|
+
- **Guard Errors**: Caught and fall back to default component
|
|
824
|
+
- **Navigation Errors**: Logged and return EMPTY observable
|
|
825
|
+
|
|
676
826
|
## Migration Guide
|
|
677
827
|
|
|
678
828
|
### From Direct area.push() to Declarative Routes
|
|
@@ -815,47 +965,67 @@ area.on('protected-area').pipe(
|
|
|
815
965
|
```typescript
|
|
816
966
|
// Route Action - used for navigation
|
|
817
967
|
interface RouteAction {
|
|
818
|
-
component: CustomElementConstructor | string | HTMLElement | Promise<
|
|
819
|
-
area: string;
|
|
820
|
-
state?: Record<string, unknown>;
|
|
821
|
-
params?: Record<string, unknown>;
|
|
822
|
-
props?: Record<string, unknown>;
|
|
968
|
+
component: CustomElementConstructor | string | HTMLElement | (() => Promise<{ default: CustomElementConstructor }>);
|
|
969
|
+
area: string; // Required
|
|
970
|
+
state?: Record<string, unknown>; // State stored in history
|
|
971
|
+
params?: Record<string, unknown>; // Component properties
|
|
972
|
+
props?: Record<string, unknown>; // Alias for params
|
|
823
973
|
historyStrategy?: 'push' | 'replace' | 'pop' | 'silent';
|
|
824
|
-
clearQueryParams?: string[] | null;
|
|
974
|
+
clearQueryParams?: string[] | boolean | null; // Clear URL query params
|
|
975
|
+
_source?: 'programmatic' | 'browser' | 'initial'; // Internal use only
|
|
825
976
|
}
|
|
826
977
|
|
|
827
978
|
// Active Route - current state of an area
|
|
828
979
|
interface ActiveRoute {
|
|
829
|
-
component: string;
|
|
980
|
+
component: string; // Always resolved to tag name
|
|
830
981
|
area: string;
|
|
831
982
|
state?: Record<string, unknown>;
|
|
832
|
-
params?: Record<string, unknown>;
|
|
833
|
-
props?: Record<string, unknown>;
|
|
983
|
+
params?: Record<string, unknown>; // Component properties
|
|
984
|
+
props?: Record<string, unknown>; // Component properties
|
|
834
985
|
}
|
|
835
986
|
|
|
836
|
-
// Guard function signatures
|
|
987
|
+
// Guard function signatures
|
|
837
988
|
export type GuardResult = boolean | string | { redirect: string };
|
|
838
989
|
type GuardFunction = () => GuardResult | Promise<GuardResult>;
|
|
839
990
|
|
|
840
|
-
// Route
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
991
|
+
// Route Component Types
|
|
992
|
+
export type RouteComponent =
|
|
993
|
+
| string // Tag name
|
|
994
|
+
| CustomElementConstructor // Constructor function
|
|
995
|
+
| HTMLElement // Existing element
|
|
996
|
+
| TemplateResult<1> // Lit template
|
|
997
|
+
| (() => Promise<{ default: CustomElementConstructor }>) // Lazy loader
|
|
998
|
+
| Promise<{ default: CustomElementConstructor }>; // Dynamic import
|
|
999
|
+
|
|
1000
|
+
// Route configuration (from route.component.ts)
|
|
1001
|
+
interface RouteConfig {
|
|
1002
|
+
when: string; // URL segment to match
|
|
1003
|
+
component: RouteComponent;
|
|
1004
|
+
exact?: boolean; // Not actively used in current implementation
|
|
1005
|
+
guard?: () => GuardResult | Promise<GuardResult>;
|
|
846
1006
|
}
|
|
847
1007
|
|
|
848
|
-
// Lazy Loading Types (from lazy.ts)
|
|
849
|
-
type CustomElementConstructor = typeof HTMLElement;
|
|
850
|
-
|
|
851
1008
|
// LazyComponent interface with preload capability
|
|
852
1009
|
interface LazyComponent<T extends CustomElementConstructor = CustomElementConstructor> {
|
|
853
1010
|
(): Promise<{ default: T }>;
|
|
854
1011
|
preload(): Promise<void>;
|
|
855
|
-
_promise?: Promise<{ default: T }>;
|
|
856
|
-
_module?: { default: T };
|
|
1012
|
+
_promise?: Promise<{ default: T }>; // Cached loading promise
|
|
1013
|
+
_module?: { default: T }; // Cached loaded module
|
|
857
1014
|
}
|
|
858
1015
|
|
|
1016
|
+
// Browser History State Structure
|
|
1017
|
+
interface SchmancyHistoryState {
|
|
1018
|
+
schmancyAreas: Record<string, ActiveRoute>;
|
|
1019
|
+
[key: string]: any; // Allow other apps to store additional state
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// History Strategy Enum
|
|
1023
|
+
enum HISTORY_STRATEGY {
|
|
1024
|
+
push = 'push',
|
|
1025
|
+
replace = 'replace',
|
|
1026
|
+
pop = 'pop',
|
|
1027
|
+
silent = 'silent'
|
|
1028
|
+
}
|
|
859
1029
|
```
|
|
860
1030
|
|
|
861
1031
|
## Related Components
|
|
@@ -881,14 +1051,19 @@ interface LazyComponent<T extends CustomElementConstructor = CustomElementConstr
|
|
|
881
1051
|
## Summary
|
|
882
1052
|
|
|
883
1053
|
Schmancy Area provides a complete routing solution with:
|
|
884
|
-
- ✅ **Declarative routing** with `<schmancy-route>`
|
|
885
|
-
- ✅ **
|
|
886
|
-
- ✅ **Navigation guards** with
|
|
1054
|
+
- ✅ **Declarative routing** with `<schmancy-route>` using slot detection
|
|
1055
|
+
- ✅ **URL segment matching** - splits paths and checks for segment presence
|
|
1056
|
+
- ✅ **Navigation guards** with boolean, string, and object return types
|
|
887
1057
|
- ✅ **Nested routing** support for complex applications
|
|
888
|
-
- ✅ **Default components** for fallback UI
|
|
889
|
-
- ✅ **Reactive subscriptions** with RxJS
|
|
890
|
-
- ✅ **Type-safe** API with TypeScript
|
|
1058
|
+
- ✅ **Default components** for fallback UI (area-level only)
|
|
1059
|
+
- ✅ **Reactive subscriptions** with RxJS observables
|
|
1060
|
+
- ✅ **Type-safe** API with TypeScript support
|
|
891
1061
|
- ✅ **Multiple router outlets** for complex layouts
|
|
892
|
-
- ✅ **
|
|
1062
|
+
- ✅ **Proper area.pop()** sends clearing signals through RxJS pipeline
|
|
1063
|
+
- ✅ **Lazy loading** with caching and preload support
|
|
1064
|
+
- ✅ **History management** with JSON encoding or pretty URLs
|
|
1065
|
+
- ✅ **Animation transitions** during component swapping (150ms fade)
|
|
1066
|
+
- ✅ **Deduplication** prevents unnecessary re-renders
|
|
1067
|
+
- ✅ **Error handling** with graceful fallbacks
|
|
893
1068
|
|
|
894
1069
|
The combination of declarative routes and programmatic navigation provides maximum flexibility for building modern web applications.
|