@ramstack/alpinegear-router 1.4.3 → 1.4.4
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/README.md +354 -387
- package/alpinegear-router.esm.js +856 -856
- package/alpinegear-router.esm.min.js +1 -1
- package/alpinegear-router.js +856 -856
- package/alpinegear-router.min.js +1 -1
- package/package.json +9 -3
package/alpinegear-router.esm.js
CHANGED
|
@@ -1,643 +1,643 @@
|
|
|
1
|
-
const warn = (...args) => console.warn("alpinegear.js:", ...args);
|
|
2
|
-
const is_array = Array.isArray;
|
|
3
|
-
const is_nullish = value => value === null || value === undefined;
|
|
4
|
-
const is_template = el => el.matches("template");
|
|
5
|
-
const is_function = value => typeof value === "function";
|
|
6
|
-
const as_array = value => is_array(value) ? value : [value];
|
|
7
|
-
const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
|
|
8
|
-
|
|
9
|
-
function assert(value, message) {
|
|
10
|
-
if (!value) {
|
|
11
|
-
throw new Error(message);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const asyncify = fn => {
|
|
16
|
-
if (is_function(fn) && fn.constructor?.name === "AsyncFunction") {
|
|
17
|
-
return fn;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return function(...args) {
|
|
21
|
-
const result = fn.apply(this, args);
|
|
22
|
-
return is_function(result?.then) ? result : Promise.resolve(result);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const listen = (target, type, listener, options) => {
|
|
27
|
-
target.addEventListener(type, listener, options);
|
|
28
|
-
return () => target.removeEventListener(type, listener, options);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const closest = (el, callback) => {
|
|
32
|
-
while (el && !callback(el)) {
|
|
33
|
-
el = (el._x_teleportBack ?? el).parentElement;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return el;
|
|
1
|
+
const warn = (...args) => console.warn("alpinegear.js:", ...args);
|
|
2
|
+
const is_array = Array.isArray;
|
|
3
|
+
const is_nullish = value => value === null || value === undefined;
|
|
4
|
+
const is_template = el => el.matches("template");
|
|
5
|
+
const is_function = value => typeof value === "function";
|
|
6
|
+
const as_array = value => is_array(value) ? value : [value];
|
|
7
|
+
const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
|
|
8
|
+
|
|
9
|
+
function assert(value, message) {
|
|
10
|
+
if (!value) {
|
|
11
|
+
throw new Error(message);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const asyncify = fn => {
|
|
16
|
+
if (is_function(fn) && fn.constructor?.name === "AsyncFunction") {
|
|
17
|
+
return fn;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return function(...args) {
|
|
21
|
+
const result = fn.apply(this, args);
|
|
22
|
+
return is_function(result?.then) ? result : Promise.resolve(result);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const listen = (target, type, listener, options) => {
|
|
27
|
+
target.addEventListener(type, listener, options);
|
|
28
|
+
return () => target.removeEventListener(type, listener, options);
|
|
37
29
|
};
|
|
38
30
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
get
|
|
121
|
-
return this.#
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
this.#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return index ? part.value : `/${ part.value }`;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
parameters.set(part.name, part);
|
|
242
|
-
|
|
243
|
-
if (segment.parts.length === 1 && part.quantifier === "?") {
|
|
244
|
-
return `(?:/(?<${ part.name }>[^/]+?))?`;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (part.catch_all) {
|
|
248
|
-
let expr = `(?<${ part.name }>.${ part.quantifier })`;
|
|
249
|
-
index || (expr = `(?:/${ expr })`);
|
|
250
|
-
part.quantifier === "*" && (expr += "?");
|
|
251
|
-
return part.quantifier === "*" ? expr + "?" : expr;
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
const expr = `(?<${ part.name }>[^/]+?)${ part.quantifier }`;
|
|
255
|
-
return index ? expr : `/${ expr }`;
|
|
256
|
-
}
|
|
257
|
-
}).join("");
|
|
258
|
-
}).join("") || "/";
|
|
259
|
-
|
|
260
|
-
expression !== "/" && (expression += "/?");
|
|
261
|
-
return new RegExp(`^${ expression }$`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function parse_route_pattern(pattern, factories) {
|
|
265
|
-
return preprocess_segments(parse_segments());
|
|
266
|
-
|
|
267
|
-
function preprocess_segments(segments) {
|
|
268
|
-
if (segments.find(s => s.parts.length > 1 && s.parts.every(p => p.optional))) {
|
|
269
|
-
throw_error("Using all segment parameters as optional is not permitted");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const parameters = new Map;
|
|
273
|
-
|
|
274
|
-
segments.flatMap(s => s.parts).forEach((part, index, parts) => {
|
|
275
|
-
if (part.kind === "literal" && part.value.indexOf("?") >= 0) {
|
|
276
|
-
throw_error("Literal segments cannot contain the '?' character");
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (part.kind === "parameter") {
|
|
280
|
-
if (part.catch_all && index !== parts.length - 1) {
|
|
281
|
-
throw_error("A catch-all parameter can only appear as the last segment");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (parameters.has(part.name)) {
|
|
285
|
-
throw_error(`The route parameter name '${part.name}' appears more than one time`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
part.quantifier === "*"
|
|
289
|
-
&& is_nullish(part.default)
|
|
290
|
-
&& (part.default = "");
|
|
291
|
-
|
|
292
|
-
part.default === ""
|
|
293
|
-
&& part.quantifier !== "*"
|
|
294
|
-
&& (part.default = null);
|
|
295
|
-
|
|
296
|
-
parameters.set(part.name, true);
|
|
297
|
-
|
|
298
|
-
for (let constraint of part.constraints) {
|
|
299
|
-
const factory = factories?.[constraint.name] ?? default_constraints[constraint.name];
|
|
300
|
-
is_nullish(factory) && throw_error(`Unknown constraint '${ constraint.name }'`);
|
|
301
|
-
Object.assign(constraint, factory(constraint.argument));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
return segments;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function parse_segments() {
|
|
310
|
-
const segments = [];
|
|
311
|
-
|
|
312
|
-
for (let i = 0; i < pattern.length;) {
|
|
313
|
-
const segment = parse_segment(i);
|
|
314
|
-
segment.template && segments.push(segment);
|
|
315
|
-
i += segment.template.length + 1;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return segments;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function parse_segment(pos) {
|
|
322
|
-
let parts = [];
|
|
323
|
-
let index = pos;
|
|
324
|
-
|
|
325
|
-
while (index < pattern.length && pattern[index] !== "/") {
|
|
326
|
-
const part = parse_literal(index) || parse_parameter(index);
|
|
327
|
-
parts.push(part);
|
|
328
|
-
|
|
329
|
-
index += part.template.length;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
template: pattern.slice(pos, index),
|
|
334
|
-
parts: parts
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function parse_constraints(text, p) {
|
|
339
|
-
const array = [];
|
|
340
|
-
|
|
341
|
-
for (let i = p; i < text.length;) {
|
|
342
|
-
if (text[i] !== ":") {
|
|
343
|
-
throw_error();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const name = get_constraint_name(text.slice(i + 1));
|
|
347
|
-
i += name.length + 1;
|
|
348
|
-
|
|
349
|
-
const arg = text[i] === "("
|
|
350
|
-
? get_parameter_template(i, text)
|
|
351
|
-
: null;
|
|
352
|
-
|
|
353
|
-
is_nullish(arg) || (i += arg.length + 2);
|
|
354
|
-
|
|
355
|
-
if (!name && !arg) {
|
|
356
|
-
throw_error();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
array.push({
|
|
360
|
-
name: name === "=" ? "default" : name || "regex",
|
|
361
|
-
argument: arg ?? ""
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return array;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function parse_parameter(p) {
|
|
370
|
-
if (pattern[p] !== "{") {
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const template = get_parameter_template(p);
|
|
375
|
-
const parameter_template = pattern.slice(p, p + template.length + 2);
|
|
376
|
-
const name = get_parameter_name(template);
|
|
377
|
-
const quantifier = /[*+?]/.exec(template[name.length])?.[0] ?? "";
|
|
378
|
-
const list = parse_constraints(template, name.length + quantifier.length);
|
|
379
|
-
|
|
380
|
-
return {
|
|
381
|
-
name: name,
|
|
382
|
-
kind: "parameter",
|
|
383
|
-
template: parameter_template,
|
|
384
|
-
quantifier: quantifier,
|
|
385
|
-
constraints: list.filter(c => c.name !== "default"),
|
|
386
|
-
default: list.find(c => c.name === "default")?.argument,
|
|
387
|
-
required: quantifier === "+" || quantifier === "",
|
|
388
|
-
optional: quantifier === "?" || quantifier === "*",
|
|
389
|
-
catch_all: quantifier === "+" || quantifier === "*"
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function parse_literal(pos) {
|
|
394
|
-
for (let i = pos; ; i++) {
|
|
395
|
-
if (i >= pattern.length || pattern[i] === "/" || pattern[i] === "{") {
|
|
396
|
-
if (i === pos) {
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const template = pattern.slice(pos, i);
|
|
401
|
-
return {
|
|
402
|
-
kind: "literal",
|
|
403
|
-
template: template,
|
|
404
|
-
value: template
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function get_parameter_template(pos, str) {
|
|
411
|
-
str ??= pattern;
|
|
412
|
-
const stack = [];
|
|
413
|
-
|
|
414
|
-
loop: for (let i = pos; i < str.length; i++) {
|
|
415
|
-
switch (str[i]) {
|
|
416
|
-
case "{": stack.push("}"); break;
|
|
417
|
-
case "(": stack.push(")"); break;
|
|
418
|
-
case "}":
|
|
419
|
-
case ")":
|
|
420
|
-
if (stack.pop() !== str[i]) break loop;
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (stack.length === 0) {
|
|
425
|
-
return str.slice(pos + 1, i);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
throw_error();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function get_parameter_name(value) {
|
|
433
|
-
const name = value.match(/^(?<name>[a-z_$][a-z0-9_$-]*?)(?:[:?+*]|$)/i)?.groups?.name;
|
|
434
|
-
is_nullish(name) && throw_error("Invalid parameter name");
|
|
435
|
-
|
|
436
|
-
return name;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function get_constraint_name(value) {
|
|
440
|
-
const name = value.match(/^(?<name>=|[a-z0-9_$]*)(?=[/:(]|$)/i)?.groups?.name;
|
|
441
|
-
is_nullish(name) && throw_error("Invalid constraint name");
|
|
442
|
-
|
|
443
|
-
return name;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function throw_error(message = "Invalid pattern") {
|
|
447
|
-
throw new Error(`${ message }: ${ pattern }`);
|
|
448
|
-
}
|
|
31
|
+
const closest = (el, callback) => {
|
|
32
|
+
while (el && !callback(el)) {
|
|
33
|
+
el = (el._x_teleportBack ?? el).parentElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return el;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const default_constraints = Object.freeze({
|
|
40
|
+
"regex"(value) {
|
|
41
|
+
const regexp = new RegExp(value);
|
|
42
|
+
return {
|
|
43
|
+
test: v => regexp.test(v)
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
"bool"() {
|
|
47
|
+
return {
|
|
48
|
+
test: v => /^(?:true|false)$/i.test(v),
|
|
49
|
+
transform: v => v.length === 4
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
"int"() {
|
|
53
|
+
return {
|
|
54
|
+
test: v => /^-?\d+$/.test(v),
|
|
55
|
+
transform: v => +v
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
"number"() {
|
|
59
|
+
return {
|
|
60
|
+
test: v => /^[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$/.test(v) && isFinite(parseFloat(v)),
|
|
61
|
+
transform: v => parseFloat(v)
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
"alpha"() {
|
|
65
|
+
return {
|
|
66
|
+
test: v => /^[a-z]+$/i.test(v)
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
"min"(value) {
|
|
70
|
+
return {
|
|
71
|
+
test: v => v >= +value
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
"max"(value) {
|
|
75
|
+
return {
|
|
76
|
+
test: v => v <= +value
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
"range"(value) {
|
|
80
|
+
let [a, b] = value.split(",", 2).map(v => v.trim());
|
|
81
|
+
return {
|
|
82
|
+
test: v => v >= +a && v <= +b
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
"length"(value) {
|
|
86
|
+
let values = value.split(",").map(v => v.trim());
|
|
87
|
+
return {
|
|
88
|
+
test: values.length == 2
|
|
89
|
+
? v => v.length >= +values[0] && v.length <= +values[1]
|
|
90
|
+
: v => v.length === +values[0]
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
"minlength"(value) {
|
|
94
|
+
return {
|
|
95
|
+
test: v => v.length >= +value
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
"maxlength"(value) {
|
|
99
|
+
return {
|
|
100
|
+
test: v => v.length <= +value
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
class RoutePattern {
|
|
106
|
+
#regex;
|
|
107
|
+
#template;
|
|
108
|
+
#segments;
|
|
109
|
+
#parameters;
|
|
110
|
+
#constraints;
|
|
111
|
+
|
|
112
|
+
get template() {
|
|
113
|
+
return this.#template;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get regex() {
|
|
117
|
+
return this.#regex;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get constraints() {
|
|
121
|
+
return this.#constraints;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
constructor(template, constraints = null) {
|
|
125
|
+
this.#template = template;
|
|
126
|
+
this.#regex = build_regex(
|
|
127
|
+
template,
|
|
128
|
+
this.#segments = [],
|
|
129
|
+
this.#parameters = new Map(),
|
|
130
|
+
this.#constraints = constraints ?? {}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
match(path) {
|
|
135
|
+
let result = this.#regex.exec(path);
|
|
136
|
+
|
|
137
|
+
if (is_nullish(result)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
result = result.groups ?? {};
|
|
142
|
+
|
|
143
|
+
for (let [name, parameter] of this.#parameters.entries()) {
|
|
144
|
+
let value = result[name];
|
|
145
|
+
|
|
146
|
+
if (is_nullish(value) && is_nullish(parameter.default)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!value && !is_nullish(parameter.default)) {
|
|
151
|
+
value = parameter.default;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const values = parameter.catch_all
|
|
155
|
+
? value.split("/").filter(v => v.length)
|
|
156
|
+
: [value];
|
|
157
|
+
|
|
158
|
+
for (let i = 0; i < values.length; i++) {
|
|
159
|
+
for (let constraint of parameter.constraints) {
|
|
160
|
+
if (constraint.test && !constraint.test(values[i])) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (constraint.transform) {
|
|
165
|
+
values[i] = constraint.transform(values[i]);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
result[name] = parameter.catch_all ? values : values[0];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
resolve(values) {
|
|
177
|
+
values = new Map(Object.entries(values));
|
|
178
|
+
const segments = [];
|
|
179
|
+
|
|
180
|
+
for (let segment of this.#segments) {
|
|
181
|
+
const parts = [];
|
|
182
|
+
|
|
183
|
+
for (let part of segment.parts) {
|
|
184
|
+
if (part.kind === "literal") {
|
|
185
|
+
parts.push(part.value);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
let value = values.get(part.name);
|
|
189
|
+
values.delete(part.name);
|
|
190
|
+
|
|
191
|
+
if (is_nullish(value) || value === "") {
|
|
192
|
+
value = this.#parameters.get(part.name)?.default;
|
|
193
|
+
if (part.catch_all && value) {
|
|
194
|
+
value = value.split("/");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (is_nullish(value) || value === "") {
|
|
199
|
+
if (part.required) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// TODO Check twice
|
|
204
|
+
if (part.optional && part.default === value) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (part.catch_all) {
|
|
210
|
+
value = as_array(value);
|
|
211
|
+
parts.push(...value.map(v => encodeURIComponent(v)).join("/"));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
parts.push(encodeURIComponent(value));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
parts.length && segments.push(parts.join(""));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let queries = [...values.entries()].map(([k, v]) => encodeURIComponent(k) + "=" + encodeURIComponent(v)).join("&");
|
|
223
|
+
queries && (queries = "?" + queries);
|
|
224
|
+
|
|
225
|
+
const result = segments.join("/") + queries;
|
|
226
|
+
return result[0] !== "/"
|
|
227
|
+
? "/" + result
|
|
228
|
+
: result;
|
|
229
|
+
}
|
|
449
230
|
}
|
|
450
231
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
232
|
+
function build_regex(pattern, segments, parameters, constraints) {
|
|
233
|
+
segments.push(...parse_route_pattern(pattern, constraints));
|
|
234
|
+
|
|
235
|
+
let expression = segments.map(segment => {
|
|
236
|
+
return segment.parts.map((part, index) => {
|
|
237
|
+
if (part.kind === "literal") {
|
|
238
|
+
return index ? part.value : `/${ part.value }`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
parameters.set(part.name, part);
|
|
242
|
+
|
|
243
|
+
if (segment.parts.length === 1 && part.quantifier === "?") {
|
|
244
|
+
return `(?:/(?<${ part.name }>[^/]+?))?`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (part.catch_all) {
|
|
248
|
+
let expr = `(?<${ part.name }>.${ part.quantifier })`;
|
|
249
|
+
index || (expr = `(?:/${ expr })`);
|
|
250
|
+
part.quantifier === "*" && (expr += "?");
|
|
251
|
+
return part.quantifier === "*" ? expr + "?" : expr;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const expr = `(?<${ part.name }>[^/]+?)${ part.quantifier }`;
|
|
255
|
+
return index ? expr : `/${ expr }`;
|
|
256
|
+
}
|
|
257
|
+
}).join("");
|
|
258
|
+
}).join("") || "/";
|
|
259
|
+
|
|
260
|
+
expression !== "/" && (expression += "/?");
|
|
261
|
+
return new RegExp(`^${ expression }$`);
|
|
470
262
|
}
|
|
471
263
|
|
|
472
|
-
function
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
}
|
|
264
|
+
function parse_route_pattern(pattern, factories) {
|
|
265
|
+
return preprocess_segments(parse_segments());
|
|
266
|
+
|
|
267
|
+
function preprocess_segments(segments) {
|
|
268
|
+
if (segments.find(s => s.parts.length > 1 && s.parts.every(p => p.optional))) {
|
|
269
|
+
throw_error("Using all segment parameters as optional is not permitted");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const parameters = new Map;
|
|
273
|
+
|
|
274
|
+
segments.flatMap(s => s.parts).forEach((part, index, parts) => {
|
|
275
|
+
if (part.kind === "literal" && part.value.indexOf("?") >= 0) {
|
|
276
|
+
throw_error("Literal segments cannot contain the '?' character");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (part.kind === "parameter") {
|
|
280
|
+
if (part.catch_all && index !== parts.length - 1) {
|
|
281
|
+
throw_error("A catch-all parameter can only appear as the last segment");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (parameters.has(part.name)) {
|
|
285
|
+
throw_error(`The route parameter name '${part.name}' appears more than one time`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
part.quantifier === "*"
|
|
289
|
+
&& is_nullish(part.default)
|
|
290
|
+
&& (part.default = "");
|
|
291
|
+
|
|
292
|
+
part.default === ""
|
|
293
|
+
&& part.quantifier !== "*"
|
|
294
|
+
&& (part.default = null);
|
|
295
|
+
|
|
296
|
+
parameters.set(part.name, true);
|
|
297
|
+
|
|
298
|
+
for (let constraint of part.constraints) {
|
|
299
|
+
const factory = factories?.[constraint.name] ?? default_constraints[constraint.name];
|
|
300
|
+
is_nullish(factory) && throw_error(`Unknown constraint '${ constraint.name }'`);
|
|
301
|
+
Object.assign(constraint, factory(constraint.argument));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return segments;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function parse_segments() {
|
|
310
|
+
const segments = [];
|
|
311
|
+
|
|
312
|
+
for (let i = 0; i < pattern.length;) {
|
|
313
|
+
const segment = parse_segment(i);
|
|
314
|
+
segment.template && segments.push(segment);
|
|
315
|
+
i += segment.template.length + 1;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return segments;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function parse_segment(pos) {
|
|
322
|
+
let parts = [];
|
|
323
|
+
let index = pos;
|
|
324
|
+
|
|
325
|
+
while (index < pattern.length && pattern[index] !== "/") {
|
|
326
|
+
const part = parse_literal(index) || parse_parameter(index);
|
|
327
|
+
parts.push(part);
|
|
328
|
+
|
|
329
|
+
index += part.template.length;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
template: pattern.slice(pos, index),
|
|
334
|
+
parts: parts
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function parse_constraints(text, p) {
|
|
339
|
+
const array = [];
|
|
340
|
+
|
|
341
|
+
for (let i = p; i < text.length;) {
|
|
342
|
+
if (text[i] !== ":") {
|
|
343
|
+
throw_error();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const name = get_constraint_name(text.slice(i + 1));
|
|
347
|
+
i += name.length + 1;
|
|
348
|
+
|
|
349
|
+
const arg = text[i] === "("
|
|
350
|
+
? get_parameter_template(i, text)
|
|
351
|
+
: null;
|
|
352
|
+
|
|
353
|
+
is_nullish(arg) || (i += arg.length + 2);
|
|
354
|
+
|
|
355
|
+
if (!name && !arg) {
|
|
356
|
+
throw_error();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
array.push({
|
|
360
|
+
name: name === "=" ? "default" : name || "regex",
|
|
361
|
+
argument: arg ?? ""
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return array;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function parse_parameter(p) {
|
|
370
|
+
if (pattern[p] !== "{") {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const template = get_parameter_template(p);
|
|
375
|
+
const parameter_template = pattern.slice(p, p + template.length + 2);
|
|
376
|
+
const name = get_parameter_name(template);
|
|
377
|
+
const quantifier = /[*+?]/.exec(template[name.length])?.[0] ?? "";
|
|
378
|
+
const list = parse_constraints(template, name.length + quantifier.length);
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
name: name,
|
|
382
|
+
kind: "parameter",
|
|
383
|
+
template: parameter_template,
|
|
384
|
+
quantifier: quantifier,
|
|
385
|
+
constraints: list.filter(c => c.name !== "default"),
|
|
386
|
+
default: list.find(c => c.name === "default")?.argument,
|
|
387
|
+
required: quantifier === "+" || quantifier === "",
|
|
388
|
+
optional: quantifier === "?" || quantifier === "*",
|
|
389
|
+
catch_all: quantifier === "+" || quantifier === "*"
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function parse_literal(pos) {
|
|
394
|
+
for (let i = pos; ; i++) {
|
|
395
|
+
if (i >= pattern.length || pattern[i] === "/" || pattern[i] === "{") {
|
|
396
|
+
if (i === pos) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const template = pattern.slice(pos, i);
|
|
401
|
+
return {
|
|
402
|
+
kind: "literal",
|
|
403
|
+
template: template,
|
|
404
|
+
value: template
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function get_parameter_template(pos, str) {
|
|
411
|
+
str ??= pattern;
|
|
412
|
+
const stack = [];
|
|
413
|
+
|
|
414
|
+
loop: for (let i = pos; i < str.length; i++) {
|
|
415
|
+
switch (str[i]) {
|
|
416
|
+
case "{": stack.push("}"); break;
|
|
417
|
+
case "(": stack.push(")"); break;
|
|
418
|
+
case "}":
|
|
419
|
+
case ")":
|
|
420
|
+
if (stack.pop() !== str[i]) break loop;
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (stack.length === 0) {
|
|
425
|
+
return str.slice(pos + 1, i);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
throw_error();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function get_parameter_name(value) {
|
|
433
|
+
const name = value.match(/^(?<name>[a-z_$][a-z0-9_$-]*?)(?:[:?+*]|$)/i)?.groups?.name;
|
|
434
|
+
is_nullish(name) && throw_error("Invalid parameter name");
|
|
435
|
+
|
|
436
|
+
return name;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function get_constraint_name(value) {
|
|
440
|
+
const name = value.match(/^(?<name>=|[a-z0-9_$]*)(?=[/:(]|$)/i)?.groups?.name;
|
|
441
|
+
is_nullish(name) && throw_error("Invalid constraint name");
|
|
442
|
+
|
|
443
|
+
return name;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function throw_error(message = "Invalid pattern") {
|
|
447
|
+
throw new Error(`${ message }: ${ pattern }`);
|
|
448
|
+
}
|
|
545
449
|
}
|
|
546
450
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
populate();
|
|
569
|
-
|
|
570
|
-
listen(window, "hashchange", populate);
|
|
571
|
-
listen(window, "popstate", populate);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return data;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function populate() {
|
|
578
|
-
for (let name in data) {
|
|
579
|
-
if (name in location) {
|
|
580
|
-
data[name] = location[name];
|
|
581
|
-
}
|
|
582
|
-
}
|
|
451
|
+
async function load_template(path) {
|
|
452
|
+
let result;
|
|
453
|
+
try {
|
|
454
|
+
result = await fetch(path);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// skip
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (!result?.ok) {
|
|
461
|
+
warn(`Failed to load template from ${ path }`);
|
|
462
|
+
return new DocumentFragment();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const fragment = new DocumentFragment();
|
|
466
|
+
const document = new DOMParser().parseFromString(await result.text(), "text/html");
|
|
467
|
+
|
|
468
|
+
fragment.append(...document.body.childNodes);
|
|
469
|
+
return fragment;
|
|
583
470
|
}
|
|
584
471
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
472
|
+
function route({ directive, $data }) {
|
|
473
|
+
directive("route", (el, { expression, value, modifiers }, { cleanup, evaluate }) => {
|
|
474
|
+
if (!is_template(el)) {
|
|
475
|
+
warn("x-route can only be used on a 'template' tag");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
//
|
|
480
|
+
// x-route:view and x-route:handler must be declared on the same element as x-route
|
|
481
|
+
//
|
|
482
|
+
const route = el._r_route;
|
|
483
|
+
|
|
484
|
+
if (!route && (value === "view" || value === "handler")) {
|
|
485
|
+
warn(`no x-route directive found`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
switch (value) {
|
|
490
|
+
case "view":
|
|
491
|
+
process_view();
|
|
492
|
+
break;
|
|
493
|
+
|
|
494
|
+
case "handler":
|
|
495
|
+
process_handler();
|
|
496
|
+
break;
|
|
497
|
+
|
|
498
|
+
default:
|
|
499
|
+
process_route();
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function process_route() {
|
|
504
|
+
const router = $data(el)?.$router;
|
|
505
|
+
if (router) {
|
|
506
|
+
const view = () => new Promise(resolve => resolve(el.content));
|
|
507
|
+
|
|
508
|
+
el._r_route = Object.assign(new RoutePattern(expression), { el, view, handler: () => Promise.resolve() });
|
|
509
|
+
router.routes.push(el._r_route);
|
|
510
|
+
|
|
511
|
+
cleanup(() => router.routes = router.routes.filter(r => r !== el._r_route));
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
warn(`no x-router directive found`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function process_handler() {
|
|
519
|
+
expression || (expression = "[]");
|
|
520
|
+
expression.startsWith("[") || (expression = `[${ expression }]`);
|
|
521
|
+
|
|
522
|
+
const handlers = evaluate(expression).map(asyncify);
|
|
523
|
+
const self = $data(el);
|
|
524
|
+
|
|
525
|
+
route.handler = async context => {
|
|
526
|
+
for (let handler of handlers) {
|
|
527
|
+
const r = await handler.call(self, context);
|
|
528
|
+
if (!is_nullish(r)) {
|
|
529
|
+
return r;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
cleanup(() => route.handler = null);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function process_view() {
|
|
538
|
+
route.view = () => load_template(expression);
|
|
539
|
+
|
|
540
|
+
cleanup(() => {
|
|
541
|
+
route.view = () => new Promise(resolve => resolve(new DocumentFragment()));
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
let data;
|
|
548
|
+
|
|
549
|
+
function use_location() {
|
|
550
|
+
assert(Alpine, "Alpine is not defined");
|
|
551
|
+
|
|
552
|
+
if (!data) {
|
|
553
|
+
data = Alpine.reactive({
|
|
554
|
+
hash: "",
|
|
555
|
+
host: "",
|
|
556
|
+
hostname: "",
|
|
557
|
+
href: "",
|
|
558
|
+
origin: "",
|
|
559
|
+
pathname: "",
|
|
560
|
+
port: 0,
|
|
561
|
+
protocol: "",
|
|
562
|
+
search: "",
|
|
563
|
+
refresh() {
|
|
564
|
+
populate();
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
populate();
|
|
569
|
+
|
|
570
|
+
listen(window, "hashchange", populate);
|
|
571
|
+
listen(window, "popstate", populate);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return data;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function populate() {
|
|
578
|
+
for (let name in data) {
|
|
579
|
+
if (name in location) {
|
|
580
|
+
data[name] = location[name];
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let location$1;
|
|
586
|
+
|
|
587
|
+
const hash_api = {
|
|
588
|
+
get path() {
|
|
589
|
+
return location$1.hash.slice(1) || "/";
|
|
590
|
+
},
|
|
591
|
+
get "location"() {
|
|
592
|
+
return location$1;
|
|
593
|
+
},
|
|
594
|
+
resolve(path) {
|
|
595
|
+
let url = new URL(path);
|
|
596
|
+
return url.hash ? url.hash.slice(1) || "/" : url.pathname;
|
|
597
|
+
},
|
|
598
|
+
navigate(path, replace = false) {
|
|
599
|
+
path.indexOf("#") < 0 && (path = "#" + path);
|
|
600
|
+
navigate(path, replace);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const html5_api = {
|
|
605
|
+
get path() {
|
|
606
|
+
return location$1.pathname;
|
|
607
|
+
},
|
|
608
|
+
get "location"() {
|
|
609
|
+
return location$1;
|
|
610
|
+
},
|
|
611
|
+
resolve(path) {
|
|
612
|
+
return new URL(path).pathname;
|
|
613
|
+
},
|
|
614
|
+
navigate(path, replace = false) {
|
|
615
|
+
navigate(path, replace);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
function navigate(path, replace) {
|
|
620
|
+
history[replace ? "replaceState" : "pushState"]({}, "", path);
|
|
621
|
+
location$1.refresh();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const known_api = {
|
|
625
|
+
html5: html5_api,
|
|
626
|
+
hash: hash_api
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
function create_history(name) {
|
|
630
|
+
location$1 ??= use_location();
|
|
631
|
+
|
|
632
|
+
name ||= "html5";
|
|
633
|
+
let api = known_api[name];
|
|
634
|
+
|
|
635
|
+
if (!api) {
|
|
636
|
+
warn(`Unknown history API: ${ name }`);
|
|
637
|
+
api = html5_api;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return api;
|
|
641
641
|
}
|
|
642
642
|
|
|
643
643
|
function watch(get_value, callback, options = null) {
|
|
@@ -665,7 +665,7 @@ function watch(get_value, callback, options = null) {
|
|
|
665
665
|
setTimeout(() => {
|
|
666
666
|
callback(new_value, old_value);
|
|
667
667
|
old_value = new_value;
|
|
668
|
-
}
|
|
668
|
+
});
|
|
669
669
|
}
|
|
670
670
|
|
|
671
671
|
initialized = true;
|
|
@@ -674,232 +674,232 @@ function watch(get_value, callback, options = null) {
|
|
|
674
674
|
return () => release(handle);
|
|
675
675
|
}
|
|
676
676
|
|
|
677
|
-
function router({ $data, addScopeToNode, directive, magic, reactive }) {
|
|
678
|
-
directive("router", (el, { modifiers, value }, { cleanup }) => {
|
|
679
|
-
value || (value = "html5");
|
|
680
|
-
|
|
681
|
-
const router = $data(el).$router;
|
|
682
|
-
|
|
683
|
-
if (!router && (value === "outlet" || value === "link")) {
|
|
684
|
-
warn(`x-router:${value} is missing a parent x-router`);
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
switch (value) {
|
|
689
|
-
case "outlet":
|
|
690
|
-
process_outlet();
|
|
691
|
-
break;
|
|
692
|
-
|
|
693
|
-
case "link":
|
|
694
|
-
process_link();
|
|
695
|
-
break;
|
|
696
|
-
|
|
697
|
-
default:
|
|
698
|
-
process_router();
|
|
699
|
-
break;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function process_router() {
|
|
703
|
-
if (is_template(el)) {
|
|
704
|
-
warn("x-router cannot be used on a 'template' tag");
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
const values = reactive({
|
|
709
|
-
pattern: "",
|
|
710
|
-
path: "",
|
|
711
|
-
params: {}
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
const api = create_history(value);
|
|
715
|
-
|
|
716
|
-
const router = {
|
|
717
|
-
routes: [],
|
|
718
|
-
outlet: null,
|
|
719
|
-
active: null,
|
|
720
|
-
history: api,
|
|
721
|
-
values: values,
|
|
722
|
-
async match(path) {
|
|
723
|
-
for (let route of this.routes) {
|
|
724
|
-
const params = route.match(path);
|
|
725
|
-
if (params) {
|
|
726
|
-
const context = { router, route, params, path };
|
|
727
|
-
if (await route.handler(context) !== false) {
|
|
728
|
-
return context;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
},
|
|
733
|
-
navigate(path, replace = false) {
|
|
734
|
-
api.navigate(path, replace);
|
|
735
|
-
return true;
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
|
|
739
|
-
addScopeToNode(el, { $route: values, $router: router });
|
|
740
|
-
|
|
741
|
-
function activate(route, path, params) {
|
|
742
|
-
if (route.nodes?.length && values.path === path) {
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
clear();
|
|
747
|
-
|
|
748
|
-
values.path = path;
|
|
749
|
-
values.pattern = route.template;
|
|
750
|
-
values.params = params ?? {};
|
|
751
|
-
|
|
752
|
-
router.active = route;
|
|
753
|
-
|
|
754
|
-
const outlet = router.outlet;
|
|
755
|
-
if (outlet) {
|
|
756
|
-
route.view().then(html => {
|
|
757
|
-
if (values.path !== path
|
|
758
|
-
|| values.pattern !== route.template
|
|
759
|
-
|| JSON.stringify(values.params) !== JSON.stringify(params)) {
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
route.nodes = [...html.cloneNode(true).childNodes];
|
|
764
|
-
is_template(outlet)
|
|
765
|
-
? route.nodes.forEach(node => outlet.parentElement.insertBefore(node, outlet))
|
|
766
|
-
: route.nodes.forEach(node => outlet.append(node));
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
function clear() {
|
|
772
|
-
if (router.active) {
|
|
773
|
-
for (let n of router.active.nodes ?? []) {
|
|
774
|
-
n.remove();
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
router.active.nodes = null;
|
|
778
|
-
router.active = null;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
const dispose = watch(() => api.path, async path => {
|
|
783
|
-
const result = await router.match(path);
|
|
784
|
-
|
|
785
|
-
if (result) {
|
|
786
|
-
if (path === api.path) {
|
|
787
|
-
activate(result.route, result.path, result.params);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
791
|
-
clear();
|
|
792
|
-
}
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
cleanup(dispose);
|
|
796
|
-
cleanup(clear);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
function process_link() {
|
|
800
|
-
let link = get_anchor_element(el);
|
|
801
|
-
if (link) {
|
|
802
|
-
el._r_routerlink = link;
|
|
803
|
-
|
|
804
|
-
const is_blank = (link.getAttribute("target") ?? "").indexOf("_blank") >= 0;
|
|
805
|
-
const unsubscribe = listen(link, "click", e => {
|
|
806
|
-
if (e.metaKey
|
|
807
|
-
|| e.altKey
|
|
808
|
-
|| e.ctrlKey
|
|
809
|
-
|| e.shiftKey
|
|
810
|
-
|| e.defaultPrevented
|
|
811
|
-
|| e.button > 0
|
|
812
|
-
|| is_blank) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
e.preventDefault();
|
|
817
|
-
|
|
818
|
-
router.navigate(`${ link.pathname }${ link.search }${ link.hash }`, has_modifier(modifiers, "replace"));
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
cleanup(unsubscribe);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
//
|
|
825
|
-
// A warning about a non-existing anchor element is printed in the get_anchor_element function.
|
|
826
|
-
// warn("<a> element not found")
|
|
827
|
-
//
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function process_outlet() {
|
|
831
|
-
if (router.outlet) {
|
|
832
|
-
warn("x-router:outlet is already present", router.outlet, el);
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
router.outlet = el;
|
|
836
|
-
cleanup(() => router.outlet = null);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
magic("active", el => {
|
|
842
|
-
const router = $data(el).$router;
|
|
843
|
-
if (!router) {
|
|
844
|
-
warn("No x-router directive found");
|
|
845
|
-
return false;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
//
|
|
849
|
-
// Create a dependency on router.values
|
|
850
|
-
//
|
|
851
|
-
JSON.stringify(router.values);
|
|
852
|
-
|
|
853
|
-
const link = is_anchor_element(el) ? el : closest(el, node => node._r_routerlink)?._r_routerlink;
|
|
854
|
-
|
|
855
|
-
//
|
|
856
|
-
// The issue is that the router:link directive is processed later than x-bind,
|
|
857
|
-
// and if $active is used in x-bind, we won’t find node._r_routerlink.
|
|
858
|
-
// Therefore, we delay execution and try again.
|
|
859
|
-
//
|
|
860
|
-
// <div x-router:link :class="{ active: $active }">
|
|
861
|
-
// ...
|
|
862
|
-
// </div>
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
if (link) {
|
|
866
|
-
return router.history.resolve(link.href) === router.values.path;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
if (el._r_routerlink_init) {
|
|
870
|
-
warn(`x-router:link directive not found`, el);
|
|
871
|
-
}
|
|
872
|
-
else {
|
|
873
|
-
queueMicrotask(() => {
|
|
874
|
-
el._r_routerlink_init = true;
|
|
875
|
-
//
|
|
876
|
-
// Force an upate
|
|
877
|
-
//
|
|
878
|
-
router.values.path = router.values.path;
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
return false;
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
function is_anchor_element(el) {
|
|
887
|
-
return el.tagName.toUpperCase() === "A";
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
function get_anchor_element(el) {
|
|
891
|
-
if (is_anchor_element(el)) {
|
|
892
|
-
return el;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const links = el.querySelectorAll("a");
|
|
896
|
-
links.length !== 1 && warn(`Expected exactly one link, but found ${links.length}`);
|
|
897
|
-
return links[0];
|
|
677
|
+
function router({ $data, addScopeToNode, directive, magic, reactive }) {
|
|
678
|
+
directive("router", (el, { modifiers, value }, { cleanup }) => {
|
|
679
|
+
value || (value = "html5");
|
|
680
|
+
|
|
681
|
+
const router = $data(el).$router;
|
|
682
|
+
|
|
683
|
+
if (!router && (value === "outlet" || value === "link")) {
|
|
684
|
+
warn(`x-router:${value} is missing a parent x-router`);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
switch (value) {
|
|
689
|
+
case "outlet":
|
|
690
|
+
process_outlet();
|
|
691
|
+
break;
|
|
692
|
+
|
|
693
|
+
case "link":
|
|
694
|
+
process_link();
|
|
695
|
+
break;
|
|
696
|
+
|
|
697
|
+
default:
|
|
698
|
+
process_router();
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function process_router() {
|
|
703
|
+
if (is_template(el)) {
|
|
704
|
+
warn("x-router cannot be used on a 'template' tag");
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const values = reactive({
|
|
709
|
+
pattern: "",
|
|
710
|
+
path: "",
|
|
711
|
+
params: {}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const api = create_history(value);
|
|
715
|
+
|
|
716
|
+
const router = {
|
|
717
|
+
routes: [],
|
|
718
|
+
outlet: null,
|
|
719
|
+
active: null,
|
|
720
|
+
history: api,
|
|
721
|
+
values: values,
|
|
722
|
+
async match(path) {
|
|
723
|
+
for (let route of this.routes) {
|
|
724
|
+
const params = route.match(path);
|
|
725
|
+
if (params) {
|
|
726
|
+
const context = { router, route, params, path };
|
|
727
|
+
if (await route.handler(context) !== false) {
|
|
728
|
+
return context;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
navigate(path, replace = false) {
|
|
734
|
+
api.navigate(path, replace);
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
addScopeToNode(el, { $route: values, $router: router });
|
|
740
|
+
|
|
741
|
+
function activate(route, path, params) {
|
|
742
|
+
if (route.nodes?.length && values.path === path) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
clear();
|
|
747
|
+
|
|
748
|
+
values.path = path;
|
|
749
|
+
values.pattern = route.template;
|
|
750
|
+
values.params = params ?? {};
|
|
751
|
+
|
|
752
|
+
router.active = route;
|
|
753
|
+
|
|
754
|
+
const outlet = router.outlet;
|
|
755
|
+
if (outlet) {
|
|
756
|
+
route.view().then(html => {
|
|
757
|
+
if (values.path !== path
|
|
758
|
+
|| values.pattern !== route.template
|
|
759
|
+
|| JSON.stringify(values.params) !== JSON.stringify(params)) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
route.nodes = [...html.cloneNode(true).childNodes];
|
|
764
|
+
is_template(outlet)
|
|
765
|
+
? route.nodes.forEach(node => outlet.parentElement.insertBefore(node, outlet))
|
|
766
|
+
: route.nodes.forEach(node => outlet.append(node));
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function clear() {
|
|
772
|
+
if (router.active) {
|
|
773
|
+
for (let n of router.active.nodes ?? []) {
|
|
774
|
+
n.remove();
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
router.active.nodes = null;
|
|
778
|
+
router.active = null;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const dispose = watch(() => api.path, async path => {
|
|
783
|
+
const result = await router.match(path);
|
|
784
|
+
|
|
785
|
+
if (result) {
|
|
786
|
+
if (path === api.path) {
|
|
787
|
+
activate(result.route, result.path, result.params);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
clear();
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
cleanup(dispose);
|
|
796
|
+
cleanup(clear);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function process_link() {
|
|
800
|
+
let link = get_anchor_element(el);
|
|
801
|
+
if (link) {
|
|
802
|
+
el._r_routerlink = link;
|
|
803
|
+
|
|
804
|
+
const is_blank = (link.getAttribute("target") ?? "").indexOf("_blank") >= 0;
|
|
805
|
+
const unsubscribe = listen(link, "click", e => {
|
|
806
|
+
if (e.metaKey
|
|
807
|
+
|| e.altKey
|
|
808
|
+
|| e.ctrlKey
|
|
809
|
+
|| e.shiftKey
|
|
810
|
+
|| e.defaultPrevented
|
|
811
|
+
|| e.button > 0
|
|
812
|
+
|| is_blank) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
e.preventDefault();
|
|
817
|
+
|
|
818
|
+
router.navigate(`${ link.pathname }${ link.search }${ link.hash }`, has_modifier(modifiers, "replace"));
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
cleanup(unsubscribe);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
//
|
|
825
|
+
// A warning about a non-existing anchor element is printed in the get_anchor_element function.
|
|
826
|
+
// warn("<a> element not found")
|
|
827
|
+
//
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function process_outlet() {
|
|
831
|
+
if (router.outlet) {
|
|
832
|
+
warn("x-router:outlet is already present", router.outlet, el);
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
router.outlet = el;
|
|
836
|
+
cleanup(() => router.outlet = null);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
magic("active", el => {
|
|
842
|
+
const router = $data(el).$router;
|
|
843
|
+
if (!router) {
|
|
844
|
+
warn("No x-router directive found");
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
//
|
|
849
|
+
// Create a dependency on router.values
|
|
850
|
+
//
|
|
851
|
+
JSON.stringify(router.values);
|
|
852
|
+
|
|
853
|
+
const link = is_anchor_element(el) ? el : closest(el, node => node._r_routerlink)?._r_routerlink;
|
|
854
|
+
|
|
855
|
+
//
|
|
856
|
+
// The issue is that the router:link directive is processed later than x-bind,
|
|
857
|
+
// and if $active is used in x-bind, we won’t find node._r_routerlink.
|
|
858
|
+
// Therefore, we delay execution and try again.
|
|
859
|
+
//
|
|
860
|
+
// <div x-router:link :class="{ active: $active }">
|
|
861
|
+
// ...
|
|
862
|
+
// </div>
|
|
863
|
+
//
|
|
864
|
+
|
|
865
|
+
if (link) {
|
|
866
|
+
return router.history.resolve(link.href) === router.values.path;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (el._r_routerlink_init) {
|
|
870
|
+
warn(`x-router:link directive not found`, el);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
queueMicrotask(() => {
|
|
874
|
+
el._r_routerlink_init = true;
|
|
875
|
+
//
|
|
876
|
+
// Force an upate
|
|
877
|
+
//
|
|
878
|
+
router.values.path = router.values.path;
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return false;
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function is_anchor_element(el) {
|
|
887
|
+
return el.tagName.toUpperCase() === "A";
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function get_anchor_element(el) {
|
|
891
|
+
if (is_anchor_element(el)) {
|
|
892
|
+
return el;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const links = el.querySelectorAll("a");
|
|
896
|
+
links.length !== 1 && warn(`Expected exactly one link, but found ${links.length}`);
|
|
897
|
+
return links[0];
|
|
898
898
|
}
|
|
899
899
|
|
|
900
|
-
function plugin(alpine) {
|
|
901
|
-
window.RoutePattern = RoutePattern;
|
|
902
|
-
alpine.plugin([router, route]);
|
|
900
|
+
function plugin(alpine) {
|
|
901
|
+
window.RoutePattern = RoutePattern;
|
|
902
|
+
alpine.plugin([router, route]);
|
|
903
903
|
}
|
|
904
904
|
|
|
905
|
-
export { RoutePattern, plugin as router };
|
|
905
|
+
export { RoutePattern, plugin as default, plugin as router };
|