@wcstack/router 2.1.1 → 2.3.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 +88 -7
- package/README.md +88 -7
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +86 -9
- package/dist/index.esm.js +227 -48
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -113,6 +113,36 @@
|
|
|
113
113
|
※<main-header><main-body><main-dashboard><product-list><product-item><admin-header><admin-body><error-404>はアプリ側のカスタムコンポーネント
|
|
114
114
|
※上記カスタム要素は、オートローダーやコードによる定義が別途必要
|
|
115
115
|
|
|
116
|
+
## ルートの本文はどこに置くか
|
|
117
|
+
|
|
118
|
+
1.32 以降は 2 つの形がどちらも動きますが、交換可能ではありません。プロジェクト単位でなく、ページごとに選びます。
|
|
119
|
+
|
|
120
|
+
**既定: 本文を `<wcs-route>` の中に置く。** router が入場時にスタンプし、退場時に取り除きます。中の `data-wcs` バインディングはスタンプ時に束ねられる(binder プロトコル)ので、state が描画するマークアップ —— `for:`・`if:`・テキスト —— も他の場所と同じように動きます。router が提供するものはすべてルート本文を基準にしています: ルートごとの `<wcs-head>`、`focus="heading"` / `announce=` ポリシー(*スタンプされた内容の中の*見出しを探す)、ルート切替ごとのビュートランジション。
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<wcs-route path="/products/:productId(int)">
|
|
124
|
+
<wcs-head><title data-wcs="textContent: product.name"></title></wcs-head>
|
|
125
|
+
<h2 data-wcs="textContent: product.name"></h2>
|
|
126
|
+
<template data-wcs="for: product.variants"><li data-wcs="textContent: .name"></li></template>
|
|
127
|
+
</wcs-route>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**例外: DOM をナビゲーションより長生きさせたいときは state で切り替える(`<template data-wcs="if: …">`)。** スタンプは破棄を伴います —— ルート本文は入場のたびに作り直されます —— ので、再生中の `<video>`、入力途中のフォーム、スクロール済みのリスト、描画済みの `<canvas>` は、離れて戻ってくると生き残りません。そうした内容は router の外に置き、router の `routeName` / `typedParams` 出力が立てる state のフラグにバインドし、ルート要素は空(または `<wcs-head>` だけ)にします。
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<wcs-router data-wcs="routeName: routeName">
|
|
134
|
+
<template>
|
|
135
|
+
<wcs-route path="/editor" name="editor"><wcs-head><title>Editor</title></wcs-head></wcs-route>
|
|
136
|
+
<wcs-route path="/help" name="help"><wcs-head><title>Help</title></wcs-head></wcs-route>
|
|
137
|
+
</template>
|
|
138
|
+
</wcs-router>
|
|
139
|
+
<template data-wcs="if: isEditor">
|
|
140
|
+
<my-editor></my-editor> <!-- /editor → /help → /editor をまたいで DOM を保つ -->
|
|
141
|
+
</template>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
例外の代償: `focus="heading"` は空のルート本文に見出しを見つけられず、ブラウザ既定のリセットにフォールバックします(`announce=` は `document.title` を読むので動きます)。ページに `<wcs-view-transition>` があれば、ビュートランジションはルート切替ではなく state の分岐更新を包みます。`examples/router-spa` は両方を示しています: About ページが既定の形、商品ページが例外の形です。
|
|
145
|
+
|
|
116
146
|
## リファレンス
|
|
117
147
|
|
|
118
148
|
### Router(wcs-router)
|
|
@@ -131,7 +161,8 @@
|
|
|
131
161
|
|
|
132
162
|
```html
|
|
133
163
|
<wcs-router data-wcs="path: path; typedParams: routeParams; searchParams: query;
|
|
134
|
-
routeName: routeName;
|
|
164
|
+
routeName: routeName; data: routeData;
|
|
165
|
+
navigateUrl: navigateUrl; replaceUrl: replaceUrl">
|
|
135
166
|
```
|
|
136
167
|
|
|
137
168
|
| メンバー | 方向 | 説明 |
|
|
@@ -141,6 +172,7 @@
|
|
|
141
172
|
| `typedParams` | output のみ | 同パラメータの型変換済み値(`:id(int)` → `number`)。イベントは `params` と共有(detail は `{ params, typedParams }`) |
|
|
142
173
|
| `searchParams` | output のみ | 現在 URL のクエリ(`Record<string, string>`)。キー重複(`?tag=a&tag=b`)は **last-wins**、デコードは `URLSearchParams` に委ねる(`+` → 空白を含む)。クエリ無しは `{}`。`wcs-router:search-changed` を発火 |
|
|
143
174
|
| `routeName` | output のみ | 最深マッチルートの `name` 属性値。fallback マッチ時は fallback ルートの `name`(404 画面も `routeName` 分岐で書ける)。無名・初期化前は `""`。`wcs-router:route-name-changed` を発火 |
|
|
175
|
+
| `data` | output のみ | 現在のナビゲーションの guard 関数がロードしたデータ — `true` の代わりにオブジェクトを返した guard の返り値([ルート commit 前のデータロード](#ルート-commit-前のデータロード) 参照)。どの guard もオブジェクトを返さなければ `null`(初期化前も `null`)。クエリのみの遷移(same-match)では前の値を保つ。上の各メンバーと違い **frozen にしない** — guard が返したオブジェクトをそのまま露出する。`wcs-router:data-changed` を発火 |
|
|
144
176
|
| `navigateUrl` | 書き込み面(null-idle transient) | ターゲットを書くと push 遷移。null は待機、文字列の書き込みで `navigate()` が起動し、完了後に自分で null へ戻る。null / `""` の書き込みは no-op |
|
|
145
177
|
| `replaceUrl` | 書き込み面(null-idle transient) | `navigateUrl` と完全同型の契約。ただし現在の履歴エントリを**置き換える** |
|
|
146
178
|
| `basename` | input | `basename` 属性のミラー |
|
|
@@ -149,9 +181,9 @@
|
|
|
149
181
|
|
|
150
182
|
output-only メンバーはバインド attach 時に**読まれ**、以後は変更イベントで流れる — 値は「読むもの」であり「待つもの」ではないので、router が最初のルートを解決した後に attach したバインドでも取りこぼしはない。
|
|
151
183
|
|
|
152
|
-
**発火規範**: commit されたナビゲーションでは、router はまず**全内部値をコミット**し、その後で `params-changed` → `route-name-changed` → `search-changed` → `path-changed`
|
|
184
|
+
**発火規範**: commit されたナビゲーションでは、router はまず**全内部値をコミット**し、その後で `data-changed` → `params-changed` → `route-name-changed` → `search-changed` → `path-changed` の順に、値が実際に変化したものだけを発火する(`data` は同一性比較 — guard は遷移ごとに新しいオブジェクトを返す)。どのイベントのリスナーから要素プロパティを読んでも遷移後スナップショットの一貫した値が見える。`path` は最後に発火し「ナビゲーション完了」の信号を兼ねる。guard 拒否されたナビゲーションでは何も更新せず何も発火しない。
|
|
153
185
|
|
|
154
|
-
露出オブジェクトは router が所有する **frozen スナップショット**(ナビゲーションごとに新しいオブジェクト、in-place 変異なし)。変異は throw する — 自分の state
|
|
186
|
+
露出オブジェクトは router が所有する **frozen スナップショット**(ナビゲーションごとに新しいオブジェクト、in-place 変異なし)。変異は throw する — 自分の state へコピーして使うこと。唯一の例外が `data` — 所有者は返した guard であり、router はコピーも freeze もしない。
|
|
155
187
|
|
|
156
188
|
**書き込み面の使い分け**:
|
|
157
189
|
|
|
@@ -195,7 +227,26 @@ basename 結合と pathname 正規化は pathname にのみ適用され、クエ
|
|
|
195
227
|
> **`params` / `typedParams` はどこへ?** `<wcs-router>` にある — 「state バインディング(wc-bindable)」参照。パース後の route 要素は detached なコントローラであり live DOM に属さないため、`querySelector` では見つからず `data-wcs` でも結線できない。マッチ結果の観測面は router 要素である。
|
|
196
228
|
|
|
197
229
|
ガード判定関数の型:
|
|
198
|
-
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
(toPath: string, fromPath: string, context: IGuardContext) => GuardResult | Promise<GuardResult>
|
|
233
|
+
|
|
234
|
+
interface IGuardContext { // 進入先マッチの frozen スナップショット —
|
|
235
|
+
params: Record<string, string>; // <wcs-router> が commit 後に露出するのと同じ語彙を
|
|
236
|
+
typedParams: Record<string, any>;// commit **前** に読める
|
|
237
|
+
searchParams: Record<string, string>;
|
|
238
|
+
routeName: string;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
| 返り値 | 効果 |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `true` | 進入する |
|
|
245
|
+
| オブジェクト | 進入し、**かつ**そのオブジェクトを `<wcs-router>.data` として commit する(loader — 下記)。チェーン上の複数 guard がオブジェクトを返したときは親→子の順で浅くマージ |
|
|
246
|
+
| 空でない文字列 | キャンセルし、その絶対パスへリダイレクト — **動的**なリダイレクト先。`guard` 属性より優先 |
|
|
247
|
+
| `false`(その他の falsy: `undefined` / `null` / `""` も同じ) | キャンセルし、`guard` 属性のパスへリダイレクト |
|
|
248
|
+
|
|
249
|
+
どの形でキャンセルされても、そのナビゲーションは何も commit せず何も発火しない。
|
|
199
250
|
|
|
200
251
|
#### GuardHandler(wcs-guard-handler)
|
|
201
252
|
|
|
@@ -215,11 +266,41 @@ basename 結合と pathname 正規化は pathname にのみ適用され、クエ
|
|
|
215
266
|
```
|
|
216
267
|
|
|
217
268
|
- `guard` 属性の値はガードがキャンセルされた場合のリダイレクト先パス
|
|
218
|
-
- 判定関数が `false` を返すとナビゲーションがキャンセルされ、`guard`
|
|
219
|
-
- 判定関数は `Promise
|
|
269
|
+
- 判定関数が `false` を返すとナビゲーションがキャンセルされ、`guard` 属性のパスへ遷移。空でない文字列を返すとそのパスへ遷移(例: `` return `/login?next=${encodeURIComponent(toPath)}` ``)
|
|
270
|
+
- 判定関数は `Promise` を返すことも可能(非同期チェック対応)— 解決するまでルートは commit されない
|
|
271
|
+
- 第 3 引数に進入先マッチ(`params` / `typedParams` / `searchParams` / `routeName`)が渡る。`toPath` / `fromPath` は basename スライス後のパス
|
|
220
272
|
- `<wcs-route>` の外に配置された `<wcs-guard-handler>` は無視される
|
|
221
273
|
- `<script type="module">` がない場合、`guardHandler` は設定されない
|
|
222
274
|
- **Content-Security-Policy 下では**、ガードスクリプトは `blob:` URL 経由で評価されるため `script-src blob:` が必要。ガードはインライン専用で、`<wcs-state>` のような `src=` 退避経路は存在しない。詳細は [docs/csp.ja.md](../../docs/csp.ja.md)
|
|
275
|
+
- **`require-trusted-types-for 'script'` 下では**、`<wcs-layout>` のテンプレート展開が `wcstack` という名前の Trusted Types policy を通るため、CSP に `trusted-types wcstack;` が必要(または自前の policy を注入する。詳細は [docs/csp.ja.md](../../docs/csp.ja.md) の §7)
|
|
276
|
+
|
|
277
|
+
#### ルート commit 前のデータロード
|
|
278
|
+
|
|
279
|
+
guard は、ルート本文が差し替わる**前**に走る唯一の非同期ステップである。guard から**オブジェクト**を返すと、router はそれを params と一緒に `<wcs-router>.data` として commit する — 新しいルートが空の本文で見えることがなくなり、`<wcs-view-transition>` も空フレームではなく実際の内容をアニメーションする。
|
|
280
|
+
|
|
281
|
+
```html
|
|
282
|
+
<wcs-router data-wcs="data: routeData; typedParams: routeParams">
|
|
283
|
+
<wcs-route path="/users/:id(int)" name="user" guard="/users">
|
|
284
|
+
<wcs-guard-handler>
|
|
285
|
+
<script type="module">
|
|
286
|
+
export default async function (toPath, fromPath, { typedParams }) {
|
|
287
|
+
const res = await fetch(`/api/users/${typedParams.id}`);
|
|
288
|
+
if (res.status === 404) return "/users"; // 動的リダイレクト
|
|
289
|
+
if (!res.ok) return false; // → guard="/users"
|
|
290
|
+
return { user: await res.json() }; // ロード完了: 進入する
|
|
291
|
+
}
|
|
292
|
+
</script>
|
|
293
|
+
</wcs-guard-handler>
|
|
294
|
+
<h1 data-wcs="textContent: routeData.user.name"></h1>
|
|
295
|
+
</wcs-route>
|
|
296
|
+
</wcs-router>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
- guard がオブジェクトを返さないナビゲーションでは `data` は `null` になるので、ページ側で分岐できる(`<template data-wcs="if: routeData">`)
|
|
300
|
+
- ネストルート: 各 guard がオブジェクトを返してよく、親→子の順で 1 つの `data` に浅くマージされる
|
|
301
|
+
- クエリのみの遷移(same-match)では guard は再実行されず `data` は据え置き
|
|
302
|
+
- router はキャッシュしない。同じルートに別 params で遷移すれば再ロードする。再利用したいものは自分の state に持つこと
|
|
303
|
+
- SSR では guard 全般と同じ規則 — guard 付きルートチェーンはサーバーで描かないので、`data` は常にクライアントで作られる
|
|
223
304
|
|
|
224
305
|
#### 型付きパラメータ
|
|
225
306
|
|
|
@@ -388,7 +469,7 @@ a.active { font-weight: bold; color: blue; }
|
|
|
388
469
|
</wcs-route>
|
|
389
470
|
```
|
|
390
471
|
|
|
391
|
-
|
|
472
|
+
定義済みの要素(組み込み要素、およびルート描画前に定義されたカスタム要素)では、パラメータは `connectedCallback` の発火前に割り当てられる。未定義のカスタム要素の場合、割り当ては `customElements.whenDefined()` の解決後まで遅延される。この解決はアップグレードの*後*に起こるため、アップグレード時に走る `connectedCallback` ではパラメータはまだ見えない。`props` / `states` / 属性の setter(または `attributeChangedCallback`)で受け取るか、ルート描画前に要素を定義しておく(例: `@wcstack/router` より先にスクリプトを読み込む)こと。
|
|
392
473
|
|
|
393
474
|
## 設定
|
|
394
475
|
|
package/README.md
CHANGED
|
@@ -113,6 +113,36 @@ That's what `<wcs-router>`, `<wcs-route>`, and friends explore. One CDN import,
|
|
|
113
113
|
* <main-header><main-body><main-dashboard><product-list><product-item><admin-header><admin-body><error-404> are custom components in your app.
|
|
114
114
|
* The custom elements above must be defined separately (via an autoloader or manual registration).
|
|
115
115
|
|
|
116
|
+
## Where route content lives
|
|
117
|
+
|
|
118
|
+
Two shapes work since 1.32, and they are not interchangeable. Pick per page, not per project.
|
|
119
|
+
|
|
120
|
+
**Default: put the content inside `<wcs-route>`.** The router stamps it on entry and removes it on exit. `data-wcs` bindings inside it are bound when it is stamped (the binder protocol), so state-rendered markup — `for:`, `if:`, text — works there like anywhere else. Everything the router offers is keyed to the route body: `<wcs-head>` per route, the `focus="heading"` / `announce=` policies (they look for the heading *in the stamped content*), and a view transition per route swap.
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<wcs-route path="/products/:productId(int)">
|
|
124
|
+
<wcs-head><title data-wcs="textContent: product.name"></title></wcs-head>
|
|
125
|
+
<h2 data-wcs="textContent: product.name"></h2>
|
|
126
|
+
<template data-wcs="for: product.variants"><li data-wcs="textContent: .name"></li></template>
|
|
127
|
+
</wcs-route>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Exception: switch it with state (`<template data-wcs="if: …">`) when the DOM must outlive the navigation.** Stamping is a teardown — a route body is rebuilt on every entry — so a `<video>` mid-playback, a half-filled form, a scrolled list, or a `<canvas>` you drew on does not survive leaving and coming back. Keep such content outside the router, bound to a state flag that the router's `routeName` / `typedParams` outputs set, and leave the route element empty (or holding only `<wcs-head>`).
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<wcs-router data-wcs="routeName: routeName">
|
|
134
|
+
<template>
|
|
135
|
+
<wcs-route path="/editor" name="editor"><wcs-head><title>Editor</title></wcs-head></wcs-route>
|
|
136
|
+
<wcs-route path="/help" name="help"><wcs-head><title>Help</title></wcs-head></wcs-route>
|
|
137
|
+
</template>
|
|
138
|
+
</wcs-router>
|
|
139
|
+
<template data-wcs="if: isEditor">
|
|
140
|
+
<my-editor></my-editor> <!-- keeps its DOM across /editor → /help → /editor -->
|
|
141
|
+
</template>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
What the exception costs: `focus="heading"` finds no heading in an empty route body and falls back to the browser-default reset (`announce=` still works — it reads `document.title`), and a view transition, if `<wcs-view-transition>` is on the page, wraps the state branch update rather than the route swap. `examples/router-spa` shows both: its About page is the default shape, its product pages are the exception.
|
|
145
|
+
|
|
116
146
|
## Reference
|
|
117
147
|
|
|
118
148
|
### Router (wcs-router)
|
|
@@ -131,7 +161,8 @@ Define routes and layout slots inside a child template tag. A direct child templ
|
|
|
131
161
|
|
|
132
162
|
```html
|
|
133
163
|
<wcs-router data-wcs="path: path; typedParams: routeParams; searchParams: query;
|
|
134
|
-
routeName: routeName;
|
|
164
|
+
routeName: routeName; data: routeData;
|
|
165
|
+
navigateUrl: navigateUrl; replaceUrl: replaceUrl">
|
|
135
166
|
```
|
|
136
167
|
|
|
137
168
|
| Member | Direction | Description |
|
|
@@ -141,6 +172,7 @@ Define routes and layout slots inside a child template tag. A direct child templ
|
|
|
141
172
|
| `typedParams` | output only | The same params, type-converted (`:id(int)` → `number`). Shares `wcs-router:params-changed` with `params` (the event detail is `{ params, typedParams }`). |
|
|
142
173
|
| `searchParams` | output only | Current URL query as `Record<string, string>`. Duplicate keys (`?tag=a&tag=b`) are **last-wins**; values are decoded by `URLSearchParams` (including `+` → space). `{}` when there is no query. Fires `wcs-router:search-changed`. |
|
|
143
174
|
| `routeName` | output only | `name` attribute of the deepest matched route. On a fallback match, the fallback route's `name` (so a 404 view can key off `routeName` too). `""` when unnamed or before initialization. Fires `wcs-router:route-name-changed`. |
|
|
175
|
+
| `data` | output only | Data the current navigation's guard functions loaded — a guard that returns an object instead of `true` (see [Loading data before a route commits](#loading-data-before-a-route-commits)). `null` when no guard returned data, including before initialization; a query-only (same-match) navigation keeps the previous value. Unlike the members above it is **not** frozen: it is the object your guard returned, passed through as-is. Fires `wcs-router:data-changed`. |
|
|
144
176
|
| `navigateUrl` | write surface (null-idle transient) | Write a target to push-navigate. `null` means idle; writing a string starts `navigate()`, and the property resets itself to `null` when the navigation finishes. `null` / `""` writes are no-ops. |
|
|
145
177
|
| `replaceUrl` | write surface (null-idle transient) | Identical contract to `navigateUrl`, but the navigation **replaces** the current history entry. |
|
|
146
178
|
| `basename` | input | Mirrors the `basename` attribute. |
|
|
@@ -149,9 +181,9 @@ Commands `navigate(path)` and `replace(path)` (both async) are also declared, so
|
|
|
149
181
|
|
|
150
182
|
Output-only members are **read** when a binding attaches and streamed through their change events afterwards — the value is read, not awaited, so a binding that attaches after the router already resolved its first route misses nothing.
|
|
151
183
|
|
|
152
|
-
**Firing contract**: on a committed navigation the router commits *all* internal values first and only then fires events, in the order `params-changed` → `route-name-changed` → `search-changed` → `path-changed`, each only when its value actually changed. Any listener that reads the element's properties sees the consistent post-navigation snapshot; `path` fires last and doubles as the "navigation finished" signal. A guard-rejected navigation updates nothing and fires nothing.
|
|
184
|
+
**Firing contract**: on a committed navigation the router commits *all* internal values first and only then fires events, in the order `data-changed` → `params-changed` → `route-name-changed` → `search-changed` → `path-changed`, each only when its value actually changed (`data` compares by identity — a guard returns a fresh object per navigation). Any listener that reads the element's properties sees the consistent post-navigation snapshot; `path` fires last and doubles as the "navigation finished" signal. A guard-rejected navigation updates nothing and fires nothing.
|
|
153
185
|
|
|
154
|
-
The exposed objects are **frozen snapshots** owned by the router: a new object per navigation, never mutated in place. Mutating them throws — copy into your own state instead.
|
|
186
|
+
The exposed objects are **frozen snapshots** owned by the router: a new object per navigation, never mutated in place. Mutating them throws — copy into your own state instead. `data` is the one exception — it is owned by the guard that returned it, and the router neither copies nor freezes it.
|
|
155
187
|
|
|
156
188
|
**Choosing a write surface**:
|
|
157
189
|
|
|
@@ -195,7 +227,26 @@ Displays children when the route path matches. Match priority is static paths ov
|
|
|
195
227
|
> **Where are `params` / `typedParams`?** On `<wcs-router>` — see "State binding (wc-bindable)". After parsing, the route elements are detached controllers: they are not part of the live DOM, so they cannot be found with `querySelector` and cannot be bound with `data-wcs`. The router element is the observation surface for match results.
|
|
196
228
|
|
|
197
229
|
Guard decision function type:
|
|
198
|
-
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
(toPath: string, fromPath: string, context: IGuardContext) => GuardResult | Promise<GuardResult>
|
|
233
|
+
|
|
234
|
+
interface IGuardContext { // frozen snapshot of the match being entered — the same
|
|
235
|
+
params: Record<string, string>; // vocabulary <wcs-router> exposes after the commit,
|
|
236
|
+
typedParams: Record<string, any>;// readable *before* it
|
|
237
|
+
searchParams: Record<string, string>;
|
|
238
|
+
routeName: string;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
| Return value | Effect |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `true` | Enter the route. |
|
|
245
|
+
| an object | Enter the route **and** commit the object as `<wcs-router>.data` (a loader — see below). When several guards on the chain return objects they are shallow-merged parent → child. |
|
|
246
|
+
| a non-empty string | Cancel and redirect to that absolute path — a **dynamic** redirect target. It wins over the `guard` attribute. |
|
|
247
|
+
| `false` (or any other falsy value: `undefined`, `null`, `""`) | Cancel and redirect to the path in the `guard` attribute. |
|
|
248
|
+
|
|
249
|
+
A cancelled navigation commits nothing and fires nothing, whichever way it was cancelled.
|
|
199
250
|
|
|
200
251
|
#### GuardHandler (wcs-guard-handler)
|
|
201
252
|
|
|
@@ -215,11 +266,41 @@ Place as a child of `<wcs-route>` to declaratively define a guard decision funct
|
|
|
215
266
|
```
|
|
216
267
|
|
|
217
268
|
- The `guard` attribute value is the redirect path when the guard cancels navigation
|
|
218
|
-
- If the function returns `false`, navigation is cancelled and the user is redirected to the `guard` path
|
|
219
|
-
- The function can return `Promise
|
|
269
|
+
- If the function returns `false`, navigation is cancelled and the user is redirected to the `guard` path; a non-empty string redirects there instead (e.g. `` return `/login?next=${encodeURIComponent(toPath)}` ``)
|
|
270
|
+
- The function can return a `Promise` for async checks — the route is not committed until it settles
|
|
271
|
+
- The third argument carries the match being entered (`params` / `typedParams` / `searchParams` / `routeName`); `toPath` / `fromPath` are basename-sliced paths
|
|
220
272
|
- `<wcs-guard-handler>` placed outside a `<wcs-route>` is ignored
|
|
221
273
|
- If no `<script type="module">` is present, `guardHandler` is not set
|
|
222
274
|
- **Under a Content-Security-Policy**, the guard script is evaluated through a `blob:` URL, so `script-src blob:` is required. Guards are inline-only — there is no `src=` escape hatch as there is for `<wcs-state>`. See [docs/csp.md](../../docs/csp.md)
|
|
275
|
+
- **Under `require-trusted-types-for 'script'`**, `<wcs-layout>` expands its template through a Trusted Types policy named `wcstack`, so the CSP needs `trusted-types wcstack;` (or your own policy, injected as described in [docs/csp.md](../../docs/csp.md) section 7)
|
|
276
|
+
|
|
277
|
+
#### Loading data before a route commits
|
|
278
|
+
|
|
279
|
+
A guard is already the one asynchronous step that runs *before* the route content is swapped in. Return an **object** from it and the router commits that object as `<wcs-router>.data` together with the params — so the page never shows the new route with empty content, and a `<wcs-view-transition>` animates real content instead of a blank frame.
|
|
280
|
+
|
|
281
|
+
```html
|
|
282
|
+
<wcs-router data-wcs="data: routeData; typedParams: routeParams">
|
|
283
|
+
<wcs-route path="/users/:id(int)" name="user" guard="/users">
|
|
284
|
+
<wcs-guard-handler>
|
|
285
|
+
<script type="module">
|
|
286
|
+
export default async function (toPath, fromPath, { typedParams }) {
|
|
287
|
+
const res = await fetch(`/api/users/${typedParams.id}`);
|
|
288
|
+
if (res.status === 404) return "/users"; // dynamic redirect
|
|
289
|
+
if (!res.ok) return false; // → guard="/users"
|
|
290
|
+
return { user: await res.json() }; // loaded: enter the route
|
|
291
|
+
}
|
|
292
|
+
</script>
|
|
293
|
+
</wcs-guard-handler>
|
|
294
|
+
<h1 data-wcs="textContent: routeData.user.name"></h1>
|
|
295
|
+
</wcs-route>
|
|
296
|
+
</wcs-router>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
- `data` is `null` on every navigation whose guards return no object, so a page can gate on it (`<template data-wcs="if: routeData">`).
|
|
300
|
+
- Nested routes: each guard may return an object; they are shallow-merged parent → child into one `data`.
|
|
301
|
+
- A query-only navigation (same-match) does not re-run guards and keeps `data`.
|
|
302
|
+
- The router does not cache. Navigating to the same route with other params loads again; keep anything you want to reuse in your own state.
|
|
303
|
+
- Under SSR the same rule as every guard applies: a route chain with a guard is not rendered on the server, so `data` is always produced on the client.
|
|
223
304
|
|
|
224
305
|
#### Typed Parameters
|
|
225
306
|
|
|
@@ -388,7 +469,7 @@ Elements with the `data-bind` attribute automatically receive matched route para
|
|
|
388
469
|
</wcs-route>
|
|
389
470
|
```
|
|
390
471
|
|
|
391
|
-
|
|
472
|
+
For elements that are already defined (built-in elements and custom elements defined before the route renders), parameters are assigned before `connectedCallback` fires. For custom elements that are not yet defined, assignment is deferred until `customElements.whenDefined()` resolves — that happens *after* the upgrade, so the `connectedCallback` run by the upgrade does not see the parameters yet. Read them in the `props` / `states` / attribute setter (or in `attributeChangedCallback`) instead, or make sure the element is defined before the route is rendered (e.g. load its script before `@wcstack/router`).
|
|
392
473
|
|
|
393
474
|
## Configuration
|
|
394
475
|
|
package/dist/auto.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const t={tagNames:{route:"wcs-route",router:"wcs-router",outlet:"wcs-outlet",layout:"wcs-layout",layoutOutlet:"wcs-layout-outlet",link:"wcs-link",head:"wcs-head",guardHandler:"wcs-guard-handler"},enableShadowRoot:!1,basenameFileExtensions:[".html"]};function e(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){const e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)})}function a(t,e){if(e&&"cause"in e)throw new Error(`[@wcstack/router] ${t}`,{cause:e.cause});throw new Error(`[@wcstack/router] ${t}`)}class n extends Error{fallbackPath;constructor(t,e){super(t),this.fallbackPath=e}}const s={int:{typeName:"int",pattern:/^-?\d+$/,parse(t){if(this.pattern.test(t))return parseInt(t,10)}},float:{typeName:"float",pattern:/^-?\d+(?:\.\d+)?$/,parse(t){if(this.pattern.test(t))return parseFloat(t)}},bool:{typeName:"bool",pattern:/^(true|false|0|1)$/,parse(t){if(this.pattern.test(t))return"true"===t||"1"===t}},uuid:{typeName:"uuid",pattern:/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,parse(t){if(this.pattern.test(t))return t}},slug:{typeName:"slug",pattern:/^[a-z0-9]+(?:-[a-z0-9]+)*$/,parse(t){if(this.pattern.test(t))return t}},isoDate:{typeName:"isoDate",pattern:/^\d{4}-\d{2}-\d{2}$/,parse(t){if(!this.pattern.test(t))return;const[e,a,n]=t.split("-").map(Number),s=new Date(e,a-1,n);return s.getFullYear()===e&&s.getMonth()===a-1&&s.getDate()===n?s:void 0}},any:{typeName:"any",pattern:/^.+$/,parse(t){if(this.pattern.test(t))return t}}},i={static:2,param:1,"catch-all":0};class r extends EventTarget{_target;_parentCore=null;_path="";_name="";_isFallbackRoute=!1;_segmentInfos=[];_absoluteSegmentInfos;_paramNames;_absoluteParamNames;_weight;_absoluteWeight;_segmentCount;_params={};_typedParams={};_active=!1;_hasGuard=!1;_guardHandler=null;_guardFallbackPath="";_waitForSetGuardHandler=null;_resolveSetGuardHandler=null;_guardHandlerLoadFailed=!1;constructor(t){super(),this._target=t??this}get parentCore(){return this._parentCore}set parentCore(t){this._parentCore=t}get path(){return this._path}get name(){return this._name}get isFallbackRoute(){return this._isFallbackRoute}get isRelative(){return!this._path.startsWith("/")}get segmentInfos(){return this._segmentInfos}_checkParentCore(e,n){return this._isFallbackRoute||(this.isRelative&&!this._parentCore&&a(`${t.tagNames.route} is relative but has no parent route.`),!this.isRelative&&this._parentCore&&a(`${t.tagNames.route} is absolute but has a parent route.`)),this.isRelative&&this._parentCore?e(this._parentCore):n()}get absolutePath(){return this._checkParentCore(t=>{const e=t.absolutePath;return e.endsWith("/")?e+this._path:e+"/"+this._path},()=>this._path)}get absoluteSegmentInfos(){return void 0===this._absoluteSegmentInfos&&(this._absoluteSegmentInfos=this._checkParentCore(t=>[...t.absoluteSegmentInfos,...this._segmentInfos],()=>[...this._segmentInfos])),this._absoluteSegmentInfos}get params(){return this._params}get typedParams(){return this._typedParams}get active(){return this._active}get paramNames(){if(void 0===this._paramNames){const t=[];for(const e of this._segmentInfos)e.paramName&&t.push(e.paramName);this._paramNames=t}return this._paramNames}get absoluteParamNames(){return void 0===this._absoluteParamNames&&(this._absoluteParamNames=this._checkParentCore(t=>[...t.absoluteParamNames,...this.paramNames],()=>[...this.paramNames])),this._absoluteParamNames}get weight(){if(void 0===this._weight){let t=0;for(const e of this._segmentInfos)t+=i[e.type];this._weight=t}return this._weight}get absoluteWeight(){return void 0===this._absoluteWeight&&(this._absoluteWeight=this._checkParentCore(t=>t.absoluteWeight+this.weight,()=>this.weight)),this._absoluteWeight}get segmentCount(){if(void 0===this._segmentCount){let t=0;for(const e of this._segmentInfos)"catch-all"!==e.type&&t++;this._segmentCount=""===this._path?0:t}return this._segmentCount}get absoluteSegmentCount(){return this._checkParentCore(t=>t.absoluteSegmentCount+this.segmentCount,()=>this.segmentCount)}parsePath(t,e={}){this._segmentInfos=[],this._absoluteSegmentInfos=void 0,this._paramNames=void 0,this._absoluteParamNames=void 0,this._weight=void 0,this._absoluteWeight=void 0,this._segmentCount=void 0,this._path=t,this._name=e.name||"",this._isFallbackRoute=e.isFallback||!1,e.isIndex&&this._segmentInfos.push({type:"static",segmentText:"",paramName:null,pattern:/^$/,isIndex:!0});const a=t.split("/");for(let t=0;t<a.length;t++){const n=a[t];if(!(""===n&&t===a.length-1&&t>0)){if("*"===n){this._segmentInfos.push({type:"catch-all",segmentText:n,paramName:"*",pattern:new RegExp("^(.*)$")});break}if(n.startsWith(":")){const t=n.match(/^:([^()]+)(\(([^)]+)\))?$/);let e,a="any";t?(e=t[1],t[3]&&Object.keys(s).includes(t[3])&&(a=t[3])):e=n.substring(1),this._segmentInfos.push({type:"param",segmentText:n,paramName:e,pattern:new RegExp("^([^\\/]+)$"),paramType:a})}else""===n&&e.isIndex||this._segmentInfos.push({type:"static",segmentText:n,paramName:null,pattern:new RegExp(`^${n}$`)})}}this._hasGuard=e.hasGuard||!1,this._hasGuard&&(this._guardFallbackPath=e.guardFallback||"/",this._waitForSetGuardHandler=new Promise(t=>{this._resolveSetGuardHandler=t}))}setParams(t,e){const a=this._active;this._params=t,this._typedParams=e,this._active=!0,this._target.dispatchEvent(new CustomEvent("wcs-route:params-changed",{detail:{params:t,typedParams:e},bubbles:!0})),a||this._target.dispatchEvent(new CustomEvent("wcs-route:active-changed",{detail:!0,bubbles:!0}))}clearParams(){const t=this._active;this._params={},this._typedParams={},this._active=!1,t&&this._target.dispatchEvent(new CustomEvent("wcs-route:active-changed",{detail:!1,bubbles:!0}))}shouldChange(t){for(const e of this.paramNames)if(this._params[e]!==t[e])return!0;return!1}get hasGuard(){return this._hasGuard}get guardHandler(){return this._guardHandler||a(`${t.tagNames.route} has no guardHandler.`),this._guardHandler}set guardHandler(t){this._guardHandler=t,this._resolveSetGuardHandler?.()}notifyGuardHandlerLoadFailed(){this._guardHandlerLoadFailed=!0,this._resolveSetGuardHandler?.()}async guardCheck(t){if(this._hasGuard&&this._waitForSetGuardHandler&&await this._waitForSetGuardHandler,this._guardHandler){const e=t.path,a=t.lastPath;if(!await this._guardHandler(e,a))throw new n("Navigation cancelled by guard.",this._guardFallbackPath)}else if(this._hasGuard&&this._guardHandlerLoadFailed)throw new n("Navigation cancelled: guard handler failed to load.",this._guardFallbackPath)}}class o extends HTMLElement{_core;_routeParentNode=null;_routeChildNodes=[];_routerNode=null;_uuid=e();_placeHolder=document.createComment(`@@route:${this._uuid}`);_childNodeArray;_childIndex=0;_initialized=!1;_routes;constructor(){super(),this._core=new r(this)}get routeParentNode(){return this._routeParentNode}get routeChildNodes(){return this._routeChildNodes}get routerNode(){return this._routerNode||a(`${t.tagNames.route} has no routerNode.`),this._routerNode}get uuid(){return this._uuid}get placeHolder(){return this._placeHolder}get childNodeArray(){return void 0===this._childNodeArray&&(this._childNodeArray=Array.from(this.childNodes)),this._childNodeArray}adoptChildNodes(t){for(this._childNodeArray=[...t];this.firstChild;)this.removeChild(this.firstChild)}get routes(){return void 0===this._routes&&(this._routes=this.routeParentNode?this.routeParentNode.routes.concat(this):[this]),this._routes}get childIndex(){return this._childIndex}get path(){return this._core.path}get name(){return this._core.name}get isRelative(){return this._core.isRelative}get absolutePath(){return this._core.absolutePath}get segmentInfos(){return this._core.segmentInfos}get absoluteSegmentInfos(){return this._core.absoluteSegmentInfos}get params(){return this._core.params}get typedParams(){return this._core.typedParams}get paramNames(){return this._core.paramNames}get absoluteParamNames(){return this._core.absoluteParamNames}get weight(){return this._core.weight}get absoluteWeight(){return this._core.absoluteWeight}get segmentCount(){return this._core.segmentCount}get absoluteSegmentCount(){return this._core.absoluteSegmentCount}get fullpath(){return this.absolutePath}get hasGuard(){return this._core.hasGuard}get guardHandler(){return this._core.guardHandler}set guardHandler(t){this._core.guardHandler=t}setParams(t,e){this._core.setParams(t,e)}clearParams(){this._core.clearParams()}shouldChange(t){return this._core.shouldChange(t)}async guardCheck(t){return this._core.guardCheck(t)}notifyGuardHandlerLoadFailed(){this._core.notifyGuardHandlerLoadFailed()}testAncestorNode(t){let e=this._routeParentNode;for(;e;){if(e===t)return!0;e=e.routeParentNode}return!1}initialize(e,n){if(this._initialized)return;let s;this._initialized=!0;let i=!1,r=!1;this.hasAttribute("path")?s=this.getAttribute("path")||"":this.hasAttribute("index")?(s="",i=!0):this.hasAttribute("fallback")?(s="",r=!0):a(`${t.tagNames.route} should have a "path" or "index" attribute.`),this._routerNode=e,this._routeParentNode=n;const o=n||e;o.routeChildNodes.push(this),this._childIndex=o.routeChildNodes.length-1,r&&(n&&a(`${t.tagNames.route} with fallback attribute must be a direct child of ${t.tagNames.router}.`),e.fallbackRoute&&a(`${t.tagNames.router} can have only one fallback route.`),e.fallbackRoute=this),n&&(this._core.parentCore=n._core),this._core.parsePath(s,{isIndex:i,isFallback:r,hasGuard:this.hasAttribute("guard"),guardFallback:this.getAttribute("guard"),name:this.getAttribute("name")||""}),this.setAttribute("fullpath",this.absolutePath)}}const l=new Map;class h extends HTMLElement{_uuid=e();constructor(){super()}async _loadTemplateFromSource(e){try{const n=await fetch(e);n.ok||a(`${t.tagNames.layout} failed to fetch layout from source: ${e}, status: ${n.status}`);const s=await n.text();return l.set(e,s),s}catch(n){a(`${t.tagNames.layout} failed to load layout from source: ${e}, error: ${n}`,{cause:n})}}_loadTemplateFromDocument(t){const e=document.getElementById(`${t}`);return e&&e instanceof HTMLTemplateElement?e.innerHTML:null}async loadTemplate(){const e=this.getAttribute("src"),a=this.getAttribute("layout");e&&a&&console.warn(`${t.tagNames.layout} have both "src" and "layout" attributes.`);const n=document.createElement("template");if(e)l.has(e)?n.innerHTML=l.get(e)||"":n.innerHTML=await this._loadTemplateFromSource(e)||"";else if(a){const e=this._loadTemplateFromDocument(a);e?n.innerHTML=e:console.warn(`${t.tagNames.layout} could not find template with id "${a}".`)}return n}get uuid(){return this._uuid}get enableShadowRoot(){return!!this.hasAttribute("enable-shadow-root")||!this.hasAttribute("disable-shadow-root")&&t.enableShadowRoot}get name(){return this.getAttribute("name")||""}}class c extends HTMLElement{_routesNode=null;_lastRoutes=[];_initialized=!1;constructor(){super()}get routesNode(){return this._routesNode||a(`${t.tagNames.outlet} has no routesNode.`),this._routesNode}set routesNode(t){this._routesNode=t}get rootNode(){return this.shadowRoot?this.shadowRoot:this}get lastRoutes(){return this._lastRoutes}set lastRoutes(t){this._lastRoutes=[...t]}_resolveEnableShadowRoot(){return!!this.hasAttribute("enable-shadow-root")||!this.hasAttribute("disable-shadow-root")&&t.enableShadowRoot}_initialize(){this._resolveEnableShadowRoot()&&this.attachShadow({mode:"open"}),this._initialized=!0}connectedCallback(){this._initialized||this._initialize()}disconnectedCallback(){this._lastRoutes=[]}}const u=new Set(["props","states","attr",""]);function d(t,e,a){for(const[n,s]of Object.entries(e))switch(a){case"props":t.props={...t.props,[n]:s};break;case"states":t.states={...t.states,[n]:s};break;case"attr":null==s?t.removeAttribute(n):t.setAttribute(n,String(s));break;case"":t[n]=s}}function m(t,e){t.hasAttribute("data-bind")||a(`${t.tagName} has no 'data-bind' attribute.`);const n=t.getAttribute("data-bind")||"";u.has(n)||a(`${t.tagName} has invalid 'data-bind' attribute: ${n}`);const s=n,i=function(t){const e=t.tagName.toLowerCase();if(e.includes("-"))return e;const a=t.getAttribute("is");return a&&a.includes("-")?a:null}(t);i&&void 0===customElements.get(i)?customElements.whenDefined(i).then(()=>{t.isConnected&&d(t,e,s)}).catch(()=>{a(`Failed to define custom element: ${i}`)}):d(t,e,s)}class p extends HTMLElement{_layout=null;_initialized=!1;_initializing=!1;_disconnectedDuringInit=!1;_layoutChildNodes=[];constructor(){super()}get layout(){return this._layout||a(`${t.tagNames.layoutOutlet} has no layout.`),this._layout}set layout(t){this._layout=t,this.setAttribute("name",t.name)}get name(){return this.layout.name}async _initialize(){this._initializing=!0;try{this._initialized=!0,this.layout.enableShadowRoot&&!this.shadowRoot&&this.attachShadow({mode:"open"});const e=await this.layout.loadTemplate();if(!this.isConnected)return void(this._initialized=!1);if(this.shadowRoot){this.shadowRoot.appendChild(e.content.cloneNode(!0));for(const t of Array.from(this.layout.childNodes))this._layoutChildNodes.push(t),this.appendChild(t)}else{const a=e.content.cloneNode(!0),n=new Map;a.querySelectorAll("slot").forEach(e=>{const a=e.getAttribute("name")||"";n.has(a)?console.warn(`${t.tagNames.layoutOutlet} duplicate slot name "${a}" in layout template.`):n.set(a,e)});const s=new Map,i=document.createDocumentFragment();for(const t of Array.from(this.layout.childNodes)){if(this._layoutChildNodes.push(t),t instanceof Element){const e=t.getAttribute("slot")||"";if(e.length>0&&n.has(e)){s.has(e)||s.set(e,document.createDocumentFragment()),s.get(e)?.appendChild(t);continue}}i.appendChild(t)}for(const[t,e]of n){const a=s.get(t);a&&e.replaceWith(a)}const r=n.get("");r&&r.replaceWith(i),this.appendChild(a)}}finally{this._initializing=!1}}async connectedCallback(){this._initialized||(this._disconnectedDuringInit=!1,await this._initialize(),!this._disconnectedDuringInit&&this.isConnected)}disconnectedCallback(){this._initializing&&(this._disconnectedDuringInit=!0)}assignParams(t){for(const e of this._layoutChildNodes)e instanceof Element&&(e.querySelectorAll("[data-bind]").forEach(e=>{m(e,t)}),e.hasAttribute("data-bind")&&m(e,t))}}function g(){return document.createElement(t.tagNames.layoutOutlet)}const _="https://github.com/wcstack/wcstack/blob/main/docs/csp.md";async function f(t,e){let n=null,s=null;const i=e.uuid||"",r=`\n//# sourceURL=${i?`wcs-guard-handler:${i}`:"wcs-guard-handler"}\n`,o=t.text+r;let l=!1;const h=t=>{t.effectiveDirective.startsWith("script-src")&&(l=!0)};document.addEventListener("securitypolicyviolation",h);try{if("function"==typeof URL.createObjectURL){const t=new Blob([o],{type:"application/javascript"}),e=URL.createObjectURL(t);try{n=await import(e)}catch(t){s=t}finally{URL.revokeObjectURL(e)}}if(!n){const t=btoa(String.fromCodePoint(...(new TextEncoder).encode(o)));try{n=await import(`data:application/javascript;base64,${t}`)}catch(t){a(function(t,e,a){return a?`The guard <script> was blocked by Content-Security-Policy. Guard scripts are inline-only and are evaluated through a blob: URL, so script-src must allow blob:. See ${_}`:`loadGuardHandler: failed to import guard script. data: URL error: ${t?.message??String(t)}`+(e?`. Blob URL error: ${e?.message??String(e)}`:"")+`. If this page sets a Content-Security-Policy, see ${_}`}(t,s,l),{cause:s??t})}}}finally{document.removeEventListener("securitypolicyviolation",h)}return n&&"function"==typeof n.default?n.default:null}function b(t,e){f(t,e).then(t=>{t?e.guardHandler=t:e.notifyGuardHandlerLoadFailed()}).catch(t=>{console.error("loadGuardHandler failed:",t),e.notifyGuardHandlerLoadFailed()})}function y(t,e){let a=t.get(e.absolutePath);a||(a=[]);for(const t of a)if(!e.testAncestorNode(t)){console.warn(`Duplicate route path detected: '${e.absolutePath}' (defined as '${e.path}')`);break}a.push(e),1===a.length&&t.set(e.absolutePath,a)}async function w(e,a,n,s){const i=n.length>0?n[n.length-1]:null,r=document.createDocumentFragment(),o=Array.from(a.childNodes);for(const a of o)if(a.nodeType===Node.ELEMENT_NODE){let o=a,l=a;const h=l.tagName.toLowerCase();if(h===t.tagNames.route){const t=document.createDocumentFragment();for(const e of Array.from(l.childNodes))t.appendChild(e);const a=document.importNode(l,!0);customElements.upgrade(a),a.appendChild(t);const r=a;r.initialize(e,i),y(s,r),n.push(r),o=r.placeHolder,l=r}else{if(h===t.tagNames.guardHandler){if(n.length>0){const t=n[n.length-1],e=l.querySelector('script[type="module"]');e&&b(e,t)}continue}if("template"===h){r.appendChild(l);continue}if(h===t.tagNames.layout){const t=document.createDocumentFragment();for(const e of Array.from(l.childNodes))t.appendChild(e);const e=document.importNode(l,!0);customElements.upgrade(e),e.appendChild(t);const a=e,n=g();n.layout=a,o=n,l=e}}const c=await w(e,l,n,s);l.innerHTML="",l.appendChild(c),r.appendChild(o)}else r.appendChild(a);return r}async function N(t){const e=new Map;return await w(t,t.template.content,[],e)}function v(t,e){if("title"===t.announcePolicy){const e=t.a11yRegion;null!==e&&(e.textContent=document.title)}if("heading"===t.focusPolicy){const t=function(t){for(const e of t){if(e.nodeType!==Node.ELEMENT_NODE)continue;const t=e;if(/^H[1-6]$/.test(t.tagName)){if(C(t))return t}else for(const e of t.querySelectorAll("h1,h2,h3,h4,h5,h6"))if(C(e))return e}return null}(e.routes[e.routes.length-1].childNodeArray);if(null!==t)t.hasAttribute("tabindex")||t.setAttribute("tabindex","-1"),t.focus();else{const t=document.querySelector("[autofocus]");null!==t?t.focus():document.activeElement instanceof HTMLElement&&document.activeElement.blur()}}}function C(t){return!1!==t.checkVisibility?.()}function P(t,e,a,n){const i=function(t,e,a){const n={},i={};let r=!0,o=!1,l=0,h=0;for(;l<t.absoluteSegmentInfos.length;){const e=t.absoluteSegmentInfos[l];if(e.isIndex){l++;continue}if(0===l&&""===e.segmentText&&"static"===e.type){l++;continue}const c=a[h];if(void 0===c){r=!1;break}let u=!1;if("param"===e.type){const t=e.paramType||"any",a=s[t].parse(c);void 0!==a&&(e.paramName&&(n[e.paramName]=c,i[e.paramName]=a),u=!0)}else u=null!==e.pattern.exec(c);if(!u){r=!1;break}if("catch-all"===e.type){const t=a.slice(h).join("/");n["*"]=t,i["*"]=t,o=!0;break}l++,h++}let c=!1;return r&&(o||l===t.absoluteSegmentInfos.length&&h===a.length)&&(c=!0),c?{path:e,routes:t.routes,params:n,typedParams:i,lastPath:""}:null}(t,e,a);i&&n.push(i);for(const s of t.routeChildNodes)P(s,e,a,n)}function E(t,e){const a=t.routeChildNodes,n=[],s=e.split("/"),i=s.filter((t,e)=>(0!==e||""!==t)&&!(e===s.length-1&&""===t&&s.length>1));for(const t of a)P(t,e,i,n);return n.sort((t,e)=>{const a=t.routes.at(-1),n=e.routes.at(-1),s=a.absoluteSegmentCount-n.absoluteSegmentCount;if(0!==s)return-s;const i=a.absoluteWeight-n.absoluteWeight;if(0!==i)return-i;return a.childIndex-n.childIndex}),n.length>0?n[0]:null}let A=null,k=null;function S(){const e=t.basenameFileExtensions;return 0===e.length?null:(A===e&&k||(A=e,k=new RegExp(`\\/[^/]+(?:${e.map(t=>t.replace(/\./g,"\\.")).join("|")})$`,"i")),k)}function $(t){let e=t||"/";e.startsWith("/")||(e="/"+e),e=e.replace(/\/{2,}/g,"/");const a=S();return a&&(e=e.replace(a,"")),""===e&&(e="/"),e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e}function x(t,e){let a=t;return""!==e&&(t===e?a="":t.startsWith(e+"/")&&(a=t.slice(e.length))),""===a?"/":a}function R(t){let e=t||"";if(!e)return"";e.startsWith("/")||(e="/"+e),e=e.replace(/\/{2,}/g,"/");const a=S();return a&&(e=e.replace(a,"")),e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),"/"===e?"":e}function L(t){t.clearParams();for(const e of t.childNodeArray)e.parentNode?.removeChild(e)}function H(e,a){const n={},s={};for(const t of e.paramNames)n[t]=a.params[t],s[t]=a.typedParams[t];e.setParams(n,s);for(const a of e.childNodeArray)if(a.nodeType===Node.ELEMENT_NODE){const n=a;n.querySelectorAll("[data-bind]").forEach(t=>{m(t,e.typedParams)}),n.hasAttribute("data-bind")&&m(n,e.typedParams),n.querySelectorAll(t.tagNames.layoutOutlet).forEach(t=>{t.assignParams(e.typedParams)}),n.tagName.toLowerCase()===t.tagNames.layoutOutlet&&n.assignParams(e.typedParams)}}function z(t,e){H(t,e);const a=t.placeHolder.parentNode,n=t.placeHolder.nextSibling;for(const e of t.childNodeArray)n?a?.insertBefore(e,n):a?.appendChild(e);return!0}const I=Symbol.for("wcstack.transition-runner");function T(t,e,a){const n=function(t){const e=globalThis[I];return null==e||"wcs-transition-runner"!==e.protocol||"number"!=typeof e.version||e.version<1||"function"!=typeof e.run?null:"function"==typeof e.accepts&&e.accepts(t)?e:null}(t);if(null!==n)return n.run(e,{source:t,types:a});e()}const F=Symbol.for("wcstack.binder");function M(){const t=globalThis[F];return null==t||"wcs-binder"!==t.protocol||"number"!=typeof t.version||t.version<1||"function"!=typeof t.bind?null:t}const O=Symbol.for("wcstack.binder.pending"),U=Symbol.for("wcstack.binder.taken");function W(){const t=globalThis;let e=t[U];return void 0===e&&(e=new WeakSet,t[U]=e),e}function D(t){const e=M();return null===e?(function(){const t=globalThis;let e=t[O];return void 0===e&&(e=[],t[O]=e),e}().push(t),!1):(W().add(t),e.bind(t),!0)}const j=new WeakSet,G="data-wcs";function B(t,e,a){!j.has(t)&&function(t){return t.hasAttribute(G)||null!==t.querySelector(`[${G}]`)}(t)&&(j.add(t),function(t){if("complete"!==document.readyState)return void window.addEventListener("load",t,{once:!0});t()}(()=>{var n;null!==M()||(n=t,W().has(n))||console.warn(`[@wcstack/router] ${e} contains ${G} bindings that will never be applied. A binding exists only for nodes that were in the document when @wcstack/state built its bindings, and these nodes were not. They will render empty. ${a}`)}))}function q(t){for(const e of t.childNodeArray)1===e.nodeType&&(D(e)||B(e,`<${e.tagName.toLowerCase()}> inside a route`,'Load @wcstack/state on this page, or render data-driven markup outside <wcs-router> — bind the router\'s `path` into state and gate the markup with <template data-wcs="if: …">. See examples/router-i18n.'))}async function K(t,e){try{for(const t of e.routes)await t.guardCheck(e)}catch(e){if(e instanceof n)return console.warn(`Navigation cancelled: ${e.message}. Redirecting to ${e.fallbackPath}`),queueMicrotask(()=>{t.navigate(e.fallbackPath).catch(t=>{console.error("Fallback navigation failed:",t)})}),!1;throw e}return!0}async function V(e,n,s,i,r=""){const o=x(s,e.basename);if(e.isSameMatch(o))return e.commitNavigation({params:e.params,typedParams:e.typedParams,routeName:e.routeName,search:r,path:o}),!0;let l=E(e,o);l||(e.fallbackRoute?l={routes:[e.fallbackRoute],params:{},typedParams:{},path:o,lastPath:i}:a(`${t.tagNames.router} No route matched for path: ${o}`)),l.lastPath=i;const h=n.lastRoutes,c=await async function(t,e,a){if(!await K(t,e))return!1;const n=new Set(e.routes),s=new Set(a),i=()=>{for(const t of a)n.has(t)||L(t);let t=!1;for(const n of e.routes)(!s.has(n)||n.shouldChange(e.params)||t)&&(t=z(n,e),a.length>0&&!s.has(n)&&q(n))};let r;return 0===a.length?i():r=T("router",i),void 0!==r&&await r,!0}(e,l,h);return!!c&&(e.commitNavigation({params:l.params,typedParams:l.typedParams,routeName:l.routes[l.routes.length-1]?.name??"",search:r,path:o}),n.lastRoutes=l.routes,h.length>0&&v(e,l),!0)}function Y(){const t=window.navigation;return t?"function"!=typeof t.addEventListener||"function"!=typeof t.removeEventListener?null:t:null}function J(t,e){const a=Object.keys(t),n=Object.keys(e);if(a.length!==n.length)return!1;for(const n of a){if(!Object.prototype.hasOwnProperty.call(e,n))return!1;if(t[n]!==e[n])return!1}return!0}function Q(t){let e=t,a="";const n=e.indexOf("#");n>=0&&(a=e.slice(n),e=e.slice(0,n));let s="";const i=e.indexOf("?");return i>=0&&(s=e.slice(i),e=e.slice(0,i)),{pathname:e,search:s,hash:a}}function X(t){return"?"===t?"":t}function Z(t,e){let a=Object.getPrototypeOf(t);for(;null!==a;){const t=Object.getOwnPropertyDescriptor(a,e);if(void 0!==t)return"function"==typeof t.get||"function"==typeof t.set;a=Object.getPrototypeOf(a)}return!1}function tt(){const t=document.documentElement;return!!t&&t.hasAttribute("data-wcs-server")}const et="data-wcs-ssr",at="data-wcs-ssr-link",nt="@@wcs-route-ph:",st="@@wcs-route-start:",it="@@wcs-route-end:",rt=Object.freeze({});class ot extends HTMLElement{static hasConnectedCallbackPromise=!0;static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"navigateUrl",event:"wcs-router:navigate-url-changed",semantics:"state"},{name:"replaceUrl",event:"wcs-router:replace-url-changed",semantics:"state"},{name:"path",event:"wcs-router:path-changed",semantics:"state"},{name:"params",event:"wcs-router:params-changed",semantics:"state",getter:t=>t.detail.params},{name:"typedParams",event:"wcs-router:params-changed",semantics:"state",getter:t=>t.detail.typedParams},{name:"searchParams",event:"wcs-router:search-changed",semantics:"state"},{name:"routeName",event:"wcs-router:route-name-changed",semantics:"state"}],inputs:[{name:"basename",attribute:"basename"},{name:"navigateUrl"},{name:"replaceUrl"}],commands:[{name:"navigate",async:!0},{name:"replace",async:!0}]};_outlet=null;_template=null;_routeChildNodes=[];_basename="";_path="";_initialized=!1;_fallbackRoute=null;_listeningPopState=!1;_listeningNavigate=!1;_navigateUrl=null;_replaceUrl=null;_disconnectedDuringInit=!1;_initializing=!1;_a11yRegion=null;_params=rt;_typedParams=rt;_searchParams=rt;_routeName="";_hasCommitted=!1;_connectedCallbackPromise;_resolveConnectedCallback=null;_rejectConnectedCallback=null;constructor(){super(),this._connectedCallbackPromise=new Promise((t,e)=>{this._resolveConnectedCallback=t,this._rejectConnectedCallback=e}),this._connectedCallbackPromise.catch(()=>{})}get connectedCallbackPromise(){return this._connectedCallbackPromise}get a11yRegion(){return this._a11yRegion}get focusPolicy(){return"heading"===this.getAttribute("focus")?"heading":null}get announcePolicy(){return"title"===this.getAttribute("announce")?"title":null}_ensureA11yRegion(){if(null!==this._a11yRegion)return;const t=document.createElement("div");t.setAttribute("role","status"),t.style.position="absolute",t.style.width="1px",t.style.height="1px",t.style.overflow="hidden",t.style.clipPath="inset(50%)",t.style.whiteSpace="nowrap",this.appendChild(t),this._a11yRegion=t}_normalizePathname(t){return $(t)}_normalizeBasename(t){return R(t)}_joinInternalPath(t,e){const a=this._normalizeBasename(t);let n=e.startsWith("/")?e:"/"+e;return n=this._normalizePathname(n),a?"/"===n?a+"/":a+n:n}_notifyLocationChange(){window.dispatchEvent(new CustomEvent("wcs:navigate"))}_getBasename(){let t=new URL(document.baseURI).pathname||"/";return"/"===t?"":this._normalizeBasename(t)}get basename(){return this._basename}_getOutlet(){const e=this.nextElementSibling;if(e&&e.matches(t.tagNames.outlet))return e;const a=document.createElement(t.tagNames.outlet);return this.parentNode?this.parentNode.insertBefore(a,this.nextSibling):document.body.appendChild(a),a}_getTemplate(){return this.querySelector("template")}get outlet(){return this._outlet||a(`${t.tagNames.router} has no outlet.`),this._outlet}get template(){return this._template||a(`${t.tagNames.router} has no template.`),this._template}get routeChildNodes(){return this._routeChildNodes}get path(){return this._path}set path(t){const e=this._path!==t;this._path=t,e&&this.dispatchEvent(new CustomEvent("wcs-router:path-changed",{detail:t,bubbles:!0}))}get params(){return this._params}get typedParams(){return this._typedParams}get searchParams(){return this._searchParams}get routeName(){return this._routeName}isSameMatch(t){return!!this._hasCommitted&&this._path===t}commitNavigation(t){const e=Object.freeze({...t.params}),a=Object.freeze({...t.typedParams}),n=function(t){const e={};for(const[a,n]of new URLSearchParams(t))e[a]=n;return Object.freeze(e)}(t.search),s=!J(this._params,e),i=this._routeName!==t.routeName,r=!J(this._searchParams,n),o=this._path!==t.path;s&&(this._params=e,this._typedParams=a),i&&(this._routeName=t.routeName),r&&(this._searchParams=n),this._path=t.path,this._hasCommitted=!0,s&&this.dispatchEvent(new CustomEvent("wcs-router:params-changed",{detail:{params:this._params,typedParams:this._typedParams},bubbles:!0})),i&&this.dispatchEvent(new CustomEvent("wcs-router:route-name-changed",{detail:this._routeName,bubbles:!0})),r&&this.dispatchEvent(new CustomEvent("wcs-router:search-changed",{detail:this._searchParams,bubbles:!0})),o&&this.dispatchEvent(new CustomEvent("wcs-router:path-changed",{detail:t.path,bubbles:!0}))}get fallbackRoute(){return this._fallbackRoute}set fallbackRoute(t){this._fallbackRoute=t}get navigateUrl(){return this._navigateUrl}set navigateUrl(e){null!=e&&""!==e&&this._navigateUrl!==e&&(this._navigateUrl=e,this.navigate(e).catch(e=>{console.error(`${t.tagNames.router} navigate failed:`,e)}).finally(()=>{this._navigateUrl=null,this.dispatchEvent(new CustomEvent("wcs-router:navigate-url-changed",{detail:null,bubbles:!0}))}))}get replaceUrl(){return this._replaceUrl}set replaceUrl(e){null!=e&&""!==e&&this._replaceUrl!==e&&(this._replaceUrl=e,this.replace(e).catch(e=>{console.error(`${t.tagNames.router} replace failed:`,e)}).finally(()=>{this._replaceUrl=null,this.dispatchEvent(new CustomEvent("wcs-router:replace-url-changed",{detail:null,bubbles:!0}))}))}async navigate(t){await this._performNavigation(t,!1)}async replace(t){await this._performNavigation(t,!0)}async _performNavigation(t,e){const a=Q(t),n=""!==a.pathname||""===a.search&&""===a.hash?this._joinInternalPath(this._basename,a.pathname):window.location.pathname,s=n+X(a.search)+a.hash,i=Y();if(i?.navigate){const t=e?i.navigate(s,{history:"replace"}):i.navigate(s);await(t?.finished)}else{e?history.replaceState(null,"",s):history.pushState(null,"",s);const t=this._normalizePathname(n),i=this.isSameMatch(x(t,this._basename));await V(this,this.outlet,t,this._path,X(a.search))&&!i&&window.scrollTo(0,0),this._notifyLocationChange()}}_isOwnPath(t){return""===this._basename||(t===this._basename||t.startsWith(this._basename+"/"))}_onNavigateFunc(e){if(!e.canIntercept||e.hashChange||null!==e.downloadRequest)return;const a=new URL(e.destination.url),n=this._normalizePathname(a.pathname);if(!this._isOwnPath(n))return;const s=this.isSameMatch(x(n,this._basename)),i=s&&("push"===e.navigationType||"replace"===e.navigationType),r=a.search,o=this;e.intercept({handler:async()=>{try{await V(o,o.outlet,n,o.path,r)}catch(e){throw console.error(`${t.tagNames.router} applyRoute failed:`,e),e}},scroll:i?"manual":"after-transition",focusReset:s||null!==o.focusPolicy?"manual":"after-transition"})}_onNavigate=this._onNavigateFunc.bind(this);_onPopState=async()=>{const t=this._normalizePathname(window.location.pathname);this._isOwnPath(t)&&(await V(this,this.outlet,t,this._path,window.location.search||""),this._notifyLocationChange())};async _initialize(){this._initializing=!0;try{const e=tt();this._basename=this._normalizeBasename(this.getAttribute("basename")||this._getBasename()||"");const n=null!==document.querySelector("base[href]"),s=new URL(window.location.href);""!==this._basename||n||"/"===s.pathname||a(`${t.tagNames.router} basename is empty, but current path is not "/".`),this._outlet=this._getOutlet(),this._outlet.routesNode=this,this._template=this._getTemplate(),this._template||a(`${t.tagNames.router} should have a <template> child element.`);const i=e?this._template.content.cloneNode(!0):null,r=await N(this),o=!e&&this._outlet.hasAttribute(et);if(o||this._outlet.rootNode.appendChild(r),null!==i){const t=this._template.content;for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(i)}0===this.routeChildNodes.length&&a(`${t.tagNames.router} has no route definitions.`),e||this._ensureA11yRegion();const l=this._normalizePathname(window.location.pathname);if(e)return await this._renderForSsr(l),void(this._initialized=!0);if(o){if(await this._hydrateFromSsr(l))return this._notifyLocationChange(),void(this._initialized=!0);this._outlet.removeAttribute(et);const t=this._outlet.rootNode;for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(r)}await V(this,this.outlet,l,this._path,window.location.search||""),(o||null!==M())&&this._offerInitialContentToBinder(),this._notifyLocationChange(),this._initialized=!0}finally{this._initializing=!1}}async _hydrateFromSsr(e){const a=this.outlet.rootNode,n=new Map;let s=!1;if(this._forEachRoute(t=>{n.has(t.absolutePath)?s=!0:n.set(t.absolutePath,t)}),s)return!1;if(null!==a.querySelector(t.tagNames.layoutOutlet))return!1;const i=new Map,r=new Map,o=new Map,l=document.createTreeWalker(a,NodeFilter.SHOW_COMMENT);let h=0;for(;l.nextNode();){const t=l.currentNode,e=t.data;if(h++,e.startsWith(nt)){const a=e.slice(15);if(i.has(a)||!n.has(a))return!1;i.set(a,t)}else if(e.startsWith(st)){const a=e.slice(18);if(r.has(a)||!n.has(a))return!1;r.set(a,{comment:t,index:h})}else if(e.startsWith(it)){const a=e.slice(16);if(o.has(a)||!n.has(a))return!1;o.set(a,{comment:t,index:h})}}if(r.size!==o.size)return!1;const c=[];for(const[t,e]of r){const a=o.get(t);if(!a)return!1;if(a.comment.parentNode!==e.comment.parentNode)return!1;if(a.index<=e.index)return!1;c.push({start:e.index,end:a.index})}for(const t of c)for(const e of c)if(t.start<e.start&&e.start<t.end&&t.end<e.end)return!1;const u=x(e,this._basename);let d=E(this,u);if(!d){if(null===this._fallbackRoute)return!1;d={routes:[this._fallbackRoute],params:{},typedParams:{},path:u,lastPath:this._path}}if(d.lastPath=this._path,d.routes.length!==r.size)return!1;for(const t of d.routes)if(!r.has(t.absolutePath))return!1;const m=new Set;for(const t of this.routeChildNodes)m.add(t.absolutePath);for(const t of d.routes)for(const e of t.routeChildNodes)m.add(e.absolutePath);if(i.size!==m.size)return!1;for(const t of m)if(!i.has(t))return!1;for(const[t,e]of i){const a=n.get(t);e.parentNode.replaceChild(a.placeHolder,e)}for(const e of d.routes){const a=r.get(e.absolutePath).comment,n=o.get(e.absolutePath).comment,s=[],i=new Set;let l=a.nextSibling;for(;null!==l&&l!==n;){if(l.nodeType===Node.COMMENT_NODE){const t=l.data;if(t.startsWith(st)){const e=t.slice(18);l=o.get(e).comment.nextSibling;continue}}if(i.has(l))l=l.nextSibling;else{if(l.nodeType===Node.ELEMENT_NODE&&l.tagName.toLowerCase()===t.tagNames.link){const t=l.anchorElement;null!==t&&i.add(t)}s.push(l),l=l.nextSibling}}e.adoptChildNodes(s)}for(const{comment:t}of r.values())t.remove();for(const{comment:t}of o.values())t.remove();this.outlet.removeAttribute(et);for(const t of d.routes)H(t,d);return this.outlet.lastRoutes=d.routes,!await K(this,d)||(this.commitNavigation({params:d.params,typedParams:d.typedParams,routeName:d.routes[d.routes.length-1].name,search:window.location.search||"",path:u}),!0)}async _renderForSsr(e){const n=x(e,this._basename),s=E(this,n),i=s?.routes??(null!==this._fallbackRoute?[this._fallbackRoute]:[]);if(0===i.length&&a(`${t.tagNames.router} No route matched for path: ${n}`),!i.some(t=>t.hasGuard)){await V(this,this.outlet,e,this._path,window.location.search||""),this._offerInitialContentToBinder(),this._forEachRoute(t=>{t.placeHolder.data=`${nt}${t.absolutePath}`}),this.outlet.setAttribute(et,"");for(const t of this.outlet.lastRoutes){const e=t.placeHolder.parentNode,a=t.childNodeArray,n=document.createComment(`${st}${t.absolutePath}`),s=document.createComment(`${it}${t.absolutePath}`);e.insertBefore(n,a[0]??t.placeHolder.nextSibling);const i=a[a.length-1];e.insertBefore(s,i?i.nextSibling:n.nextSibling)}}}_offerInitialContentToBinder(){for(const t of this.outlet.lastRoutes)for(const e of t.childNodeArray)e.nodeType===Node.ELEMENT_NODE&&D(e)}_forEachRoute(t,e=this){for(const a of e.routeChildNodes)t(a),this._forEachRoute(t,a)}async connectedCallback(){if(function(t){const e=t.constructor?.wcBindable,a=e?.inputs;if(void 0!==a)for(const e of a){const a=e.name;if(!Object.prototype.hasOwnProperty.call(t,a))continue;if(!Z(t,a))continue;const n=t,s=n[a];delete n[a],n[a]=s}}(this),tt()){try{await Promise.resolve(),this.hasAttribute("enable-ssr")&&!this._initialized&&await this._initialize()}catch(t){throw this._rejectConnectedCallback?.(t),t}return void this._resolveConnectedCallback?.()}if(!this._initialized){this._disconnectedDuringInit=!1;try{null===this._getTemplate()&&await Promise.resolve(),await this._initialize()}catch(t){throw this._rejectConnectedCallback?.(t),t}if(this._disconnectedDuringInit)return void this._resolveConnectedCallback?.()}this._initialized&&this._ensureA11yRegion();const t=Y();t&&!this._listeningNavigate&&(t.addEventListener("navigate",this._onNavigate),this._listeningNavigate=!0),t||this._listeningPopState||(window.addEventListener("popstate",this._onPopState),this._listeningPopState=!0),this._resolveConnectedCallback?.()}disconnectedCallback(){this._initializing&&(this._disconnectedDuringInit=!0),this._listeningNavigate&&(Y()?.removeEventListener("navigate",this._onNavigate),this._listeningNavigate=!1),this._listeningPopState&&(window.removeEventListener("popstate",this._onPopState),this._listeningPopState=!1),null!==this._a11yRegion&&(this._a11yRegion.remove(),this._a11yRegion=null)}}const lt=["title","rel","target","download","hreflang","lang","dir"];class ht extends HTMLElement{static get observedAttributes(){return["to",...lt]}_childNodeArray=[];_uuid=e();_path="";_router=null;_anchorElement=null;_initialized=!1;_onClick;constructor(){super()}get uuid(){return this._uuid}get router(){if(this._router)return this._router;const e=this.closest(t.tagNames.router);if(e)return this._router=e;const n=this.getRootNode(),s=n.querySelector?.(t.tagNames.router);if(s)return this._router=s;a(`${t.tagNames.link} is not connected to a router.`)}_initialize(){this.style.display="none",this._childNodeArray=Array.from(this.childNodes),this._path=this.getAttribute("to")||"",this._initialized=!0}_normalizePathname(t){return $(t)}_joinInternalPath(t,e){const a=R(t),n=e.startsWith("/")?e:"/"+e,s=this._normalizePathname(n);return a?"/"===s?a+"/":a+s:s}_isInternalTarget(t){return t.startsWith("/")||t.startsWith("?")}_setAnchorHref(e,n){if(this._isInternalTarget(n)){const{pathname:t,search:a,hash:s}=Q(n),i=""===t?window.location.pathname:this._joinInternalPath(this.router.basename,t);e.href=i+X(a)+s}else try{e.href=new URL(n).toString()}catch{a(`[${t.tagNames.link}] Invalid URL in 'to' attribute: ${n}`)}}connectedCallback(){tt()?queueMicrotask(()=>{this.isConnected&&this._connect()}):this._connect()}_findSsrAnchor(){const t=this.nextElementSibling;return null!==t&&"A"===t.tagName&&t.hasAttribute(at)?t:null}_connect(){this._initialized||this._initialize();const t=this.parentNode;if(!t)return;const e=this._findSsrAnchor();let a;if(null!==e)a=e,a.removeAttribute(at),this._childNodeArray=Array.from(a.childNodes),this._setAnchorHref(a,this._path);else{const e=this.nextSibling;a=document.createElement("a"),this._setAnchorHref(a,this._path);for(const t of Array.from(this.attributes))(t.name.startsWith("aria-")||lt.includes(t.name))&&a.setAttribute(t.name,t.value);for(const t of this._childNodeArray)a.appendChild(t);e?t.insertBefore(a,e):t.appendChild(a)}if(this._anchorElement=a,tt())return this._updateActiveState(),void a.setAttribute(at,"");Y()?.addEventListener("currententrychange",this._updateActiveState),window.addEventListener("wcs:navigate",this._updateActiveState),window.addEventListener("popstate",this._updateActiveState),this._isInternalTarget(this._path)&&!Y()?.navigate&&(this._onClick=async t=>{t.defaultPrevented||0===t.button&&(t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||this._isInternalTarget(this._path)&&(t.preventDefault(),await this.router.navigate(this._path),this._updateActiveState()))},a.addEventListener("click",this._onClick)),this._updateActiveState()}disconnectedCallback(){Y()?.removeEventListener("currententrychange",this._updateActiveState),window.removeEventListener("wcs:navigate",this._updateActiveState),window.removeEventListener("popstate",this._updateActiveState);const t=this._anchorElement;t&&(this._onClick&&(t.removeEventListener("click",this._onClick),this._onClick=void 0),t.remove(),this._anchorElement=null);for(const e of this._childNodeArray)t&&e.parentNode===t&&t.removeChild(e);this._router=null}attributeChangedCallback(t,e,a){if("to"===t&&e!==a)this._path=a||"",this._anchorElement&&(this._setAnchorHref(this._anchorElement,this._path),this._updateActiveState());else if(lt.includes(t)){const e=this._anchorElement;e&&(null===a?e.removeAttribute(t):e.setAttribute(t,a))}}_updateActiveState=()=>{this._path.startsWith("?")&&this._anchorElement&&this._setAnchorHref(this._anchorElement,this._path);const t=this._normalizePathname(new URL(window.location.href).pathname),{pathname:e}=Q(this._path),a=this._normalizePathname(this._isInternalTarget(this._path)?""===e?window.location.pathname:this._joinInternalPath(this.router.basename,e):e);this._anchorElement&&(t===a?(this._anchorElement.classList.add("active"),this._anchorElement.setAttribute("aria-current","page")):(this._anchorElement.classList.remove("active"),this._anchorElement.removeAttribute("aria-current")))};get anchorElement(){return this._anchorElement}}const ct=[],ut=new Map;let dt=!1;const mt=new WeakMap;class pt extends HTMLElement{_initialized=!1;_childElementArray=[];constructor(){super(),this.style.display="none"}_initialize(){if(!this._initialized){this._initialized=!0,this._childElementArray=Array.from(this.children);for(const t of this._childElementArray)this.removeChild(t)}}connectedCallback(){this._initialize(),dt||(this._captureInitialHead(),dt=!0),ct.push(this),this._reapplyHead()}disconnectedCallback(){const t=ct.indexOf(this);-1!==t&&ct.splice(t,1),this._reapplyHead()}get childElementArray(){return this._initialized||a("Head component is not initialized yet."),this._childElementArray}_getKey(t){const e=mt.get(t);if(void 0!==e)return e;const a=this._computeKey(t);return mt.set(t,a),a}_computeKey(t){const e=t.tagName.toLowerCase();if("title"===e)return"title";if("meta"===e){return`meta:${t.getAttribute("name")||""}:${t.getAttribute("property")||""}:${t.getAttribute("http-equiv")||""}:${t.hasAttribute("charset")?"charset":""}:${t.getAttribute("media")||""}`}if("link"===e){return`link:${t.getAttribute("rel")||""}:${t.getAttribute("href")||""}:${t.getAttribute("media")||""}:${t.getAttribute("hreflang")||""}`}if("base"===e)return"base";if("script"===e){const e=t.getAttribute("src")||"",a=t.getAttribute("id")||"",n=t.getAttribute("type")||"";return e||a?`script:${e}:${a}:${n}`:`script::${n}:${t.outerHTML.slice(0,100)}`}if("style"===e){const e=t.getAttribute("id")||"",a=t.getAttribute("media")||"";return e?`style:${e}:${a}`:`style::${a}:${t.outerHTML.slice(0,100)}`}return`${e}:${t.outerHTML.slice(0,100)}`}_buildHeadElementMap(){const t=new Map;for(const e of Array.from(document.head.children)){const a=this._getKey(e);t.has(a)||t.set(a,e)}return t}_captureInitialHead(){const t=document.head;for(const e of Array.from(t.children)){const t=this._getKey(e);ut.has(t)||ut.set(t,e.cloneNode(!0))}}_reapplyHead(){const e=new Set;for(const t of ct)for(const a of t._childElementArray)e.add(this._getKey(a));for(const t of ut.keys())e.add(t);const a=this._buildHeadElementMap();for(const t of a.keys())e.add(t);for(const n of e){let e=null;for(let t=ct.length-1;t>=0;t--){const a=ct[t];for(const t of a._childElementArray)if(this._getKey(t)===n){e=t.cloneNode(!0);break}if(e)break}if(!e&&ut.has(n)){e=ut.get(n).cloneNode(!0)}const s=a.get(n)??null;e?(s?s.replaceWith(e):document.head.appendChild(e),a.set(n,e),D(e)||B(e,`<${e.tagName.toLowerCase()}> inside <${t.tagNames.head}>`,`<${t.tagNames.head}> reflects its children into <head> with cloneNode, so the clone is not the node that was bound. Load @wcstack/state on this page, or write the value statically here.`)):(s?.remove(),a.delete(n))}}}function gt(e=customElements){e.get(t.tagNames.layout)||e.define(t.tagNames.layout,h),e.get(t.tagNames.layoutOutlet)||e.define(t.tagNames.layoutOutlet,p),e.get(t.tagNames.outlet)||e.define(t.tagNames.outlet,c),e.get(t.tagNames.route)||e.define(t.tagNames.route,o),e.get(t.tagNames.router)||e.define(t.tagNames.router,ot),e.get(t.tagNames.link)||e.define(t.tagNames.link,ht),e.get(t.tagNames.head)||e.define(t.tagNames.head,pt)}var _t;await void gt(_t);
|
|
1
|
+
const t={tagNames:{route:"wcs-route",router:"wcs-router",outlet:"wcs-outlet",layout:"wcs-layout",layoutOutlet:"wcs-layout-outlet",link:"wcs-link",head:"wcs-head",guardHandler:"wcs-guard-handler"},enableShadowRoot:!1,basenameFileExtensions:[".html"]};function e(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){const e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)})}function a(t,e){if(e&&"cause"in e)throw new Error(`[@wcstack/router] ${t}`,{cause:e.cause});throw new Error(`[@wcstack/router] ${t}`)}class n extends Error{fallbackPath;constructor(t,e){super(t),this.fallbackPath=e}}const s={int:{typeName:"int",pattern:/^-?\d+$/,parse(t){if(this.pattern.test(t))return parseInt(t,10)}},float:{typeName:"float",pattern:/^-?\d+(?:\.\d+)?$/,parse(t){if(this.pattern.test(t))return parseFloat(t)}},bool:{typeName:"bool",pattern:/^(true|false|0|1)$/,parse(t){if(this.pattern.test(t))return"true"===t||"1"===t}},uuid:{typeName:"uuid",pattern:/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,parse(t){if(this.pattern.test(t))return t}},slug:{typeName:"slug",pattern:/^[a-z0-9]+(?:-[a-z0-9]+)*$/,parse(t){if(this.pattern.test(t))return t}},isoDate:{typeName:"isoDate",pattern:/^\d{4}-\d{2}-\d{2}$/,parse(t){if(!this.pattern.test(t))return;const[e,a,n]=t.split("-").map(Number),s=new Date(e,a-1,n);return s.getFullYear()===e&&s.getMonth()===a-1&&s.getDate()===n?s:void 0}},any:{typeName:"any",pattern:/^.+$/,parse(t){if(this.pattern.test(t))return t}}};function i(t){const e={};for(const[a,n]of new URLSearchParams(t))e[a]=n;return Object.freeze(e)}function r(t,e){const a=Object.keys(t),n=Object.keys(e);if(a.length!==n.length)return!1;for(const n of a){if(!Object.prototype.hasOwnProperty.call(e,n))return!1;if(t[n]!==e[n])return!1}return!0}const o={static:2,param:1,"catch-all":0};class l extends EventTarget{_target;_parentCore=null;_path="";_name="";_isFallbackRoute=!1;_segmentInfos=[];_absoluteSegmentInfos;_paramNames;_absoluteParamNames;_weight;_absoluteWeight;_segmentCount;_params={};_typedParams={};_active=!1;_hasGuard=!1;_guardHandler=null;_guardFallbackPath="";_waitForSetGuardHandler=null;_resolveSetGuardHandler=null;_guardHandlerLoadFailed=!1;constructor(t){super(),this._target=t??this}get parentCore(){return this._parentCore}set parentCore(t){this._parentCore=t}get path(){return this._path}get name(){return this._name}get isFallbackRoute(){return this._isFallbackRoute}get isRelative(){return!this._path.startsWith("/")}get segmentInfos(){return this._segmentInfos}_checkParentCore(e,n){return this._isFallbackRoute||(this.isRelative&&!this._parentCore&&a(`${t.tagNames.route} is relative but has no parent route.`),!this.isRelative&&this._parentCore&&a(`${t.tagNames.route} is absolute but has a parent route.`)),this.isRelative&&this._parentCore?e(this._parentCore):n()}get absolutePath(){return this._checkParentCore(t=>{const e=t.absolutePath;return e.endsWith("/")?e+this._path:e+"/"+this._path},()=>this._path)}get absoluteSegmentInfos(){return void 0===this._absoluteSegmentInfos&&(this._absoluteSegmentInfos=this._checkParentCore(t=>[...t.absoluteSegmentInfos,...this._segmentInfos],()=>[...this._segmentInfos])),this._absoluteSegmentInfos}get params(){return this._params}get typedParams(){return this._typedParams}get active(){return this._active}get paramNames(){if(void 0===this._paramNames){const t=[];for(const e of this._segmentInfos)e.paramName&&t.push(e.paramName);this._paramNames=t}return this._paramNames}get absoluteParamNames(){return void 0===this._absoluteParamNames&&(this._absoluteParamNames=this._checkParentCore(t=>[...t.absoluteParamNames,...this.paramNames],()=>[...this.paramNames])),this._absoluteParamNames}get weight(){if(void 0===this._weight){let t=0;for(const e of this._segmentInfos)t+=o[e.type];this._weight=t}return this._weight}get absoluteWeight(){return void 0===this._absoluteWeight&&(this._absoluteWeight=this._checkParentCore(t=>t.absoluteWeight+this.weight,()=>this.weight)),this._absoluteWeight}get segmentCount(){if(void 0===this._segmentCount){let t=0;for(const e of this._segmentInfos)"catch-all"!==e.type&&t++;this._segmentCount=""===this._path?0:t}return this._segmentCount}get absoluteSegmentCount(){return this._checkParentCore(t=>t.absoluteSegmentCount+this.segmentCount,()=>this.segmentCount)}parsePath(t,e={}){this._segmentInfos=[],this._absoluteSegmentInfos=void 0,this._paramNames=void 0,this._absoluteParamNames=void 0,this._weight=void 0,this._absoluteWeight=void 0,this._segmentCount=void 0,this._path=t,this._name=e.name||"",this._isFallbackRoute=e.isFallback||!1,e.isIndex&&this._segmentInfos.push({type:"static",segmentText:"",paramName:null,pattern:/^$/,isIndex:!0});const a=t.split("/");for(let t=0;t<a.length;t++){const n=a[t];if(!(""===n&&t===a.length-1&&t>0)){if("*"===n){this._segmentInfos.push({type:"catch-all",segmentText:n,paramName:"*",pattern:new RegExp("^(.*)$")});break}if(n.startsWith(":")){const t=n.match(/^:([^()]+)(\(([^)]+)\))?$/);let e,a="any";t?(e=t[1],t[3]&&Object.keys(s).includes(t[3])&&(a=t[3])):e=n.substring(1),this._segmentInfos.push({type:"param",segmentText:n,paramName:e,pattern:new RegExp("^([^\\/]+)$"),paramType:a})}else""===n&&e.isIndex||this._segmentInfos.push({type:"static",segmentText:n,paramName:null,pattern:new RegExp(`^${n}$`)})}}this._hasGuard=e.hasGuard||!1,this._hasGuard&&(this._guardFallbackPath=e.guardFallback||"/",this._waitForSetGuardHandler=new Promise(t=>{this._resolveSetGuardHandler=t}))}setParams(t,e){const a=this._active;this._params=t,this._typedParams=e,this._active=!0,this._target.dispatchEvent(new CustomEvent("wcs-route:params-changed",{detail:{params:t,typedParams:e},bubbles:!0})),a||this._target.dispatchEvent(new CustomEvent("wcs-route:active-changed",{detail:!0,bubbles:!0}))}clearParams(){const t=this._active;this._params={},this._typedParams={},this._active=!1,t&&this._target.dispatchEvent(new CustomEvent("wcs-route:active-changed",{detail:!1,bubbles:!0}))}shouldChange(t){for(const e of this.paramNames)if(this._params[e]!==t[e])return!0;return!1}get hasGuard(){return this._hasGuard}get guardHandler(){return this._guardHandler||a(`${t.tagNames.route} has no guardHandler.`),this._guardHandler}set guardHandler(t){this._guardHandler=t,this._resolveSetGuardHandler?.()}notifyGuardHandlerLoadFailed(){this._guardHandlerLoadFailed=!0,this._resolveSetGuardHandler?.()}async guardCheck(t){if(this._hasGuard&&this._waitForSetGuardHandler&&await this._waitForSetGuardHandler,this._guardHandler){const e=t.path,a=t.lastPath,s=await this._guardHandler(e,a,function(t){return Object.freeze({params:Object.freeze({...t.params}),typedParams:Object.freeze({...t.typedParams}),searchParams:i(t.search??""),routeName:t.routes[t.routes.length-1]?.name??""})}(t));if("string"==typeof s&&""!==s)throw new n("Navigation cancelled by guard.",s);if(!s)throw new n("Navigation cancelled by guard.",this._guardFallbackPath);return"object"==typeof s?s:null}if(this._hasGuard&&this._guardHandlerLoadFailed)throw new n("Navigation cancelled: guard handler failed to load.",this._guardFallbackPath);return null}}class c extends HTMLElement{_core;_routeParentNode=null;_routeChildNodes=[];_routerNode=null;_uuid=e();_placeHolder=document.createComment(`@@route:${this._uuid}`);_childNodeArray;_childIndex=0;_initialized=!1;_routes;constructor(){super(),this._core=new l(this)}get routeParentNode(){return this._routeParentNode}get routeChildNodes(){return this._routeChildNodes}get routerNode(){return this._routerNode||a(`${t.tagNames.route} has no routerNode.`),this._routerNode}get uuid(){return this._uuid}get placeHolder(){return this._placeHolder}get childNodeArray(){return void 0===this._childNodeArray&&(this._childNodeArray=Array.from(this.childNodes)),this._childNodeArray}adoptChildNodes(t){for(this._childNodeArray=[...t];this.firstChild;)this.removeChild(this.firstChild)}get routes(){return void 0===this._routes&&(this._routes=this.routeParentNode?this.routeParentNode.routes.concat(this):[this]),this._routes}get childIndex(){return this._childIndex}get path(){return this._core.path}get name(){return this._core.name}get isRelative(){return this._core.isRelative}get absolutePath(){return this._core.absolutePath}get segmentInfos(){return this._core.segmentInfos}get absoluteSegmentInfos(){return this._core.absoluteSegmentInfos}get params(){return this._core.params}get typedParams(){return this._core.typedParams}get paramNames(){return this._core.paramNames}get absoluteParamNames(){return this._core.absoluteParamNames}get weight(){return this._core.weight}get absoluteWeight(){return this._core.absoluteWeight}get segmentCount(){return this._core.segmentCount}get absoluteSegmentCount(){return this._core.absoluteSegmentCount}get fullpath(){return this.absolutePath}get hasGuard(){return this._core.hasGuard}get guardHandler(){return this._core.guardHandler}set guardHandler(t){this._core.guardHandler=t}setParams(t,e){this._core.setParams(t,e)}clearParams(){this._core.clearParams()}shouldChange(t){return this._core.shouldChange(t)}async guardCheck(t){return this._core.guardCheck(t)}notifyGuardHandlerLoadFailed(){this._core.notifyGuardHandlerLoadFailed()}testAncestorNode(t){let e=this._routeParentNode;for(;e;){if(e===t)return!0;e=e.routeParentNode}return!1}initialize(e,n){if(this._initialized)return;let s;this._initialized=!0;let i=!1,r=!1;this.hasAttribute("path")?s=this.getAttribute("path")||"":this.hasAttribute("index")?(s="",i=!0):this.hasAttribute("fallback")?(s="",r=!0):a(`${t.tagNames.route} should have a "path" or "index" attribute.`),this._routerNode=e,this._routeParentNode=n;const o=n||e;o.routeChildNodes.push(this),this._childIndex=o.routeChildNodes.length-1,r&&(n&&a(`${t.tagNames.route} with fallback attribute must be a direct child of ${t.tagNames.router}.`),e.fallbackRoute&&a(`${t.tagNames.router} can have only one fallback route.`),e.fallbackRoute=this),n&&(this._core.parentCore=n._core),this._core.parsePath(s,{isIndex:i,isFallback:r,hasGuard:this.hasAttribute("guard"),guardFallback:this.getAttribute("guard"),name:this.getAttribute("name")||""}),this.setAttribute("fullpath",this.absolutePath)}}const h=Symbol.for("wcstack.trustedTypes.policy"),u=Symbol.for("wcstack.trustedTypes.internal"),d="wcstack";function m(){const t=globalThis[h];return null===t||"object"!=typeof t?null:t}let p=!1;function g(t){const e=function(){const t=globalThis,e=t[u];if(void 0!==e)return e;const a=globalThis.trustedTypes;let n=null;if(a&&"function"==typeof a.createPolicy)try{n=a.createPolicy(d,{createHTML:t=>t,createScriptURL:t=>t})}catch(t){"function"!=typeof m()?.createHTML&&console.error(`[@wcstack/router] Could not create the Trusted Types policy "${d}". Allow it in the CSP (\`trusted-types ${d};\`), or inject your own policy at globalThis[Symbol.for("wcstack.trustedTypes.policy")]. See docs/csp.md section 7.`,t)}return t[u]=n,n}(),a=e?.createHTML;if("function"==typeof a)return a.call(e,t);if(!("trustedTypes"in globalThis))return t;const n=m(),s=n?.createHTML;return"function"==typeof s?(p||(p=!0,console.warn(`[@wcstack/router] Falling back to the injected Trusted Types policy to expand a layout template, because the "${d}" policy could not be created. If that policy sanitizes (e.g. DOMPurify), custom elements in the layout may be stripped. Allow \`trusted-types ${d};\` in the CSP to avoid this. See docs/csp.md section 7.`)),s.call(n,t)):t}const f=new Map;class _ extends HTMLElement{_uuid=e();constructor(){super()}async _loadTemplateFromSource(e){try{const n=await fetch(e);n.ok||a(`${t.tagNames.layout} failed to fetch layout from source: ${e}, status: ${n.status}`);const s=await n.text();return f.set(e,s),s}catch(n){a(`${t.tagNames.layout} failed to load layout from source: ${e}, error: ${n}`,{cause:n})}}_loadTemplateFromDocument(t){const e=document.getElementById(`${t}`);return e&&e instanceof HTMLTemplateElement?e.innerHTML:null}async loadTemplate(){const e=this.getAttribute("src"),a=this.getAttribute("layout");e&&a&&console.warn(`${t.tagNames.layout} have both "src" and "layout" attributes.`);const n=document.createElement("template");if(e)f.has(e)?n.innerHTML=g(f.get(e)||""):n.innerHTML=g(await this._loadTemplateFromSource(e)||"");else if(a){const e=this._loadTemplateFromDocument(a);e?n.innerHTML=g(e):console.warn(`${t.tagNames.layout} could not find template with id "${a}".`)}return n}get uuid(){return this._uuid}get enableShadowRoot(){return!!this.hasAttribute("enable-shadow-root")||!this.hasAttribute("disable-shadow-root")&&t.enableShadowRoot}get name(){return this.getAttribute("name")||""}}class b extends HTMLElement{_routesNode=null;_lastRoutes=[];_initialized=!1;constructor(){super()}get routesNode(){return this._routesNode||a(`${t.tagNames.outlet} has no routesNode.`),this._routesNode}set routesNode(t){this._routesNode=t}get rootNode(){return this.shadowRoot?this.shadowRoot:this}get lastRoutes(){return this._lastRoutes}set lastRoutes(t){this._lastRoutes=[...t]}_resolveEnableShadowRoot(){return!!this.hasAttribute("enable-shadow-root")||!this.hasAttribute("disable-shadow-root")&&t.enableShadowRoot}_initialize(){this._resolveEnableShadowRoot()&&this.attachShadow({mode:"open"}),this._initialized=!0}connectedCallback(){this._initialized||this._initialize()}disconnectedCallback(){this._lastRoutes=[]}}const y=new Set(["props","states","attr",""]);function w(t,e,a){for(const[n,s]of Object.entries(e))switch(a){case"props":t.props={...t.props,[n]:s};break;case"states":t.states={...t.states,[n]:s};break;case"attr":null==s?t.removeAttribute(n):t.setAttribute(n,String(s));break;case"":t[n]=s}}function N(t,e){t.hasAttribute("data-bind")||a(`${t.tagName} has no 'data-bind' attribute.`);const n=t.getAttribute("data-bind")||"";y.has(n)||a(`${t.tagName} has invalid 'data-bind' attribute: ${n}`);const s=n,i=function(t){const e=t.tagName.toLowerCase();if(e.includes("-"))return e;const a=t.getAttribute("is");return a&&a.includes("-")?a:null}(t);i&&void 0===customElements.get(i)?customElements.whenDefined(i).then(()=>{t.isConnected&&w(t,e,s)}).catch(()=>{a(`Failed to define custom element: ${i}`)}):w(t,e,s)}class v extends HTMLElement{_layout=null;_initialized=!1;_initializing=!1;_disconnectedDuringInit=!1;_layoutChildNodes=[];constructor(){super()}get layout(){return this._layout||a(`${t.tagNames.layoutOutlet} has no layout.`),this._layout}set layout(t){this._layout=t,this.setAttribute("name",t.name)}get name(){return this.layout.name}async _initialize(){this._initializing=!0;try{this._initialized=!0,this.layout.enableShadowRoot&&!this.shadowRoot&&this.attachShadow({mode:"open"});const e=await this.layout.loadTemplate();if(!this.isConnected)return void(this._initialized=!1);if(this.shadowRoot){this.shadowRoot.appendChild(e.content.cloneNode(!0));for(const t of Array.from(this.layout.childNodes))this._layoutChildNodes.push(t),this.appendChild(t)}else{const a=e.content.cloneNode(!0),n=new Map;a.querySelectorAll("slot").forEach(e=>{const a=e.getAttribute("name")||"";n.has(a)?console.warn(`${t.tagNames.layoutOutlet} duplicate slot name "${a}" in layout template.`):n.set(a,e)});const s=new Map,i=document.createDocumentFragment();for(const t of Array.from(this.layout.childNodes)){if(this._layoutChildNodes.push(t),t instanceof Element){const e=t.getAttribute("slot")||"";if(e.length>0&&n.has(e)){s.has(e)||s.set(e,document.createDocumentFragment()),s.get(e)?.appendChild(t);continue}}i.appendChild(t)}for(const[t,e]of n){const a=s.get(t);a&&e.replaceWith(a)}const r=n.get("");r&&r.replaceWith(i),this.appendChild(a)}}finally{this._initializing=!1}}async connectedCallback(){this._initialized||(this._disconnectedDuringInit=!1,await this._initialize(),!this._disconnectedDuringInit&&this.isConnected)}disconnectedCallback(){this._initializing&&(this._disconnectedDuringInit=!0)}assignParams(t){for(const e of this._layoutChildNodes)e instanceof Element&&(e.querySelectorAll("[data-bind]").forEach(e=>{N(e,t)}),e.hasAttribute("data-bind")&&N(e,t))}}function C(){return document.createElement(t.tagNames.layoutOutlet)}const P="https://github.com/wcstack/wcstack/blob/main/docs/csp.md";async function E(t,e){let n=null,s=null;const i=e.uuid||"",r=`\n//# sourceURL=${i?`wcs-guard-handler:${i}`:"wcs-guard-handler"}\n`,o=t.text+r;let l=!1;const c=t=>{t.effectiveDirective.startsWith("script-src")&&(l=!0)};document.addEventListener("securitypolicyviolation",c);try{if("function"==typeof URL.createObjectURL){const t=new Blob([o],{type:"application/javascript"}),e=URL.createObjectURL(t);try{n=await import(e)}catch(t){s=t}finally{URL.revokeObjectURL(e)}}if(!n){const t=btoa(String.fromCodePoint(...(new TextEncoder).encode(o)));try{n=await import(`data:application/javascript;base64,${t}`)}catch(t){a(function(t,e,a){return a?`The guard <script> was blocked by Content-Security-Policy. Guard scripts are inline-only and are evaluated through a blob: URL, so script-src must allow blob:. See ${P}`:`loadGuardHandler: failed to import guard script. data: URL error: ${t?.message??String(t)}`+(e?`. Blob URL error: ${e?.message??String(e)}`:"")+`. If this page sets a Content-Security-Policy, see ${P}`}(t,s,l),{cause:s??t})}}}finally{document.removeEventListener("securitypolicyviolation",c)}return n&&"function"==typeof n.default?n.default:null}function k(t,e){E(t,e).then(t=>{t?e.guardHandler=t:e.notifyGuardHandlerLoadFailed()}).catch(t=>{console.error("loadGuardHandler failed:",t),e.notifyGuardHandlerLoadFailed()})}function A(t,e){let a=t.get(e.absolutePath);a||(a=[]);for(const t of a)if(!e.testAncestorNode(t)){console.warn(`Duplicate route path detected: '${e.absolutePath}' (defined as '${e.path}')`);break}a.push(e),1===a.length&&t.set(e.absolutePath,a)}async function S(e,a,n,s){const i=n.length>0?n[n.length-1]:null,r=document.createDocumentFragment(),o=Array.from(a.childNodes);for(const a of o)if(a.nodeType===Node.ELEMENT_NODE){let o=a,l=a;const c=l.tagName.toLowerCase();if(c===t.tagNames.route){const t=document.createDocumentFragment();for(const e of Array.from(l.childNodes))t.appendChild(e);const a=document.importNode(l,!0);customElements.upgrade(a),a.appendChild(t);const r=a;r.initialize(e,i),A(s,r),n.push(r),o=r.placeHolder,l=r}else{if(c===t.tagNames.guardHandler){if(n.length>0){const t=n[n.length-1],e=l.querySelector('script[type="module"]');e&&k(e,t)}continue}if("template"===c){r.appendChild(l);continue}if(c===t.tagNames.layout){const t=document.createDocumentFragment();for(const e of Array.from(l.childNodes))t.appendChild(e);const e=document.importNode(l,!0);customElements.upgrade(e),e.appendChild(t);const a=e,n=C();n.layout=a,o=n,l=e}}const h=await S(e,l,n,s);l.replaceChildren(),l.appendChild(h),r.appendChild(o)}else r.appendChild(a);return r}async function $(t){const e=new Map;return await S(t,t.template.content,[],e)}function x(t,e){if("title"===t.announcePolicy){const e=t.a11yRegion;null!==e&&(e.textContent=document.title)}if("heading"===t.focusPolicy){const t=function(t){for(const e of t){if(e.nodeType!==Node.ELEMENT_NODE)continue;const t=e;if(/^H[1-6]$/.test(t.tagName)){if(R(t))return t}else for(const e of t.querySelectorAll("h1,h2,h3,h4,h5,h6"))if(R(e))return e}return null}(e.routes[e.routes.length-1].childNodeArray);if(null!==t)t.hasAttribute("tabindex")||t.setAttribute("tabindex","-1"),t.focus();else{const t=document.querySelector("[autofocus]");null!==t?t.focus():document.activeElement instanceof HTMLElement&&document.activeElement.blur()}}}function R(t){return!1!==t.checkVisibility?.()}function T(t,e,a,n){const i=function(t,e,a){const n={},i={};let r=!0,o=!1,l=0,c=0;for(;l<t.absoluteSegmentInfos.length;){const e=t.absoluteSegmentInfos[l];if(e.isIndex){l++;continue}if(0===l&&""===e.segmentText&&"static"===e.type){l++;continue}const h=a[c];if(void 0===h){r=!1;break}let u=!1;if("param"===e.type){const t=e.paramType||"any",a=s[t].parse(h);void 0!==a&&(e.paramName&&(n[e.paramName]=h,i[e.paramName]=a),u=!0)}else u=null!==e.pattern.exec(h);if(!u){r=!1;break}if("catch-all"===e.type){const t=a.slice(c).join("/");n["*"]=t,i["*"]=t,o=!0;break}l++,c++}let h=!1;return r&&(o||l===t.absoluteSegmentInfos.length&&c===a.length)&&(h=!0),h?{path:e,routes:t.routes,params:n,typedParams:i,lastPath:""}:null}(t,e,a);i&&n.push(i);for(const s of t.routeChildNodes)T(s,e,a,n)}function L(t,e){const a=t.routeChildNodes,n=[],s=e.split("/"),i=s.filter((t,e)=>(0!==e||""!==t)&&!(e===s.length-1&&""===t&&s.length>1));for(const t of a)T(t,e,i,n);return n.sort((t,e)=>{const a=t.routes.at(-1),n=e.routes.at(-1),s=a.absoluteSegmentCount-n.absoluteSegmentCount;if(0!==s)return-s;const i=a.absoluteWeight-n.absoluteWeight;if(0!==i)return-i;return a.childIndex-n.childIndex}),n.length>0?n[0]:null}let H=null,z=null;function I(){const e=t.basenameFileExtensions;return 0===e.length?null:(H===e&&z||(H=e,z=new RegExp(`\\/[^/]+(?:${e.map(t=>t.replace(/\./g,"\\.")).join("|")})$`,"i")),z)}function O(t){let e=t||"/";e.startsWith("/")||(e="/"+e),e=e.replace(/\/{2,}/g,"/");const a=I();return a&&(e=e.replace(a,"")),""===e&&(e="/"),e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e}function M(t,e){let a=t;return""!==e&&(t===e?a="":t.startsWith(e+"/")&&(a=t.slice(e.length))),""===a?"/":a}function F(t){let e=t||"";if(!e)return"";e.startsWith("/")||(e="/"+e),e=e.replace(/\/{2,}/g,"/");const a=I();return a&&(e=e.replace(a,"")),e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),"/"===e?"":e}function U(t){t.clearParams();for(const e of t.childNodeArray)e.parentNode?.removeChild(e)}function W(e,a){const n={},s={};for(const t of e.paramNames)n[t]=a.params[t],s[t]=a.typedParams[t];e.setParams(n,s);for(const a of e.childNodeArray)if(a.nodeType===Node.ELEMENT_NODE){const n=a;n.querySelectorAll("[data-bind]").forEach(t=>{N(t,e.typedParams)}),n.hasAttribute("data-bind")&&N(n,e.typedParams),n.querySelectorAll(t.tagNames.layoutOutlet).forEach(t=>{t.assignParams(e.typedParams)}),n.tagName.toLowerCase()===t.tagNames.layoutOutlet&&n.assignParams(e.typedParams)}}function j(t,e){W(t,e);const a=t.placeHolder.parentNode,n=t.placeHolder.nextSibling;for(const e of t.childNodeArray)n?a?.insertBefore(e,n):a?.appendChild(e);return!0}const D=Symbol.for("wcstack.transition-runner");function G(t,e,a){const n=function(t){const e=globalThis[D];return null==e||"wcs-transition-runner"!==e.protocol||"number"!=typeof e.version||e.version<1||"function"!=typeof e.run?null:"function"==typeof e.accepts&&e.accepts(t)?e:null}(t);if(null!==n)return n.run(e,{source:t,types:a});e()}const B=Symbol.for("wcstack.binder");function q(){const t=globalThis[B];return null==t||"wcs-binder"!==t.protocol||"number"!=typeof t.version||t.version<1||"function"!=typeof t.bind?null:t}const K=Symbol.for("wcstack.binder.pending"),V=Symbol.for("wcstack.binder.taken");function Y(){const t=globalThis;let e=t[V];return void 0===e&&(e=new WeakSet,t[V]=e),e}function J(t){const e=q();return null===e?(function(){const t=globalThis;let e=t[K];return void 0===e&&(e=[],t[K]=e),e}().push(t),!1):(Y().add(t),e.bind(t),!0)}const Q=new WeakSet,X="data-wcs";function Z(t,e,a){!Q.has(t)&&function(t){return t.hasAttribute(X)||null!==t.querySelector(`[${X}]`)}(t)&&(Q.add(t),function(t){if("complete"!==document.readyState)return void window.addEventListener("load",t,{once:!0});t()}(()=>{var n;null!==q()||(n=t,Y().has(n))||console.warn(`[@wcstack/router] ${e} contains ${X} bindings that will never be applied. A binding exists only for nodes that were in the document when @wcstack/state built its bindings, and these nodes were not. They will render empty. ${a}`)}))}function tt(t){for(const e of t.childNodeArray)1===e.nodeType&&(J(e)||Z(e,`<${e.tagName.toLowerCase()}> inside a route`,'Load @wcstack/state on this page, or render data-driven markup outside <wcs-router> — bind the router\'s `path` into state and gate the markup with <template data-wcs="if: …">. See examples/router-i18n.'))}async function et(t,e){try{let t=null;for(const a of e.routes){const n=await a.guardCheck(e);n&&(t=null===t?n:Object.assign({},t,n))}e.data=t}catch(e){if(e instanceof n)return console.warn(`Navigation cancelled: ${e.message}. Redirecting to ${e.fallbackPath}`),queueMicrotask(()=>{t.navigate(e.fallbackPath).catch(t=>{console.error("Fallback navigation failed:",t)})}),!1;throw e}return!0}async function at(e,n,s,i,r=""){const o=M(s,e.basename);if(e.isSameMatch(o))return e.commitNavigation({params:e.params,typedParams:e.typedParams,routeName:e.routeName,data:e.data,search:r,path:o}),!0;let l=L(e,o);l||(e.fallbackRoute?l={routes:[e.fallbackRoute],params:{},typedParams:{},path:o,lastPath:i}:a(`${t.tagNames.router} No route matched for path: ${o}`)),l.lastPath=i,l.search=r;const c=n.lastRoutes,h=await async function(t,e,a){if(!await et(t,e))return!1;const n=new Set(e.routes),s=new Set(a),i=()=>{for(const t of a)n.has(t)||U(t);let t=!1;for(const n of e.routes)(!s.has(n)||n.shouldChange(e.params)||t)&&(t=j(n,e),a.length>0&&!s.has(n)&&tt(n))};let r;return 0===a.length?i():r=G("router",i),void 0!==r&&await r,!0}(e,l,c);return!!h&&(e.commitNavigation({params:l.params,typedParams:l.typedParams,routeName:l.routes[l.routes.length-1]?.name??"",data:l.data??null,search:r,path:o}),n.lastRoutes=l.routes,c.length>0&&x(e,l),!0)}function nt(){const t=window.navigation;return t?"function"!=typeof t.addEventListener||"function"!=typeof t.removeEventListener?null:t:null}function st(t){let e=t,a="";const n=e.indexOf("#");n>=0&&(a=e.slice(n),e=e.slice(0,n));let s="";const i=e.indexOf("?");return i>=0&&(s=e.slice(i),e=e.slice(0,i)),{pathname:e,search:s,hash:a}}function it(t){return"?"===t?"":t}function rt(t,e){let a=Object.getPrototypeOf(t);for(;null!==a;){const t=Object.getOwnPropertyDescriptor(a,e);if(void 0!==t)return"function"==typeof t.get||"function"==typeof t.set;a=Object.getPrototypeOf(a)}return!1}function ot(){const t=document.documentElement;return!!t&&t.hasAttribute("data-wcs-server")}const lt="data-wcs-ssr",ct="data-wcs-ssr-link",ht="@@wcs-route-ph:",ut="@@wcs-route-start:",dt="@@wcs-route-end:",mt=Object.freeze({});class pt extends HTMLElement{static hasConnectedCallbackPromise=!0;static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"navigateUrl",event:"wcs-router:navigate-url-changed",semantics:"state"},{name:"replaceUrl",event:"wcs-router:replace-url-changed",semantics:"state"},{name:"path",event:"wcs-router:path-changed",semantics:"state"},{name:"params",event:"wcs-router:params-changed",semantics:"state",getter:t=>t.detail.params},{name:"typedParams",event:"wcs-router:params-changed",semantics:"state",getter:t=>t.detail.typedParams},{name:"searchParams",event:"wcs-router:search-changed",semantics:"state"},{name:"routeName",event:"wcs-router:route-name-changed",semantics:"state"},{name:"data",event:"wcs-router:data-changed",semantics:"state"}],inputs:[{name:"basename",attribute:"basename"},{name:"navigateUrl"},{name:"replaceUrl"}],commands:[{name:"navigate",async:!0},{name:"replace",async:!0}]};_outlet=null;_template=null;_routeChildNodes=[];_basename="";_path="";_initialized=!1;_fallbackRoute=null;_listeningPopState=!1;_listeningNavigate=!1;_navigateUrl=null;_replaceUrl=null;_disconnectedDuringInit=!1;_initializing=!1;_a11yRegion=null;_params=mt;_typedParams=mt;_searchParams=mt;_routeName="";_data=null;_hasCommitted=!1;_connectedCallbackPromise;_resolveConnectedCallback=null;_rejectConnectedCallback=null;constructor(){super(),this._connectedCallbackPromise=new Promise((t,e)=>{this._resolveConnectedCallback=t,this._rejectConnectedCallback=e}),this._connectedCallbackPromise.catch(()=>{})}get connectedCallbackPromise(){return this._connectedCallbackPromise}get a11yRegion(){return this._a11yRegion}get focusPolicy(){return"heading"===this.getAttribute("focus")?"heading":null}get announcePolicy(){return"title"===this.getAttribute("announce")?"title":null}_ensureA11yRegion(){if(null!==this._a11yRegion)return;const t=document.createElement("div");t.setAttribute("role","status"),t.style.position="absolute",t.style.width="1px",t.style.height="1px",t.style.overflow="hidden",t.style.clipPath="inset(50%)",t.style.whiteSpace="nowrap",this.appendChild(t),this._a11yRegion=t}_normalizePathname(t){return O(t)}_normalizeBasename(t){return F(t)}_joinInternalPath(t,e){const a=this._normalizeBasename(t);let n=e.startsWith("/")?e:"/"+e;return n=this._normalizePathname(n),a?"/"===n?a+"/":a+n:n}_notifyLocationChange(){window.dispatchEvent(new CustomEvent("wcs:navigate"))}_getBasename(){let t=new URL(document.baseURI).pathname||"/";return"/"===t?"":this._normalizeBasename(t)}get basename(){return this._basename}_getOutlet(){const e=this.nextElementSibling;if(e&&e.matches(t.tagNames.outlet))return e;const a=document.createElement(t.tagNames.outlet);return this.parentNode?this.parentNode.insertBefore(a,this.nextSibling):document.body.appendChild(a),a}_getTemplate(){return this.querySelector("template")}get outlet(){return this._outlet||a(`${t.tagNames.router} has no outlet.`),this._outlet}get template(){return this._template||a(`${t.tagNames.router} has no template.`),this._template}get routeChildNodes(){return this._routeChildNodes}get path(){return this._path}set path(t){const e=this._path!==t;this._path=t,e&&this.dispatchEvent(new CustomEvent("wcs-router:path-changed",{detail:t,bubbles:!0}))}get params(){return this._params}get typedParams(){return this._typedParams}get searchParams(){return this._searchParams}get routeName(){return this._routeName}get data(){return this._data}isSameMatch(t){return!!this._hasCommitted&&this._path===t}commitNavigation(t){const e=Object.freeze({...t.params}),a=Object.freeze({...t.typedParams}),n=i(t.search),s=t.data??null,o=this._data!==s,l=!r(this._params,e),c=this._routeName!==t.routeName,h=!r(this._searchParams,n),u=this._path!==t.path;o&&(this._data=s),l&&(this._params=e,this._typedParams=a),c&&(this._routeName=t.routeName),h&&(this._searchParams=n),this._path=t.path,this._hasCommitted=!0,o&&this.dispatchEvent(new CustomEvent("wcs-router:data-changed",{detail:this._data,bubbles:!0})),l&&this.dispatchEvent(new CustomEvent("wcs-router:params-changed",{detail:{params:this._params,typedParams:this._typedParams},bubbles:!0})),c&&this.dispatchEvent(new CustomEvent("wcs-router:route-name-changed",{detail:this._routeName,bubbles:!0})),h&&this.dispatchEvent(new CustomEvent("wcs-router:search-changed",{detail:this._searchParams,bubbles:!0})),u&&this.dispatchEvent(new CustomEvent("wcs-router:path-changed",{detail:t.path,bubbles:!0}))}get fallbackRoute(){return this._fallbackRoute}set fallbackRoute(t){this._fallbackRoute=t}get navigateUrl(){return this._navigateUrl}set navigateUrl(e){null!=e&&""!==e&&this._navigateUrl!==e&&(this._navigateUrl=e,this.navigate(e).catch(e=>{console.error(`${t.tagNames.router} navigate failed:`,e)}).finally(()=>{this._navigateUrl=null,this.dispatchEvent(new CustomEvent("wcs-router:navigate-url-changed",{detail:null,bubbles:!0}))}))}get replaceUrl(){return this._replaceUrl}set replaceUrl(e){null!=e&&""!==e&&this._replaceUrl!==e&&(this._replaceUrl=e,this.replace(e).catch(e=>{console.error(`${t.tagNames.router} replace failed:`,e)}).finally(()=>{this._replaceUrl=null,this.dispatchEvent(new CustomEvent("wcs-router:replace-url-changed",{detail:null,bubbles:!0}))}))}async navigate(t){await this._performNavigation(t,!1)}async replace(t){await this._performNavigation(t,!0)}async _performNavigation(t,e){const a=st(t),n=""!==a.pathname||""===a.search&&""===a.hash?this._joinInternalPath(this._basename,a.pathname):window.location.pathname,s=n+it(a.search)+a.hash,i=nt();if(i?.navigate){const t=e?i.navigate(s,{history:"replace"}):i.navigate(s);await(t?.finished)}else{e?history.replaceState(null,"",s):history.pushState(null,"",s);const t=this._normalizePathname(n),i=this.isSameMatch(M(t,this._basename));await at(this,this.outlet,t,this._path,it(a.search))&&!i&&window.scrollTo(0,0),this._notifyLocationChange()}}_isOwnPath(t){return""===this._basename||(t===this._basename||t.startsWith(this._basename+"/"))}_onNavigateFunc(e){if(!e.canIntercept||e.hashChange||null!==e.downloadRequest)return;const a=new URL(e.destination.url),n=this._normalizePathname(a.pathname);if(!this._isOwnPath(n))return;const s=this.isSameMatch(M(n,this._basename)),i=s&&("push"===e.navigationType||"replace"===e.navigationType),r=a.search,o=this;e.intercept({handler:async()=>{try{await at(o,o.outlet,n,o.path,r)}catch(e){throw console.error(`${t.tagNames.router} applyRoute failed:`,e),e}},scroll:i?"manual":"after-transition",focusReset:s||null!==o.focusPolicy?"manual":"after-transition"})}_onNavigate=this._onNavigateFunc.bind(this);_onPopState=async()=>{const t=this._normalizePathname(window.location.pathname);this._isOwnPath(t)&&(await at(this,this.outlet,t,this._path,window.location.search||""),this._notifyLocationChange())};async _initialize(){this._initializing=!0;try{const e=ot();this._basename=this._normalizeBasename(this.getAttribute("basename")||this._getBasename()||"");const n=null!==document.querySelector("base[href]"),s=new URL(window.location.href);""!==this._basename||n||"/"===s.pathname||a(`${t.tagNames.router} basename is empty, but current path is not "/".`),this._outlet=this._getOutlet(),this._outlet.routesNode=this,this._template=this._getTemplate(),this._template||a(`${t.tagNames.router} should have a <template> child element.`);const i=e?this._template.content.cloneNode(!0):null,r=await $(this),o=!e&&this._outlet.hasAttribute(lt);if(o||this._outlet.rootNode.appendChild(r),null!==i){const t=this._template.content;for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(i)}0===this.routeChildNodes.length&&a(`${t.tagNames.router} has no route definitions.`),e||this._ensureA11yRegion();const l=this._normalizePathname(window.location.pathname);if(e)return await this._renderForSsr(l),void(this._initialized=!0);if(o){if(await this._hydrateFromSsr(l))return this._notifyLocationChange(),void(this._initialized=!0);this._outlet.removeAttribute(lt);const t=this._outlet.rootNode;for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(r)}await at(this,this.outlet,l,this._path,window.location.search||""),(o||null!==q())&&this._offerInitialContentToBinder(),this._notifyLocationChange(),this._initialized=!0}finally{this._initializing=!1}}async _hydrateFromSsr(e){const a=this.outlet.rootNode,n=new Map;let s=!1;if(this._forEachRoute(t=>{n.has(t.absolutePath)?s=!0:n.set(t.absolutePath,t)}),s)return!1;if(null!==a.querySelector(t.tagNames.layoutOutlet))return!1;const i=new Map,r=new Map,o=new Map,l=document.createTreeWalker(a,NodeFilter.SHOW_COMMENT);let c=0;for(;l.nextNode();){const t=l.currentNode,e=t.data;if(c++,e.startsWith(ht)){const a=e.slice(15);if(i.has(a)||!n.has(a))return!1;i.set(a,t)}else if(e.startsWith(ut)){const a=e.slice(18);if(r.has(a)||!n.has(a))return!1;r.set(a,{comment:t,index:c})}else if(e.startsWith(dt)){const a=e.slice(16);if(o.has(a)||!n.has(a))return!1;o.set(a,{comment:t,index:c})}}if(r.size!==o.size)return!1;const h=[];for(const[t,e]of r){const a=o.get(t);if(!a)return!1;if(a.comment.parentNode!==e.comment.parentNode)return!1;if(a.index<=e.index)return!1;h.push({start:e.index,end:a.index})}for(const t of h)for(const e of h)if(t.start<e.start&&e.start<t.end&&t.end<e.end)return!1;const u=M(e,this._basename);let d=L(this,u);if(!d){if(null===this._fallbackRoute)return!1;d={routes:[this._fallbackRoute],params:{},typedParams:{},path:u,lastPath:this._path}}if(d.lastPath=this._path,d.routes.length!==r.size)return!1;for(const t of d.routes)if(!r.has(t.absolutePath))return!1;const m=new Set;for(const t of this.routeChildNodes)m.add(t.absolutePath);for(const t of d.routes)for(const e of t.routeChildNodes)m.add(e.absolutePath);if(i.size!==m.size)return!1;for(const t of m)if(!i.has(t))return!1;for(const[t,e]of i){const a=n.get(t);e.parentNode.replaceChild(a.placeHolder,e)}for(const e of d.routes){const a=r.get(e.absolutePath).comment,n=o.get(e.absolutePath).comment,s=[],i=new Set;let l=a.nextSibling;for(;null!==l&&l!==n;){if(l.nodeType===Node.COMMENT_NODE){const t=l.data;if(t.startsWith(ut)){const e=t.slice(18);l=o.get(e).comment.nextSibling;continue}}if(i.has(l))l=l.nextSibling;else{if(l.nodeType===Node.ELEMENT_NODE&&l.tagName.toLowerCase()===t.tagNames.link){const t=l.anchorElement;null!==t&&i.add(t)}s.push(l),l=l.nextSibling}}e.adoptChildNodes(s)}for(const{comment:t}of r.values())t.remove();for(const{comment:t}of o.values())t.remove();this.outlet.removeAttribute(lt);for(const t of d.routes)W(t,d);return this.outlet.lastRoutes=d.routes,!await et(this,d)||(this.commitNavigation({params:d.params,typedParams:d.typedParams,routeName:d.routes[d.routes.length-1].name,data:d.data??null,search:window.location.search||"",path:u}),!0)}async _renderForSsr(e){const n=M(e,this._basename),s=L(this,n),i=s?.routes??(null!==this._fallbackRoute?[this._fallbackRoute]:[]);if(0===i.length&&a(`${t.tagNames.router} No route matched for path: ${n}`),!i.some(t=>t.hasGuard)){await at(this,this.outlet,e,this._path,window.location.search||""),this._offerInitialContentToBinder(),this._forEachRoute(t=>{t.placeHolder.data=`${ht}${t.absolutePath}`}),this.outlet.setAttribute(lt,"");for(const t of this.outlet.lastRoutes){const e=t.placeHolder.parentNode,a=t.childNodeArray,n=document.createComment(`${ut}${t.absolutePath}`),s=document.createComment(`${dt}${t.absolutePath}`);e.insertBefore(n,a[0]??t.placeHolder.nextSibling);const i=a[a.length-1];e.insertBefore(s,i?i.nextSibling:n.nextSibling)}}}_offerInitialContentToBinder(){for(const t of this.outlet.lastRoutes)for(const e of t.childNodeArray)e.nodeType===Node.ELEMENT_NODE&&J(e)}_forEachRoute(t,e=this){for(const a of e.routeChildNodes)t(a),this._forEachRoute(t,a)}async connectedCallback(){if(function(t){const e=t.constructor?.wcBindable,a=e?.inputs;if(void 0!==a)for(const e of a){const a=e.name;if(!Object.prototype.hasOwnProperty.call(t,a))continue;if(!rt(t,a))continue;const n=t,s=n[a];delete n[a],n[a]=s}}(this),ot()){try{await Promise.resolve(),this.hasAttribute("enable-ssr")&&!this._initialized&&await this._initialize()}catch(t){throw this._rejectConnectedCallback?.(t),t}return void this._resolveConnectedCallback?.()}if(!this._initialized){this._disconnectedDuringInit=!1;try{null===this._getTemplate()&&await Promise.resolve(),await this._initialize()}catch(t){throw this._rejectConnectedCallback?.(t),t}if(this._disconnectedDuringInit)return void this._resolveConnectedCallback?.()}this._initialized&&this._ensureA11yRegion();const t=nt();t&&!this._listeningNavigate&&(t.addEventListener("navigate",this._onNavigate),this._listeningNavigate=!0),t||this._listeningPopState||(window.addEventListener("popstate",this._onPopState),this._listeningPopState=!0),this._resolveConnectedCallback?.()}disconnectedCallback(){this._initializing&&(this._disconnectedDuringInit=!0),this._listeningNavigate&&(nt()?.removeEventListener("navigate",this._onNavigate),this._listeningNavigate=!1),this._listeningPopState&&(window.removeEventListener("popstate",this._onPopState),this._listeningPopState=!1),null!==this._a11yRegion&&(this._a11yRegion.remove(),this._a11yRegion=null)}}const gt=["title","rel","target","download","hreflang","lang","dir"];class ft extends HTMLElement{static get observedAttributes(){return["to",...gt]}_childNodeArray=[];_uuid=e();_path="";_router=null;_anchorElement=null;_initialized=!1;_onClick;constructor(){super()}get uuid(){return this._uuid}get router(){if(this._router)return this._router;const e=this.closest(t.tagNames.router);if(e)return this._router=e;const n=this.getRootNode(),s=n.querySelector?.(t.tagNames.router);if(s)return this._router=s;a(`${t.tagNames.link} is not connected to a router.`)}_initialize(){this.style.display="none",this._childNodeArray=Array.from(this.childNodes),this._path=this.getAttribute("to")||"",this._initialized=!0}_normalizePathname(t){return O(t)}_joinInternalPath(t,e){const a=F(t),n=e.startsWith("/")?e:"/"+e,s=this._normalizePathname(n);return a?"/"===s?a+"/":a+s:s}_isInternalTarget(t){return t.startsWith("/")||t.startsWith("?")}_setAnchorHref(e,n){if(this._isInternalTarget(n)){const{pathname:t,search:a,hash:s}=st(n),i=""===t?window.location.pathname:this._joinInternalPath(this.router.basename,t);e.href=i+it(a)+s}else try{e.href=new URL(n).toString()}catch{a(`[${t.tagNames.link}] Invalid URL in 'to' attribute: ${n}`)}}connectedCallback(){ot()?queueMicrotask(()=>{this.isConnected&&this._connect()}):this._connect()}_findSsrAnchor(){const t=this.nextElementSibling;return null!==t&&"A"===t.tagName&&t.hasAttribute(ct)?t:null}_connect(){this._initialized||this._initialize();const t=this.parentNode;if(!t)return;const e=this._findSsrAnchor();let a;if(null!==e)a=e,a.removeAttribute(ct),this._childNodeArray=Array.from(a.childNodes),this._setAnchorHref(a,this._path);else{const e=this.nextSibling;a=document.createElement("a"),this._setAnchorHref(a,this._path);for(const t of Array.from(this.attributes))(t.name.startsWith("aria-")||gt.includes(t.name))&&a.setAttribute(t.name,t.value);for(const t of this._childNodeArray)a.appendChild(t);e?t.insertBefore(a,e):t.appendChild(a)}if(this._anchorElement=a,ot())return this._updateActiveState(),void a.setAttribute(ct,"");nt()?.addEventListener("currententrychange",this._updateActiveState),window.addEventListener("wcs:navigate",this._updateActiveState),window.addEventListener("popstate",this._updateActiveState),this._isInternalTarget(this._path)&&!nt()?.navigate&&(this._onClick=async t=>{t.defaultPrevented||0===t.button&&(t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||this._isInternalTarget(this._path)&&(t.preventDefault(),await this.router.navigate(this._path),this._updateActiveState()))},a.addEventListener("click",this._onClick)),this._updateActiveState()}disconnectedCallback(){nt()?.removeEventListener("currententrychange",this._updateActiveState),window.removeEventListener("wcs:navigate",this._updateActiveState),window.removeEventListener("popstate",this._updateActiveState);const t=this._anchorElement;t&&(this._onClick&&(t.removeEventListener("click",this._onClick),this._onClick=void 0),t.remove(),this._anchorElement=null);for(const e of this._childNodeArray)t&&e.parentNode===t&&t.removeChild(e);this._router=null}attributeChangedCallback(t,e,a){if("to"===t&&e!==a)this._path=a||"",this._anchorElement&&(this._setAnchorHref(this._anchorElement,this._path),this._updateActiveState());else if(gt.includes(t)){const e=this._anchorElement;e&&(null===a?e.removeAttribute(t):e.setAttribute(t,a))}}_updateActiveState=()=>{this._path.startsWith("?")&&this._anchorElement&&this._setAnchorHref(this._anchorElement,this._path);const t=this._normalizePathname(new URL(window.location.href).pathname),{pathname:e}=st(this._path),a=this._normalizePathname(this._isInternalTarget(this._path)?""===e?window.location.pathname:this._joinInternalPath(this.router.basename,e):e);this._anchorElement&&(t===a?(this._anchorElement.classList.add("active"),this._anchorElement.setAttribute("aria-current","page")):(this._anchorElement.classList.remove("active"),this._anchorElement.removeAttribute("aria-current")))};get anchorElement(){return this._anchorElement}}const _t=[],bt=new Map;let yt=!1;const wt=new WeakMap;class Nt extends HTMLElement{_initialized=!1;_childElementArray=[];constructor(){super(),this.style.display="none"}_initialize(){if(!this._initialized){this._initialized=!0,this._childElementArray=Array.from(this.children);for(const t of this._childElementArray)this.removeChild(t)}}connectedCallback(){this._initialize(),yt||(this._captureInitialHead(),yt=!0),_t.push(this),this._reapplyHead()}disconnectedCallback(){const t=_t.indexOf(this);-1!==t&&_t.splice(t,1),this._reapplyHead()}get childElementArray(){return this._initialized||a("Head component is not initialized yet."),this._childElementArray}_getKey(t){const e=wt.get(t);if(void 0!==e)return e;const a=this._computeKey(t);return wt.set(t,a),a}_computeKey(t){const e=t.tagName.toLowerCase();if("title"===e)return"title";if("meta"===e){return`meta:${t.getAttribute("name")||""}:${t.getAttribute("property")||""}:${t.getAttribute("http-equiv")||""}:${t.hasAttribute("charset")?"charset":""}:${t.getAttribute("media")||""}`}if("link"===e){return`link:${t.getAttribute("rel")||""}:${t.getAttribute("href")||""}:${t.getAttribute("media")||""}:${t.getAttribute("hreflang")||""}`}if("base"===e)return"base";if("script"===e){const e=t.getAttribute("src")||"",a=t.getAttribute("id")||"",n=t.getAttribute("type")||"";return e||a?`script:${e}:${a}:${n}`:`script::${n}:${t.outerHTML.slice(0,100)}`}if("style"===e){const e=t.getAttribute("id")||"",a=t.getAttribute("media")||"";return e?`style:${e}:${a}`:`style::${a}:${t.outerHTML.slice(0,100)}`}return`${e}:${t.outerHTML.slice(0,100)}`}_buildHeadElementMap(){const t=new Map;for(const e of Array.from(document.head.children)){const a=this._getKey(e);t.has(a)||t.set(a,e)}return t}_captureInitialHead(){const t=document.head;for(const e of Array.from(t.children)){const t=this._getKey(e);bt.has(t)||bt.set(t,e.cloneNode(!0))}}_reapplyHead(){const e=new Set;for(const t of _t)for(const a of t._childElementArray)e.add(this._getKey(a));for(const t of bt.keys())e.add(t);const a=this._buildHeadElementMap();for(const t of a.keys())e.add(t);for(const n of e){let e=null;for(let t=_t.length-1;t>=0;t--){const a=_t[t];for(const t of a._childElementArray)if(this._getKey(t)===n){e=t.cloneNode(!0);break}if(e)break}if(!e&&bt.has(n)){e=bt.get(n).cloneNode(!0)}const s=a.get(n)??null;e?(s?s.replaceWith(e):document.head.appendChild(e),a.set(n,e),J(e)||Z(e,`<${e.tagName.toLowerCase()}> inside <${t.tagNames.head}>`,`<${t.tagNames.head}> reflects its children into <head> with cloneNode, so the clone is not the node that was bound. Load @wcstack/state on this page, or write the value statically here.`)):(s?.remove(),a.delete(n))}}}function vt(e=customElements){e.get(t.tagNames.layout)||e.define(t.tagNames.layout,_),e.get(t.tagNames.layoutOutlet)||e.define(t.tagNames.layoutOutlet,v),e.get(t.tagNames.outlet)||e.define(t.tagNames.outlet,b),e.get(t.tagNames.route)||e.define(t.tagNames.route,c),e.get(t.tagNames.router)||e.define(t.tagNames.router,pt),e.get(t.tagNames.link)||e.define(t.tagNames.link,ft),e.get(t.tagNames.head)||e.define(t.tagNames.head,Nt)}var Ct;await void vt(Ct);
|
|
2
2
|
//# sourceMappingURL=auto.min.js.map
|