@wcstack/router 1.8.5 → 1.9.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/README.ja.md CHANGED
@@ -115,7 +115,7 @@
115
115
 
116
116
  ### Router(wcs-router)
117
117
 
118
- 子要素のtemplateタグ内にルーティング・レイアウトスロット定義する。ドキュメント内で1つのみ存在可能。直下にtemplateタグが必要。定義に従って、`<wcs-outlet>`へ出力する
118
+ 子要素のtemplateタグ内にルーティング・レイアウトスロット定義する。直下にtemplateタグが必要。定義に従って、`<wcs-outlet>`へ出力する。`basename` が異なれば、同一ドキュメント内に複数の Router を共存可能
119
119
 
120
120
  | 属性 | 説明 |
121
121
  |------|------|
package/README.md CHANGED
@@ -115,7 +115,7 @@ That's what `<wcs-router>`, `<wcs-route>`, and friends explore. One CDN import,
115
115
 
116
116
  ### Router (wcs-router)
117
117
 
118
- Define routes and layout slots inside a child template tag. Only one can exist in a document. A direct child template tag is required. Outputs according to definitions to `<wcs-outlet>`.
118
+ Define routes and layout slots inside a child template tag. A direct child template tag is required. Outputs according to definitions to `<wcs-outlet>`. Multiple routers can coexist in the same document when each has a distinct `basename`.
119
119
 
120
120
  | Attribute | Description |
121
121
  |------|------|
