@wcstack/router 1.3.11

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.
Files changed (118) hide show
  1. package/README.ja.md +488 -0
  2. package/README.md +486 -0
  3. package/dist/GuardCancel.d.ts +6 -0
  4. package/dist/GuardCancel.d.ts.map +1 -0
  5. package/dist/GuardCancel.js +8 -0
  6. package/dist/GuardCancel.js.map +1 -0
  7. package/dist/Navigation.d.ts +7 -0
  8. package/dist/Navigation.d.ts.map +1 -0
  9. package/dist/Navigation.js +11 -0
  10. package/dist/Navigation.js.map +1 -0
  11. package/dist/applyRoute.d.ts +3 -0
  12. package/dist/applyRoute.d.ts.map +1 -0
  13. package/dist/applyRoute.js +40 -0
  14. package/dist/applyRoute.js.map +1 -0
  15. package/dist/assignParams.d.ts +2 -0
  16. package/dist/assignParams.d.ts.map +1 -0
  17. package/dist/assignParams.js +52 -0
  18. package/dist/assignParams.js.map +1 -0
  19. package/dist/auto.js +3 -0
  20. package/dist/auto.min.js +3 -0
  21. package/dist/bootstrapRouter.d.ts +8 -0
  22. package/dist/bootstrapRouter.d.ts.map +1 -0
  23. package/dist/bootstrapRouter.js +14 -0
  24. package/dist/bootstrapRouter.js.map +1 -0
  25. package/dist/builtinParamTypes.d.ts +38 -0
  26. package/dist/builtinParamTypes.d.ts.map +1 -0
  27. package/dist/builtinParamTypes.js +79 -0
  28. package/dist/builtinParamTypes.js.map +1 -0
  29. package/dist/components/Head.d.ts +29 -0
  30. package/dist/components/Head.d.ts.map +1 -0
  31. package/dist/components/Head.js +173 -0
  32. package/dist/components/Head.js.map +1 -0
  33. package/dist/components/Layout.d.ts +15 -0
  34. package/dist/components/Layout.d.ts.map +1 -0
  35. package/dist/components/Layout.js +86 -0
  36. package/dist/components/Layout.js.map +1 -0
  37. package/dist/components/LayoutOutlet.d.ts +15 -0
  38. package/dist/components/LayoutOutlet.d.ts.map +1 -0
  39. package/dist/components/LayoutOutlet.js +101 -0
  40. package/dist/components/LayoutOutlet.js.map +1 -0
  41. package/dist/components/Link.d.ts +25 -0
  42. package/dist/components/Link.d.ts.map +1 -0
  43. package/dist/components/Link.js +155 -0
  44. package/dist/components/Link.js.map +1 -0
  45. package/dist/components/Outlet.d.ts +16 -0
  46. package/dist/components/Outlet.d.ts.map +1 -0
  47. package/dist/components/Outlet.js +46 -0
  48. package/dist/components/Outlet.js.map +1 -0
  49. package/dist/components/Route.d.ts +61 -0
  50. package/dist/components/Route.d.ts.map +1 -0
  51. package/dist/components/Route.js +348 -0
  52. package/dist/components/Route.js.map +1 -0
  53. package/dist/components/Router.d.ts +62 -0
  54. package/dist/components/Router.d.ts.map +1 -0
  55. package/dist/components/Router.js +237 -0
  56. package/dist/components/Router.js.map +1 -0
  57. package/dist/components/types.d.ts +86 -0
  58. package/dist/components/types.d.ts.map +1 -0
  59. package/dist/components/types.js +2 -0
  60. package/dist/components/types.js.map +1 -0
  61. package/dist/config.d.ts +5 -0
  62. package/dist/config.d.ts.map +1 -0
  63. package/dist/config.js +53 -0
  64. package/dist/config.js.map +1 -0
  65. package/dist/exports.d.ts +3 -0
  66. package/dist/exports.d.ts.map +1 -0
  67. package/dist/exports.js +2 -0
  68. package/dist/exports.js.map +1 -0
  69. package/dist/getCustomTagName.d.ts +2 -0
  70. package/dist/getCustomTagName.d.ts.map +1 -0
  71. package/dist/getCustomTagName.js +12 -0
  72. package/dist/getCustomTagName.js.map +1 -0
  73. package/dist/getUUID.d.ts +2 -0
  74. package/dist/getUUID.d.ts.map +1 -0
  75. package/dist/getUUID.js +11 -0
  76. package/dist/getUUID.js.map +1 -0
  77. package/dist/hideRoute.d.ts +3 -0
  78. package/dist/hideRoute.d.ts.map +1 -0
  79. package/dist/hideRoute.js +7 -0
  80. package/dist/hideRoute.js.map +1 -0
  81. package/dist/index.d.ts +24 -0
  82. package/dist/index.esm.js +1682 -0
  83. package/dist/index.esm.js.map +1 -0
  84. package/dist/index.esm.min.js +2 -0
  85. package/dist/index.esm.min.js.map +1 -0
  86. package/dist/matchRoutes.d.ts +3 -0
  87. package/dist/matchRoutes.d.ts.map +1 -0
  88. package/dist/matchRoutes.js +52 -0
  89. package/dist/matchRoutes.js.map +1 -0
  90. package/dist/parse.d.ts +3 -0
  91. package/dist/parse.d.ts.map +1 -0
  92. package/dist/parse.js +77 -0
  93. package/dist/parse.js.map +1 -0
  94. package/dist/raiseError.d.ts +2 -0
  95. package/dist/raiseError.d.ts.map +1 -0
  96. package/dist/raiseError.js +4 -0
  97. package/dist/raiseError.js.map +1 -0
  98. package/dist/registerComponents.d.ts +2 -0
  99. package/dist/registerComponents.d.ts.map +1 -0
  100. package/dist/registerComponents.js +33 -0
  101. package/dist/registerComponents.js.map +1 -0
  102. package/dist/showRoute.d.ts +3 -0
  103. package/dist/showRoute.d.ts.map +1 -0
  104. package/dist/showRoute.js +38 -0
  105. package/dist/showRoute.js.map +1 -0
  106. package/dist/showRouteContent.d.ts +3 -0
  107. package/dist/showRouteContent.d.ts.map +1 -0
  108. package/dist/showRouteContent.js +38 -0
  109. package/dist/showRouteContent.js.map +1 -0
  110. package/dist/testPath.d.ts +3 -0
  111. package/dist/testPath.d.ts.map +1 -0
  112. package/dist/testPath.js +85 -0
  113. package/dist/testPath.js.map +1 -0
  114. package/dist/types.d.ts +38 -0
  115. package/dist/types.d.ts.map +1 -0
  116. package/dist/types.js +2 -0
  117. package/dist/types.js.map +1 -0
  118. package/package.json +74 -0
