@pithyjs/router 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +621 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/router/core/config.d.ts +22 -0
- package/dist/router/core/config.d.ts.map +1 -0
- package/dist/router/core/config.js +54 -0
- package/dist/router/core/config.js.map +1 -0
- package/dist/router/core/constants.d.ts +89 -0
- package/dist/router/core/constants.d.ts.map +1 -0
- package/dist/router/core/constants.js +95 -0
- package/dist/router/core/constants.js.map +1 -0
- package/dist/router/core/history.d.ts +54 -0
- package/dist/router/core/history.d.ts.map +1 -0
- package/dist/router/core/history.js +89 -0
- package/dist/router/core/history.js.map +1 -0
- package/dist/router/core/matcher.d.ts +43 -0
- package/dist/router/core/matcher.d.ts.map +1 -0
- package/dist/router/core/matcher.js +242 -0
- package/dist/router/core/matcher.js.map +1 -0
- package/dist/router/core/params.d.ts +88 -0
- package/dist/router/core/params.d.ts.map +1 -0
- package/dist/router/core/params.js +150 -0
- package/dist/router/core/params.js.map +1 -0
- package/dist/router/core/pipeline.d.ts +90 -0
- package/dist/router/core/pipeline.d.ts.map +1 -0
- package/dist/router/core/pipeline.js +323 -0
- package/dist/router/core/pipeline.js.map +1 -0
- package/dist/router/core/route-tree.d.ts +76 -0
- package/dist/router/core/route-tree.d.ts.map +1 -0
- package/dist/router/core/route-tree.js +143 -0
- package/dist/router/core/route-tree.js.map +1 -0
- package/dist/router/core/router.d.ts +229 -0
- package/dist/router/core/router.d.ts.map +1 -0
- package/dist/router/core/router.js +762 -0
- package/dist/router/core/router.js.map +1 -0
- package/dist/router/core/types.d.ts +387 -0
- package/dist/router/core/types.d.ts.map +1 -0
- package/dist/router/core/types.js +22 -0
- package/dist/router/core/types.js.map +1 -0
- package/dist/router/core/utils.d.ts +66 -0
- package/dist/router/core/utils.d.ts.map +1 -0
- package/dist/router/core/utils.js +152 -0
- package/dist/router/core/utils.js.map +1 -0
- package/dist/router/features/coordinator.d.ts +75 -0
- package/dist/router/features/coordinator.d.ts.map +1 -0
- package/dist/router/features/coordinator.js +203 -0
- package/dist/router/features/coordinator.js.map +1 -0
- package/dist/router/features/errors/error-codes.d.ts +107 -0
- package/dist/router/features/errors/error-codes.d.ts.map +1 -0
- package/dist/router/features/errors/error-codes.js +247 -0
- package/dist/router/features/errors/error-codes.js.map +1 -0
- package/dist/router/features/errors/error-resolution.d.ts +31 -0
- package/dist/router/features/errors/error-resolution.d.ts.map +1 -0
- package/dist/router/features/errors/error-resolution.js +47 -0
- package/dist/router/features/errors/error-resolution.js.map +1 -0
- package/dist/router/features/errors/error-types.d.ts +60 -0
- package/dist/router/features/errors/error-types.d.ts.map +1 -0
- package/dist/router/features/errors/error-types.js +57 -0
- package/dist/router/features/errors/error-types.js.map +1 -0
- package/dist/router/features/errors/global-error-fallback.d.ts +33 -0
- package/dist/router/features/errors/global-error-fallback.d.ts.map +1 -0
- package/dist/router/features/errors/global-error-fallback.js +182 -0
- package/dist/router/features/errors/global-error-fallback.js.map +1 -0
- package/dist/router/features/errors/index.d.ts +15 -0
- package/dist/router/features/errors/index.d.ts.map +1 -0
- package/dist/router/features/errors/index.js +18 -0
- package/dist/router/features/errors/index.js.map +1 -0
- package/dist/router/features/errors/router-errors.d.ts +44 -0
- package/dist/router/features/errors/router-errors.d.ts.map +1 -0
- package/dist/router/features/errors/router-errors.js +53 -0
- package/dist/router/features/errors/router-errors.js.map +1 -0
- package/dist/router/features/guards/engine.d.ts +40 -0
- package/dist/router/features/guards/engine.d.ts.map +1 -0
- package/dist/router/features/guards/engine.js +67 -0
- package/dist/router/features/guards/engine.js.map +1 -0
- package/dist/router/features/guards/index.d.ts +52 -0
- package/dist/router/features/guards/index.d.ts.map +1 -0
- package/dist/router/features/guards/index.js +95 -0
- package/dist/router/features/guards/index.js.map +1 -0
- package/dist/router/features/loaders/cache.d.ts +60 -0
- package/dist/router/features/loaders/cache.d.ts.map +1 -0
- package/dist/router/features/loaders/cache.js +80 -0
- package/dist/router/features/loaders/cache.js.map +1 -0
- package/dist/router/features/loaders/engine.d.ts +17 -0
- package/dist/router/features/loaders/engine.d.ts.map +1 -0
- package/dist/router/features/loaders/engine.js +27 -0
- package/dist/router/features/loaders/engine.js.map +1 -0
- package/dist/router/features/loaders/index.d.ts +49 -0
- package/dist/router/features/loaders/index.d.ts.map +1 -0
- package/dist/router/features/loaders/index.js +112 -0
- package/dist/router/features/loaders/index.js.map +1 -0
- package/dist/router/features/loading-ui-manager.d.ts +69 -0
- package/dist/router/features/loading-ui-manager.d.ts.map +1 -0
- package/dist/router/features/loading-ui-manager.js +209 -0
- package/dist/router/features/loading-ui-manager.js.map +1 -0
- package/dist/router/features/module-loader.d.ts +50 -0
- package/dist/router/features/module-loader.d.ts.map +1 -0
- package/dist/router/features/module-loader.js +121 -0
- package/dist/router/features/module-loader.js.map +1 -0
- package/dist/router/features/outlet/errors.d.ts +24 -0
- package/dist/router/features/outlet/errors.d.ts.map +1 -0
- package/dist/router/features/outlet/errors.js +96 -0
- package/dist/router/features/outlet/errors.js.map +1 -0
- package/dist/router/features/outlet/index.d.ts +45 -0
- package/dist/router/features/outlet/index.d.ts.map +1 -0
- package/dist/router/features/outlet/index.js +95 -0
- package/dist/router/features/outlet/index.js.map +1 -0
- package/dist/router/features/outlet/mount.d.ts +38 -0
- package/dist/router/features/outlet/mount.d.ts.map +1 -0
- package/dist/router/features/outlet/mount.js +301 -0
- package/dist/router/features/outlet/mount.js.map +1 -0
- package/dist/router/features/outlet/segment-chain.d.ts +16 -0
- package/dist/router/features/outlet/segment-chain.d.ts.map +1 -0
- package/dist/router/features/outlet/segment-chain.js +62 -0
- package/dist/router/features/outlet/segment-chain.js.map +1 -0
- package/dist/router/features/prefetch/index.d.ts +97 -0
- package/dist/router/features/prefetch/index.d.ts.map +1 -0
- package/dist/router/features/prefetch/index.js +345 -0
- package/dist/router/features/prefetch/index.js.map +1 -0
- package/dist/router/features/ux/focus.d.ts +62 -0
- package/dist/router/features/ux/focus.d.ts.map +1 -0
- package/dist/router/features/ux/focus.js +136 -0
- package/dist/router/features/ux/focus.js.map +1 -0
- package/dist/router/features/ux/live-region.d.ts +81 -0
- package/dist/router/features/ux/live-region.d.ts.map +1 -0
- package/dist/router/features/ux/live-region.js +166 -0
- package/dist/router/features/ux/live-region.js.map +1 -0
- package/dist/router/features/ux/scroll-focus.d.ts +105 -0
- package/dist/router/features/ux/scroll-focus.d.ts.map +1 -0
- package/dist/router/features/ux/scroll-focus.js +111 -0
- package/dist/router/features/ux/scroll-focus.js.map +1 -0
- package/dist/router/features/ux/scroll.d.ts +71 -0
- package/dist/router/features/ux/scroll.d.ts.map +1 -0
- package/dist/router/features/ux/scroll.js +137 -0
- package/dist/router/features/ux/scroll.js.map +1 -0
- package/dist/router/features/ux/transitions.d.ts +120 -0
- package/dist/router/features/ux/transitions.d.ts.map +1 -0
- package/dist/router/features/ux/transitions.js +235 -0
- package/dist/router/features/ux/transitions.js.map +1 -0
- package/dist/router/integration/directive-binder.d.ts +20 -0
- package/dist/router/integration/directive-binder.d.ts.map +1 -0
- package/dist/router/integration/directive-binder.js +139 -0
- package/dist/router/integration/directive-binder.js.map +1 -0
- package/dist/router/integration/directives.d.ts +42 -0
- package/dist/router/integration/directives.d.ts.map +1 -0
- package/dist/router/integration/directives.js +109 -0
- package/dist/router/integration/directives.js.map +1 -0
- package/dist/router/integration/history-adapter.d.ts +76 -0
- package/dist/router/integration/history-adapter.d.ts.map +1 -0
- package/dist/router/integration/history-adapter.js +157 -0
- package/dist/router/integration/history-adapter.js.map +1 -0
- package/dist/router/integration/hmr.d.ts +83 -0
- package/dist/router/integration/hmr.d.ts.map +1 -0
- package/dist/router/integration/hmr.js +160 -0
- package/dist/router/integration/hmr.js.map +1 -0
- package/dist/router/integration/link-interceptor.d.ts +49 -0
- package/dist/router/integration/link-interceptor.d.ts.map +1 -0
- package/dist/router/integration/link-interceptor.js +94 -0
- package/dist/router/integration/link-interceptor.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.matcher",
|
|
4
|
+
"title": "Route Path Matching Engine",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import { SegmentType, } from './types';
|
|
9
|
+
import { MATCHER_SCORES } from './constants';
|
|
10
|
+
/** @codexApi {"parent":"pithy.router.matcher","name":"parseRoutePattern","stability":"stable","signature":"(pattern: string) => RouteSegment[]","description":"Parse a route pattern into typed segments (static, dynamic, catch-all, group)"} */
|
|
11
|
+
/**
|
|
12
|
+
* Parse a route pattern into segments with type information
|
|
13
|
+
* Handles static, dynamic [id], catch-all [...slug], and groups (group)
|
|
14
|
+
*/
|
|
15
|
+
export function parseRoutePattern(pattern) {
|
|
16
|
+
// Normalize pattern - remove leading/trailing slashes, handle root
|
|
17
|
+
const normalizedPattern = pattern === '/' ? '' : pattern.replace(/^\/+|\/+$/g, '');
|
|
18
|
+
if (!normalizedPattern) {
|
|
19
|
+
return []; // Root route
|
|
20
|
+
}
|
|
21
|
+
const segments = normalizedPattern.split('/');
|
|
22
|
+
return segments.map((segment) => {
|
|
23
|
+
// Route group: (group) - excluded from URL but affects nesting
|
|
24
|
+
if (segment.startsWith('(') && segment.endsWith(')')) {
|
|
25
|
+
return {
|
|
26
|
+
raw: segment,
|
|
27
|
+
type: SegmentType.Static, // Groups don't affect URL matching precedence
|
|
28
|
+
isGroup: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Catch-all: * (new syntax)
|
|
32
|
+
if (segment === '*') {
|
|
33
|
+
return {
|
|
34
|
+
raw: segment,
|
|
35
|
+
type: SegmentType.CatchAll,
|
|
36
|
+
paramName: 'slug',
|
|
37
|
+
isCatchAll: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Dynamic: :id (new syntax)
|
|
41
|
+
if (segment.startsWith(':')) {
|
|
42
|
+
const paramName = segment.slice(1);
|
|
43
|
+
return {
|
|
44
|
+
raw: segment,
|
|
45
|
+
type: SegmentType.Dynamic,
|
|
46
|
+
paramName,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Catch-all: [...slug] (existing syntax)
|
|
50
|
+
if (segment.startsWith('[...') && segment.endsWith(']')) {
|
|
51
|
+
const paramName = segment.slice(4, -1); // Remove [...] wrapper
|
|
52
|
+
return {
|
|
53
|
+
raw: segment,
|
|
54
|
+
type: SegmentType.CatchAll,
|
|
55
|
+
paramName,
|
|
56
|
+
isCatchAll: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Dynamic: [id] (existing syntax)
|
|
60
|
+
if (segment.startsWith('[') && segment.endsWith(']')) {
|
|
61
|
+
const paramName = segment.slice(1, -1); // Remove [] wrapper
|
|
62
|
+
return {
|
|
63
|
+
raw: segment,
|
|
64
|
+
type: SegmentType.Dynamic,
|
|
65
|
+
paramName,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Static segment
|
|
69
|
+
return {
|
|
70
|
+
raw: segment,
|
|
71
|
+
type: SegmentType.Static,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** @codexApi {"parent":"pithy.router.matcher","name":"calculateRouteScore","stability":"stable","signature":"(segments: RouteSegment[]) => number","description":"Calculate route precedence score for conflict resolution"} */
|
|
76
|
+
/**
|
|
77
|
+
* Calculate route precedence score for conflict resolution
|
|
78
|
+
* Higher score = higher precedence
|
|
79
|
+
* Rules: Static > Dynamic > CatchAll, Longer > Shorter
|
|
80
|
+
*/
|
|
81
|
+
export function calculateRouteScore(segments) {
|
|
82
|
+
let score = 0;
|
|
83
|
+
let segmentCount = 0;
|
|
84
|
+
for (const segment of segments) {
|
|
85
|
+
// Skip groups for URL matching but count for nesting depth
|
|
86
|
+
if (segment.isGroup) {
|
|
87
|
+
score += 0.1; // Small bonus for nesting depth
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
segmentCount++;
|
|
91
|
+
// Base score by segment type
|
|
92
|
+
score += segment.type * MATCHER_SCORES.SCORE_MULTIPLIER;
|
|
93
|
+
// Length bonus - longer routes are more specific
|
|
94
|
+
score += segmentCount * 10;
|
|
95
|
+
}
|
|
96
|
+
return score;
|
|
97
|
+
}
|
|
98
|
+
/** @codexApi {"parent":"pithy.router.matcher","name":"normalizePath","stability":"stable","signature":"(path: string, policy?: TrailingSlashPolicy) => string","description":"Normalize path according to trailing slash policy"} */
|
|
99
|
+
/**
|
|
100
|
+
* Normalize path according to trailing slash policy
|
|
101
|
+
*/
|
|
102
|
+
export function normalizePath(path, policy = 'never') {
|
|
103
|
+
// Always preserve root
|
|
104
|
+
if (path === '/' || path === '') {
|
|
105
|
+
return '/';
|
|
106
|
+
}
|
|
107
|
+
switch (policy) {
|
|
108
|
+
case 'never':
|
|
109
|
+
return path.replace(/\/+$/, '') || '/';
|
|
110
|
+
case 'always':
|
|
111
|
+
return path.replace(/\/+$/, '') + '/';
|
|
112
|
+
case 'preserve':
|
|
113
|
+
default:
|
|
114
|
+
return path;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** @codexApi {"parent":"pithy.router.matcher","name":"extractParams","stability":"stable","signature":"(pathSegments: string[], routeSegments: RouteSegment[], decodeParams?: boolean) => RouteParams","description":"Extract parameters from a matched path using route segments"} */
|
|
118
|
+
/**
|
|
119
|
+
* Extract parameters from a matched path using route segments
|
|
120
|
+
*/
|
|
121
|
+
export function extractParams(pathSegments, routeSegments, decodeParams = true) {
|
|
122
|
+
const params = {};
|
|
123
|
+
let pathIndex = 0;
|
|
124
|
+
for (let routeIndex = 0; routeIndex < routeSegments.length; routeIndex++) {
|
|
125
|
+
const routeSegment = routeSegments[routeIndex];
|
|
126
|
+
if (!routeSegment)
|
|
127
|
+
continue;
|
|
128
|
+
// Skip groups - they don't consume path segments
|
|
129
|
+
if (routeSegment.isGroup) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// Ensure we have path segments to match
|
|
133
|
+
if (pathIndex >= pathSegments.length) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
const pathValue = pathSegments[pathIndex];
|
|
137
|
+
if (!pathValue)
|
|
138
|
+
break;
|
|
139
|
+
if (routeSegment.type === SegmentType.Dynamic && routeSegment.paramName) {
|
|
140
|
+
// Single dynamic parameter
|
|
141
|
+
const value = decodeParams ? decodeURIComponent(pathValue) : pathValue;
|
|
142
|
+
params[routeSegment.paramName] = value;
|
|
143
|
+
pathIndex++;
|
|
144
|
+
}
|
|
145
|
+
else if (routeSegment.type === SegmentType.CatchAll &&
|
|
146
|
+
routeSegment.paramName) {
|
|
147
|
+
// Catch-all parameter - consume remaining segments
|
|
148
|
+
const remainingSegments = pathSegments.slice(pathIndex);
|
|
149
|
+
const values = decodeParams
|
|
150
|
+
? remainingSegments.map(s => decodeURIComponent(s))
|
|
151
|
+
: remainingSegments;
|
|
152
|
+
params[routeSegment.paramName] = values;
|
|
153
|
+
break; // Catch-all consumes everything
|
|
154
|
+
}
|
|
155
|
+
else if (routeSegment.type === SegmentType.Static) {
|
|
156
|
+
// Static segment - just advance
|
|
157
|
+
pathIndex++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return params;
|
|
161
|
+
}
|
|
162
|
+
/** @codexApi {"parent":"pithy.router.matcher","name":"matchRoute","stability":"stable","signature":"(path: string, pattern: string, config?: RouterConfig) => RouteMatch | null","description":"Check if a path matches a route pattern and return match details"} */
|
|
163
|
+
/**
|
|
164
|
+
* Check if a path matches a route pattern
|
|
165
|
+
*/
|
|
166
|
+
export function matchRoute(path, pattern, config = {}) {
|
|
167
|
+
// Normalize paths
|
|
168
|
+
const normalizedPath = normalizePath(path, config.trailingSlash);
|
|
169
|
+
const pathSegments = normalizedPath === '/' ? [] : normalizedPath.split('/').filter(Boolean);
|
|
170
|
+
// Parse route pattern
|
|
171
|
+
const routeSegments = parseRoutePattern(pattern);
|
|
172
|
+
const urlSegments = routeSegments.filter(s => !s.isGroup); // Only non-group segments affect URL matching
|
|
173
|
+
// Check if path can match this route
|
|
174
|
+
if (!canPathMatchRoute(pathSegments, urlSegments)) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
// Extract parameters
|
|
178
|
+
const params = extractParams(pathSegments, routeSegments, config.decodeParams);
|
|
179
|
+
// Calculate precedence score
|
|
180
|
+
const score = calculateRouteScore(routeSegments);
|
|
181
|
+
return {
|
|
182
|
+
pattern,
|
|
183
|
+
path: normalizedPath,
|
|
184
|
+
params,
|
|
185
|
+
segments: pathSegments,
|
|
186
|
+
score,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check if a path can potentially match a route pattern
|
|
191
|
+
* Used for early filtering before expensive parameter extraction
|
|
192
|
+
*/
|
|
193
|
+
function canPathMatchRoute(pathSegments, routeSegments) {
|
|
194
|
+
let pathIndex = 0;
|
|
195
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
|
196
|
+
const seg = routeSegments[i];
|
|
197
|
+
if (!seg)
|
|
198
|
+
continue;
|
|
199
|
+
// A catch-all matches the rest (including zero segments) *but only after*
|
|
200
|
+
// all previous segments have matched.
|
|
201
|
+
if (seg.type === SegmentType.CatchAll) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
// If we still have a non-catch-all to match but no path left → no match.
|
|
205
|
+
if (pathIndex >= pathSegments.length) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
const currentPathSegment = pathSegments[pathIndex];
|
|
209
|
+
if (seg.type === SegmentType.Static) {
|
|
210
|
+
if (currentPathSegment !== seg.raw) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Dynamic always matches any single segment.
|
|
215
|
+
pathIndex++;
|
|
216
|
+
}
|
|
217
|
+
// All route segments consumed; path must also be fully consumed.
|
|
218
|
+
return pathIndex === pathSegments.length;
|
|
219
|
+
}
|
|
220
|
+
/** @codexApi {"parent":"pithy.router.matcher","name":"findBestMatch","stability":"stable","signature":"(path: string, patterns: string[], config?: RouterConfig) => RouteMatch | null","description":"Find the best matching route by highest precedence score"} */
|
|
221
|
+
/**
|
|
222
|
+
* Find the best matching route from a list of patterns
|
|
223
|
+
* Returns the route with the highest precedence score
|
|
224
|
+
*/
|
|
225
|
+
export function findBestMatch(path, patterns, config = {}) {
|
|
226
|
+
const matches = [];
|
|
227
|
+
// Find all matching routes
|
|
228
|
+
for (const pattern of patterns) {
|
|
229
|
+
const match = matchRoute(path, pattern, config);
|
|
230
|
+
if (match) {
|
|
231
|
+
matches.push(match);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (matches.length === 0) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
// Sort by precedence score (highest first)
|
|
238
|
+
matches.sort((a, b) => b.score - a.score);
|
|
239
|
+
// Return the best match
|
|
240
|
+
return matches[0] || null;
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../../src/router/core/matcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.core.params",
|
|
4
|
+
"title": "Parameter Parsing and Query String Utilities",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import type { RouteParams, QueryParams } from './types';
|
|
9
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"parseQueryParams","stability":"stable","signature":"(search: string) => QueryParams"} */
|
|
10
|
+
/**
|
|
11
|
+
* Parse query parameters from URL search string
|
|
12
|
+
* Handles multi-value parameters (e.g., ?tag=foo&tag=bar becomes tag: ['foo', 'bar'])
|
|
13
|
+
*
|
|
14
|
+
* @param search - URL search string (e.g., "?foo=bar&baz=qux")
|
|
15
|
+
* @returns Parsed query parameters object
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* parseQueryParams("?name=John&age=30") // { name: "John", age: "30" }
|
|
19
|
+
* parseQueryParams("?tag=a&tag=b") // { tag: ["a", "b"] }
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseQueryParams(search: string): QueryParams;
|
|
22
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"stringifyQueryParams","stability":"stable","signature":"(params: QueryParams) => string"} */
|
|
23
|
+
/**
|
|
24
|
+
* Convert query parameters object to URL search string
|
|
25
|
+
* Handles arrays as multiple parameters with the same key
|
|
26
|
+
*
|
|
27
|
+
* @param params - Query parameters object
|
|
28
|
+
* @returns URL search string (without leading ?)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* stringifyQueryParams({ name: "John", age: "30" }) // "name=John&age=30"
|
|
32
|
+
* stringifyQueryParams({ tag: ["a", "b"] }) // "tag=a&tag=b"
|
|
33
|
+
*/
|
|
34
|
+
export declare function stringifyQueryParams(params: QueryParams): string;
|
|
35
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"decodeParam","stability":"stable","signature":"(value: string) => string"} */
|
|
36
|
+
/**
|
|
37
|
+
* Decode a single route parameter value
|
|
38
|
+
* Handles percent-encoded values and special characters
|
|
39
|
+
*
|
|
40
|
+
* @param value - Encoded parameter value
|
|
41
|
+
* @returns Decoded parameter value
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* decodeParam("hello%20world") // "hello world"
|
|
45
|
+
* decodeParam("user%2F123") // "user/123"
|
|
46
|
+
*/
|
|
47
|
+
export declare function decodeParam(value: string): string;
|
|
48
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"encodeParam","stability":"stable","signature":"(value: string | number | boolean) => string"} */
|
|
49
|
+
/**
|
|
50
|
+
* Encode a route parameter value for use in URLs
|
|
51
|
+
*
|
|
52
|
+
* @param value - Parameter value to encode
|
|
53
|
+
* @returns Encoded parameter value
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* encodeParam("hello world") // "hello%20world"
|
|
57
|
+
* encodeParam("user/123") // "user%2F123"
|
|
58
|
+
*/
|
|
59
|
+
export declare function encodeParam(value: string | number | boolean): string;
|
|
60
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"mergeParams","stability":"stable","signature":"(...paramSets: RouteParams[]) => RouteParams"} */
|
|
61
|
+
/**
|
|
62
|
+
* Merge multiple parameter objects with proper precedence
|
|
63
|
+
* Later parameters override earlier ones
|
|
64
|
+
*
|
|
65
|
+
* @param paramSets - Parameter objects to merge
|
|
66
|
+
* @returns Merged parameters object
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* mergeParams({ a: "1" }, { b: "2" }) // { a: "1", b: "2" }
|
|
70
|
+
* mergeParams({ a: "1" }, { a: "2" }) // { a: "2" } (last wins)
|
|
71
|
+
*/
|
|
72
|
+
export declare function mergeParams(...paramSets: RouteParams[]): RouteParams;
|
|
73
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"coerceParam","stability":"stable","signature":"(value: string | string[], type: 'string' | 'number' | 'boolean') => string | number | boolean | (string | number | boolean)[]"} */
|
|
74
|
+
/**
|
|
75
|
+
* Coerce a parameter value to a specific type
|
|
76
|
+
* Useful for converting string parameters to numbers, booleans, etc.
|
|
77
|
+
*
|
|
78
|
+
* @param value - Parameter value (string or array of strings)
|
|
79
|
+
* @param type - Target type ("string" | "number" | "boolean")
|
|
80
|
+
* @returns Coerced value
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* coerceParam("123", "number") // 123
|
|
84
|
+
* coerceParam("true", "boolean") // true
|
|
85
|
+
* coerceParam(["1", "2"], "number") // [1, 2]
|
|
86
|
+
*/
|
|
87
|
+
export declare function coerceParam(value: string | string[], type: 'string' | 'number' | 'boolean'): string | number | boolean | (string | number | boolean)[];
|
|
88
|
+
//# sourceMappingURL=params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../../../src/router/core/params.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAExD,mJAAmJ;AACnJ;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAkB5D;AAED,uJAAuJ;AACvJ;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAehE;AAED,wIAAwI;AACxI;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,2JAA2J;AAC3J;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAEpE;AAED,2JAA2J;AAC3J;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,SAAS,EAAE,WAAW,EAAE,GAAG,WAAW,CAEpE;AAED,6OAA6O;AAC7O;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GACpC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAoB3D"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.core.params",
|
|
4
|
+
"title": "Parameter Parsing and Query String Utilities",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"parseQueryParams","stability":"stable","signature":"(search: string) => QueryParams"} */
|
|
9
|
+
/**
|
|
10
|
+
* Parse query parameters from URL search string
|
|
11
|
+
* Handles multi-value parameters (e.g., ?tag=foo&tag=bar becomes tag: ['foo', 'bar'])
|
|
12
|
+
*
|
|
13
|
+
* @param search - URL search string (e.g., "?foo=bar&baz=qux")
|
|
14
|
+
* @returns Parsed query parameters object
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* parseQueryParams("?name=John&age=30") // { name: "John", age: "30" }
|
|
18
|
+
* parseQueryParams("?tag=a&tag=b") // { tag: ["a", "b"] }
|
|
19
|
+
*/
|
|
20
|
+
export function parseQueryParams(search) {
|
|
21
|
+
const params = {};
|
|
22
|
+
const urlParams = new URLSearchParams(search);
|
|
23
|
+
for (const [key, value] of urlParams.entries()) {
|
|
24
|
+
if (params[key]) {
|
|
25
|
+
// Handle multiple values for same key
|
|
26
|
+
if (Array.isArray(params[key])) {
|
|
27
|
+
params[key].push(value);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
params[key] = [params[key], value];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
params[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return params;
|
|
38
|
+
}
|
|
39
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"stringifyQueryParams","stability":"stable","signature":"(params: QueryParams) => string"} */
|
|
40
|
+
/**
|
|
41
|
+
* Convert query parameters object to URL search string
|
|
42
|
+
* Handles arrays as multiple parameters with the same key
|
|
43
|
+
*
|
|
44
|
+
* @param params - Query parameters object
|
|
45
|
+
* @returns URL search string (without leading ?)
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* stringifyQueryParams({ name: "John", age: "30" }) // "name=John&age=30"
|
|
49
|
+
* stringifyQueryParams({ tag: ["a", "b"] }) // "tag=a&tag=b"
|
|
50
|
+
*/
|
|
51
|
+
export function stringifyQueryParams(params) {
|
|
52
|
+
const urlParams = new URLSearchParams();
|
|
53
|
+
for (const [key, value] of Object.entries(params)) {
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
// Add each array value as separate parameter
|
|
56
|
+
for (const v of value) {
|
|
57
|
+
urlParams.append(key, v);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (value !== undefined && value !== null) {
|
|
61
|
+
urlParams.append(key, String(value));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return urlParams.toString();
|
|
65
|
+
}
|
|
66
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"decodeParam","stability":"stable","signature":"(value: string) => string"} */
|
|
67
|
+
/**
|
|
68
|
+
* Decode a single route parameter value
|
|
69
|
+
* Handles percent-encoded values and special characters
|
|
70
|
+
*
|
|
71
|
+
* @param value - Encoded parameter value
|
|
72
|
+
* @returns Decoded parameter value
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* decodeParam("hello%20world") // "hello world"
|
|
76
|
+
* decodeParam("user%2F123") // "user/123"
|
|
77
|
+
*/
|
|
78
|
+
export function decodeParam(value) {
|
|
79
|
+
try {
|
|
80
|
+
return decodeURIComponent(value);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// If decoding fails, return original value
|
|
84
|
+
if (typeof window !== 'undefined' &&
|
|
85
|
+
import.meta.hot) {
|
|
86
|
+
console.warn(`Failed to decode parameter: ${value}`, err);
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"encodeParam","stability":"stable","signature":"(value: string | number | boolean) => string"} */
|
|
92
|
+
/**
|
|
93
|
+
* Encode a route parameter value for use in URLs
|
|
94
|
+
*
|
|
95
|
+
* @param value - Parameter value to encode
|
|
96
|
+
* @returns Encoded parameter value
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* encodeParam("hello world") // "hello%20world"
|
|
100
|
+
* encodeParam("user/123") // "user%2F123"
|
|
101
|
+
*/
|
|
102
|
+
export function encodeParam(value) {
|
|
103
|
+
return encodeURIComponent(String(value));
|
|
104
|
+
}
|
|
105
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"mergeParams","stability":"stable","signature":"(...paramSets: RouteParams[]) => RouteParams"} */
|
|
106
|
+
/**
|
|
107
|
+
* Merge multiple parameter objects with proper precedence
|
|
108
|
+
* Later parameters override earlier ones
|
|
109
|
+
*
|
|
110
|
+
* @param paramSets - Parameter objects to merge
|
|
111
|
+
* @returns Merged parameters object
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* mergeParams({ a: "1" }, { b: "2" }) // { a: "1", b: "2" }
|
|
115
|
+
* mergeParams({ a: "1" }, { a: "2" }) // { a: "2" } (last wins)
|
|
116
|
+
*/
|
|
117
|
+
export function mergeParams(...paramSets) {
|
|
118
|
+
return Object.assign({}, ...paramSets);
|
|
119
|
+
}
|
|
120
|
+
/** @codexApi {"parent":"pithy.router.core.params","name":"coerceParam","stability":"stable","signature":"(value: string | string[], type: 'string' | 'number' | 'boolean') => string | number | boolean | (string | number | boolean)[]"} */
|
|
121
|
+
/**
|
|
122
|
+
* Coerce a parameter value to a specific type
|
|
123
|
+
* Useful for converting string parameters to numbers, booleans, etc.
|
|
124
|
+
*
|
|
125
|
+
* @param value - Parameter value (string or array of strings)
|
|
126
|
+
* @param type - Target type ("string" | "number" | "boolean")
|
|
127
|
+
* @returns Coerced value
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* coerceParam("123", "number") // 123
|
|
131
|
+
* coerceParam("true", "boolean") // true
|
|
132
|
+
* coerceParam(["1", "2"], "number") // [1, 2]
|
|
133
|
+
*/
|
|
134
|
+
export function coerceParam(value, type) {
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
return value.map(v => coerceParam(v, type));
|
|
137
|
+
}
|
|
138
|
+
switch (type) {
|
|
139
|
+
case 'number': {
|
|
140
|
+
const num = Number(value);
|
|
141
|
+
return isNaN(num) ? value : num;
|
|
142
|
+
}
|
|
143
|
+
case 'boolean':
|
|
144
|
+
return value === 'true' || value === '1' || value === 'yes';
|
|
145
|
+
case 'string':
|
|
146
|
+
default:
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=params.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"params.js","sourceRoot":"","sources":["../../../src/router/core/params.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.core.pipeline",
|
|
4
|
+
"title": "Navigation Pipeline - Multi-Phase Navigation Orchestration",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import type { NavigationId, RouteMatch, RouteNode, RouterConfig, QueryParams, RouterState, NavigationOptions, GuardContext, GuardFunction, LoaderContext, GuardExecutionResult } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* Navigation source type
|
|
11
|
+
*/
|
|
12
|
+
export type NavSource = 'programmatic' | 'intercept' | 'pop' | 'hmr';
|
|
13
|
+
/**
|
|
14
|
+
* Navigation context
|
|
15
|
+
* Provides router state access and callbacks needed during navigation
|
|
16
|
+
*/
|
|
17
|
+
export interface NavigationContext {
|
|
18
|
+
getCurrentPath: () => string;
|
|
19
|
+
getCurrentMatch: () => RouteMatch | null;
|
|
20
|
+
updateState: (updates: Partial<RouterState>) => Promise<void>;
|
|
21
|
+
config: RouterConfig;
|
|
22
|
+
routes: RouteNode[];
|
|
23
|
+
routePatterns: string[];
|
|
24
|
+
onLoadGuards: (pattern: string) => Promise<GuardFunction[]>;
|
|
25
|
+
onExecuteGuards: (guards: GuardFunction[], context: GuardContext) => Promise<GuardExecutionResult>;
|
|
26
|
+
onExecuteLoaders: (pattern: string, context: LoaderContext) => Promise<boolean>;
|
|
27
|
+
onShowLoadingUI: (route: RouteNode | undefined, match: RouteMatch, navId: NavigationId, source: string) => Promise<void>;
|
|
28
|
+
onClearLoadingUI: () => void;
|
|
29
|
+
onSaveScrollToHistory: () => void;
|
|
30
|
+
onRestoreSnapshotIfEmpty: () => void;
|
|
31
|
+
onHistoryPush: (state: unknown, url: string) => void;
|
|
32
|
+
onHistoryReplace: (state: unknown, url: string) => void;
|
|
33
|
+
onHistorySnapBack: () => void;
|
|
34
|
+
normalizePath: (path: string) => string;
|
|
35
|
+
parseQueryParams: (search: string) => QueryParams;
|
|
36
|
+
findMatch: (pathname: string) => RouteMatch | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Navigation Pipeline
|
|
40
|
+
* Orchestrates multi-phase navigation flow: GUARD → HISTORY → LOADER → MOUNT
|
|
41
|
+
*
|
|
42
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"NavigationPipeline","stability":"stable","signature":"class NavigationPipeline"}
|
|
43
|
+
*/
|
|
44
|
+
export declare class NavigationPipeline {
|
|
45
|
+
private nextNavId;
|
|
46
|
+
private activeNavId;
|
|
47
|
+
private activeTransaction;
|
|
48
|
+
/**
|
|
49
|
+
* Execute a navigation transaction
|
|
50
|
+
* Returns true if navigation succeeded, false if aborted/failed
|
|
51
|
+
*
|
|
52
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"execute","stability":"stable","signature":"async execute(path: string, options: NavigationOptions, context: NavigationContext): Promise<boolean>"}
|
|
53
|
+
*/
|
|
54
|
+
execute(path: string, options: NavigationOptions, context: NavigationContext): Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Create a new navigation transaction
|
|
57
|
+
*
|
|
58
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"createTransaction","stability":"internal","signature":"private createTransaction(id: NavigationId, path: string): NavigationTransaction"}
|
|
59
|
+
*/
|
|
60
|
+
private createTransaction;
|
|
61
|
+
/**
|
|
62
|
+
* Cancel active navigation
|
|
63
|
+
* Useful for cleanup when disposing the router
|
|
64
|
+
*
|
|
65
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"cancelActiveNavigation","stability":"stable","signature":"public cancelActiveNavigation(): void"}
|
|
66
|
+
*/
|
|
67
|
+
cancelActiveNavigation(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Check if navigation is active
|
|
70
|
+
*
|
|
71
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"isActive","stability":"stable","signature":"isActive(navId: NavigationId): boolean"}
|
|
72
|
+
*/
|
|
73
|
+
isActive(navId: NavigationId): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Get current active navigation ID
|
|
76
|
+
*
|
|
77
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"getActiveNavId","stability":"stable","signature":"getActiveNavId(): NavigationId"}
|
|
78
|
+
*/
|
|
79
|
+
getActiveNavId(): NavigationId;
|
|
80
|
+
/**
|
|
81
|
+
* Initialize next nav ID (for HMR or testing)
|
|
82
|
+
*/
|
|
83
|
+
initializeNavId(startId: NavigationId): void;
|
|
84
|
+
/**
|
|
85
|
+
* COMMIT phase: Execute loaders, show loading UI, commit route change
|
|
86
|
+
* This is the core navigation commit logic extracted from router.ts
|
|
87
|
+
*/
|
|
88
|
+
private commitPhase;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../src/router/core/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,KAAK,EACV,YAAY,EAEZ,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,WAAW,GAAG,KAAK,GAAG,KAAK,CAAC;AAErE;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAEhC,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,UAAU,GAAG,IAAI,CAAC;IAGzC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAG9D,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IAGxB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5D,eAAe,EAAE,CACf,MAAM,EAAE,aAAa,EAAE,EACvB,OAAO,EAAE,YAAY,KAClB,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnC,gBAAgB,EAAE,CAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,KACnB,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,eAAe,EAAE,CACf,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAClC,wBAAwB,EAAE,MAAM,IAAI,CAAC;IAGrC,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAG9B,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,CAAC;IAClD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;CACpD;AAED;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,iBAAiB,CAAsC;IAE/D;;;;;OAKG;IACG,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,OAAO,CAAC;IA0LnB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;;OAKG;IACI,sBAAsB,IAAI,IAAI;IAOrC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO;IAatC;;;;OAIG;IACH,cAAc,IAAI,YAAY;IAI9B;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAI5C;;;OAGG;YACW,WAAW;CAqG1B"}
|