@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.
@@ -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 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
- }
233
- }
234
-
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 }$`);
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
- 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;
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 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
- });
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
- 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
- }
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
- 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;
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
- }, 0);
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.addEventListener("alpine:init", () => { Alpine.plugin(plugin); });
908
+ listen(document, "alpine:init", () => Alpine.plugin(plugin));
909
909
 
910
910
  })();