@wcstack/router 1.33.0 → 2.1.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 +3 -3
- package/README.md +3 -3
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +16 -6
- package/dist/index.esm.js +59 -23
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -185,10 +185,14 @@ interface IRouter extends IRouteChildContainer {
|
|
|
185
185
|
commitNavigation(commit: IRouterCommit): void;
|
|
186
186
|
/** `announce=` 用 live region。未生成なら null(docs/a11y-design.md §3-4) */
|
|
187
187
|
readonly a11yRegion: HTMLElement | null;
|
|
188
|
-
/**
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
/**
|
|
189
|
+
* `<wcs-router focus=...>` の正規化済みポリシー。有効値以外(属性なし・空文字・
|
|
190
|
+
* 未知値)はすべて null — 判定箇所ごとに生文字列の解釈が割れないよう、
|
|
191
|
+
* 解釈はこの getter の 1 箇所に閉じる(docs/a11y-design.md §3-5)。
|
|
192
|
+
*/
|
|
193
|
+
readonly focusPolicy: "heading" | null;
|
|
194
|
+
/** `<wcs-router announce=...>` の正規化済みポリシー(focusPolicy と同じ規範) */
|
|
195
|
+
readonly announcePolicy: "title" | null;
|
|
192
196
|
}
|
|
193
197
|
interface IOutlet {
|
|
194
198
|
routesNode: IRouter;
|
|
@@ -245,8 +249,14 @@ declare class Router extends HTMLElement implements IRouter {
|
|
|
245
249
|
constructor();
|
|
246
250
|
get connectedCallbackPromise(): Promise<void>;
|
|
247
251
|
get a11yRegion(): HTMLElement | null;
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
/**
|
|
253
|
+
* `focus=` / `announce=` は有効値の union へ正規化して露出する。空文字やタイポは
|
|
254
|
+
* 「ポリシーなし」= null — intercept の focusReset 決定(_onNavigateFunc)と
|
|
255
|
+
* applyA11yPolicies の適用判定が同じ値を見るため、「manual だけ渡して実フォーカス
|
|
256
|
+
* 移動が無い」不整合が構造的に起きない(docs/a11y-design.md §3-5)。
|
|
257
|
+
*/
|
|
258
|
+
get focusPolicy(): "heading" | null;
|
|
259
|
+
get announcePolicy(): "title" | null;
|
|
250
260
|
/**
|
|
251
261
|
* `announce=` 用 live region を <wcs-router> 直下に空のまま用意する
|
|
252
262
|
* (docs/a11y-design.md §3-4)。
|
package/dist/index.esm.js
CHANGED
|
@@ -1262,11 +1262,15 @@ async function parse(routerNode) {
|
|
|
1262
1262
|
* - `announce="title"`: commit 時点の document.title のスナップショットを
|
|
1263
1263
|
* live region へ書き込む(D2)。<wcs-head> の静的 title は mutate() 内で同期に
|
|
1264
1264
|
* 差し替わるため、ここでは必ず新ルートの値が読める。バインド title の遅延窓・
|
|
1265
|
-
* ナビゲーション外の title
|
|
1266
|
-
*
|
|
1267
|
-
*
|
|
1268
|
-
*
|
|
1269
|
-
* focusReset
|
|
1265
|
+
* ナビゲーション外の title 変化・同一 title 連続遷移の再読み上げには追従しない
|
|
1266
|
+
* (README の明記された制限)。
|
|
1267
|
+
* - `focus="heading"`: リーフ route が挿入した内容の最初の可視 h1〜h6 に
|
|
1268
|
+
* tabindex="-1" を付けて focus() する。可視見出しが無ければ、仕様既定の
|
|
1269
|
+
* focusReset(after-transition)を自前で再現する — [autofocus] があればそこへ、
|
|
1270
|
+
* 無ければ blur で body へ落とす(§3-4 の規定)。focus= オプトインは Navigation
|
|
1271
|
+
* API 経路で focusReset: "manual" を渡してブラウザ既定を止めているため、旧
|
|
1272
|
+
* フォーカス要素(永続ナビのリンク等)が遷移後も生き残るケースでは、ここで
|
|
1273
|
+
* 落とさない限りフォーカスが前画面に取り残される。
|
|
1270
1274
|
*/
|
|
1271
1275
|
function applyA11yPolicies(routerNode, matchResult) {
|
|
1272
1276
|
if (routerNode.announcePolicy === "title") {
|
|
@@ -1285,13 +1289,28 @@ function applyA11yPolicies(routerNode, matchResult) {
|
|
|
1285
1289
|
}
|
|
1286
1290
|
heading.focus();
|
|
1287
1291
|
}
|
|
1292
|
+
else {
|
|
1293
|
+
// 仕様既定の focusReset の再現(優先順も仕様と同じ: [autofocus] → body)。
|
|
1294
|
+
// フォールバック経路(pushState)にはブラウザのリセットが元々無いため、
|
|
1295
|
+
// ここで両経路の挙動が揃う(§3-2 の scroll と同じ構図)。
|
|
1296
|
+
const autofocus = document.querySelector("[autofocus]");
|
|
1297
|
+
if (autofocus !== null) {
|
|
1298
|
+
autofocus.focus();
|
|
1299
|
+
}
|
|
1300
|
+
else if (document.activeElement instanceof HTMLElement) {
|
|
1301
|
+
document.activeElement.blur();
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1288
1304
|
}
|
|
1289
1305
|
}
|
|
1290
1306
|
/**
|
|
1291
|
-
* リーフ route のトップレベルノード列を document order
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1307
|
+
* リーフ route のトップレベルノード列を document order で走査し、最初の可視の
|
|
1308
|
+
* 見出しを返す。祖先 route の内容へは遡らない — 読者が「新しい画面」と認識する
|
|
1309
|
+
* 単位はリーフである(docs/a11y-design.md §3-4)。ルート内容は Comment placeholder
|
|
1310
|
+
* の兄弟として挿入されるため安定した「箱」が無く、内容から探すのが唯一の現実解。
|
|
1311
|
+
*
|
|
1312
|
+
* 非表示(hidden / display:none 等)の見出しへの focus() は no-op になるため、
|
|
1313
|
+
* checkVisibility でスキップする。未実装環境(happy-dom)では可視扱い。
|
|
1295
1314
|
*/
|
|
1296
1315
|
function findFirstHeading(nodes) {
|
|
1297
1316
|
for (const node of nodes) {
|
|
@@ -1299,15 +1318,22 @@ function findFirstHeading(nodes) {
|
|
|
1299
1318
|
continue;
|
|
1300
1319
|
const element = node;
|
|
1301
1320
|
if (/^H[1-6]$/.test(element.tagName)) {
|
|
1302
|
-
|
|
1321
|
+
if (isVisible(element)) {
|
|
1322
|
+
return element;
|
|
1323
|
+
}
|
|
1324
|
+
continue;
|
|
1303
1325
|
}
|
|
1304
|
-
const descendant
|
|
1305
|
-
|
|
1306
|
-
|
|
1326
|
+
for (const descendant of element.querySelectorAll("h1,h2,h3,h4,h5,h6")) {
|
|
1327
|
+
if (isVisible(descendant)) {
|
|
1328
|
+
return descendant;
|
|
1329
|
+
}
|
|
1307
1330
|
}
|
|
1308
1331
|
}
|
|
1309
1332
|
return null;
|
|
1310
1333
|
}
|
|
1334
|
+
function isVisible(element) {
|
|
1335
|
+
return element.checkVisibility?.() !== false;
|
|
1336
|
+
}
|
|
1311
1337
|
|
|
1312
1338
|
function testPath(route, path, segments) {
|
|
1313
1339
|
const params = {};
|
|
@@ -2247,11 +2273,17 @@ class Router extends HTMLElement {
|
|
|
2247
2273
|
get a11yRegion() {
|
|
2248
2274
|
return this._a11yRegion;
|
|
2249
2275
|
}
|
|
2276
|
+
/**
|
|
2277
|
+
* `focus=` / `announce=` は有効値の union へ正規化して露出する。空文字やタイポは
|
|
2278
|
+
* 「ポリシーなし」= null — intercept の focusReset 決定(_onNavigateFunc)と
|
|
2279
|
+
* applyA11yPolicies の適用判定が同じ値を見るため、「manual だけ渡して実フォーカス
|
|
2280
|
+
* 移動が無い」不整合が構造的に起きない(docs/a11y-design.md §3-5)。
|
|
2281
|
+
*/
|
|
2250
2282
|
get focusPolicy() {
|
|
2251
|
-
return this.getAttribute('focus');
|
|
2283
|
+
return this.getAttribute('focus') === 'heading' ? 'heading' : null;
|
|
2252
2284
|
}
|
|
2253
2285
|
get announcePolicy() {
|
|
2254
|
-
return this.getAttribute('announce');
|
|
2286
|
+
return this.getAttribute('announce') === 'title' ? 'title' : null;
|
|
2255
2287
|
}
|
|
2256
2288
|
/**
|
|
2257
2289
|
* `announce=` 用 live region を <wcs-router> 直下に空のまま用意する
|
|
@@ -2614,8 +2646,10 @@ class Router extends HTMLElement {
|
|
|
2614
2646
|
// 契約の変更にあたる(docs/a11y-design.md §3-1)。same-match の扱いは
|
|
2615
2647
|
// docs/router-state-contract-design.md §4.4 / D6b。
|
|
2616
2648
|
scroll: sameMatchScrollManual ? "manual" : "after-transition",
|
|
2617
|
-
// focus=
|
|
2618
|
-
// after-transition リセットが二重処理になる(docs/a11y-design.md §3-5)。
|
|
2649
|
+
// focus= が有効値のときのみ manual。渡さないと router のフォーカス移動と
|
|
2650
|
+
// ブラウザの after-transition リセットが二重処理になる(docs/a11y-design.md §3-5)。
|
|
2651
|
+
// focusPolicy は正規化済み getter — 空文字・未知値は null になり仕様既定へ
|
|
2652
|
+
// 委譲されるため、「manual だけ渡して何もしない」状態は構造的に生じない。
|
|
2619
2653
|
// same-match は常に manual — 1 打鍵ごとにフォーカスが body へ飛ぶ事故の防止。
|
|
2620
2654
|
focusReset: sameMatch || routesNode.focusPolicy !== null ? "manual" : "after-transition",
|
|
2621
2655
|
});
|
|
@@ -3091,10 +3125,12 @@ class Router extends HTMLElement {
|
|
|
3091
3125
|
}
|
|
3092
3126
|
}
|
|
3093
3127
|
|
|
3094
|
-
// 生成 anchor へミラーする固定属性(docs/a11y-design.md §5)。`
|
|
3095
|
-
//
|
|
3096
|
-
// aria
|
|
3097
|
-
|
|
3128
|
+
// 生成 anchor へミラーする固定属性(docs/a11y-design.md §5)。`lang` / `dir` は SR の
|
|
3129
|
+
// 読み上げ言語・方向に直結するため含める(多言語ナビで anchor 側に届かないと
|
|
3130
|
+
// 読み上げが崩れる)。`aria-*` は開集合なので observedAttributes には載せられず、
|
|
3131
|
+
// anchor 生成時の一括コピーのみ(接続後の動的 aria-* 変更 — data-wcs バインド経由を
|
|
3132
|
+
// 含む — には追従しない。README の明記された制限)。
|
|
3133
|
+
const MIRRORED_ATTRIBUTES = ['title', 'rel', 'target', 'download', 'hreflang', 'lang', 'dir'];
|
|
3098
3134
|
class Link extends HTMLElement {
|
|
3099
3135
|
static get observedAttributes() {
|
|
3100
3136
|
return ['to', ...MIRRORED_ATTRIBUTES];
|
|
@@ -3236,7 +3272,7 @@ class Link extends HTMLElement {
|
|
|
3236
3272
|
const nextSibling = this.nextSibling;
|
|
3237
3273
|
link = document.createElement('a');
|
|
3238
3274
|
this._setAnchorHref(link, this._path);
|
|
3239
|
-
// ホスト属性の転送: `aria-*` prefix + 固定
|
|
3275
|
+
// ホスト属性の転送: `aria-*` prefix + 固定 7 名の一括コピー。
|
|
3240
3276
|
// to / style / class は除外 — ホストは display:none であり、class は active 契約を持つ。
|
|
3241
3277
|
for (const attr of Array.from(this.attributes)) {
|
|
3242
3278
|
if (attr.name.startsWith('aria-') || MIRRORED_ATTRIBUTES.includes(attr.name)) {
|
|
@@ -3642,7 +3678,7 @@ function bootstrapRouter(config, registry) {
|
|
|
3642
3678
|
registerComponents(registry);
|
|
3643
3679
|
}
|
|
3644
3680
|
|
|
3645
|
-
var version = "1.
|
|
3681
|
+
var version = "2.1.0";
|
|
3646
3682
|
var pkg = {
|
|
3647
3683
|
version: version};
|
|
3648
3684
|
|