@wcstack/router 1.3.19 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -132,73 +132,81 @@ const weights = {
132
132
  'param': 1,
133
133
  'catch-all': 0
134
134
  };
135
- class Route extends HTMLElement {
136
- _name = '';
135
+ class RouteCore extends EventTarget {
136
+ static wcBindable = {
137
+ protocol: "wc-bindable",
138
+ version: 1,
139
+ properties: [
140
+ { name: "params", event: "wcs-route:params-changed" },
141
+ { name: "typedParams", event: "wcs-route:params-changed", getter: (e) => e.detail.typedParams },
142
+ { name: "active", event: "wcs-route:active-changed" },
143
+ ],
144
+ };
145
+ _target;
146
+ _parentCore = null;
137
147
  _path = '';
138
- _routeParentNode = null;
139
- _routeChildNodes = [];
140
- _routerNode = null;
141
- _uuid = getUUID();
142
- _placeHolder = document.createComment(`@@route:${this._uuid}`);
143
- _childNodeArray;
148
+ _name = '';
149
+ _isFallbackRoute = false;
150
+ _segmentInfos = [];
151
+ _absoluteSegmentInfos;
144
152
  _paramNames;
145
153
  _absoluteParamNames;
146
- _params = {};
147
- _typedParams = {};
148
154
  _weight;
149
155
  _absoluteWeight;
150
- _childIndex = 0;
156
+ _segmentCount;
157
+ _params = {};
158
+ _typedParams = {};
159
+ _active = false;
160
+ // Guard
151
161
  _hasGuard = false;
152
162
  _guardHandler = null;
163
+ _guardFallbackPath = '';
153
164
  _waitForSetGuardHandler = null;
154
165
  _resolveSetGuardHandler = null;
155
- _guardFallbackPath = '';
156
- _initialized = false;
157
- _isFallbackRoute = false;
158
- _segmentCount;
159
- _segmentInfos = [];
160
- _absoluteSegmentInfos;
161
- constructor() {
166
+ constructor(target) {
162
167
  super();
168
+ this._target = target ?? this;
163
169
  }
164
- get routeParentNode() {
165
- return this._routeParentNode;
170
+ get parentCore() {
171
+ return this._parentCore;
166
172
  }
167
- get routeChildNodes() {
168
- return this._routeChildNodes;
169
- }
170
- get routerNode() {
171
- if (!this._routerNode) {
172
- raiseError(`${config.tagNames.route} has no routerNode.`);
173
- }
174
- return this._routerNode;
173
+ set parentCore(value) {
174
+ this._parentCore = value;
175
175
  }
176
176
  get path() {
177
177
  return this._path;
178
178
  }
179
+ get name() {
180
+ return this._name;
181
+ }
182
+ get isFallbackRoute() {
183
+ return this._isFallbackRoute;
184
+ }
179
185
  get isRelative() {
180
186
  return !this._path.startsWith('/');
181
187
  }
182
- _checkParentNode(hasParentCallback, noParentCallback) {
183
- // fallbackはルーター直下のみ許可されるため、相対パスチェックはスキップ
188
+ get segmentInfos() {
189
+ return this._segmentInfos;
190
+ }
191
+ _checkParentCore(hasParentCallback, noParentCallback) {
184
192
  if (!this._isFallbackRoute) {
185
- if (this.isRelative && !this._routeParentNode) {
193
+ if (this.isRelative && !this._parentCore) {
186
194
  raiseError(`${config.tagNames.route} is relative but has no parent route.`);
187
195
  }
188
- if (!this.isRelative && this._routeParentNode) {
196
+ if (!this.isRelative && this._parentCore) {
189
197
  raiseError(`${config.tagNames.route} is absolute but has a parent route.`);
190
198
  }
191
199
  }
192
- if (this.isRelative && this._routeParentNode) {
193
- return hasParentCallback(this._routeParentNode);
200
+ if (this.isRelative && this._parentCore) {
201
+ return hasParentCallback(this._parentCore);
194
202
  }
195
203
  else {
196
204
  return noParentCallback();
197
205
  }
198
206
  }
199
207
  get absolutePath() {
200
- return this._checkParentNode((routeParentNode) => {
201
- const parentPath = routeParentNode.absolutePath;
208
+ return this._checkParentCore((parentCore) => {
209
+ const parentPath = parentCore.absolutePath;
202
210
  return parentPath.endsWith('/')
203
211
  ? parentPath + this._path
204
212
  : parentPath + '/' + this._path;
@@ -206,35 +214,11 @@ class Route extends HTMLElement {
206
214
  return this._path;
207
215
  });
208
216
  }
209
- get uuid() {
210
- return this._uuid;
211
- }
212
- get placeHolder() {
213
- return this._placeHolder;
214
- }
215
- get childNodeArray() {
216
- if (typeof this._childNodeArray === 'undefined') {
217
- this._childNodeArray = Array.from(this.childNodes);
218
- }
219
- return this._childNodeArray;
220
- }
221
- get routes() {
222
- if (this.routeParentNode) {
223
- return this.routeParentNode.routes.concat(this);
224
- }
225
- else {
226
- return [this];
227
- }
228
- }
229
- get segmentInfos() {
230
- return this._segmentInfos;
231
- }
232
- // indexの場合、{ type: 'static', segmentText: '' }となる、indexが複数連続する場合もある
233
217
  get absoluteSegmentInfos() {
234
218
  if (typeof this._absoluteSegmentInfos === 'undefined') {
235
- this._absoluteSegmentInfos = this._checkParentNode((routeParentNode) => {
219
+ this._absoluteSegmentInfos = this._checkParentCore((parentCore) => {
236
220
  return [
237
- ...routeParentNode.absoluteSegmentInfos,
221
+ ...parentCore.absoluteSegmentInfos,
238
222
  ...this._segmentInfos
239
223
  ];
240
224
  }, () => {
@@ -249,6 +233,9 @@ class Route extends HTMLElement {
249
233
  get typedParams() {
250
234
  return this._typedParams;
251
235
  }
236
+ get active() {
237
+ return this._active;
238
+ }
252
239
  get paramNames() {
253
240
  if (typeof this._paramNames === 'undefined') {
254
241
  const names = [];
@@ -263,9 +250,9 @@ class Route extends HTMLElement {
263
250
  }
264
251
  get absoluteParamNames() {
265
252
  if (typeof this._absoluteParamNames === 'undefined') {
266
- this._absoluteParamNames = this._checkParentNode((routeParentNode) => {
253
+ this._absoluteParamNames = this._checkParentCore((parentCore) => {
267
254
  return [
268
- ...routeParentNode.absoluteParamNames,
255
+ ...parentCore.absoluteParamNames,
269
256
  ...this.paramNames
270
257
  ];
271
258
  }, () => {
@@ -286,87 +273,38 @@ class Route extends HTMLElement {
286
273
  }
287
274
  get absoluteWeight() {
288
275
  if (typeof this._absoluteWeight === 'undefined') {
289
- this._absoluteWeight = this._checkParentNode((routeParentNode) => {
290
- return routeParentNode.absoluteWeight + this.weight;
276
+ this._absoluteWeight = this._checkParentCore((parentCore) => {
277
+ return parentCore.absoluteWeight + this.weight;
291
278
  }, () => {
292
279
  return this.weight;
293
280
  });
294
281
  }
295
282
  return this._absoluteWeight;
296
283
  }
297
- get childIndex() {
298
- return this._childIndex;
299
- }
300
- get name() {
301
- return this._name;
302
- }
303
- async guardCheck(matchResult) {
304
- if (this._hasGuard && this._waitForSetGuardHandler) {
305
- await this._waitForSetGuardHandler;
306
- }
307
- if (this._guardHandler) {
308
- const toPath = matchResult.path;
309
- const fromPath = matchResult.lastPath;
310
- const allowed = await this._guardHandler(toPath, fromPath);
311
- if (!allowed) {
312
- throw new GuardCancel('Navigation cancelled by guard.', this._guardFallbackPath);
313
- }
314
- }
315
- }
316
- shouldChange(newParams) {
317
- for (const key of this.paramNames) {
318
- if (this.params[key] !== newParams[key]) {
319
- return true;
284
+ get segmentCount() {
285
+ if (typeof this._segmentCount === 'undefined') {
286
+ let count = 0;
287
+ for (const info of this._segmentInfos) {
288
+ if (info.type !== 'catch-all') {
289
+ count++;
290
+ }
320
291
  }
292
+ this._segmentCount = this._path === "" ? 0 : count;
321
293
  }
322
- return false;
323
- }
324
- get guardHandler() {
325
- if (!this._guardHandler) {
326
- raiseError(`${config.tagNames.route} has no guardHandler.`);
327
- }
328
- return this._guardHandler;
294
+ return this._segmentCount;
329
295
  }
330
- set guardHandler(value) {
331
- this._resolveSetGuardHandler?.();
332
- this._guardHandler = value;
296
+ get absoluteSegmentCount() {
297
+ return this._checkParentCore((parentCore) => {
298
+ return parentCore.absoluteSegmentCount + this.segmentCount;
299
+ }, () => {
300
+ return this.segmentCount;
301
+ });
333
302
  }
334
- initialize(routerNode, routeParentNode) {
335
- if (this._initialized) {
336
- return;
337
- }
338
- this._initialized = true;
339
- // 単独で影響のないものから設定していく
340
- if (this.hasAttribute('path')) {
341
- this._path = this.getAttribute('path') || '';
342
- }
343
- else if (this.hasAttribute('index')) {
344
- this._path = '';
345
- }
346
- else if (this.hasAttribute('fallback')) {
347
- this._path = '';
348
- this._isFallbackRoute = true;
349
- }
350
- else {
351
- raiseError(`${config.tagNames.route} should have a "path" or "index" attribute.`);
352
- }
353
- this._name = this.getAttribute('name') || '';
354
- this._routerNode = routerNode;
355
- this._routeParentNode = routeParentNode;
356
- const routeChildContainer = routeParentNode || routerNode;
357
- routeChildContainer.routeChildNodes.push(this);
358
- this._childIndex = routeChildContainer.routeChildNodes.length - 1;
359
- if (this._isFallbackRoute) {
360
- if (routeParentNode) {
361
- raiseError(`${config.tagNames.route} with fallback attribute must be a direct child of ${config.tagNames.router}.`);
362
- }
363
- if (routerNode.fallbackRoute) {
364
- raiseError(`${config.tagNames.router} can have only one fallback route.`);
365
- }
366
- routerNode.fallbackRoute = this;
367
- }
368
- // index属性の場合は特別扱い(セグメントを消費しない)
369
- if (this.hasAttribute('index')) {
303
+ parsePath(path, options = {}) {
304
+ this._path = path;
305
+ this._name = options.name || '';
306
+ this._isFallbackRoute = options.isFallback || false;
307
+ if (options.isIndex) {
370
308
  this._segmentInfos.push({
371
309
  type: 'static',
372
310
  segmentText: '',
@@ -375,7 +313,7 @@ class Route extends HTMLElement {
375
313
  isIndex: true
376
314
  });
377
315
  }
378
- const segments = this._path.split('/');
316
+ const segments = path.split('/');
379
317
  for (let idx = 0; idx < segments.length; idx++) {
380
318
  const segment = segments[idx];
381
319
  // 末尾の空セグメントはスキップ(/parent/ のような場合)
@@ -413,7 +351,7 @@ class Route extends HTMLElement {
413
351
  paramType: typeName
414
352
  });
415
353
  }
416
- else if (segment !== '' || !this.hasAttribute('index')) {
354
+ else if (segment !== '' || !options.isIndex) {
417
355
  // 空セグメントはindex以外の場合のみ追加(絶対パスの先頭 '' など)
418
356
  this._segmentInfos.push({
419
357
  type: 'static',
@@ -423,36 +361,175 @@ class Route extends HTMLElement {
423
361
  });
424
362
  }
425
363
  }
426
- this._hasGuard = this.hasAttribute('guard');
364
+ this._hasGuard = options.hasGuard || false;
427
365
  if (this._hasGuard) {
428
- this._guardFallbackPath = this.getAttribute('guard') || '/';
366
+ this._guardFallbackPath = options.guardFallback || '/';
429
367
  this._waitForSetGuardHandler = new Promise((resolve) => {
430
368
  this._resolveSetGuardHandler = resolve;
431
369
  });
432
370
  }
433
- this.setAttribute('fullpath', this.absolutePath);
434
371
  }
435
- get fullpath() {
436
- return this.absolutePath;
372
+ setParams(params, typedParams) {
373
+ this._params = params;
374
+ this._typedParams = typedParams;
375
+ this._active = true;
376
+ this._target.dispatchEvent(new CustomEvent("wcs-route:params-changed", {
377
+ detail: { params, typedParams },
378
+ bubbles: true,
379
+ }));
437
380
  }
438
- get segmentCount() {
439
- if (typeof this._segmentCount === 'undefined') {
440
- let count = 0;
441
- for (const info of this._segmentInfos) {
442
- if (info.type !== 'catch-all') {
443
- count++;
444
- }
381
+ clearParams() {
382
+ this._params = {};
383
+ this._typedParams = {};
384
+ this._active = false;
385
+ }
386
+ shouldChange(newParams) {
387
+ for (const key of this.paramNames) {
388
+ if (this._params[key] !== newParams[key]) {
389
+ return true;
445
390
  }
446
- this._segmentCount = this._path === "" ? 0 : count;
447
391
  }
448
- return this._segmentCount;
392
+ return false;
393
+ }
394
+ get guardHandler() {
395
+ if (!this._guardHandler) {
396
+ raiseError(`${config.tagNames.route} has no guardHandler.`);
397
+ }
398
+ return this._guardHandler;
399
+ }
400
+ set guardHandler(value) {
401
+ this._resolveSetGuardHandler?.();
402
+ this._guardHandler = value;
403
+ }
404
+ async guardCheck(matchResult) {
405
+ if (this._hasGuard && this._waitForSetGuardHandler) {
406
+ await this._waitForSetGuardHandler;
407
+ }
408
+ if (this._guardHandler) {
409
+ const toPath = matchResult.path;
410
+ const fromPath = matchResult.lastPath;
411
+ const allowed = await this._guardHandler(toPath, fromPath);
412
+ if (!allowed) {
413
+ throw new GuardCancel('Navigation cancelled by guard.', this._guardFallbackPath);
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ class Route extends HTMLElement {
420
+ static wcBindable = RouteCore.wcBindable;
421
+ _core;
422
+ _routeParentNode = null;
423
+ _routeChildNodes = [];
424
+ _routerNode = null;
425
+ _uuid = getUUID();
426
+ _placeHolder = document.createComment(`@@route:${this._uuid}`);
427
+ _childNodeArray;
428
+ _childIndex = 0;
429
+ _initialized = false;
430
+ constructor() {
431
+ super();
432
+ this._core = new RouteCore(this);
433
+ }
434
+ // Shell-only properties
435
+ get routeParentNode() {
436
+ return this._routeParentNode;
437
+ }
438
+ get routeChildNodes() {
439
+ return this._routeChildNodes;
440
+ }
441
+ get routerNode() {
442
+ if (!this._routerNode) {
443
+ raiseError(`${config.tagNames.route} has no routerNode.`);
444
+ }
445
+ return this._routerNode;
446
+ }
447
+ get uuid() {
448
+ return this._uuid;
449
+ }
450
+ get placeHolder() {
451
+ return this._placeHolder;
452
+ }
453
+ get childNodeArray() {
454
+ if (typeof this._childNodeArray === 'undefined') {
455
+ this._childNodeArray = Array.from(this.childNodes);
456
+ }
457
+ return this._childNodeArray;
458
+ }
459
+ get routes() {
460
+ if (this.routeParentNode) {
461
+ return this.routeParentNode.routes.concat(this);
462
+ }
463
+ else {
464
+ return [this];
465
+ }
466
+ }
467
+ get childIndex() {
468
+ return this._childIndex;
469
+ }
470
+ // Core delegates
471
+ get path() {
472
+ return this._core.path;
473
+ }
474
+ get name() {
475
+ return this._core.name;
476
+ }
477
+ get isRelative() {
478
+ return this._core.isRelative;
479
+ }
480
+ get absolutePath() {
481
+ return this._core.absolutePath;
482
+ }
483
+ get segmentInfos() {
484
+ return this._core.segmentInfos;
485
+ }
486
+ get absoluteSegmentInfos() {
487
+ return this._core.absoluteSegmentInfos;
488
+ }
489
+ get params() {
490
+ return this._core.params;
491
+ }
492
+ get typedParams() {
493
+ return this._core.typedParams;
494
+ }
495
+ get paramNames() {
496
+ return this._core.paramNames;
497
+ }
498
+ get absoluteParamNames() {
499
+ return this._core.absoluteParamNames;
500
+ }
501
+ get weight() {
502
+ return this._core.weight;
503
+ }
504
+ get absoluteWeight() {
505
+ return this._core.absoluteWeight;
506
+ }
507
+ get segmentCount() {
508
+ return this._core.segmentCount;
449
509
  }
450
510
  get absoluteSegmentCount() {
451
- return this._checkParentNode((routeParentNode) => {
452
- return routeParentNode.absoluteSegmentCount + this.segmentCount;
453
- }, () => {
454
- return this.segmentCount;
455
- });
511
+ return this._core.absoluteSegmentCount;
512
+ }
513
+ get fullpath() {
514
+ return this.absolutePath;
515
+ }
516
+ get guardHandler() {
517
+ return this._core.guardHandler;
518
+ }
519
+ set guardHandler(value) {
520
+ this._core.guardHandler = value;
521
+ }
522
+ setParams(params, typedParams) {
523
+ this._core.setParams(params, typedParams);
524
+ }
525
+ clearParams() {
526
+ this._core.clearParams();
527
+ }
528
+ shouldChange(newParams) {
529
+ return this._core.shouldChange(newParams);
530
+ }
531
+ async guardCheck(matchResult) {
532
+ return this._core.guardCheck(matchResult);
456
533
  }
457
534
  testAncestorNode(ancestorNode) {
458
535
  let currentNode = this._routeParentNode;
@@ -464,9 +541,58 @@ class Route extends HTMLElement {
464
541
  }
465
542
  return false;
466
543
  }
467
- clearParams() {
468
- this._params = {};
469
- this._typedParams = {};
544
+ initialize(routerNode, routeParentNode) {
545
+ if (this._initialized) {
546
+ return;
547
+ }
548
+ this._initialized = true;
549
+ // 属性からパス情報を読み取り
550
+ let path;
551
+ let isIndex = false;
552
+ let isFallback = false;
553
+ if (this.hasAttribute('path')) {
554
+ path = this.getAttribute('path') || '';
555
+ }
556
+ else if (this.hasAttribute('index')) {
557
+ path = '';
558
+ isIndex = true;
559
+ }
560
+ else if (this.hasAttribute('fallback')) {
561
+ path = '';
562
+ isFallback = true;
563
+ }
564
+ else {
565
+ raiseError(`${config.tagNames.route} should have a "path" or "index" attribute.`);
566
+ }
567
+ // ルートツリーの構築
568
+ this._routerNode = routerNode;
569
+ this._routeParentNode = routeParentNode;
570
+ const routeChildContainer = routeParentNode || routerNode;
571
+ routeChildContainer.routeChildNodes.push(this);
572
+ this._childIndex = routeChildContainer.routeChildNodes.length - 1;
573
+ // Fallback検証
574
+ if (isFallback) {
575
+ if (routeParentNode) {
576
+ raiseError(`${config.tagNames.route} with fallback attribute must be a direct child of ${config.tagNames.router}.`);
577
+ }
578
+ if (routerNode.fallbackRoute) {
579
+ raiseError(`${config.tagNames.router} can have only one fallback route.`);
580
+ }
581
+ routerNode.fallbackRoute = this;
582
+ }
583
+ // 親CoreをCoreに設定
584
+ if (routeParentNode) {
585
+ this._core.parentCore = routeParentNode._core;
586
+ }
587
+ // Coreでパス解析
588
+ this._core.parsePath(path, {
589
+ isIndex,
590
+ isFallback,
591
+ hasGuard: this.hasAttribute('guard'),
592
+ guardFallback: this.getAttribute('guard'),
593
+ name: this.getAttribute('name') || '',
594
+ });
595
+ this.setAttribute('fullpath', this.absolutePath);
470
596
  }
471
597
  }
472
598
 
@@ -975,11 +1101,13 @@ function hideRoute(route) {
975
1101
  }
976
1102
 
977
1103
  function showRoute(route, matchResult) {
978
- route.clearParams();
1104
+ const params = {};
1105
+ const typedParams = {};
979
1106
  for (const key of route.paramNames) {
980
- route.params[key] = matchResult.params[key];
981
- route.typedParams[key] = matchResult.typedParams[key];
1107
+ params[key] = matchResult.params[key];
1108
+ typedParams[key] = matchResult.typedParams[key];
982
1109
  }
1110
+ route.setParams(params, typedParams);
983
1111
  const parentNode = route.placeHolder.parentNode;
984
1112
  const nextSibling = route.placeHolder.nextSibling;
985
1113
  for (const node of route.childNodeArray) {
@@ -1678,5 +1806,5 @@ function bootstrapRouter(config) {
1678
1806
  registerComponents();
1679
1807
  }
1680
1808
 
1681
- export { bootstrapRouter };
1809
+ export { RouteCore, bootstrapRouter };
1682
1810
  //# sourceMappingURL=index.esm.js.map