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