@jskit-ai/ui-generator 0.1.15 → 0.1.17
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/package.descriptor.mjs +19 -16
- package/package.json +3 -2
- package/src/server/buildTemplateContext.js +2 -2
- package/src/server/subcommands/addSubpages.js +4 -5
- package/src/server/subcommands/element.js +1 -2
- package/src/server/subcommands/outlet.js +20 -21
- package/src/server/subcommands/page.js +16 -24
- package/src/server/subcommands/pageSupport.js +57 -114
- package/src/server/subcommands/support.js +5 -31
- package/test/addSubpagesSubcommand.test.js +38 -20
- package/test/buildTemplateContext.test.js +82 -28
- package/test/elementSubcommand.test.js +9 -7
- package/test/outletSubcommand.test.js +43 -17
- package/test/pageSubcommand.test.js +129 -14
- package/README.md +0 -214
|
@@ -38,8 +38,12 @@ async function writeAppFixture(appRoot, { configSource = "" } = {}) {
|
|
|
38
38
|
path.join(appRoot, "src", "components", "ShellLayout.vue"),
|
|
39
39
|
`<template>
|
|
40
40
|
<div>
|
|
41
|
-
<ShellOutlet
|
|
42
|
-
|
|
41
|
+
<ShellOutlet
|
|
42
|
+
target="shell-layout:primary-menu"
|
|
43
|
+
default
|
|
44
|
+
default-link-component-token="local.main.ui.surface-aware-menu-link-item"
|
|
45
|
+
/>
|
|
46
|
+
<ShellOutlet target="shell-layout:top-right" />
|
|
43
47
|
</div>
|
|
44
48
|
</template>
|
|
45
49
|
`,
|
|
@@ -73,6 +77,7 @@ test("ui-generator page subcommand creates an index page from an explicit target
|
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
|
|
80
|
+
assert.equal(result.summary, 'Generated UI page "/practice" at src/pages/w/[workspaceSlug]/admin/practice/index.vue.');
|
|
76
81
|
|
|
77
82
|
const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
|
|
78
83
|
assert.match(pageSource, /<h1 class="text-h5 mb-2">Practice<\/h1>/);
|
|
@@ -108,6 +113,36 @@ test("ui-generator page subcommand creates a file route and derives label from t
|
|
|
108
113
|
});
|
|
109
114
|
});
|
|
110
115
|
|
|
116
|
+
test("ui-generator page subcommand omits the auth guard for public surface links", async () => {
|
|
117
|
+
await withTempApp(async (appRoot) => {
|
|
118
|
+
await writeAppFixture(appRoot, {
|
|
119
|
+
configSource: `export const config = {
|
|
120
|
+
surfaceAccessPolicies: {
|
|
121
|
+
public: {}
|
|
122
|
+
},
|
|
123
|
+
surfaceDefinitions: {
|
|
124
|
+
home: { id: "home", pagesRoot: "home", enabled: true, accessPolicyId: "public" }
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
`
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const targetFile = "home/reports/index.vue";
|
|
131
|
+
await runGeneratorSubcommand({
|
|
132
|
+
appRoot,
|
|
133
|
+
subcommand: "page",
|
|
134
|
+
args: [targetFile],
|
|
135
|
+
options: {
|
|
136
|
+
name: "Reports"
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
|
|
141
|
+
assert.match(placementSource, /id: "ui-generator\.page\.home\.reports\.link"/);
|
|
142
|
+
assert.doesNotMatch(placementSource, /when: \(\{ auth \}\) => Boolean\(auth\?\.authenticated\)/);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
111
146
|
test("ui-generator page subcommand supports link placement options", async () => {
|
|
112
147
|
await withTempApp(async (appRoot) => {
|
|
113
148
|
await writeAppFixture(appRoot);
|
|
@@ -125,8 +160,7 @@ test("ui-generator page subcommand supports link placement options", async () =>
|
|
|
125
160
|
});
|
|
126
161
|
|
|
127
162
|
const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
|
|
128
|
-
assert.match(placementSource, /
|
|
129
|
-
assert.match(placementSource, /position: "top-right"/);
|
|
163
|
+
assert.match(placementSource, /target: "shell-layout:top-right"/);
|
|
130
164
|
assert.match(placementSource, /componentToken: "local\.main\.ui\.tab-link-item"/);
|
|
131
165
|
assert.match(placementSource, /to: "\.\/notes"/);
|
|
132
166
|
});
|
|
@@ -143,7 +177,7 @@ test("ui-generator page subcommand infers subpage link placement, tab token, and
|
|
|
143
177
|
`<template>
|
|
144
178
|
<SectionContainerShell>
|
|
145
179
|
<template #tabs>
|
|
146
|
-
<ShellOutlet
|
|
180
|
+
<ShellOutlet target="contact-view:sub-pages" />
|
|
147
181
|
</template>
|
|
148
182
|
<RouterView />
|
|
149
183
|
</SectionContainerShell>
|
|
@@ -161,8 +195,7 @@ test("ui-generator page subcommand infers subpage link placement, tab token, and
|
|
|
161
195
|
});
|
|
162
196
|
|
|
163
197
|
const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
|
|
164
|
-
assert.match(placementSource, /
|
|
165
|
-
assert.match(placementSource, /position: "sub-pages"/);
|
|
198
|
+
assert.match(placementSource, /target: "contact-view:sub-pages"/);
|
|
166
199
|
assert.match(placementSource, /componentToken: "local\.main\.ui\.tab-link-item"/);
|
|
167
200
|
assert.match(placementSource, /to: "\.\/notes"/);
|
|
168
201
|
});
|
|
@@ -180,7 +213,7 @@ test("ui-generator page subcommand prefers the nearest index-route parent host",
|
|
|
180
213
|
`<template>
|
|
181
214
|
<SectionContainerShell>
|
|
182
215
|
<template #tabs>
|
|
183
|
-
<ShellOutlet
|
|
216
|
+
<ShellOutlet target="catalog:sub-pages" />
|
|
184
217
|
</template>
|
|
185
218
|
<RouterView />
|
|
186
219
|
</SectionContainerShell>
|
|
@@ -193,7 +226,7 @@ test("ui-generator page subcommand prefers the nearest index-route parent host",
|
|
|
193
226
|
`<template>
|
|
194
227
|
<SectionContainerShell>
|
|
195
228
|
<template #tabs>
|
|
196
|
-
<ShellOutlet
|
|
229
|
+
<ShellOutlet target="catalog-products:sub-pages" />
|
|
197
230
|
</template>
|
|
198
231
|
<RouterView />
|
|
199
232
|
</SectionContainerShell>
|
|
@@ -212,8 +245,7 @@ test("ui-generator page subcommand prefers the nearest index-route parent host",
|
|
|
212
245
|
});
|
|
213
246
|
|
|
214
247
|
const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
|
|
215
|
-
assert.match(placementSource, /
|
|
216
|
-
assert.match(placementSource, /position: "sub-pages"/);
|
|
248
|
+
assert.match(placementSource, /target: "catalog-products:sub-pages"/);
|
|
217
249
|
assert.match(placementSource, /componentToken: "local\.main\.ui\.tab-link-item"/);
|
|
218
250
|
assert.match(placementSource, /to: "\.\/variants"/);
|
|
219
251
|
});
|
|
@@ -265,7 +297,28 @@ test("ui-generator page subcommand rejects unsupported options", async () => {
|
|
|
265
297
|
});
|
|
266
298
|
});
|
|
267
299
|
|
|
268
|
-
test("ui-generator page subcommand
|
|
300
|
+
test("ui-generator page subcommand accepts target files with a src/pages prefix", async () => {
|
|
301
|
+
await withTempApp(async (appRoot) => {
|
|
302
|
+
await writeAppFixture(appRoot);
|
|
303
|
+
|
|
304
|
+
const targetFile = "src/pages/w/[workspaceSlug]/admin/practice/index.vue";
|
|
305
|
+
const result = await runGeneratorSubcommand({
|
|
306
|
+
appRoot,
|
|
307
|
+
subcommand: "page",
|
|
308
|
+
args: [targetFile],
|
|
309
|
+
options: {}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
assert.deepEqual(result.touchedFiles, [targetFile, "src/placement.js"]);
|
|
313
|
+
const pageSource = await readFile(path.join(appRoot, targetFile), "utf8");
|
|
314
|
+
assert.match(pageSource, /Practice/);
|
|
315
|
+
const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
|
|
316
|
+
assert.match(placementSource, /id: "ui-generator\.page\.admin\.practice\.link"/);
|
|
317
|
+
assert.match(placementSource, /workspaceSuffix: "\/practice"/);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("ui-generator page subcommand still rejects non-page src targets", async () => {
|
|
269
322
|
await withTempApp(async (appRoot) => {
|
|
270
323
|
await writeAppFixture(appRoot);
|
|
271
324
|
|
|
@@ -273,10 +326,10 @@ test("ui-generator page subcommand rejects target files with a src/pages prefix"
|
|
|
273
326
|
runGeneratorSubcommand({
|
|
274
327
|
appRoot,
|
|
275
328
|
subcommand: "page",
|
|
276
|
-
args: ["src/
|
|
329
|
+
args: ["src/components/PracticePanel.vue"],
|
|
277
330
|
options: {}
|
|
278
331
|
}),
|
|
279
|
-
/must be relative to src\/pages
|
|
332
|
+
/must be relative to src\/pages\/ or start with src\/pages\/:/
|
|
280
333
|
);
|
|
281
334
|
});
|
|
282
335
|
});
|
|
@@ -350,3 +403,65 @@ test("ui-generator page subcommand overwrites an existing page when --force is p
|
|
|
350
403
|
assert.doesNotMatch(pageSource, /custom practice page/);
|
|
351
404
|
});
|
|
352
405
|
});
|
|
406
|
+
|
|
407
|
+
test("ui-generator page subcommand rejects invalid link placement before creating a new page", async () => {
|
|
408
|
+
await withTempApp(async (appRoot) => {
|
|
409
|
+
await writeAppFixture(appRoot);
|
|
410
|
+
|
|
411
|
+
const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
|
|
412
|
+
const placementPath = path.join(appRoot, "src", "placement.js");
|
|
413
|
+
const originalPlacementSource = await readFile(placementPath, "utf8");
|
|
414
|
+
|
|
415
|
+
await assert.rejects(
|
|
416
|
+
runGeneratorSubcommand({
|
|
417
|
+
appRoot,
|
|
418
|
+
subcommand: "page",
|
|
419
|
+
args: [targetFile],
|
|
420
|
+
options: {
|
|
421
|
+
"link-placement": "missing:target"
|
|
422
|
+
}
|
|
423
|
+
}),
|
|
424
|
+
/ui-generator page option "placement" target "missing:target" is not declared/
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
await assert.rejects(readFile(path.join(appRoot, toPagePath(targetFile)), "utf8"), /ENOENT/);
|
|
428
|
+
const placementSource = await readFile(placementPath, "utf8");
|
|
429
|
+
assert.equal(placementSource, originalPlacementSource);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("ui-generator page subcommand rejects invalid link placement before overwriting an existing page", async () => {
|
|
434
|
+
await withTempApp(async (appRoot) => {
|
|
435
|
+
await writeAppFixture(appRoot);
|
|
436
|
+
|
|
437
|
+
const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
|
|
438
|
+
const targetPath = path.join(appRoot, toPagePath(targetFile));
|
|
439
|
+
const originalPageSource = `<template>
|
|
440
|
+
<div>custom practice page</div>
|
|
441
|
+
</template>
|
|
442
|
+
`;
|
|
443
|
+
const placementPath = path.join(appRoot, "src", "placement.js");
|
|
444
|
+
const originalPlacementSource = await readFile(placementPath, "utf8");
|
|
445
|
+
|
|
446
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
447
|
+
await writeFile(targetPath, originalPageSource, "utf8");
|
|
448
|
+
|
|
449
|
+
await assert.rejects(
|
|
450
|
+
runGeneratorSubcommand({
|
|
451
|
+
appRoot,
|
|
452
|
+
subcommand: "page",
|
|
453
|
+
args: [targetFile],
|
|
454
|
+
options: {
|
|
455
|
+
force: "true",
|
|
456
|
+
"link-placement": "missing:target"
|
|
457
|
+
}
|
|
458
|
+
}),
|
|
459
|
+
/ui-generator page option "placement" target "missing:target" is not declared/
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
const pageSource = await readFile(targetPath, "utf8");
|
|
463
|
+
assert.equal(pageSource, originalPageSource);
|
|
464
|
+
const placementSource = await readFile(placementPath, "utf8");
|
|
465
|
+
assert.equal(placementSource, originalPlacementSource);
|
|
466
|
+
});
|
|
467
|
+
});
|
package/README.md
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# @jskit-ai/ui-generator
|
|
2
|
-
|
|
3
|
-
Generate app-local UI pages, page links, placed elements, and routed subpage hosts for JSKIT apps.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
List available placement targets in the current app:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx jskit list placements
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Create a normal page at an explicit file:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npx jskit generate @jskit-ai/ui-generator page \
|
|
17
|
-
admin/reports-dashboard/index.vue \
|
|
18
|
-
--name "Reports Dashboard"
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Create a file-route page:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npx jskit generate @jskit-ai/ui-generator page \
|
|
25
|
-
admin/contacts/[contactId].vue \
|
|
26
|
-
--name "Contact"
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Upgrade an existing page into a routed subpage host:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npx jskit generate @jskit-ai/ui-generator add-subpages \
|
|
33
|
-
admin/contacts/[contactId].vue \
|
|
34
|
-
--title "Contact" \
|
|
35
|
-
--subtitle "Manage contact modules."
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Create a reusable placed element:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx jskit generate @jskit-ai/ui-generator placed-element \
|
|
42
|
-
--name "Alerts Widget" \
|
|
43
|
-
--surface admin
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Inject a plain generic outlet into an existing Vue file:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npx jskit generate @jskit-ai/ui-generator outlet \
|
|
50
|
-
src/components/ContactSummaryCard.vue \
|
|
51
|
-
--target contact-view:summary-actions
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Commands
|
|
55
|
-
|
|
56
|
-
- `page <target-file>`: create a route page at that exact file relative to `src/pages/` and add a link placement entry for it.
|
|
57
|
-
- `add-subpages <target-file>`: upgrade an existing page relative to `src/pages/` into the standard `SectionContainerShell + ShellOutlet + RouterView` host shape.
|
|
58
|
-
- `placed-element`: create a reusable component and register a placement for it.
|
|
59
|
-
- `outlet <target-file>`: inject a plain `ShellOutlet` into an existing Vue SFC.
|
|
60
|
-
|
|
61
|
-
For `placed-element`, the default placement target is `shell-layout:top-right`.
|
|
62
|
-
Use `--placement host:position` to override it.
|
|
63
|
-
|
|
64
|
-
## The Mental Model
|
|
65
|
-
|
|
66
|
-
`page` and `add-subpages` operate on explicit page files relative to `src/pages/`. `outlet` still targets an explicit Vue file path relative to the app root.
|
|
67
|
-
|
|
68
|
-
That means:
|
|
69
|
-
|
|
70
|
-
- `catalog/index.vue` is obviously an index-route page
|
|
71
|
-
- `catalog.vue` is obviously a file-route page
|
|
72
|
-
- there is no extra route-shape flag to remember
|
|
73
|
-
- the owning surface is derived from where the file lives
|
|
74
|
-
|
|
75
|
-
This is the reference model for JSKIT page-producing generators.
|
|
76
|
-
|
|
77
|
-
- `@jskit-ai/ui-generator page <target-file>` works from an explicit page file relative to `src/pages/`.
|
|
78
|
-
- `@jskit-ai/crud-ui-generator crud <target-root>` works from an explicit route root relative to `src/pages/`.
|
|
79
|
-
- `@jskit-ai/assistant page <target-file>` and `settings-page <target-file>` work from explicit page files relative to `src/pages/`.
|
|
80
|
-
|
|
81
|
-
## Page Links
|
|
82
|
-
|
|
83
|
-
`page` creates a page file and appends a link placement block for it.
|
|
84
|
-
|
|
85
|
-
These options control that generated page link:
|
|
86
|
-
|
|
87
|
-
- `--link-placement`: where the page link renders
|
|
88
|
-
- `--link-component-token`: how the page link is rendered
|
|
89
|
-
- `--link-to`: explicit `props.to` override for the page link
|
|
90
|
-
|
|
91
|
-
This is intentionally separate from `placed-element`, which still uses `--placement` because it places arbitrary UI, not a page link.
|
|
92
|
-
|
|
93
|
-
## Routed Subpages
|
|
94
|
-
|
|
95
|
-
`add-subpages` is the only routed-subpages command.
|
|
96
|
-
|
|
97
|
-
It upgrades an existing page so the page itself owns:
|
|
98
|
-
|
|
99
|
-
- `SectionContainerShell`
|
|
100
|
-
- `ShellOutlet host="..." position="sub-pages"`
|
|
101
|
-
- `RouterView`
|
|
102
|
-
|
|
103
|
-
`--target` controls that outlet target:
|
|
104
|
-
|
|
105
|
-
- if omitted, the target is derived from the page path
|
|
106
|
-
- `--target contact-view` means `contact-view:sub-pages`
|
|
107
|
-
- `--target contact-view:secondary-tabs` uses an explicit custom position
|
|
108
|
-
|
|
109
|
-
Derived target examples:
|
|
110
|
-
|
|
111
|
-
- `src/pages/admin/catalog/index.vue` -> `catalog:sub-pages`
|
|
112
|
-
- `src/pages/admin/catalog.vue` -> `catalog:sub-pages`
|
|
113
|
-
- `src/pages/admin/contacts/[contactId].vue` -> `contacts-contact-id:sub-pages`
|
|
114
|
-
- `src/pages/admin/catalog/products/index.vue` -> `catalog-products:sub-pages`
|
|
115
|
-
|
|
116
|
-
If the page already contains a `RouterView`, `add-subpages` fails instead of trying to update an existing routed host.
|
|
117
|
-
|
|
118
|
-
It also ensures the shared support scaffold exists:
|
|
119
|
-
|
|
120
|
-
- `src/components/SectionContainerShell.vue`
|
|
121
|
-
- `src/components/TabLinkItem.vue`
|
|
122
|
-
- `packages/main/src/client/providers/MainClientProvider.js` registration for `local.main.ui.tab-link-item`
|
|
123
|
-
|
|
124
|
-
## Child Route Placement Rule
|
|
125
|
-
|
|
126
|
-
Child routes attach differently depending on the parent page file shape.
|
|
127
|
-
|
|
128
|
-
If the parent is a file route:
|
|
129
|
-
|
|
130
|
-
- parent: `src/pages/admin/catalog.vue`
|
|
131
|
-
- child pages go under: `src/pages/admin/catalog/...`
|
|
132
|
-
- example child page: `src/pages/admin/catalog/products/index.vue`
|
|
133
|
-
|
|
134
|
-
If the parent is an index route:
|
|
135
|
-
|
|
136
|
-
- parent: `src/pages/admin/catalog/index.vue`
|
|
137
|
-
- child pages go under: `src/pages/admin/catalog/index/...`
|
|
138
|
-
- example child page: `src/pages/admin/catalog/index/products/index.vue`
|
|
139
|
-
|
|
140
|
-
That `index/...` folder shape is the native file-based routing rule for nesting children under an `index.vue` page.
|
|
141
|
-
|
|
142
|
-
## Example: File Route Parent
|
|
143
|
-
|
|
144
|
-
Create the parent page:
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
npx jskit generate @jskit-ai/ui-generator page \
|
|
148
|
-
admin/contacts/[contactId].vue \
|
|
149
|
-
--name "Contact"
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Upgrade it to host subpages:
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
npx jskit generate @jskit-ai/ui-generator add-subpages \
|
|
156
|
-
admin/contacts/[contactId].vue \
|
|
157
|
-
--title "Contact" \
|
|
158
|
-
--subtitle "Manage contact modules."
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Generate a child page link inside that host:
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
npx jskit generate @jskit-ai/ui-generator page \
|
|
165
|
-
admin/contacts/[contactId]/notes/index.vue \
|
|
166
|
-
--name "Notes"
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Example: Index Route Parent
|
|
170
|
-
|
|
171
|
-
Create the parent page:
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
npx jskit generate @jskit-ai/ui-generator page \
|
|
175
|
-
admin/catalog/index.vue \
|
|
176
|
-
--name "Catalog"
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Upgrade it to host subpages:
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
npx jskit generate @jskit-ai/ui-generator add-subpages \
|
|
183
|
-
admin/catalog/index.vue \
|
|
184
|
-
--title "Catalog"
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Because the parent page is `index.vue`, nested child pages live under the matching `index/` folder.
|
|
188
|
-
|
|
189
|
-
Generate a child page link inside that host:
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
npx jskit generate @jskit-ai/ui-generator page \
|
|
193
|
-
admin/catalog/index/products/index.vue \
|
|
194
|
-
--name "Products"
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
When `page` finds the nearest parent page upgraded with `add-subpages`, it reuses that parent’s real outlet target, defaults the link renderer to `local.main.ui.tab-link-item`, and derives `to` from the child route automatically.
|
|
198
|
-
|
|
199
|
-
## Generic Outlet Injection
|
|
200
|
-
|
|
201
|
-
`outlet` is intentionally small.
|
|
202
|
-
|
|
203
|
-
It only adds:
|
|
204
|
-
|
|
205
|
-
- `import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";`
|
|
206
|
-
- `<ShellOutlet host="..." position="..." />`
|
|
207
|
-
|
|
208
|
-
It does not:
|
|
209
|
-
|
|
210
|
-
- add `RouterView`
|
|
211
|
-
- add `SectionContainerShell`
|
|
212
|
-
- add routed subpage scaffolding
|
|
213
|
-
|
|
214
|
-
Use `add-subpages` when the goal is routed child pages inside a page.
|