@wcstack/router 1.7.1 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -141,7 +141,30 @@
141
141
  | `guardHandler` | ガード判定関数を設定 |
142
142
 
143
143
  ガード判定関数の型:
144
- function (toPath: string, fromPath: string): boolean | Promise<boolean>
144
+ `(toPath: string, fromPath: string) => boolean | Promise<boolean>`
145
+
146
+ #### GuardHandler(wcs-guard-handler)
147
+
148
+ `<wcs-route>` の子要素として配置し、ガード判定関数を宣言的に定義します。`<script type="module">` の `default export` で判定関数を返してください。`<wcs-guard-handler>` 要素自体はパース後にDOMから除去されます。
149
+
150
+ ```html
151
+ <wcs-route path="/dashboard" guard="/login">
152
+ <wcs-guard-handler>
153
+ <script type="module">
154
+ export default function(toPath, fromPath) {
155
+ return document.cookie.includes('session=');
156
+ }
157
+ </script>
158
+ </wcs-guard-handler>
159
+ <dashboard-page></dashboard-page>
160
+ </wcs-route>
161
+ ```
162
+
163
+ - `guard` 属性の値はガードがキャンセルされた場合のリダイレクト先パス
164
+ - 判定関数が `false` を返すとナビゲーションがキャンセルされ、`guard` 属性のパスへ遷移
165
+ - 判定関数は `Promise<boolean>` を返すことも可能(非同期チェック対応)
166
+ - `<wcs-route>` の外に配置された `<wcs-guard-handler>` は無視される
167
+ - `<script type="module">` がない場合、`guardHandler` は設定されない
145
168
 
146
169
  #### 型付きパラメータ
147
170
 
package/README.md CHANGED
@@ -141,7 +141,30 @@ Displays children when the route path matches. Match priority is static paths ov
141
141
  | `guardHandler` | Sets the guard decision function. |
142
142
 
143
143
  Guard decision function type:
144
- function (toPath: string, fromPath: string): boolean | Promise<boolean>
144
+ `(toPath: string, fromPath: string) => boolean | Promise<boolean>`
145
+
146
+ #### GuardHandler (wcs-guard-handler)
147
+
148
+ Place as a child of `<wcs-route>` to declaratively define a guard decision function. Export the function as the `default export` from a `<script type="module">`. The `<wcs-guard-handler>` element itself is removed from the DOM after parsing.
149
+
150
+ ```html
151
+ <wcs-route path="/dashboard" guard="/login">
152
+ <wcs-guard-handler>
153
+ <script type="module">
154
+ export default function(toPath, fromPath) {
155
+ return document.cookie.includes('session=');
156
+ }
157
+ </script>
158
+ </wcs-guard-handler>
159
+ <dashboard-page></dashboard-page>
160
+ </wcs-route>
161
+ ```
162
+
163
+ - The `guard` attribute value is the redirect path when the guard cancels navigation
164
+ - If the function returns `false`, navigation is cancelled and the user is redirected to the `guard` path
165
+ - The function can return `Promise<boolean>` for async checks
166
+ - `<wcs-guard-handler>` placed outside a `<wcs-route>` is ignored
167
+ - If no `<script type="module">` is present, `guardHandler` is not set
145
168
 
146
169
  #### Typed Parameters
147
170
 
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ interface ITagNames {
6
6
  readonly layoutOutlet: string;
7
7
  readonly link: string;
8
8
  readonly head: string;
9
+ readonly guardHandler: string;
9
10
  }
