@furystack/shades 11.1.0 → 12.0.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.
- package/CHANGELOG.md +291 -0
- package/README.md +13 -13
- package/esm/component-factory.spec.js +13 -5
- package/esm/component-factory.spec.js.map +1 -1
- package/esm/components/index.d.ts +4 -1
- package/esm/components/index.d.ts.map +1 -1
- package/esm/components/index.js +4 -1
- package/esm/components/index.js.map +1 -1
- package/esm/components/lazy-load.d.ts +2 -4
- package/esm/components/lazy-load.d.ts.map +1 -1
- package/esm/components/lazy-load.js +40 -24
- package/esm/components/lazy-load.js.map +1 -1
- package/esm/components/lazy-load.spec.js +57 -50
- package/esm/components/lazy-load.spec.js.map +1 -1
- package/esm/components/link-to-route.d.ts +2 -0
- package/esm/components/link-to-route.d.ts.map +1 -1
- package/esm/components/link-to-route.js +3 -2
- package/esm/components/link-to-route.js.map +1 -1
- package/esm/components/link-to-route.spec.js +13 -9
- package/esm/components/link-to-route.spec.js.map +1 -1
- package/esm/components/nested-route-link.d.ts +62 -0
- package/esm/components/nested-route-link.d.ts.map +1 -0
- package/esm/components/nested-route-link.js +66 -0
- package/esm/components/nested-route-link.js.map +1 -0
- package/esm/components/nested-route-link.spec.d.ts +2 -0
- package/esm/components/nested-route-link.spec.d.ts.map +1 -0
- package/esm/components/nested-route-link.spec.js +179 -0
- package/esm/components/nested-route-link.spec.js.map +1 -0
- package/esm/components/nested-route-types.d.ts +37 -0
- package/esm/components/nested-route-types.d.ts.map +1 -0
- package/esm/components/nested-route-types.js +2 -0
- package/esm/components/nested-route-types.js.map +1 -0
- package/esm/components/nested-router.d.ts +103 -0
- package/esm/components/nested-router.d.ts.map +1 -0
- package/esm/components/nested-router.js +178 -0
- package/esm/components/nested-router.js.map +1 -0
- package/esm/components/nested-router.spec.d.ts +2 -0
- package/esm/components/nested-router.spec.d.ts.map +1 -0
- package/esm/components/nested-router.spec.js +659 -0
- package/esm/components/nested-router.spec.js.map +1 -0
- package/esm/components/route-link.d.ts +4 -0
- package/esm/components/route-link.d.ts.map +1 -1
- package/esm/components/route-link.js +5 -5
- package/esm/components/route-link.js.map +1 -1
- package/esm/components/route-link.spec.js +16 -12
- package/esm/components/route-link.spec.js.map +1 -1
- package/esm/components/router.d.ts +20 -2
- package/esm/components/router.d.ts.map +1 -1
- package/esm/components/router.js +3 -0
- package/esm/components/router.js.map +1 -1
- package/esm/components/router.spec.js +75 -74
- package/esm/components/router.spec.js.map +1 -1
- package/esm/initialize.d.ts +11 -0
- package/esm/initialize.d.ts.map +1 -1
- package/esm/initialize.js +5 -0
- package/esm/initialize.js.map +1 -1
- package/esm/jsx.d.ts +83 -2
- package/esm/jsx.d.ts.map +1 -1
- package/esm/models/children-list.d.ts +5 -1
- package/esm/models/children-list.d.ts.map +1 -1
- package/esm/models/partial-element.d.ts +12 -2
- package/esm/models/partial-element.d.ts.map +1 -1
- package/esm/models/render-options.d.ts +89 -3
- package/esm/models/render-options.d.ts.map +1 -1
- package/esm/models/selection-state.d.ts +4 -0
- package/esm/models/selection-state.d.ts.map +1 -1
- package/esm/services/location-service.d.ts +11 -0
- package/esm/services/location-service.d.ts.map +1 -1
- package/esm/services/location-service.js +11 -0
- package/esm/services/location-service.js.map +1 -1
- package/esm/services/resource-manager.d.ts +24 -0
- package/esm/services/resource-manager.d.ts.map +1 -1
- package/esm/services/resource-manager.js +30 -0
- package/esm/services/resource-manager.js.map +1 -1
- package/esm/services/resource-manager.spec.js +93 -0
- package/esm/services/resource-manager.spec.js.map +1 -1
- package/esm/services/screen-service.d.ts +81 -4
- package/esm/services/screen-service.d.ts.map +1 -1
- package/esm/services/screen-service.js +75 -4
- package/esm/services/screen-service.js.map +1 -1
- package/esm/services/screen-service.spec.js +91 -7
- package/esm/services/screen-service.spec.js.map +1 -1
- package/esm/shade-component.d.ts +17 -4
- package/esm/shade-component.d.ts.map +1 -1
- package/esm/shade-component.js +67 -5
- package/esm/shade-component.js.map +1 -1
- package/esm/shade-host-props-ref.integration.spec.d.ts +2 -0
- package/esm/shade-host-props-ref.integration.spec.d.ts.map +1 -0
- package/esm/shade-host-props-ref.integration.spec.js +381 -0
- package/esm/shade-host-props-ref.integration.spec.js.map +1 -0
- package/esm/shade-resources.integration.spec.js +208 -39
- package/esm/shade-resources.integration.spec.js.map +1 -1
- package/esm/shade.d.ts +20 -17
- package/esm/shade.d.ts.map +1 -1
- package/esm/shade.js +172 -33
- package/esm/shade.js.map +1 -1
- package/esm/shade.spec.js +31 -30
- package/esm/shade.spec.js.map +1 -1
- package/esm/shades.integration.spec.js +135 -72
- package/esm/shades.integration.spec.js.map +1 -1
- package/esm/style-manager.d.ts +2 -2
- package/esm/style-manager.js +2 -2
- package/esm/svg-types.d.ts +389 -0
- package/esm/svg-types.d.ts.map +1 -0
- package/esm/svg-types.js +9 -0
- package/esm/svg-types.js.map +1 -0
- package/esm/svg.d.ts +15 -0
- package/esm/svg.d.ts.map +1 -0
- package/esm/svg.js +76 -0
- package/esm/svg.js.map +1 -0
- package/esm/svg.spec.d.ts +2 -0
- package/esm/svg.spec.d.ts.map +1 -0
- package/esm/svg.spec.js +80 -0
- package/esm/svg.spec.js.map +1 -0
- package/esm/vnode.d.ts +103 -0
- package/esm/vnode.d.ts.map +1 -0
- package/esm/vnode.integration.spec.d.ts +2 -0
- package/esm/vnode.integration.spec.d.ts.map +1 -0
- package/esm/vnode.integration.spec.js +494 -0
- package/esm/vnode.integration.spec.js.map +1 -0
- package/esm/vnode.js +453 -0
- package/esm/vnode.js.map +1 -0
- package/esm/vnode.spec.d.ts +2 -0
- package/esm/vnode.spec.d.ts.map +1 -0
- package/esm/vnode.spec.js +473 -0
- package/esm/vnode.spec.js.map +1 -0
- package/package.json +3 -3
- package/src/component-factory.spec.tsx +18 -5
- package/src/components/index.ts +4 -1
- package/src/components/lazy-load.spec.tsx +82 -75
- package/src/components/lazy-load.tsx +49 -27
- package/src/components/link-to-route.spec.tsx +25 -21
- package/src/components/link-to-route.tsx +4 -2
- package/src/components/nested-route-link.spec.tsx +303 -0
- package/src/components/nested-route-link.tsx +100 -0
- package/src/components/nested-route-types.ts +42 -0
- package/src/components/nested-router.spec.tsx +817 -0
- package/src/components/nested-router.tsx +256 -0
- package/src/components/route-link.spec.tsx +22 -18
- package/src/components/route-link.tsx +6 -5
- package/src/components/router.spec.tsx +109 -108
- package/src/components/router.tsx +15 -2
- package/src/initialize.ts +12 -0
- package/src/jsx.ts +129 -2
- package/src/models/children-list.ts +7 -1
- package/src/models/partial-element.ts +13 -2
- package/src/models/render-options.ts +90 -3
- package/src/models/selection-state.ts +4 -0
- package/src/services/location-service.tsx +11 -0
- package/src/services/resource-manager.spec.ts +116 -0
- package/src/services/resource-manager.ts +30 -0
- package/src/services/screen-service.spec.ts +109 -7
- package/src/services/screen-service.ts +81 -4
- package/src/shade-component.ts +72 -6
- package/src/shade-host-props-ref.integration.spec.tsx +460 -0
- package/src/shade-resources.integration.spec.tsx +276 -52
- package/src/shade.spec.tsx +40 -39
- package/src/shade.ts +186 -58
- package/src/shades.integration.spec.tsx +154 -80
- package/src/style-manager.ts +2 -2
- package/src/svg-types.ts +437 -0
- package/src/svg.spec.ts +89 -0
- package/src/svg.ts +78 -0
- package/src/vnode.integration.spec.tsx +657 -0
- package/src/vnode.spec.ts +579 -0
- package/src/vnode.ts +508 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-route-types.js","sourceRoot":"","sources":["../../src/components/nested-route-types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { MatchOptions, MatchResult } from 'path-to-regexp';
|
|
2
|
+
import type { RenderOptions } from '../models/render-options.js';
|
|
3
|
+
/**
|
|
4
|
+
* A single route entry in a NestedRouter configuration.
|
|
5
|
+
* Unlike flat `Route`, the URL is the Record key (not a field), and the
|
|
6
|
+
* `component` receives an `outlet` for rendering matched child content.
|
|
7
|
+
* @typeParam TMatchResult - The type of matched URL parameters
|
|
8
|
+
*/
|
|
9
|
+
export type NestedRoute<TMatchResult = unknown> = {
|
|
10
|
+
component: (options: {
|
|
11
|
+
currentUrl: string;
|
|
12
|
+
match: MatchResult<TMatchResult extends object ? TMatchResult : object>;
|
|
13
|
+
outlet?: JSX.Element;
|
|
14
|
+
}) => JSX.Element;
|
|
15
|
+
routingOptions?: MatchOptions;
|
|
16
|
+
onVisit?: (options: RenderOptions<unknown> & {
|
|
17
|
+
element: JSX.Element;
|
|
18
|
+
}) => Promise<void>;
|
|
19
|
+
onLeave?: (options: RenderOptions<unknown> & {
|
|
20
|
+
element: JSX.Element;
|
|
21
|
+
}) => Promise<void>;
|
|
22
|
+
children?: Record<string, NestedRoute<any>>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Props for the NestedRouter component.
|
|
26
|
+
* Routes are defined as a Record where keys are URL patterns.
|
|
27
|
+
*/
|
|
28
|
+
export type NestedRouterProps = {
|
|
29
|
+
routes: Record<string, NestedRoute<any>>;
|
|
30
|
+
notFound?: JSX.Element;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A single entry in a match chain, pairing a matched route with its match result.
|
|
34
|
+
*/
|
|
35
|
+
export type MatchChainEntry = {
|
|
36
|
+
route: NestedRoute<unknown>;
|
|
37
|
+
match: MatchResult<object>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Internal state for the NestedRouter component.
|
|
41
|
+
* `matchChain` is `null` when a notFound fallback has been rendered,
|
|
42
|
+
* distinguishing it from the initial empty array (not yet processed).
|
|
43
|
+
*/
|
|
44
|
+
export type NestedRouterState = {
|
|
45
|
+
matchChain: MatchChainEntry[] | null;
|
|
46
|
+
jsx: JSX.Element;
|
|
47
|
+
chainElements: JSX.Element[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Recursively builds a match chain from outermost to innermost matched route.
|
|
51
|
+
*
|
|
52
|
+
* For routes with children, a prefix match (`end: false`) is attempted first.
|
|
53
|
+
* If a child matches the remaining URL, the parent and child chain are combined.
|
|
54
|
+
* If no child matches, an exact match on the parent alone is attempted.
|
|
55
|
+
*
|
|
56
|
+
* For leaf routes (no children), only exact matching is used.
|
|
57
|
+
*
|
|
58
|
+
* @param routes - The route definitions to match against
|
|
59
|
+
* @param currentUrl - The URL path to match
|
|
60
|
+
* @returns An array of matched chain entries from outermost to innermost, or null if no match
|
|
61
|
+
*/
|
|
62
|
+
export declare const buildMatchChain: (routes: Record<string, NestedRoute<any>>, currentUrl: string) => MatchChainEntry[] | null;
|
|
63
|
+
/**
|
|
64
|
+
* Finds the first index where two match chains diverge.
|
|
65
|
+
* Returns the length of the shorter chain if one is a prefix of the other.
|
|
66
|
+
*/
|
|
67
|
+
export declare const findDivergenceIndex: (oldChain: MatchChainEntry[], newChain: MatchChainEntry[]) => number;
|
|
68
|
+
/**
|
|
69
|
+
* The result of rendering a match chain, containing both the fully composed
|
|
70
|
+
* JSX tree and per-entry elements for scoped lifecycle animations.
|
|
71
|
+
*/
|
|
72
|
+
export type RenderMatchChainResult = {
|
|
73
|
+
jsx: JSX.Element;
|
|
74
|
+
chainElements: JSX.Element[];
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Renders a match chain inside-out: starts with the innermost (leaf) route
|
|
78
|
+
* rendered with `outlet: undefined`, then passes its JSX as `outlet` to
|
|
79
|
+
* each successive parent up the chain.
|
|
80
|
+
*
|
|
81
|
+
* Returns per-entry elements so that lifecycle hooks (`onLeave`/`onVisit`)
|
|
82
|
+
* receive only the element for their own route level, not the full tree.
|
|
83
|
+
*
|
|
84
|
+
* @param chain - The match chain from outermost to innermost
|
|
85
|
+
* @param currentUrl - The current URL path
|
|
86
|
+
* @returns The fully composed JSX element and per-entry rendered elements
|
|
87
|
+
*/
|
|
88
|
+
export declare const renderMatchChain: (chain: MatchChainEntry[], currentUrl: string) => RenderMatchChainResult;
|
|
89
|
+
/**
|
|
90
|
+
* A nested router component that supports hierarchical route definitions
|
|
91
|
+
* with parent/child relationships. Parent routes receive an `outlet` prop
|
|
92
|
+
* containing the rendered child route, enabling layout composition.
|
|
93
|
+
*
|
|
94
|
+
* Routes are defined as a Record where keys are URL patterns (following the
|
|
95
|
+
* RestApi pattern). The matching algorithm builds a chain from outermost to
|
|
96
|
+
* innermost route, then renders inside-out so each parent wraps its child.
|
|
97
|
+
*/
|
|
98
|
+
export declare const NestedRouter: (props: NestedRouterProps & Omit<Partial<HTMLElement>, "style"> & {
|
|
99
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
100
|
+
} & {
|
|
101
|
+
ref?: import("../models/render-options.js").RefObject<Element>;
|
|
102
|
+
}, children?: import("../index.js").ChildrenList) => JSX.Element;
|
|
103
|
+
//# sourceMappingURL=nested-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-router.d.ts","sourceRoot":"","sources":["../../src/components/nested-router.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAKhE;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CAAC,YAAY,GAAG,OAAO,IAAI;IAChD,SAAS,EAAE,CAAC,OAAO,EAAE;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,WAAW,CAAC,YAAY,SAAS,MAAM,GAAG,YAAY,GAAG,MAAM,CAAC,CAAA;QACvE,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;KACrB,KAAK,GAAG,CAAC,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,YAAY,CAAA;IAC7B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG;QAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvF,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG;QAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAA;CAC5C,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAA;IACxC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC3B,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,eAAe,EAAE,GAAG,IAAI,CAAA;IACpC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAA;IAChB,aAAa,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;CAC7B,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EACxC,YAAY,MAAM,KACjB,eAAe,EAAE,GAAG,IAuCtB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,eAAe,EAAE,EAAE,UAAU,eAAe,EAAE,KAAG,MAW9F,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAA;IAChB,aAAa,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;CAC7B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,eAAe,EAAE,EAAE,YAAY,MAAM,KAAG,sBAe/E,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY;;;;gEA+EvB,CAAA"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { ObservableAlreadyDisposedError } from '@furystack/utils';
|
|
2
|
+
import { match } from 'path-to-regexp';
|
|
3
|
+
import { Lock } from 'semaphore-async-await';
|
|
4
|
+
import { LocationService } from '../services/location-service.js';
|
|
5
|
+
import { createComponent, setRenderMode } from '../shade-component.js';
|
|
6
|
+
import { Shade } from '../shade.js';
|
|
7
|
+
/**
|
|
8
|
+
* Recursively builds a match chain from outermost to innermost matched route.
|
|
9
|
+
*
|
|
10
|
+
* For routes with children, a prefix match (`end: false`) is attempted first.
|
|
11
|
+
* If a child matches the remaining URL, the parent and child chain are combined.
|
|
12
|
+
* If no child matches, an exact match on the parent alone is attempted.
|
|
13
|
+
*
|
|
14
|
+
* For leaf routes (no children), only exact matching is used.
|
|
15
|
+
*
|
|
16
|
+
* @param routes - The route definitions to match against
|
|
17
|
+
* @param currentUrl - The URL path to match
|
|
18
|
+
* @returns An array of matched chain entries from outermost to innermost, or null if no match
|
|
19
|
+
*/
|
|
20
|
+
export const buildMatchChain = (routes, currentUrl) => {
|
|
21
|
+
for (const [pattern, route] of Object.entries(routes)) {
|
|
22
|
+
if (route.children) {
|
|
23
|
+
const prefixMatchFn = match(pattern, { ...route.routingOptions, end: false });
|
|
24
|
+
let prefixResult = prefixMatchFn(currentUrl);
|
|
25
|
+
// In path-to-regexp v8, match('/', { end: false }) only matches exact '/'.
|
|
26
|
+
// For the root pattern, any URL is logically under '/', so force a prefix match.
|
|
27
|
+
if (!prefixResult && pattern === '/') {
|
|
28
|
+
prefixResult = { path: '/', params: {} };
|
|
29
|
+
}
|
|
30
|
+
if (prefixResult) {
|
|
31
|
+
let remainingUrl = currentUrl.slice(prefixResult.path.length);
|
|
32
|
+
if (!remainingUrl.startsWith('/')) {
|
|
33
|
+
remainingUrl = `/${remainingUrl}`;
|
|
34
|
+
}
|
|
35
|
+
const childChain = buildMatchChain(route.children, remainingUrl);
|
|
36
|
+
if (childChain) {
|
|
37
|
+
return [{ route, match: prefixResult }, ...childChain];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const exactMatchFn = match(pattern, route.routingOptions);
|
|
41
|
+
const exactResult = exactMatchFn(currentUrl);
|
|
42
|
+
if (exactResult) {
|
|
43
|
+
return [{ route, match: exactResult }];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const matchFn = match(pattern, route.routingOptions);
|
|
48
|
+
const matchResult = matchFn(currentUrl);
|
|
49
|
+
if (matchResult) {
|
|
50
|
+
return [{ route, match: matchResult }];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Finds the first index where two match chains diverge.
|
|
58
|
+
* Returns the length of the shorter chain if one is a prefix of the other.
|
|
59
|
+
*/
|
|
60
|
+
export const findDivergenceIndex = (oldChain, newChain) => {
|
|
61
|
+
const minLength = Math.min(oldChain.length, newChain.length);
|
|
62
|
+
for (let i = 0; i < minLength; i++) {
|
|
63
|
+
if (oldChain[i].route !== newChain[i].route ||
|
|
64
|
+
JSON.stringify(oldChain[i].match.params) !== JSON.stringify(newChain[i].match.params)) {
|
|
65
|
+
return i;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return minLength;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Renders a match chain inside-out: starts with the innermost (leaf) route
|
|
72
|
+
* rendered with `outlet: undefined`, then passes its JSX as `outlet` to
|
|
73
|
+
* each successive parent up the chain.
|
|
74
|
+
*
|
|
75
|
+
* Returns per-entry elements so that lifecycle hooks (`onLeave`/`onVisit`)
|
|
76
|
+
* receive only the element for their own route level, not the full tree.
|
|
77
|
+
*
|
|
78
|
+
* @param chain - The match chain from outermost to innermost
|
|
79
|
+
* @param currentUrl - The current URL path
|
|
80
|
+
* @returns The fully composed JSX element and per-entry rendered elements
|
|
81
|
+
*/
|
|
82
|
+
export const renderMatchChain = (chain, currentUrl) => {
|
|
83
|
+
let outlet;
|
|
84
|
+
const chainElements = new Array(chain.length);
|
|
85
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
86
|
+
const entry = chain[i];
|
|
87
|
+
outlet = entry.route.component({
|
|
88
|
+
currentUrl,
|
|
89
|
+
match: entry.match,
|
|
90
|
+
outlet,
|
|
91
|
+
});
|
|
92
|
+
chainElements[i] = outlet;
|
|
93
|
+
}
|
|
94
|
+
return { jsx: outlet, chainElements };
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* A nested router component that supports hierarchical route definitions
|
|
98
|
+
* with parent/child relationships. Parent routes receive an `outlet` prop
|
|
99
|
+
* containing the rendered child route, enabling layout composition.
|
|
100
|
+
*
|
|
101
|
+
* Routes are defined as a Record where keys are URL patterns (following the
|
|
102
|
+
* RestApi pattern). The matching algorithm builds a chain from outermost to
|
|
103
|
+
* innermost route, then renders inside-out so each parent wraps its child.
|
|
104
|
+
*/
|
|
105
|
+
export const NestedRouter = Shade({
|
|
106
|
+
shadowDomName: 'shade-nested-router',
|
|
107
|
+
render: (options) => {
|
|
108
|
+
const { useState, useObservable, injector } = options;
|
|
109
|
+
const [lock] = useState('lock', new Lock());
|
|
110
|
+
const [state, setState] = useState('routerState', {
|
|
111
|
+
matchChain: [],
|
|
112
|
+
jsx: createComponent("div", null),
|
|
113
|
+
chainElements: [],
|
|
114
|
+
});
|
|
115
|
+
const updateUrl = async (currentUrl) => {
|
|
116
|
+
const [lastState] = useState('routerState', state);
|
|
117
|
+
const { matchChain: lastChain, chainElements: lastChainElements } = lastState;
|
|
118
|
+
try {
|
|
119
|
+
await lock.acquire();
|
|
120
|
+
const newChain = buildMatchChain(options.props.routes, currentUrl);
|
|
121
|
+
if (newChain) {
|
|
122
|
+
const lastChainEntries = lastChain ?? [];
|
|
123
|
+
const divergeIndex = findDivergenceIndex(lastChainEntries, newChain);
|
|
124
|
+
const hasChanged = divergeIndex < lastChainEntries.length ||
|
|
125
|
+
divergeIndex < newChain.length ||
|
|
126
|
+
lastChainEntries.length !== newChain.length;
|
|
127
|
+
if (hasChanged) {
|
|
128
|
+
// Call onLeave for routes that are being left (from divergence point to end of old chain)
|
|
129
|
+
for (let i = lastChainEntries.length - 1; i >= divergeIndex; i--) {
|
|
130
|
+
await lastChainEntries[i].route.onLeave?.({ ...options, element: lastChainElements[i] });
|
|
131
|
+
}
|
|
132
|
+
let newResult;
|
|
133
|
+
setRenderMode(true);
|
|
134
|
+
try {
|
|
135
|
+
newResult = renderMatchChain(newChain, currentUrl);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setRenderMode(false);
|
|
139
|
+
}
|
|
140
|
+
setState({ matchChain: newChain, jsx: newResult.jsx, chainElements: newResult.chainElements });
|
|
141
|
+
// Call onVisit for routes that are being entered (from divergence point to end of new chain)
|
|
142
|
+
for (let i = divergeIndex; i < newChain.length; i++) {
|
|
143
|
+
await newChain[i].route.onVisit?.({ ...options, element: newResult.chainElements[i] });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (lastChain !== null) {
|
|
148
|
+
// No match found — call onLeave for all active routes and show notFound.
|
|
149
|
+
// The null sentinel prevents re-entering this block on re-render.
|
|
150
|
+
for (let i = (lastChain?.length ?? 0) - 1; i >= 0; i--) {
|
|
151
|
+
await lastChain[i].route.onLeave?.({ ...options, element: lastChainElements[i] });
|
|
152
|
+
}
|
|
153
|
+
setState({
|
|
154
|
+
matchChain: null,
|
|
155
|
+
jsx: options.props.notFound || createComponent("div", null),
|
|
156
|
+
chainElements: [],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
if (!(e instanceof ObservableAlreadyDisposedError)) {
|
|
162
|
+
throw e;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
lock?.release();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const [locationPath] = useObservable('locationPathChanged', injector.getInstance(LocationService).onLocationPathChanged, {
|
|
170
|
+
onChange: (newValue) => {
|
|
171
|
+
void updateUrl(newValue);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
void updateUrl(locationPath);
|
|
175
|
+
return state.jsx;
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
//# sourceMappingURL=nested-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-router.js","sourceRoot":"","sources":["../../src/components/nested-router.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-router.spec.d.ts","sourceRoot":"","sources":["../../src/components/nested-router.spec.tsx"],"names":[],"mappings":""}
|