@canmi/seam-react 0.4.15 → 0.4.18
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.md +13 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/scripts/skeleton/layout.mjs +37 -9
- package/scripts/skeleton/render.mjs +1 -1
- package/scripts/skeleton/schema.mjs +1 -1
package/README.md
CHANGED
|
@@ -4,14 +4,19 @@ React bindings for SeamJS, providing hooks and components to consume server-inje
|
|
|
4
4
|
|
|
5
5
|
## Key Exports
|
|
6
6
|
|
|
7
|
-
| Export | Purpose
|
|
8
|
-
| --------------------- |
|
|
9
|
-
| `defineRoutes` | Define client-side route configuration
|
|
10
|
-
| `useSeamData` | Access server-injected data from `SeamDataProvider` context
|
|
11
|
-
| `SeamDataProvider` | Context provider for server data
|
|
12
|
-
| `parseSeamData` | Parse JSON from `<script id="__data">`
|
|
13
|
-
| `buildSentinelData` | Build sentinel data for skeleton rendering
|
|
14
|
-
| `useSeamSubscription` | Hook for SSE subscriptions, returns `{ data, error, status }`
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --------------------- | ---------------------------------------------------------------------- |
|
|
9
|
+
| `defineRoutes` | Define client-side route configuration |
|
|
10
|
+
| `useSeamData` | Access server-injected data from `SeamDataProvider` context |
|
|
11
|
+
| `SeamDataProvider` | Context provider for server data |
|
|
12
|
+
| `parseSeamData` | Parse JSON from `<script id="__data">` |
|
|
13
|
+
| `buildSentinelData` | Build sentinel data for skeleton rendering |
|
|
14
|
+
| `useSeamSubscription` | Hook for SSE subscriptions, returns `{ data, error, status }` |
|
|
15
|
+
| `LazyComponentLoader` | Type for dynamic `() => import(...)` page loaders (per-page splitting) |
|
|
16
|
+
|
|
17
|
+
## Types
|
|
18
|
+
|
|
19
|
+
`RouteDef.component` accepts either a `ComponentType` or a `LazyComponentLoader` (a function returning `Promise<{ default: ComponentType }>`). The lazy variant is produced by `@canmi/seam-vite` when per-page splitting is active.
|
|
15
20
|
|
|
16
21
|
## Structure
|
|
17
22
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { SeamClientError } from "@canmi/seam-client";
|
|
|
4
4
|
|
|
5
5
|
//#region src/types.d.ts
|
|
6
6
|
interface ParamMapping {
|
|
7
|
-
from:
|
|
7
|
+
from: string;
|
|
8
8
|
type?: "string" | "int";
|
|
9
9
|
}
|
|
10
10
|
interface LoaderDef {
|
|
@@ -27,6 +27,8 @@ interface RouteDef {
|
|
|
27
27
|
mock?: Record<string, unknown>;
|
|
28
28
|
nullable?: string[];
|
|
29
29
|
staleTime?: number;
|
|
30
|
+
/** Internal: override layout ID for group layouts to avoid toLayoutId collision */
|
|
31
|
+
_layoutId?: string;
|
|
30
32
|
}
|
|
31
33
|
//#endregion
|
|
32
34
|
//#region src/define-routes.d.ts
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/define-routes.ts","../src/use-seam-data.ts","../src/sentinel.ts","../src/use-seam-subscription.ts","../src/use-seam-navigate.ts"],"mappings":";;;;;UAIiB,YAAA;EACf,IAAA;EACA,IAAA;AAAA;AAAA,UAGe,SAAA;EACf,SAAA;EACA,MAAA,GAAS,MAAA,SAAe,YAAA;AAAA;;KAId,mBAAA,SAA4B,OAAA;EACtC,OAAA,EAAS,aAAA,CAAc,MAAA;EAAA,CACtB,GAAA;AAAA;AAAA,UAGc,QAAA;EACf,IAAA;EACA,SAAA,GAAY,aAAA,CAAc,MAAA,qBAA2B,mBAAA;EACrD,MAAA,GAAS,aAAA;IAAgB,QAAA,EAAU,SAAA;EAAA;EACnC,QAAA,GAAW,QAAA;EACX,OAAA,GAAU,MAAA,SAAe,SAAA;EACzB,IAAA,GAAO,MAAA;EACP,QAAA;EACA,SAAA;AAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/define-routes.ts","../src/use-seam-data.ts","../src/sentinel.ts","../src/use-seam-subscription.ts","../src/use-seam-navigate.ts"],"mappings":";;;;;UAIiB,YAAA;EACf,IAAA;EACA,IAAA;AAAA;AAAA,UAGe,SAAA;EACf,SAAA;EACA,MAAA,GAAS,MAAA,SAAe,YAAA;AAAA;;KAId,mBAAA,SAA4B,OAAA;EACtC,OAAA,EAAS,aAAA,CAAc,MAAA;EAAA,CACtB,GAAA;AAAA;AAAA,UAGc,QAAA;EACf,IAAA;EACA,SAAA,GAAY,aAAA,CAAc,MAAA,qBAA2B,mBAAA;EACrD,MAAA,GAAS,aAAA;IAAgB,QAAA,EAAU,SAAA;EAAA;EACnC,QAAA,GAAW,QAAA;EACX,OAAA,GAAU,MAAA,SAAe,SAAA;EACzB,IAAA,GAAO,MAAA;EACP,QAAA;EACA,SAAA;EAbsC;EAetC,SAAA;AAAA;;;iBC1Bc,YAAA,CAAa,MAAA,EAAQ,QAAA,KAAa,QAAA;;;cCErC,gBAAA,EAA2C,KAAA,CAA3B,QAAA;AAAA,iBAEb,WAAA,oBAA+B,MAAA,kBAAA,CAAA,GAA4B,CAAA;AAAA,iBAO3D,aAAA,CAAc,MAAA,YAAoB,MAAA;;;;;;;;AFXlD;;;iBGMgB,iBAAA,CACd,GAAA,EAAK,MAAA,mBACL,MAAA,WACA,SAAA,GAAY,GAAA,WACX,MAAA;;;KCTS,kBAAA;AAAA,UAEK,yBAAA;EACf,IAAA,EAAM,CAAA;EACN,KAAA,EAAO,eAAA;EACP,MAAA,EAAQ,kBAAA;AAAA;AAAA,iBAGM,mBAAA,GAAA,CACd,OAAA,UACA,SAAA,UACA,KAAA,YACC,yBAAA,CAA0B,CAAA;;;cCThB,oBAAA,EAAmD,KAAA,CAA/B,QAAA,EAAA,GAAA;AAAA,iBAEjB,eAAA,CAAA,IAAoB,GAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canmi/seam-react",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.18",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@canmi/seam-client": "0.4.
|
|
21
|
+
"@canmi/seam-client": "0.4.18",
|
|
22
22
|
"esbuild": "^0.27.3"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@canmi/seam-engine": "0.4.
|
|
26
|
-
"@canmi/seam-i18n": "0.4.
|
|
25
|
+
"@canmi/seam-engine": "0.4.18",
|
|
26
|
+
"@canmi/seam-i18n": "0.4.18",
|
|
27
27
|
"@types/react": "^19.2.14",
|
|
28
28
|
"@types/react-dom": "^19.2.3",
|
|
29
29
|
"jsdom": "^28.1.0",
|
|
@@ -19,18 +19,21 @@ function toLayoutId(path) {
|
|
|
19
19
|
: `_layout_${path.replace(/^\/|\/$/g, "").replace(/\//g, "-")}`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/** Extract layout components and metadata from route tree
|
|
22
|
+
/** Extract layout components and metadata from route tree.
|
|
23
|
+
* When a node has both layout AND component, the loaders/mock belong to the
|
|
24
|
+
* page (component), not the layout — emit the layout with empty loaders. */
|
|
23
25
|
function extractLayouts(routes) {
|
|
24
26
|
const seen = new Map();
|
|
25
27
|
(function walk(defs, parentId) {
|
|
26
28
|
for (const def of defs) {
|
|
27
29
|
if (def.layout && def.children) {
|
|
28
|
-
const id = toLayoutId(def.path);
|
|
30
|
+
const id = def._layoutId || toLayoutId(def.path);
|
|
29
31
|
if (!seen.has(id)) {
|
|
32
|
+
const isPageRoute = !!def.component;
|
|
30
33
|
seen.set(id, {
|
|
31
34
|
component: def.layout,
|
|
32
|
-
loaders: def.loaders || {},
|
|
33
|
-
mock: def.mock || null,
|
|
35
|
+
loaders: isPageRoute ? {} : def.loaders || {},
|
|
36
|
+
mock: isPageRoute ? null : def.mock || null,
|
|
34
37
|
parentId: parentId || null,
|
|
35
38
|
});
|
|
36
39
|
}
|
|
@@ -80,7 +83,7 @@ function renderLayout(LayoutComponent, id, entry, manifest, i18nValue, ctx) {
|
|
|
80
83
|
|
|
81
84
|
const fieldWarnings = checkFieldAccess(accessed, schema, `layout:${id}`);
|
|
82
85
|
for (const w of fieldWarnings) {
|
|
83
|
-
const msg =
|
|
86
|
+
const msg = w;
|
|
84
87
|
if (!ctx.seenWarnings.has(msg)) {
|
|
85
88
|
ctx.seenWarnings.add(msg);
|
|
86
89
|
ctx.buildWarnings.push(msg);
|
|
@@ -90,15 +93,40 @@ function renderLayout(LayoutComponent, id, entry, manifest, i18nValue, ctx) {
|
|
|
90
93
|
return html;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
96
|
+
/** Join parent path prefix with a child path segment.
|
|
97
|
+
* Handles root ("/"), absolute child paths, and relative segments. */
|
|
98
|
+
function joinPaths(parent, child) {
|
|
99
|
+
if (child === "/") return parent || "/";
|
|
100
|
+
if (!parent || parent === "/") return child;
|
|
101
|
+
return parent + child;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Flatten routes, annotating each leaf with its parent layout id.
|
|
105
|
+
* Accumulates parent path segments so nested children get full paths
|
|
106
|
+
* (e.g. /blog + /:slug -> /blog/:slug). When a node has both layout
|
|
107
|
+
* and component, the component is emitted as a leaf route. */
|
|
108
|
+
function flattenRoutes(routes, currentLayout, parentPath) {
|
|
95
109
|
const leaves = [];
|
|
96
110
|
for (const route of routes) {
|
|
111
|
+
const fullPath = parentPath !== null ? joinPaths(parentPath, route.path) : route.path;
|
|
112
|
+
|
|
97
113
|
if (route.layout && route.children) {
|
|
98
|
-
|
|
114
|
+
const layoutId = route._layoutId || toLayoutId(route.path);
|
|
115
|
+
// Layout boundary with both component and layout: emit the page as a leaf
|
|
116
|
+
if (route.component) {
|
|
117
|
+
const leaf = { ...route, path: fullPath };
|
|
118
|
+
delete leaf.children;
|
|
119
|
+
delete leaf.layout;
|
|
120
|
+
leaf._layoutId = layoutId;
|
|
121
|
+
leaves.push(leaf);
|
|
122
|
+
}
|
|
123
|
+
leaves.push(...flattenRoutes(route.children, layoutId, fullPath));
|
|
99
124
|
} else if (route.children) {
|
|
100
|
-
|
|
125
|
+
// Container without layout: flatten children with accumulated path
|
|
126
|
+
leaves.push(...flattenRoutes(route.children, currentLayout, fullPath));
|
|
101
127
|
} else {
|
|
128
|
+
// Leaf route: assign full accumulated path
|
|
129
|
+
route.path = fullPath;
|
|
102
130
|
if (currentLayout) route._layoutId = currentLayout;
|
|
103
131
|
leaves.push(route);
|
|
104
132
|
}
|
|
@@ -144,7 +144,7 @@ function guardedRender(routePath, component, data, i18nValue, ctx) {
|
|
|
144
144
|
|
|
145
145
|
// After fatal check, only warnings remain — dedup per message
|
|
146
146
|
for (const v of violations) {
|
|
147
|
-
const msg =
|
|
147
|
+
const msg = `${routePath}\n ${v.reason}`;
|
|
148
148
|
if (!ctx.seenWarnings.has(msg)) {
|
|
149
149
|
ctx.seenWarnings.add(msg);
|
|
150
150
|
ctx.buildWarnings.push(msg);
|
|
@@ -98,7 +98,7 @@ function renderRoute(route, manifest, i18nValue, ctx) {
|
|
|
98
98
|
|
|
99
99
|
const fieldWarnings = checkFieldAccess(accessed, pageSchema, route.path);
|
|
100
100
|
for (const w of fieldWarnings) {
|
|
101
|
-
const msg =
|
|
101
|
+
const msg = w;
|
|
102
102
|
if (!ctx.seenWarnings.has(msg)) {
|
|
103
103
|
ctx.seenWarnings.add(msg);
|
|
104
104
|
ctx.buildWarnings.push(msg);
|