package/README.md ADDED
@@ -0,0 +1,486 @@
1
+ # @wcstack/router
2
+
3
+ Provides SPA routing with declarative definitions using custom elements.
4
+
5
+ ## Features
6
+
7
+ ### Basic Features
8
+ * **Declarative Routing**: Simply list `<wcs-route>` tags within an HTML `<template>`. No JS configuration object required.
9
+ * **Nested Route Definitions**: Intuitively express nested structures like `/products/:id`.
10
+ * **Parameter Support**: Supports path parameters (`:id`).
11
+ * **Fallback (404)**: Handle undefined paths with `<wcs-route fallback>`.
12
+ * **Navigation API Based**: Built on the modern standard Navigation API, offering high affinity with native browser behavior.
13
+ * **Zero Config / Buildless**: Works directly in the browser without bundling.
14
+
15
+ ### Unique Features
16
+ * **Light DOM Layout System**: Defines layout templates in normal DOM (Light DOM) without forcing Shadow DOM. Makes global CSS application and `<slot>` insertion easy.
17
+ * **Typed Parameters**: Specify type constraints like `:id(int)`. Automatically converts values to `number` type.
18
+ * **Mixed Layouts & Routes**: Freely nest `<wcs-layout>` within the routing tree, managing layout switching per area purely through HTML structure.
19
+ * **Auto-Binding**: Automatically injects URL parameters into components using `data-bind` attribute (supports `props`, `states`, `attr`, and direct property modes).
20
+ * **Declarative `<head>` Management**: Declaratively switch `title` and `meta` tags for each page using `<wcs-head>`.
21
+
22
+ ## Usage
23
+
24
+ ```html
25
+ <wcs-router>
26
+ <template>
27
+ <!-- When path is "/" -->
28
+ <wcs-route path="/">
29
+ <!-- Apply the "main-layout" layout -->
30
+ <wcs-layout layout="main-layout">
31
+ <main-header slot="header"></main-header>
32
+ <main-body>
33
+ <!-- When path is "/" -->
34
+ <wcs-route index>
35
+ <wcs-head>
36
+ <title>Main Page</title>
37
+ </wcs-head>
38
+ <main-dashboard></main-dashboard>
39
+ </wcs-route>
40
+
41
+ <!-- When path is "/products" (relative paths below top-level) -->
42
+ <wcs-route path="products">
43
+ <wcs-head>
44
+ <title>Product Page</title>
45
+ </wcs-head>
46
+ <!-- When path is "/products" -->
47
+ <wcs-route index>
48
+ <product-list></product-list>
49
+ </wcs-route>
50
+ <!-- When path is "/products/:productId" -->
51
+ <wcs-route path=":productId">
52
+ <!-- productItem.props.productId = productId -->
53
+ <product-item data-bind="props"></product-item>
54
+ </wcs-route>
55
+ </wcs-route>
56
+ </main-body>
57
+ </wcs-layout>
58
+ </wcs-route>
59
+
60
+ <!-- When path is "/admin" -->
61
+ <wcs-route path="/admin">
62
+ <!-- Apply the "admin-layout" layout -->
63
+ <wcs-layout layout="admin-layout">
64
+ <wcs-head>
65
+ <title>Admin Page</title>
66
+ </wcs-head>
67
+ <admin-header slot="header"></admin-header>
68
+ <admin-body></admin-body>
69
+ </wcs-layout>
70
+ </wcs-route>
71
+
72
+ <!-- When no path matches -->
73
+ <wcs-route fallback>
74
+ <error-404></error-404>
75
+ </wcs-route>
76
+ </template>
77
+ </wcs-router>
78
+
79
+ <wcs-outlet>
80
+ <!-- Build a DOM tree according to the route path and layout and render it here -->
81
+ </wcs-outlet>
82
+
83
+ <!-- "main-layout" layout -->
84
+ <template id="main-layout">
85
+ <section>
86
+ <h1> Main </h1>
87
+ <slot name="header"></slot>
88
+ </section>
89
+ <section>
90
+ <slot></slot>
91
+ </section>
92
+ </template>
93
+
94
+ <!-- "admin-layout" layout -->
95
+ <template id="admin-layout">
96
+ <section>
97
+ <h1> Admin Main </h1>
98
+ <slot name="header"></slot>
99
+ </section>
100
+ <section>
101
+ <slot></slot>
102
+ </section>
103
+ </template>
104
+
105
+ ```
106
+
107
+ * <main-header><main-body><main-dashboard><product-list><product-item><admin-header><admin-body><error-404> are custom components in your app.
108
+ * The custom elements above must be defined separately (via an autoloader or manual registration).
109
+
110
+ ## Reference
111
+
112
+ ### Router (wcs-router)
113
+
114
+ Define routes and layout slots inside a child template tag. Only one can exist in a document. A direct child template tag is required. Outputs according to definitions to `<wcs-outlet>`.
115
+
116
+ | Attribute | Description |
117
+ |------|------|
118
+ | `basename` | When routing in a subfolder URL, specify the subfolder. Not required if you don’t run in a subfolder. |
119
+
120
+ ### Route (wcs-route)
121
+
122
+ Displays children when the route path matches. Match priority is static paths over parameters.
123
+
124
+ | Attribute | Description |
125
+ |------|------|
126
+ | `path` | For top-level routes, specify an absolute path starting with `/`. Otherwise, specify a relative path. For parameters, use `:paramName`. For catch-all, use `*`. Top-level routes cannot use relative paths. |
127
+ | `index` | Inherits the upper path. |
128
+ | `fallback` | Displayed when no route matches the path. |
129
+ | `fullpath` | Path including parent routes (read-only). |
130
+ | `name` | Identifier. |
131
+ | `guard` | Enables guard handling. Specify the full path to navigate to on guard cancellation. |
132
+
133
+ | Property | Description |
134
+ |------|------|
135
+ | `params` | Matched parameters (strings). |
136
+ | `typedParams` | Matched parameters (converted types). |
137
+ | `guardHandler` | Sets the guard decision function. |
138
+
139
+ Guard decision function type:
140
+ function (toPath: string, fromPath: string): boolean | Promise<boolean>
141
+
142
+ #### Typed Parameters
143
+
144
+ By specifying types for path parameters, you can perform value validation and automatic conversion.
145
+
146
+ **Syntax**: `:paramName(typeName)`
147
+
148
+ ```html
149
+ <!-- Integer parameter -->
150
+ <wcs-route path="/users/:userId(int)">
151
+ <user-detail></user-detail>
152
+ </wcs-route>
153
+
154
+ <!-- Complex parameters -->
155
+ <wcs-route path="/posts/:date(isoDate)/:slug(slug)">
156
+ <post-detail></post-detail>
157
+ </wcs-route>
158
+ ```
159
+
160
+ **Built-in Types**:
161
+
162
+ | Type Name | Description | Example | Converted Type |
163
+ |------|------|------|------|
164
+ | `int` | Integer | `123`, `-45` | `number` |
165
+ | `float` | Floating point number | `3.14`, `-2.5` | `number` |
166
+ | `bool` | Boolean | `true`, `false`, `0`, `1` | `boolean` |
167
+ | `uuid` | UUID v1-5 | `550e8400-e29b-41d4-a716-446655440000` | `string` |
168
+ | `slug` | Slug (lowercase alphanumeric and hyphens) | `my-post-title` | `string` |
169
+ | `isoDate` | ISO 8601 Date | `2024-01-23` | `Date` |
170
+ | `any` | Any string (default) | Any | `string` |
171
+
172
+ **Retrieving Values**:
173
+
174
+ ```javascript
175
+ // Get from the route element
176
+ const route = document.querySelector('wcs-route[path="/users/:userId(int)"]');
177
+
178
+ // Get as string
179
+ console.log(route.params.userId); // "123"
180
+
181
+ // Get as typed value
182
+ console.log(route.typedParams.userId); // 123 (number)
183
+ ```
184
+
185
+ **Behavior**:
186
+ - If the value does not match the type, the route will not match (it does not result in an error).
187
+ - If no type is specified, it is treated as `any` (same as previous behavior).
188
+ - Specifying an unknown type name also falls back to `any`.
189
+
190
+ ### Layout (wcs-layout)
191
+
192
+ Loads a template, inserts children into `<slot>`, and writes to `<wcs-layout-outlet>`. Light DOM supported. External file supported.
193
+
194
+ | Attribute | Description |
195
+ |------|------|
196
+ | `layout` | The id attribute of the template tag used as the template. |
197
+ | `src` | URL of an external template file. |
198
+ | `name` | Identifier passed to `wcs-layout-outlet`. |
199
+ | `enable-shadow-root` | Use Shadow DOM in `<wcs-layout-outlet>`. |
200
+ | `disable-shadow-root` | Use Light DOM in `<wcs-layout-outlet>`. |
201
+
202
+ ### Outlet (wcs-outlet)
203
+
204
+ Displays a DOM tree according to the routing and layout settings. Define it in HTML, or if missing it is created by `<wcs-router>`.
205
+
206
+ ### LayoutOutlet (wcs-layout-outlet)
207
+
208
+ Displays a DOM tree into `<wcs-outlet>` according to the layout (`<wcs-layout>`) settings. Inherits the name attribute from `<wcs-layout>`. Use the name attribute to identify styling targets.
209
+
210
+ | Attribute | Description |
211
+ |------|------|
212
+ | `name` | The name attribute of `<wcs-layout>`. Use it to identify styling targets. |
213
+
214
+ #### Light DOM Limitations
215
+
216
+ When utilizing `disable-shadow-root` (Light DOM), slot replacement targets **only direct children** of `<wcs-layout>`. Elements with `slot` attributes inside `<wcs-route>` will not be placed in the slot.
217
+
218
+ ```html
219
+ <!-- NG: <div slot="header"> is not a direct child of wcs-layout, so it doesn't go into the slot -->
220
+ <wcs-layout layout="main" disable-shadow-root>
221
+ <wcs-route path="/page">
222
+ <div slot="header">Header Content</div>
223
+ </wcs-route>
224
+ </wcs-layout>
225
+
226
+ <!-- OK: Make the element with slot attribute a direct child of wcs-layout -->
227
+ <wcs-layout layout="main" disable-shadow-root>
228
+ <div slot="header">Header Content</div>
229
+ <wcs-route path="/page">
230
+ <!-- Page content -->
231
+ </wcs-route>
232
+ </wcs-layout>
233
+ ```
234
+
235
+ In the case of `enable-shadow-root` (Shadow DOM), this limitation does not apply because the native `<slot>` function is used.
236
+
237
+ ### Head (wcs-head)
238
+
239
+ Manages document `<head>` elements per route. Uses a stack-based system where the most recently connected Head is prioritized.
240
+
241
+ ```html
242
+ <wcs-route path="/about">
243
+ <wcs-head>
244
+ <title>About Us</title>
245
+ <meta name="description" content="About our company">
246
+ </wcs-head>
247
+ <about-page></about-page>
248
+ </wcs-route>
249
+ ```
250
+
251
+ **Supported elements**: `<title>`, `<meta>`, `<link>`, `<base>`, `<script>`, `<style>`
252
+
253
+ **Behavior**:
254
+ - Captures the initial `<head>` state on first connection
255
+ - When multiple `<wcs-head>` elements are active, the last connected one takes priority
256
+ - When all `<wcs-head>` elements disconnect, the initial state is restored
257
+ - Elements are identified by key (e.g., `<meta>` by `name`/`property`/`http-equiv`, `<link>` by `rel`/`href`)
258
+
259
+ ### Link (wcs-link)
260
+
261
+ Link. Converted to an `<a>`, and the route path in the `to` attribute is converted to a URL. When the link path matches the current URL, the `active` CSS class is automatically added to the generated `<a>` element.
262
+
263
+ | Attribute | Description |
264
+ |------|------|
265
+ | `to` | Destination path or URL. Paths starting with `/` are treated as internal paths (basename is prepended). Other values are treated as external URLs. |
266
+
267
+ **Active state**: The generated `<a>` receives the `active` class when its path matches the current location. Tracking is updated on navigation events (`currententrychange`, `wcs:navigate`, `popstate`).
268
+
269
+ ```css
270
+ /* Style active links */
271
+ a.active { font-weight: bold; color: blue; }
272
+ ```
273
+
274
+ ## Auto-Binding (`data-bind`)
275
+
276
+ Elements with the `data-bind` attribute automatically receive matched route parameters. Four binding modes are available:
277
+
278
+ | `data-bind` value | Target | Description |
279
+ |------|------|------|
280
+ | `"props"` | `element.props` | Merges params into the `props` property |
281
+ | `"states"` | `element.states` | Merges params into the `states` property |
282
+ | `"attr"` | HTML attributes | Sets params as HTML attributes via `setAttribute()` |
283
+ | `""` (empty) | Direct properties | Sets params directly on the element (e.g., `element.id = value`) |
284
+
285
+ ```html
286
+ <wcs-route path="/users/:userId(int)">
287
+ <!-- element.props = { userId: 123 } -->
288
+ <user-detail data-bind="props"></user-detail>
289
+
290
+ <!-- element.setAttribute("userId", 123) -->
291
+ <div data-bind="attr"></div>
292
+ </wcs-route>
293
+ ```
294
+
295
+ Parameters are assigned before `connectedCallback` fires. For custom elements that are not yet defined, assignment is deferred until `customElements.whenDefined()` resolves.
296
+
297
+ ## Configuration
298
+
299
+ Initialize the router with optional configuration via `bootstrapRouter()`:
300
+
301
+ ```javascript
302
+ import { bootstrapRouter } from '@wcstack/router';
303
+
304
+ bootstrapRouter({
305
+ // Custom tag names (all optional)
306
+ tagNames: {
307
+ router: 'wcs-router', // default
308
+ route: 'wcs-route', // default
309
+ outlet: 'wcs-outlet', // default
310
+ layout: 'wcs-layout', // default
311
+ layoutOutlet: 'wcs-layout-outlet', // default
312
+ link: 'wcs-link', // default
313
+ head: 'wcs-head' // default
314
+ },
315
+ // Use Shadow DOM for outlets (default: false)
316
+ enableShadowRoot: false,
317
+ // File extensions stripped from basename (default: [".html"])
318
+ basenameFileExtensions: [".html"]
319
+ });
320
+ ```
321
+
322
+ ## Path Specification (Router / Route / Link)
323
+
324
+ ### Terminology
325
+
326
+ * **URL Pathname**: `location.pathname` (e.g. `/app/products/42`)
327
+ * **basename**: The app mount path (e.g. `/app`)
328
+ * **internalPath**: The routing path inside the app after removing basename (e.g. `/products/42`)
329
+
330
+ ---
331
+
332
+ ## 1) basename specification
333
+
334
+ ### 1.1 basename resolution order
335
+
336
+ 1. The `basename` attribute on `<wcs-router basename="/app">`
337
+ 2. If `<base href="/app/">` exists, derive from `new URL(document.baseURI).pathname`
338
+ 3. If neither exists, use **empty string** `""` (assumes running at root)
339
+
340
+ ### 1.2 basename normalization (important)
341
+
342
+ basename is always normalized as follows:
343
+
344
+ * Add leading `/` (except empty string)
345
+ * Collapse multiple slashes into one
346
+ * Remove trailing `/` (except `/` itself, which is treated as empty)
347
+ * Treat `.../index.html` or `.../*.html` as files and remove them
348
+ * If the result is `/`, basename becomes `""`
349
+
350
+ Examples:
351
+
352
+ * `"/"` → `""`
353
+ * `"/app/"` → `"/app"`
354
+ * `"/app/index.html"` → `"/app"`
355
+
356
+ ### 1.3 basename and direct links
357
+
358
+ * If basename is `""`, no `<base>` exists, and the initial `pathname !== "/"`, it is **an error**
359
+ * If basename is `"/app"`:
360
+
361
+ * `"/app"` and `"/app/"` are **the same** (app root)
362
+ * `"/app"` matches only `"/app"` or `"/app/..."` (does not match `"/appX"`)
363
+
364
+ ---
365
+
366
+ ## 2) internalPath specification
367
+
368
+ ### 2.1 internalPath normalization
369
+
370
+ internalPath is always treated as an **absolute path**.
371
+
372
+ * Add leading `/`
373
+ * Collapse multiple slashes
374
+ * Remove trailing `/` (except root `/`)
375
+ * If empty, become `/`
376
+ * In Router normalization, remove trailing `*.html` when present
377
+
378
+ Examples:
379
+
380
+ * `""` → `/`
381
+ * `"products"` → `/products`
382
+ * `"/products/"` → `/products`
383
+ * `"///a//b/"` → `/a/b`
384
+
385
+ ### 2.2 Get internalPath from URL
386
+
387
+ Obtain `internalPath` by matching `URL Pathname` with `basename`.
388
+
389
+ * If `pathname === basename`, then `internalPath = "/"`
390
+ * If `pathname` starts with `basename + "/"`, then `internalPath = pathname.slice(basename.length)`
391
+ * Otherwise `internalPath = pathname`
392
+ * If the slice result is `""`, then `internalPath = "/"`
393
+
394
+ Examples (basename=`/app`):
395
+
396
+ * pathname=`/app` → internalPath=`/`
397
+ * pathname=`/app/` → internalPath=`/`
398
+ * pathname=`/app/products/42` → internalPath=`/products/42`
399
+
400
+ ---
401
+
402
+ ## 3) `<wcs-route path="...">` specification
403
+
404
+ ### 3.1 path notation
405
+
406
+ `path` follows **internalPath rules**.
407
+
408
+ * Root (top-level) is `"/"`
409
+ * Child routes allow **relative** paths (recommended)
410
+
411
+ * Example: parent `/products`, child `":id"` → `/products/:id`
412
+
413
+ > In implementation, paths are converted to absolute during parsing.
414
+
415
+ ### 3.2 Matching rules
416
+
417
+ * **Exact match** by segment
418
+ * Parameter `:id` matches a single segment
419
+ * Catch-all `*` matches the remaining path (accessible via `params['*']`)
420
+
421
+ ### 3.3 Priority (longest match definition)
422
+
423
+ If multiple candidates exist, pick the higher priority:
424
+
425
+ 1. **More segments**
426
+ 2. If same, **more static segments** (`"users"` > `":id"` > `"*"`)
427
+ 3. If still same, **definition order**
428
+
429
+ > Catch-all `*` has the lowest priority, so more specific routes always take precedence.
430
+
431
+ Example:
432
+
433
+ * `/admin/users/:id` (static2 + param1)
434
+ * `/admin/users/profile` (static3)
435
+ → latter wins
436
+
437
+ ### 3.4 Trailing slash
438
+
439
+ * Matching is done after internal normalization, so
440
+
441
+ * `/products` and `/products/` are treated the same (either URL is OK)
442
+
443
+ ### 3.5 Catch-all (`*`)
444
+
445
+ Specify `*` at the end of a path to match the entire remaining path.
446
+
447
+ ```html
448
+ <wcs-route path="/admin/profile"></wcs-route> <!-- Priority -->
449
+ <wcs-route path="/admin/*"></wcs-route> <!-- Fallback for /admin/xxx -->
450
+ <wcs-route path="/*"></wcs-route> <!-- Last resort -->
451
+ ```
452
+
453
+ | Path | Match | Reason |
454
+ |------|-------|--------|
455
+ | `/admin/profile` | `/admin/profile` | More segments |
456
+ | `/admin/setting` | `/admin/*` | `*` matches `setting` |
457
+ | `/admin/a/b/c` | `/admin/*` | `*` matches `a/b/c` |
458
+ | `/other` | `/*` | Top-level catch-all |
459
+
460
+ The matched remaining path is accessible via `params['*']`.
461
+
462
+ ---
463
+
464
+ ## 4) `<wcs-link to="...">` specification
465
+
466
+ ### 4.1 When `to` starts with `/`
467
+
468
+ `to` is treated as **internalPath**.
469
+
470
+ * The actual `href` is created by joining `basename + internalPath`
471
+ * Join: `"/app" + "/products"` → `"/app/products"` (no `//`)
472
+
473
+ ### 4.2 When `to` does not start with `/`
474
+
475
+ Treated as an external URL (`new URL(to)` is expected to succeed).
476
+
477
+ * Example: `https://example.com/`
478
+
479
+ ---
480
+
481
+ ## 5) “Drop HTML files” is limited
482
+
483
+ Dropping `.html` only applies when the pathname **actually looks like a file**.
484
+
485
+ * `"/app/index.html"` → `"/app"` (OK)
486
+ * `"/products"` → `"/"` is **NG** (do not drop segments)
@@ -0,0 +1,6 @@
1
+ import { IGuardCancel } from "./types";
2
+ export declare class GuardCancel extends Error implements IGuardCancel {
3
+ fallbackPath: string;
4
+ constructor(message: string, fallbackPath: string);
5
+ }
6
+ //# sourceMappingURL=GuardCancel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GuardCancel.d.ts","sourceRoot":"","sources":["../src/GuardCancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,qBAAa,WAAY,SAAQ,KAAM,YAAW,YAAY;IAC5D,YAAY,EAAE,MAAM,CAAC;gBAET,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAIlD"}
@@ -0,0 +1,8 @@
1
+ export class GuardCancel extends Error {
2
+ fallbackPath;
3
+ constructor(message, fallbackPath) {
4
+ super(message);
5
+ this.fallbackPath = fallbackPath;
6
+ }
7
+ }
8
+ //# sourceMappingURL=GuardCancel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GuardCancel.js","sourceRoot":"","sources":["../src/GuardCancel.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,CAAS;IAErB,YAAY,OAAe,EAAE,YAAoB;QAC/C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ export type NavigationLike = {
2
+ navigate?: (url: string) => void;
3
+ addEventListener: (type: string, listener: EventListenerOrEventListenerObject) => void;
4
+ removeEventListener: (type: string, listener: EventListenerOrEventListenerObject) => void;
5
+ };
6
+ export declare function getNavigation(): NavigationLike | null;
7
+ //# sourceMappingURL=Navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Navigation.d.ts","sourceRoot":"","sources":["../src/Navigation.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,kCAAkC,KAAK,IAAI,CAAC;IACvF,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,kCAAkC,KAAK,IAAI,CAAC;CAC3F,CAAC;AAEF,wBAAgB,aAAa,IAAI,cAAc,GAAG,IAAI,CASrD"}
@@ -0,0 +1,11 @@
1
+ export function getNavigation() {
2
+ const nav = window.navigation;
3
+ if (!nav) {
4
+ return null;
5
+ }
6
+ if (typeof nav.addEventListener !== "function" || typeof nav.removeEventListener !== "function") {
7
+ return null;
8
+ }
9
+ return nav;
10
+ }
11
+ //# sourceMappingURL=Navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Navigation.js","sourceRoot":"","sources":["../src/Navigation.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAI,MAAc,CAAC,UAAwC,CAAC;IACrE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,gBAAgB,KAAK,UAAU,IAAI,OAAO,GAAG,CAAC,mBAAmB,KAAK,UAAU,EAAE,CAAC;QAChG,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { IOutlet, IRouter } from "./components/types";
2
+ export declare function applyRoute(routerNode: IRouter, outlet: IOutlet, fullPath: string, lastPath: string): Promise<void>;
3
+ //# sourceMappingURL=applyRoute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyRoute.d.ts","sourceRoot":"","sources":["../src/applyRoute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAMtD,wBAAsB,UAAU,CAC9B,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -0,0 +1,40 @@
1
+ import { config } from "./config";
2
+ import { matchRoutes } from "./matchRoutes";
3
+ import { raiseError } from "./raiseError";
4
+ import { showRouteContent } from "./showRouteContent";
5
+ export async function applyRoute(routerNode, outlet, fullPath, lastPath) {
6
+ const basename = routerNode.basename;
7
+ let sliced = fullPath;
8
+ if (basename !== "") {
9
+ if (fullPath === basename) {
10
+ sliced = "";
11
+ }
12
+ else if (fullPath.startsWith(basename + "/")) {
13
+ sliced = fullPath.slice(basename.length);
14
+ }
15
+ }
16
+ // when fullPath === basename (e.g. "/app"), treat it as root "/"
17
+ const path = sliced === "" ? "/" : sliced;
18
+ let matchResult = matchRoutes(routerNode, path);
19
+ if (!matchResult) {
20
+ if (routerNode.fallbackRoute) {
21
+ matchResult = {
22
+ routes: [routerNode.fallbackRoute],
23
+ params: {},
24
+ typedParams: {},
25
+ path: path,
26
+ lastPath: lastPath
27
+ };
28
+ }
29
+ else {
30
+ raiseError(`${config.tagNames.router} No route matched for path: ${path}`);
31
+ }
32
+ }
33
+ matchResult.lastPath = lastPath;
34
+ const lastRoutes = outlet.lastRoutes;
35
+ await showRouteContent(routerNode, matchResult, lastRoutes);
36
+ // if successful, update router and outlet state
37
+ routerNode.path = path;
38
+ outlet.lastRoutes = matchResult.routes;
39
+ }
40
+ //# sourceMappingURL=applyRoute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyRoute.js","sourceRoot":"","sources":["../src/applyRoute.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAmB,EACnB,MAAe,EACf,QAAgB,EAChB,QAAgB;IAEhB,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;IACrC,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACpB,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,GAAG,EAAE,CAAC;QACd,CAAC;aAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,iEAAiE;IACjE,MAAM,IAAI,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;IAE1C,IAAI,WAAW,GAAG,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,IAAI,UAAU,CAAC,aAAa,EAAE,CAAC;YAC7B,WAAW,GAAG;gBACZ,MAAM,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;gBAClC,MAAM,EAAE,EAAE;gBACV,WAAW,EAAE,EAAE;gBACf,IAAI,EAAE,IAAI;gBACV,QAAQ,EAAE,QAAQ;aACnB,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,+BAA+B,IAAI,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IACD,WAAW,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAChC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,MAAM,gBAAgB,CAAC,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAC5D,gDAAgD;IAChD,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC;IACvB,MAAM,CAAC,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC;AACzC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function assignParams(element: Element, params: Record<string, any>): void;
2
+ //# sourceMappingURL=assignParams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignParams.d.ts","sourceRoot":"","sources":["../src/assignParams.ts"],"names":[],"mappings":"AA+BA,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAsBzE"}
@@ -0,0 +1,52 @@
1
+ import { getCustomTagName } from "./getCustomTagName";
2
+ import { raiseError } from "./raiseError";
3
+ const bindTypeSet = new Set(["props", "states", "attr", ""]);
4
+ function _assignParams(element, params, bindType) {
5
+ for (const [key, value] of Object.entries(params)) {
6
+ switch (bindType) {
7
+ case "props":
8
+ element.props = {
9
+ ...element.props,
10
+ [key]: value
11
+ };
12
+ break;
13
+ case "states":
14
+ element.states = {
15
+ ...element.states,
16
+ [key]: value
17
+ };
18
+ break;
19
+ case "attr":
20
+ element.setAttribute(key, value);
21
+ break;
22
+ case "":
23
+ element[key] = value;
24
+ break;
25
+ }
26
+ }
27
+ }
28
+ export function assignParams(element, params) {
29
+ if (!element.hasAttribute('data-bind')) {
30
+ raiseError(`${element.tagName} has no 'data-bind' attribute.`);
31
+ }
32
+ const bindTypeText = element.getAttribute('data-bind') || '';
33
+ if (!bindTypeSet.has(bindTypeText)) {
34
+ raiseError(`${element.tagName} has invalid 'data-bind' attribute: ${bindTypeText}`);
35
+ }
36
+ const bindType = bindTypeText;
37
+ const customTagName = getCustomTagName(element);
38
+ if (customTagName && customElements.get(customTagName) === undefined) {
39
+ customElements.whenDefined(customTagName).then(() => {
40
+ if (element.isConnected) {
41
+ // 要素が削除されていない場合のみ割り当てを行う
42
+ _assignParams(element, params, bindType);
43
+ }
44
+ }).catch(() => {
45
+ raiseError(`Failed to define custom element: ${customTagName}`);
46
+ });
47
+ }
48
+ else {
49
+ _assignParams(element, params, bindType);
50
+ }
51
+ }
52
+ //# sourceMappingURL=assignParams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignParams.js","sourceRoot":"","sources":["../src/assignParams.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,WAAW,GAAkB,IAAI,GAAG,CAAC,CAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAE,CAAC,CAAC;AAE9E,SAAS,aAAa,CAAC,OAAgB,EAAE,MAA2B,EAAE,QAAkB;IACtF,KAAI,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACjD,QAAO,QAAQ,EAAE,CAAC;YAChB,KAAK,OAAO;gBACT,OAAe,CAAC,KAAK,GAAG;oBACvB,GAAI,OAAe,CAAC,KAAK;oBACzB,CAAC,GAAG,CAAC,EAAE,KAAK;iBACb,CAAC;gBACF,MAAM;YACR,KAAK,QAAQ;gBACV,OAAe,CAAC,MAAM,GAAG;oBACxB,GAAI,OAAe,CAAC,MAAM;oBAC1B,CAAC,GAAG,CAAC,EAAE,KAAK;iBACb,CAAC;gBACF,MAAM;YACR,KAAK,MAAM;gBACT,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,EAAE;gBACJ,OAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC9B,MAAM;QACV,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,MAA2B;IACxE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,UAAU,CAAC,GAAG,OAAO,CAAC,OAAO,gCAAgC,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IAC7D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAwB,CAAC,EAAE,CAAC;QAC/C,UAAU,CAAC,GAAG,OAAO,CAAC,OAAO,uCAAuC,YAAY,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,QAAQ,GAAG,YAAwB,CAAC;IAC1C,MAAM,aAAa,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,aAAa,IAAI,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,SAAS,EAAE,CAAC;QACrE,cAAc,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;YAClD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;gBACxB,yBAAyB;gBACzB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,UAAU,CAAC,oCAAoC,aAAa,EAAE,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC"}
package/dist/auto.js ADDED
@@ -0,0 +1,3 @@
1
+ import { bootstrapRouter } from "./index.esm.js";
2
+
3
+ await bootstrapRouter();
@@ -0,0 +1,3 @@
1
+ import { bootstrapRouter } from "./index.esm.min.js";
2
+
3
+ await bootstrapRouter();