10
11
  interface IWritableTagNames {
11
12
  route?: string;
@@ -15,6 +16,7 @@ interface IWritableTagNames {
15
16
  layoutOutlet?: string;
16
17
  link?: string;
17
18
  head?: string;
19
+ guardHandler?: string;
18
20
  }
19
21
  interface IConfig {
20
22
  readonly tagNames: ITagNames;
@@ -112,6 +114,116 @@ interface IOutlet {
112
114
  lastRoutes: IRoute[];
113
115
  }
114
116
 
117
+ /**
118
+ * AppRoutes - Root component for @wcstack/router
119
+ *
120
+ * Container element that manages route definitions and navigation.
121
+ */
122
+ declare class Router extends HTMLElement implements IRouter {
123
+ static wcBindable: IWcBindable;
124
+ private static _instance;
125
+ private _outlet;
126
+ private _template;
127
+ private _routeChildNodes;
128
+ private _basename;
129
+ private _path;
130
+ private _initialized;
131
+ private _fallbackRoute;
132
+ private _listeningPopState;
133
+ private _navigateUrl;
134
+ constructor();
135
+ /**
136
+ * Normalize a URL pathname to a route path.
137
+ * - ensure leading slash
138
+ * - collapse multiple slashes
139
+ * - treat trailing file extensions (e.g. .html) as directory root
140
+ * - remove trailing slash except root "/"
141
+ */
142
+ private _normalizePathname;
143
+ /**
144
+ * Normalize basename.
145
+ * - "" or "/" -> ""
146
+ * - "/app/" -> "/app"
147
+ * - "/app/index.html" -> "/app"
148
+ */
149
+ private _normalizeBasename;
150
+ private _joinInternalPath;
151
+ private _notifyLocationChange;
152
+ private _getBasename;
153
+ static get instance(): IRouter;
154
+ static navigate(path: string): void;
155
+ get basename(): string;
156
+ private _getOutlet;
157
+ private _getTemplate;
158
+ get outlet(): IOutlet;
159
+ get template(): HTMLTemplateElement;
160
+ get routeChildNodes(): IRoute[];
161
+ get path(): string;
162
+ /**
163
+ * applyRoute 内で設定される値です。
164
+ */
165
+ set path(value: string);
166
+ get fallbackRoute(): IRoute | null;
167
+ /**
168
+ * Routeのfallback属性がある場合にそのルートを設定します。
169
+ */
170
+ set fallbackRoute(value: IRoute | null);
171
+ get navigateUrl(): string | null;
172
+ set navigateUrl(value: string | null);
173
+ navigate(path: string): Promise<void>;
174
+ private _onNavigateFunc;
175
+ private _onNavigate;
176
+ private _onPopState;
177
+ private _initialize;
178
+ connectedCallback(): Promise<void>;
179
+ disconnectedCallback(): void;
180
+ }
181
+
182
+ declare class Route extends HTMLElement implements IRoute {
183
+ static wcBindable: IWcBindable;
184
+ private _core;
185
+ private _routeParentNode;
186
+ private _routeChildNodes;
187
+ private _routerNode;
188
+ private _uuid;
189
+ private _placeHolder;
190
+ private _childNodeArray;
191
+ private _childIndex;
192
+ private _initialized;
193
+ constructor();
194
+ get routeParentNode(): IRoute | null;
195
+ get routeChildNodes(): IRoute[];
196
+ get routerNode(): IRouter;
197
+ get uuid(): string;
198
+ get placeHolder(): Comment;
199
+ get childNodeArray(): Node[];
200
+ get routes(): IRoute[];
201
+ get childIndex(): number;
202
+ get path(): string;
203
+ get name(): string;
204
+ get isRelative(): boolean;
205
+ get absolutePath(): string;
206
+ get segmentInfos(): ISegmentInfo[];
207
+ get absoluteSegmentInfos(): ISegmentInfo[];
208
+ get params(): Record<string, string>;
209
+ get typedParams(): Record<string, any>;
210
+ get paramNames(): string[];
211
+ get absoluteParamNames(): string[];
212
+ get weight(): number;
213
+ get absoluteWeight(): number;
214
+ get segmentCount(): number;
215
+ get absoluteSegmentCount(): number;
216
+ get fullpath(): string;
217
+ get guardHandler(): GuardHandler;
218
+ set guardHandler(value: GuardHandler);
219
+ setParams(params: Record<string, string>, typedParams: Record<string, any>): void;
220
+ clearParams(): void;
221
+ shouldChange(newParams: Record<string, string>): boolean;
222
+ guardCheck(matchResult: IRouteMatchResult): Promise<void>;
223
+ testAncestorNode(ancestorNode: IRoute): boolean;
224
+ initialize(routerNode: IRouter, routeParentNode: IRoute | null): void;
225
+ }
226
+
115
227
  interface RouteParseOptions {
116
228
  isIndex?: boolean;
117
229
  isFallback?: boolean;
@@ -170,5 +282,5 @@ declare class RouteCore extends EventTarget {
170
282
  guardCheck(matchResult: IRouteMatchResult): Promise<void>;
171
283
  }
172
284
 
173
- export { RouteCore, bootstrapRouter, getConfig };
285
+ export { Route, RouteCore, Router, bootstrapRouter, getConfig };
174
286
  export type { IWritableConfig, IWritableTagNames, RouteParseOptions };
package/dist/index.esm.js CHANGED
@@ -6,7 +6,8 @@ const _config = {
6
6
  layout: "wcs-layout",
7
7
  layoutOutlet: "wcs-layout-outlet",
8
8
  link: "wcs-link",
9
- head: "wcs-head"
9
+ head: "wcs-head",
10
+ guardHandler: "wcs-guard-handler"
10
11
  },
11
12
  enableShadowRoot: false,
12
13
  basenameFileExtensions: [".html"]
@@ -909,6 +910,41 @@ function createLayoutOutlet() {
909
910
  return document.createElement(config.tagNames.layoutOutlet);
910
911
  }
911
912
 
913
+ async function importModule(script) {
914
+ let scriptModule = null;
915
+ const sourceComment = `\n//# sourceURL=wcs-guard-handler\n`;
916
+ const scriptText = script.text + sourceComment;
917
+ if (typeof URL.createObjectURL === 'function') {
918
+ const blob = new Blob([scriptText], { type: "application/javascript" });
919
+ const url = URL.createObjectURL(blob);
920
+ try {
921
+ scriptModule = await import(url);
922
+ }
923
+ catch {
924
+ // Blob URL import failed (e.g. happy-dom), fall through to data: URL
925
+ }
926
+ finally {
927
+ URL.revokeObjectURL(url);
928
+ }
929
+ }
930
+ if (!scriptModule) {
931
+ // Fallback: Base64 data: URL (for test environments)
932
+ const b64 = btoa(String.fromCodePoint(...new TextEncoder().encode(scriptText)));
933
+ scriptModule = await import(`data:application/javascript;base64,${b64}`);
934
+ }
935
+ if (scriptModule && typeof scriptModule.default === 'function') {
936
+ return scriptModule.default;
937
+ }
938
+ return null;
939
+ }
940
+ function loadGuardHandler(script, route) {
941
+ importModule(script).then(handler => {
942
+ if (handler) {
943
+ route.guardHandler = handler;
944
+ }
945
+ });
946
+ }
947
+
912
948
  function _duplicateCheck(routesByPath, route) {
913
949
  let routes = routesByPath.get(route.absolutePath);
914
950
  if (!routes) {
@@ -951,6 +987,16 @@ async function _parseNode(routerNode, node, routes, map, routesByPath) {
951
987
  appendNode = route.placeHolder;
952
988
  element = route;
953
989
  }
990
+ else if (tagName === config.tagNames.guardHandler) {
991
+ if (routes.length > 0) {
992
+ const route = routes[routes.length - 1];
993
+ const script = element.querySelector('script[type="module"]');
994
+ if (script) {
995
+ loadGuardHandler(script, route);
996
+ }
997
+ }
998
+ continue;
999
+ }
954
1000
  else if (tagName === config.tagNames.layout) {
955
1001
  const childFragment = document.createDocumentFragment();
956
1002
  // Move child nodes to fragment to avoid duplication of
@@ -1253,6 +1299,14 @@ function getNavigation() {
1253
1299
  * Container element that manages route definitions and navigation.
1254
1300
  */
1255
1301
  class Router extends HTMLElement {
1302
+ static wcBindable = {
1303
+ protocol: "wc-bindable",
1304
+ version: 1,
1305
+ properties: [
1306
+ { name: "navigateUrl", event: "wcs-router:navigate-url-changed" },
1307
+ { name: "path", event: "wcs-router:path-changed" },
1308
+ ],
1309
+ };
1256
1310
  static _instance = null;
1257
1311
  _outlet = null;
1258
1312
  _template = null;
@@ -1262,6 +1316,7 @@ class Router extends HTMLElement {
1262
1316
  _initialized = false;
1263
1317
  _fallbackRoute = null;
1264
1318
  _listeningPopState = false;
1319
+ _navigateUrl = null;
1265
1320
  constructor() {
1266
1321
  super();
1267
1322
  if (Router._instance) {
@@ -1387,7 +1442,14 @@ class Router extends HTMLElement {
1387
1442
  * applyRoute 内で設定される値です。
1388
1443
  */
1389
1444
  set path(value) {
1445
+ const changed = this._path !== value;
1390
1446
  this._path = value;
1447
+ if (changed) {
1448
+ this.dispatchEvent(new CustomEvent("wcs-router:path-changed", {
1449
+ detail: value,
1450
+ bubbles: true,
1451
+ }));
1452
+ }
1391
1453
  }
1392
1454
  get fallbackRoute() {
1393
1455
  return this._fallbackRoute;
@@ -1398,6 +1460,21 @@ class Router extends HTMLElement {
1398
1460
  set fallbackRoute(value) {
1399
1461
  this._fallbackRoute = value;
1400
1462
  }
1463
+ get navigateUrl() {
1464
+ return this._navigateUrl;
1465
+ }
1466
+ set navigateUrl(value) {
1467
+ if (value === null || value === undefined || value === "")
1468
+ return;
1469
+ this._navigateUrl = value;
1470
+ this.navigate(value).then(() => {
1471
+ this._navigateUrl = null;
1472
+ this.dispatchEvent(new CustomEvent("wcs-router:navigate-url-changed", {
1473
+ detail: null,
1474
+ bubbles: true,
1475
+ }));
1476
+ });
1477
+ }
1401
1478
  async navigate(path) {
1402
1479
  const fullPath = this._joinInternalPath(this._basename, path);
1403
1480
  const navigation = getNavigation();
@@ -1832,5 +1909,5 @@ function bootstrapRouter(config) {
1832
1909
  registerComponents();
1833
1910
  }
1834
1911
 
1835
- export { RouteCore, bootstrapRouter, getConfig };
1912
+ export { Route, RouteCore, Router, bootstrapRouter, getConfig };
1836
1913
  //# sourceMappingURL=index.esm.js.map