package/dist/index.d.ts CHANGED
@@ -121,7 +121,6 @@ interface IOutlet {
121
121
  */
122
122
  declare class Router extends HTMLElement implements IRouter {
123
123
  static wcBindable: IWcBindable;
124
- private static _instance;
125
124
  private _outlet;
126
125
  private _template;
127
126
  private _routeChildNodes;
@@ -150,8 +149,6 @@ declare class Router extends HTMLElement implements IRouter {
150
149
  private _joinInternalPath;
151
150
  private _notifyLocationChange;
152
151
  private _getBasename;
153
- static get instance(): IRouter;
154
- static navigate(path: string): void;
155
152
  get basename(): string;
156
153
  private _getOutlet;
157
154
  private _getTemplate;
@@ -171,6 +168,11 @@ declare class Router extends HTMLElement implements IRouter {
171
168
  get navigateUrl(): string | null;
172
169
  set navigateUrl(value: string | null);
173
170
  navigate(path: string): Promise<void>;
171
+ /**
172
+ * basename 配下の URL かどうかを判定する。
173
+ * basename が空の場合はすべての URL にマッチする。
174
+ */
175
+ private _isOwnPath;
174
176
  private _onNavigateFunc;
175
177
  private _onNavigate;
176
178
  private _onPopState;
package/dist/index.esm.js CHANGED
@@ -1307,7 +1307,6 @@ class Router extends HTMLElement {
1307
1307
  { name: "path", event: "wcs-router:path-changed" },
1308
1308
  ],
1309
1309
  };
1310
- static _instance = null;
1311
1310
  _outlet = null;
1312
1311
  _template = null;
1313
1312
  _routeChildNodes = [];
@@ -1319,10 +1318,6 @@ class Router extends HTMLElement {
1319
1318
  _navigateUrl = null;
1320
1319
  constructor() {
1321
1320
  super();
1322
- if (Router._instance) {
1323
- raiseError(`${config.tagNames.router} can only be instantiated once.`);
1324
- }
1325
- Router._instance = this;
1326
1321
  }
1327
1322
  /**
1328
1323
  * Normalize a URL pathname to a route path.
@@ -1396,22 +1391,21 @@ class Router extends HTMLElement {
1396
1391
  }
1397
1392
  return this._normalizeBasename(path);
1398
1393
  }
1399
- static get instance() {
1400
- if (!Router._instance) {
1401
- raiseError(`${config.tagNames.router} has not been instantiated.`);
1402
- }
1403
- return Router._instance;
1404
- }
1405
- static navigate(path) {
1406
- Router.instance.navigate(path);
1407
- }
1408
1394
  get basename() {
1409
1395
  return this._basename;
1410
1396
  }
1411
1397
  _getOutlet() {
1412
- let outlet = document.querySelector(config.tagNames.outlet);
1413
- if (!outlet) {
1414
- outlet = createOutlet();
1398
+ // 自身を起点に兄弟・子孫から Outlet を探す(マルチ Router 対応)
1399
+ const next = this.nextElementSibling;
1400
+ if (next && next.matches(config.tagNames.outlet)) {
1401
+ return next;
1402
+ }
1403
+ // なければ新規作成して自身の直後に挿入
1404
+ const outlet = createOutlet();
1405
+ if (this.parentNode) {
1406
+ this.parentNode.insertBefore(outlet, this.nextSibling);
1407
+ }
1408
+ else {
1415
1409
  document.body.appendChild(outlet);
1416
1410
  }
1417
1411
  return outlet;
@@ -1487,17 +1481,29 @@ class Router extends HTMLElement {
1487
1481
  this._notifyLocationChange();
1488
1482
  }
1489
1483
  }
1484
+ /**
1485
+ * basename 配下の URL かどうかを判定する。
1486
+ * basename が空の場合はすべての URL にマッチする。
1487
+ */
1488
+ _isOwnPath(fullPath) {
1489
+ if (this._basename === "")
1490
+ return true;
1491
+ return fullPath === this._basename || fullPath.startsWith(this._basename + "/");
1492
+ }
1490
1493
  _onNavigateFunc(navEvent) {
1491
1494
  if (!navEvent.canIntercept ||
1492
1495
  navEvent.hashChange ||
1493
1496
  navEvent.downloadRequest !== null) {
1494
1497
  return;
1495
1498
  }
1499
+ const url = new URL(navEvent.destination.url);
1500
+ const fullPath = this._normalizePathname(url.pathname);
1501
+ // basename 配下でない URL は無視(マルチ Router 対応)
1502
+ if (!this._isOwnPath(fullPath))
1503
+ return;
1496
1504
  const routesNode = this;
1497
1505
  navEvent.intercept({
1498
1506
  handler: async () => {
1499
- const url = new URL(navEvent.destination.url);
1500
- const fullPath = routesNode._normalizePathname(url.pathname);
1501
1507
  await applyRoute(routesNode, routesNode.outlet, fullPath, routesNode.path);
1502
1508
  },
1503
1509
  });
@@ -1506,6 +1512,9 @@ class Router extends HTMLElement {
1506
1512
  _onPopState = async () => {
1507
1513
  // back/forward for environments without Navigation API
1508
1514
  const fullPath = this._normalizePathname(window.location.pathname);
1515
+ // basename 配下でない URL は無視(マルチ Router 対応)
1516
+ if (!this._isOwnPath(fullPath))
1517
+ return;
1509
1518
  await applyRoute(this, this.outlet, fullPath, this._path);
1510
1519
  this._notifyLocationChange();
1511
1520
  };
@@ -1549,9 +1558,6 @@ class Router extends HTMLElement {
1549
1558
  window.removeEventListener("popstate", this._onPopState);
1550
1559
  this._listeningPopState = false;
1551
1560
  }
1552
- if (Router._instance === this) {
1553
- Router._instance = null;
1554
- }
1555
1561
  }
1556
1562
  }
1557
1563
 
@@ -1576,9 +1582,16 @@ class Link extends HTMLElement {
1576
1582
  if (this._router) {
1577
1583
  return this._router;
1578
1584
  }
1579
- const router = document.querySelector(config.tagNames.router);
1580
- if (router) {
1581
- return (this._router = router);
1585
+ // DOM 祖先走査で最寄りの Router を探す(マルチ Router 対応)
1586
+ const ancestor = this.closest(config.tagNames.router);
1587
+ if (ancestor) {
1588
+ return (this._router = ancestor);
1589
+ }
1590
+ // 祖先にない場合は ownerDocument 内の Router を探す
1591
+ const root = this.getRootNode();
1592
+ const found = root.querySelector?.(config.tagNames.router);
1593
+ if (found) {
1594
+ return (this._router = found);
1582
1595
  }
1583
1596
  raiseError(`${config.tagNames.link} is not connected to a router.`);
1584
1597
  }