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