@box-kite/react 2.0.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.
Files changed (100) hide show
  1. package/.claude/rules/box-kite-rules.md +16 -0
  2. package/.claude/skills/box-kite/SKILL.md +16 -0
  3. package/.claude/skills/box-kite/references/components.md +8 -3
  4. package/.claude/skills/box-kite/references/patterns.md +23 -0
  5. package/.cursor/rules/box-kite.mdc +16 -0
  6. package/AGENTS.md +19 -0
  7. package/BOX_AI_CONTEXT.md +562 -1
  8. package/BOX_KITE_AI_CONTEXT.md +562 -1
  9. package/README.md +39 -7
  10. package/catalog.cjs +1 -0
  11. package/catalog.d.ts +8 -0
  12. package/catalog.mjs +2333 -0
  13. package/client.cjs +1 -1
  14. package/client.mjs +40 -42
  15. package/components/agent.cjs +1 -0
  16. package/components/agent.d.ts +215 -0
  17. package/components/agent.mjs +405 -0
  18. package/components/chart.d.ts +1 -0
  19. package/components/dashboard.cjs +1 -0
  20. package/components/dashboard.d.ts +121 -0
  21. package/components/dashboard.mjs +690 -0
  22. package/components/dataGrid/components/dataGridBody.d.ts +2 -1
  23. package/components/dataGrid/components/dataGridDetailRow.d.ts +13 -2
  24. package/components/dataGrid/components/dataGridGroupRow.d.ts +13 -2
  25. package/components/dataGrid/components/dataGridRow.d.ts +13 -2
  26. package/components/dataGrid/contracts/dataGridContract.d.ts +66 -2
  27. package/components/dataGrid/models/cellModel.d.ts +3 -0
  28. package/components/dataGrid/models/editModel.d.ts +27 -0
  29. package/components/dataGrid/models/exportModel.d.ts +2 -1
  30. package/components/dataGrid/models/gridModel.d.ts +3 -0
  31. package/components/dataGrid/models/rangeModel.d.ts +119 -0
  32. package/components/dataGrid/models/viewportModel.d.ts +23 -2
  33. package/components/dataGrid/useGridNavigation.d.ts +10 -2
  34. package/components/dataGrid.cjs +4 -1
  35. package/components/dataGrid.d.ts +3 -0
  36. package/components/dataGrid.mjs +1056 -765
  37. package/components/markdown.cjs +1 -0
  38. package/components/markdown.d.ts +55 -0
  39. package/components/markdown.mjs +179 -0
  40. package/components/semantics.d.ts +29 -29
  41. package/components/skeleton.cjs +1 -0
  42. package/components/skeleton.d.ts +34 -0
  43. package/components/skeleton.mjs +25 -0
  44. package/dashboardSchema.cjs +1 -0
  45. package/dashboardSchema.mjs +78 -0
  46. package/docs/a11y.md +2 -3
  47. package/docs/catalog.md +451 -0
  48. package/docs/components.md +4 -0
  49. package/docs/index.md +3 -0
  50. package/docs/interop.md +237 -0
  51. package/docs/mcp.md +69 -0
  52. package/docs/props.md +3 -3
  53. package/interop.cjs +1 -0
  54. package/interop.d.ts +51 -0
  55. package/interop.mjs +454 -0
  56. package/mcp/src/data.d.ts +88 -0
  57. package/mcp/src/data.test.d.ts +1 -0
  58. package/mcp/src/engine.d.ts +23 -0
  59. package/mcp/src/index.d.ts +1 -0
  60. package/mcp/src/search.d.ts +26 -0
  61. package/mcp/src/search.test.d.ts +1 -0
  62. package/mcp/src/server.d.ts +18 -0
  63. package/mcp/src/server.test.d.ts +1 -0
  64. package/mcp/src/styles.d.ts +27 -0
  65. package/mcp/src/styles.test.d.ts +1 -0
  66. package/mcp/src/tools.d.ts +18 -0
  67. package/mcp/src/vocabulary.d.ts +41 -0
  68. package/package.json +124 -17
  69. package/react/spec/renderSpec.d.ts +51 -0
  70. package/react/spec/specBoundary.d.ts +25 -0
  71. package/react/spec/specRegistry.d.ts +42 -0
  72. package/react/spec/specRenderer.d.ts +10 -0
  73. package/registry/blocks/dashboard-shell/dashboard-shell.d.ts +22 -0
  74. package/registry/blocks/dashboard-shell/dashboard-stats.d.ts +13 -0
  75. package/registry/blocks/data-grid/invoice-rows.d.ts +15 -0
  76. package/registry/blocks/data-grid/invoices-grid.d.ts +1 -0
  77. package/registry/blocks/settings-form/settings-form.d.ts +17 -0
  78. package/spec.cjs +2 -0
  79. package/spec.d.ts +12 -0
  80. package/spec.mjs +382 -0
  81. package/specData.cjs +1 -0
  82. package/specData.mjs +69 -0
  83. package/specDefs.cjs +1 -0
  84. package/specDefs.mjs +33 -0
  85. package/specSchema.cjs +1 -0
  86. package/specSchema.mjs +88 -0
  87. package/utils/agent/agentUtils.d.ts +81 -0
  88. package/utils/catalog/catalogContracts.d.ts +19 -0
  89. package/utils/catalog/catalogManifest.d.ts +8 -0
  90. package/utils/dashboard/dashboardSchema.d.ts +18 -0
  91. package/utils/dashboard/dashboardUtils.d.ts +97 -0
  92. package/utils/interop/a2uiInterop.d.ts +82 -0
  93. package/utils/interop/assistantUiInterop.d.ts +34 -0
  94. package/utils/interop/toolInterop.d.ts +30 -0
  95. package/utils/spec/specData.d.ts +19 -0
  96. package/utils/spec/specDefs.d.ts +11 -0
  97. package/utils/spec/specProps.d.ts +15 -0
  98. package/utils/spec/specSchema.d.ts +23 -0
  99. package/utils/spec/specTypes.d.ts +71 -0
  100. package/utils/spec/specValidate.d.ts +10 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Every key a Box takes that is *not* one of the 221 style props: the nesting keys, and the four
3
+ * reserved names. Imported from the engine's own records rather than listed here, so a key added to
4
+ * the library is in the MCP server the same day — the whole reason this package lives in this repo.
5
+ */
6
+ /** What a nesting key is for, in one line — the engine's records carry the selector, not the reason. */
7
+ declare const KINDS: {
8
+ readonly pseudoClass: "a state of this element";
9
+ readonly pseudoElement: "a pseudo-element of this element (one per Box)";
10
+ readonly breakpoint: "a viewport width, min-width and up";
11
+ readonly media: "a reader preference or input device";
12
+ readonly container: "the width of the nearest query container";
13
+ readonly variant: "an attribute or position on this element's own selector";
14
+ readonly group: "an ancestor's or a preceding sibling's state";
15
+ readonly theme: "one named theme";
16
+ readonly startingStyle: "what these props start from, the first time the element is styled";
17
+ };
18
+ export type NestingKind = keyof typeof KINDS;
19
+ export interface NestingKey {
20
+ key: string;
21
+ kind: NestingKind;
22
+ /** What it compiles to — a selector fragment, an at-rule prelude, or a width in px. */
23
+ compiles: string;
24
+ deprecated: boolean;
25
+ }
26
+ /** Every nesting key the engine dispatches on, in the order a reader meets them. */
27
+ export declare const nestingKeys: readonly NestingKey[];
28
+ export declare const nestingKey: (key: string) => NestingKey | undefined;
29
+ export declare const nestingKindLabel: (kind: NestingKind) => string;
30
+ /**
31
+ * The four names a Box takes that are neither a style prop nor a nesting key. `props` is the one an
32
+ * agent gets wrong most often: an HTML attribute written at the top level typechecks and is dropped.
33
+ */
34
+ export declare const reservedProps: Readonly<Record<string, string>>;
35
+ /**
36
+ * HTML attributes an agent most often writes at the top level, where Box drops them. Names the engine
37
+ * already claims are filtered out rather than listed away: `checked` and `placeholder` are nesting
38
+ * keys on a Box, and a hint that contradicted the engine would be worse than no hint.
39
+ */
40
+ export declare const attributeNames: ReadonlySet<string>;
41
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@box-kite/react",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "pages/**"
@@ -34,6 +34,21 @@
34
34
  "import": "./anchor.mjs",
35
35
  "require": "./anchor.cjs"
36
36
  },
37
+ "./catalog": {
38
+ "types": "./catalog.d.ts",
39
+ "import": "./catalog.mjs",
40
+ "require": "./catalog.cjs"
41
+ },
42
+ "./interop": {
43
+ "types": "./interop.d.ts",
44
+ "import": "./interop.mjs",
45
+ "require": "./interop.cjs"
46
+ },
47
+ "./spec": {
48
+ "types": "./spec.d.ts",
49
+ "import": "./spec.mjs",
50
+ "require": "./spec.cjs"
51
+ },
37
52
  "./ssg": {
38
53
  "types": "./ssg.d.ts",
39
54
  "import": "./ssg.mjs",
@@ -59,6 +74,7 @@
59
74
  "preview": "vite preview ./pages --config ./pages.vite.config.ts --outDir ../dist-pages",
60
75
  "build": "npm run build:core && vite build",
61
76
  "build:core": "vite build --config vite.core.config.ts && node scripts/postbuild-core.mjs",
77
+ "build:mcp": "vite build --config vite.mcp.config.ts && node scripts/postbuild-mcp.mjs",
62
78
  "postbuild": "node scripts/postbuild.mjs",
63
79
  "build:bridge": "node scripts/build-bridge.mjs",
64
80
  "build:dev": "npm run build:core && vite build --mode dev",
@@ -75,10 +91,17 @@
75
91
  "docs:props": "node scripts/check-props-api.mjs --fix",
76
92
  "check:components": "node scripts/check-components-api.mjs",
77
93
  "docs:components": "node scripts/check-components-api.mjs --fix",
94
+ "check:catalog": "node scripts/check-catalog-api.mjs",
95
+ "docs:catalog": "node scripts/check-catalog-api.mjs --fix",
78
96
  "check:conventions": "node scripts/check-component-conventions.mjs",
79
97
  "check:agents": "node scripts/check-agent-files.mjs",
80
98
  "docs:agents": "node scripts/check-agent-files.mjs --fix",
81
99
  "check:package": "publint dist && attw --pack dist --ignore-rules false-esm cjs-resolves-to-esm internal-resolution-error && publint dist-core && attw --pack dist-core --ignore-rules false-esm cjs-resolves-to-esm internal-resolution-error",
100
+ "bench": "node bench/run.mjs",
101
+ "compare": "node dev/compare/report.mjs",
102
+ "compare:tailwind": "node dev/compare/report.mjs tailwind",
103
+ "compare:radix": "node dev/compare/report.mjs radix",
104
+ "compare:grid": "node dev/compare/report.mjs grid",
82
105
  "size": "size-limit",
83
106
  "size:published": "node dev/published-size.mjs",
84
107
  "release": "node scripts/release.mjs"
@@ -92,7 +115,7 @@
92
115
  "react-dom"
93
116
  ],
94
117
  "gzip": true,
95
- "limit": "30.40 KB"
118
+ "limit": "32.63 KB"
96
119
  },
97
120
  {
98
121
  "name": "Box in a Server Component (react-server entry)",
@@ -102,7 +125,7 @@
102
125
  "react-dom"
103
126
  ],
104
127
  "gzip": true,
105
- "limit": "28.90 KB"
128
+ "limit": "31.17 KB"
106
129
  },
107
130
  {
108
131
  "name": "Behavior primitives (a11y entry)",
@@ -124,11 +147,41 @@
124
147
  "gzip": true,
125
148
  "limit": "2.25 KB"
126
149
  },
150
+ {
151
+ "name": "Component catalog (catalog entry)",
152
+ "path": "dist/catalog.mjs",
153
+ "ignore": [
154
+ "react",
155
+ "react-dom"
156
+ ],
157
+ "gzip": true,
158
+ "limit": "50.72 KB"
159
+ },
160
+ {
161
+ "name": "Spec renderer (spec entry)",
162
+ "path": "dist/spec.mjs",
163
+ "ignore": [
164
+ "react",
165
+ "react-dom"
166
+ ],
167
+ "gzip": true,
168
+ "limit": "4.7 KB"
169
+ },
170
+ {
171
+ "name": "Ecosystem interop (interop entry)",
172
+ "path": "dist/interop.mjs",
173
+ "ignore": [
174
+ "react",
175
+ "react-dom"
176
+ ],
177
+ "gzip": true,
178
+ "limit": "4 KB"
179
+ },
127
180
  {
128
181
  "name": "Core engine (@box-kite/core, no React)",
129
182
  "path": "dist-core/core.mjs",
130
183
  "gzip": true,
131
- "limit": "28.17 KB"
184
+ "limit": "31.96 KB"
132
185
  },
133
186
  {
134
187
  "name": "Dialog",
@@ -138,7 +191,7 @@
138
191
  "react-dom"
139
192
  ],
140
193
  "gzip": true,
141
- "limit": "32.55 KB"
194
+ "limit": "34.76 KB"
142
195
  },
143
196
  {
144
197
  "name": "Popover",
@@ -148,7 +201,7 @@
148
201
  "react-dom"
149
202
  ],
150
203
  "gzip": true,
151
- "limit": "34.02 KB"
204
+ "limit": "36.24 KB"
152
205
  },
153
206
  {
154
207
  "name": "Menu",
@@ -158,7 +211,7 @@
158
211
  "react-dom"
159
212
  ],
160
213
  "gzip": true,
161
- "limit": "36.27 KB"
214
+ "limit": "38.5 KB"
162
215
  },
163
216
  {
164
217
  "name": "Accordion",
@@ -168,7 +221,37 @@
168
221
  "react-dom"
169
222
  ],
170
223
  "gzip": true,
171
- "limit": "31.76 KB"
224
+ "limit": "33.96 KB"
225
+ },
226
+ {
227
+ "name": "Agent",
228
+ "path": "dist/components/agent.mjs",
229
+ "ignore": [
230
+ "react",
231
+ "react-dom"
232
+ ],
233
+ "gzip": true,
234
+ "limit": "34.69 KB"
235
+ },
236
+ {
237
+ "name": "Markdown",
238
+ "path": "dist/components/markdown.mjs",
239
+ "ignore": [
240
+ "react",
241
+ "react-dom"
242
+ ],
243
+ "gzip": true,
244
+ "limit": "33.24 KB"
245
+ },
246
+ {
247
+ "name": "Skeleton",
248
+ "path": "dist/components/skeleton.mjs",
249
+ "ignore": [
250
+ "react",
251
+ "react-dom"
252
+ ],
253
+ "gzip": true,
254
+ "limit": "32.55 KB"
172
255
  },
173
256
  {
174
257
  "name": "Tabs",
@@ -178,7 +261,7 @@
178
261
  "react-dom"
179
262
  ],
180
263
  "gzip": true,
181
- "limit": "32.96 KB"
264
+ "limit": "35.2 KB"
182
265
  },
183
266
  {
184
267
  "name": "Slider",
@@ -188,7 +271,7 @@
188
271
  "react-dom"
189
272
  ],
190
273
  "gzip": true,
191
- "limit": "32.12 KB"
274
+ "limit": "34.34 KB"
192
275
  },
193
276
  {
194
277
  "name": "Progress",
@@ -198,7 +281,7 @@
198
281
  "react-dom"
199
282
  ],
200
283
  "gzip": true,
201
- "limit": "30.28 KB"
284
+ "limit": "32.51 KB"
202
285
  },
203
286
  {
204
287
  "name": "Toaster",
@@ -208,7 +291,7 @@
208
291
  "react-dom"
209
292
  ],
210
293
  "gzip": true,
211
- "limit": "33.77 KB"
294
+ "limit": "36.0 KB"
212
295
  },
213
296
  {
214
297
  "name": "Combobox",
@@ -218,7 +301,17 @@
218
301
  "react-dom"
219
302
  ],
220
303
  "gzip": true,
221
- "limit": "39.54 KB"
304
+ "limit": "41.73 KB"
305
+ },
306
+ {
307
+ "name": "Dashboard",
308
+ "path": "dist/components/dashboard.mjs",
309
+ "ignore": [
310
+ "react",
311
+ "react-dom"
312
+ ],
313
+ "gzip": true,
314
+ "limit": "38.2 KB"
222
315
  },
223
316
  {
224
317
  "name": "DataGrid",
@@ -228,7 +321,7 @@
228
321
  "react-dom"
229
322
  ],
230
323
  "gzip": true,
231
- "limit": "74.86 KB"
324
+ "limit": "79.67 KB"
232
325
  },
233
326
  {
234
327
  "name": "DataGrid export, the chunk fetched on the first press",
@@ -248,7 +341,7 @@
248
341
  "react-dom"
249
342
  ],
250
343
  "gzip": true,
251
- "limit": "96.82 KB"
344
+ "limit": "111.25 KB"
252
345
  }
253
346
  ],
254
347
  "repository": {
@@ -282,18 +375,29 @@
282
375
  "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
283
376
  },
284
377
  "dependencies": {
285
- "@box-kite/core": "2.0.0",
378
+ "@box-kite/core": "2.1.0",
286
379
  "csstype": "^3.2.3",
287
380
  "use-sync-external-store": "^1.6.0"
288
381
  },
289
382
  "devDependencies": {
290
383
  "@arethetypeswrong/cli": "^0.18.5",
384
+ "@assistant-ui/core": "^0.3.20",
385
+ "@copilotkit/a2ui-renderer": "^1.73.0",
386
+ "@emotion/react": "^11.14.0",
387
+ "@emotion/styled": "^11.14.1",
291
388
  "@eslint/js": "^10.0.1",
292
389
  "@iconify-json/simple-icons": "^1.2.94",
293
390
  "@iconify/react": "^6.0.2",
391
+ "@json-render/core": "^0.20.0",
392
+ "@json-render/react": "^0.20.0",
393
+ "@modelcontextprotocol/sdk": "^1.30.0",
394
+ "@mui/material": "^9.4.0",
395
+ "@mui/x-data-grid": "^9.14.0",
294
396
  "@size-limit/preset-small-lib": "^13.0.3",
295
397
  "@svgr/core": "^8.1.0",
296
398
  "@svgr/plugin-jsx": "^8.1.0",
399
+ "@tanstack/react-table": "^9.2.4",
400
+ "@tanstack/react-virtual": "^3.14.13",
297
401
  "@testing-library/dom": "^10.4.1",
298
402
  "@testing-library/jest-dom": "^7.0.0",
299
403
  "@testing-library/react": "^16.3.2",
@@ -305,6 +409,8 @@
305
409
  "@types/use-sync-external-store": "^1.5.0",
306
410
  "@vitejs/plugin-react": "^6.0.5",
307
411
  "@vitest/coverage-v8": "^4.1.11",
412
+ "ag-grid-community": "^36.2.0",
413
+ "ag-grid-react": "^36.2.0",
308
414
  "axe-core": "^4.13.0",
309
415
  "eslint": "^10.8.0",
310
416
  "eslint-config-prettier": "^10.1.8",
@@ -330,6 +436,7 @@
330
436
  "unplugin-icons": "^23.0.1",
331
437
  "vite": "^8.2.0",
332
438
  "vite-plugin-dts": "^5.0.3",
333
- "vitest": "^4.1.10"
439
+ "vitest": "^4.1.10",
440
+ "zod": "^4.6.5"
334
441
  }
335
442
  }
@@ -0,0 +1,51 @@
1
+ import { ReactNode } from 'react';
2
+ import { SpecIssue } from '../../utils/spec/specTypes';
3
+ import { SpecRegistry } from './specRegistry';
4
+ /** What a bound event hands the host. Everything about where it came from, and nothing about what to do. */
5
+ export interface SpecActionDetails {
6
+ /** What the spec sent with the action, resolved against the data the way a prop is. */
7
+ payload?: unknown;
8
+ /** The arguments the component called its handler with. */
9
+ args: unknown[];
10
+ /** The event prop that fired. */
11
+ prop: string;
12
+ component: string;
13
+ path: string;
14
+ }
15
+ export type SpecActionHandler = (action: string, details: SpecActionDetails) => void;
16
+ export interface SpecRenderOptions {
17
+ /** What the spec may name, and what each name renders. */
18
+ registry: SpecRegistry;
19
+ /** What `{ $data: … }` reads from. */
20
+ data?: unknown;
21
+ /** Every action the spec binds arrives here. The host decides what any of them mean. */
22
+ onAction?: SpecActionHandler;
23
+ /** What stands in for a node that could not render. Nothing, unless the app says otherwise. */
24
+ fallback?: (issue: SpecIssue) => ReactNode;
25
+ /** A tree bigger than this stops rendering — a generated spec has no author to notice a runaway one. */
26
+ maxNodes?: number;
27
+ maxDepth?: number;
28
+ /** A node that threw renders its fallback until this changes: the next spec is a fresh chance. */
29
+ resetKey?: unknown;
30
+ /** A node that threw during a *commit*, which is after this function has returned. */
31
+ onError?: (issue: SpecIssue) => void;
32
+ /**
33
+ * The last value each node was given for a prop it cannot render without. A stream is not monotone —
34
+ * a column's `align` passes through `"e"` on its way to `"end"`, which fails its own schema and takes
35
+ * the whole `def` with it — so without this a grid on screen is unmounted and rebuilt several times
36
+ * in the last moments of a spec. `<SpecRenderer>` keeps one; a one-shot render needs none.
37
+ */
38
+ memory?: SpecMemory;
39
+ }
40
+ /** Keyed by a node's path and component: a different component at that path remembers nothing. */
41
+ export type SpecMemory = Map<string, Record<string, unknown>>;
42
+ export interface SpecRenderResult {
43
+ element: ReactNode;
44
+ /** Everything that did not render, and why. Advisory — the tree renders around every one of them. */
45
+ issues: SpecIssue[];
46
+ }
47
+ /**
48
+ * The spec as React elements, and everything that did not render. No hook in it, so a static spec
49
+ * renders on a server and a test can assert the issues without mounting anything.
50
+ */
51
+ export declare function renderSpec(spec: unknown, options: SpecRenderOptions): SpecRenderResult;
@@ -0,0 +1,25 @@
1
+ import { Component, ReactNode } from 'react';
2
+ import { SpecIssue } from '../../utils/spec/specTypes';
3
+ export interface SpecBoundaryProps {
4
+ /** Where the node sits in the spec, for the issue this reports. */
5
+ path: string;
6
+ component: string;
7
+ /** A new spec resets the boundary: the next version of a node deserves its own chance to render. */
8
+ resetKey: unknown;
9
+ fallback: (issue: SpecIssue) => ReactNode;
10
+ onError?: (issue: SpecIssue) => void;
11
+ children: ReactNode;
12
+ }
13
+ interface SpecBoundaryState {
14
+ message: string | null;
15
+ resetKey: unknown;
16
+ }
17
+ export default class SpecBoundary extends Component<SpecBoundaryProps, SpecBoundaryState> {
18
+ state: SpecBoundaryState;
19
+ static getDerivedStateFromProps(props: SpecBoundaryProps, state: SpecBoundaryState): Partial<SpecBoundaryState> | null;
20
+ static getDerivedStateFromError(error: unknown): Partial<SpecBoundaryState>;
21
+ componentDidCatch(error: unknown): void;
22
+ private issue;
23
+ render(): ReactNode;
24
+ }
25
+ export {};
@@ -0,0 +1,42 @@
1
+ import { ComponentType } from 'react';
2
+ import { CatalogSchema } from '@box-kite/core';
3
+ import { SpecRules } from '../../utils/spec/specTypes';
4
+ /** A component the catalog does not describe, or one whose rules the app wants to narrow. */
5
+ export interface SpecComponent {
6
+ component: ComponentType<any>;
7
+ /** What its props may be, as JSON Schema. Without one, a spec may set none of them. */
8
+ props?: CatalogSchema;
9
+ /** Where children may go. `['default']` unless the catalog says otherwise. */
10
+ slots?: string[];
11
+ /** The props a spec may bind an action to — the only props that can ever become a function. */
12
+ events?: string[];
13
+ description?: string;
14
+ }
15
+ export interface SpecRegistryEntry extends SpecRules {
16
+ component: ComponentType<any>;
17
+ }
18
+ export interface SpecRegistry {
19
+ /** Every component a spec may name, in the order they were registered. */
20
+ readonly names: string[];
21
+ /** The rules alone, which is what `specSchema()` takes: registry in, the model's constraint out. */
22
+ readonly components: Record<string, SpecRules>;
23
+ get(type: string): SpecRegistryEntry | undefined;
24
+ }
25
+ /** What a `catalog()` result carries per component. Structural, so the catalog entry is not imported. */
26
+ interface CatalogSource {
27
+ components: Record<string, {
28
+ description?: string;
29
+ props?: CatalogSchema;
30
+ slots?: string[];
31
+ events?: string[];
32
+ }>;
33
+ }
34
+ export interface SpecRegistryOptions {
35
+ /** The implementations, by the name a spec writes. A bare component takes its rules from the catalog. */
36
+ components: Record<string, ComponentType<any> | SpecComponent>;
37
+ /** `catalog()`, narrowed however the app narrowed it. Anything it describes needs no rules of its own. */
38
+ catalog?: CatalogSource;
39
+ }
40
+ /** The registry a `<SpecRenderer>` renders against. */
41
+ export declare function createSpecRegistry(options: SpecRegistryOptions): SpecRegistry;
42
+ export default createSpecRegistry;
@@ -0,0 +1,10 @@
1
+ import { SpecIssue } from '../../utils/spec/specTypes';
2
+ import { SpecRenderOptions } from './renderSpec';
3
+ export interface SpecRendererProps extends Omit<SpecRenderOptions, 'onError' | 'resetKey' | 'memory'> {
4
+ /** Whatever arrived. Every field of it is checked, so a partial object from a stream is welcome. */
5
+ spec: unknown;
6
+ /** Called after each render whose issues changed, and again when a node throws during a commit. */
7
+ onIssues?: (issues: SpecIssue[]) => void;
8
+ }
9
+ /** A generated spec, rendered against what the app allows. */
10
+ export default function SpecRenderer({ spec, onIssues, ...options }: SpecRendererProps): import('react').ReactNode;
@@ -0,0 +1,22 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ /**
3
+ * The frame an admin app hangs off: a sidebar, a header with search, a theme toggle and an account
4
+ * menu, and the page itself. The sidebar sticks to the top of its column and folds away below the `md`
5
+ * breakpoint; nothing here is measured or positioned by script.
6
+ *
7
+ * `Box.Theme` is `use="local"` here, so the theme lands on this element and the block can sit inside a
8
+ * page that has its own. Move it to your root layout as `use="global"` for an app-wide switch.
9
+ */
10
+ export interface NavItem {
11
+ id: string;
12
+ label: string;
13
+ href: string;
14
+ icon: ReactElement;
15
+ }
16
+ export interface DashboardShellProps {
17
+ /** The heading above the page, and the `id` of the nav item that is marked as current. */
18
+ title: string;
19
+ current?: string;
20
+ children: ReactNode;
21
+ }
22
+ export default function DashboardShell({ title, current, children }: DashboardShellProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The row of tiles above a dashboard. The shapes are `components/chart` primitives — an SVG each, no
3
+ * chart library — and there is no `'use client'` here on purpose: nothing in this file holds state, so
4
+ * it renders in a Server Component, styles and all.
5
+ */
6
+ export interface Stat {
7
+ label: string;
8
+ value: string;
9
+ delta: string;
10
+ up: boolean;
11
+ trend: number[];
12
+ }
13
+ export default function DashboardStats(): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ /** The rows the block is wired to. Replace this file with your own fetch — the grid reads plain objects. */
2
+ export interface Invoice {
3
+ id: number;
4
+ reference: string;
5
+ customer: string;
6
+ team: string;
7
+ status: 'paid' | 'pending' | 'overdue';
8
+ amount: number;
9
+ issued: string;
10
+ }
11
+ /**
12
+ * Two hundred invoices generated from the index, so the block has something to sort, filter, group and
13
+ * total without shipping a data file — and every reload shows the same table.
14
+ */
15
+ export declare const invoices: Invoice[];
@@ -0,0 +1 @@
1
+ export default function InvoicesGrid(): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A settings panel: text fields, a searchable select, a radio group, a slider and a switch, each a real
3
+ * form control. `Form` reads its own fields when it submits, so none of this needs state — `values` is
4
+ * built from the elements carrying a `name`.
5
+ *
6
+ * `<Toaster />` has to be mounted once near the root of the app for the confirmation to show.
7
+ */
8
+ export interface Settings {
9
+ name: string;
10
+ email: string;
11
+ bio: string;
12
+ timezone: string;
13
+ theme: string;
14
+ density: string;
15
+ notify: boolean;
16
+ }
17
+ export default function SettingsForm(): import("react").JSX.Element;
package/spec.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use client";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./effects.cjs"),t=require("./specSchema.cjs"),n=require("./specData.cjs");let r=require("react"),i=require("react/jsx-runtime");var a;(function(e){let t=[`__proto__`,`constructor`,`prototype`],n=new Map;function r(e){if(!n.has(e))try{n.set(e,new RegExp(e))}catch{n.set(e,null)}return n.get(e)??null}function i(e){return!t.includes(e)}e.isSafeKey=i;function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}e.isPlainObject=a;function o(e,t){if(!e.$ref)return e;let n=e.$ref.startsWith(`#/$defs/`)?e.$ref.slice(8):null,r=n?t.$defs?.[n]:void 0;return r?o(r,t):null}function s(e,t,n){return!a(t)||e.required?.some(e=>!(e in t))?!1:Object.keys(t).every(r=>{let a=e.properties?.[r];return a?i(r)&&c(a,t[r],n):e.additionalProperties!==!1&&i(r)})}function c(e,t,n=e){let i=o(e,n);if(!i)return!1;if(i.anyOf)return i.anyOf.some(e=>c(e,t,n));if(i.enum&&!i.enum.includes(t))return!1;switch(i.type){case`string`:return typeof t==`string`?!i.pattern||(r(i.pattern)?.test(t)??!1):!1;case`number`:return typeof t==`number`&&Number.isFinite(t);case`integer`:return typeof t==`number`&&Number.isInteger(t);case`boolean`:return typeof t==`boolean`;case`array`:return Array.isArray(t)&&(!i.items||t.every(e=>c(i.items,e,n)));case`object`:return s(i,t,n);default:return t!==void 0}}e.matches=c})(a||={});var o=a,s;(function(e){function t(e){let t=JSON.stringify(e)??`undefined`;return t.length>40?`${t.slice(0,39)}…`:t}function r(e,t){return typeof e==`string`?{action:e}:!o.isPlainObject(e)||typeof e.action!=`string`?null:{action:e.action,payload:n.t.deep(e.payload,t)}}function i(e,i,a,s){let c=typeof e.type==`string`?e.type:void 0,l=[],u={},d={},f=i.props,p=f?.properties;if(o.isPlainObject(e.props))for(let[r,i]of Object.entries(e.props)){let e=`${s}.props.${r}`;if(!o.isSafeKey(r)){l.push({code:`unknown-prop`,path:e,component:c,prop:r,message:`${r} is not a prop a spec can set.`});continue}let d=p?.[r];if(!d&&(!f||f.additionalProperties===!1)){l.push({code:`unknown-prop`,path:e,component:c,prop:r,message:`${c??`The component`} has no ${r} prop.`});continue}let m=null,h=n.t.deep(i,a,e=>{m??=e});if(m){l.push({code:`unresolved-data`,path:e,component:c,prop:r,message:`${m} resolved to nothing.`});continue}if(d&&!o.matches(d,h,f)){l.push({code:`invalid-prop`,path:e,component:c,prop:r,message:`${t(h)} is not a value ${r} takes.`});continue}u[r]=h}if(o.isPlainObject(e.on))for(let[t,n]of Object.entries(e.on)){let e=`${s}.on.${t}`,o=i.events.includes(t)?r(n,a):null;if(!o){l.push({code:`unknown-event`,path:e,component:c,prop:t,message:`${c??`The component`} has no ${t} event.`});continue}d[t]=o}return{props:u,actions:d,issues:l,missing:(f?.required??[]).filter(e=>!(e in u))}}e.resolve=i})(s||={});var c=s,l=class extends r.Component{state={message:null,resetKey:this.props.resetKey};static getDerivedStateFromProps(e,t){return e.resetKey===t.resetKey?null:{message:null,resetKey:e.resetKey}}static getDerivedStateFromError(e){return{message:e instanceof Error?e.message:String(e)}}componentDidCatch(e){this.props.onError?.(this.issue(e instanceof Error?e.message:String(e)))}issue(e){return{code:`render-error`,path:this.props.path,component:this.props.component,message:e}}render(){return this.state.message===null?this.props.children:this.props.fallback(this.issue(this.state.message))}},u=1e3,d=32;function f(e,t){e.issues.push(t)}function p(e,t,n){f(e,t);let i=e.fallback(t);return n===void 0||i==null?i??null:(0,r.createElement)(r.Fragment,{key:n},i)}function m(e,t,r,i,a){if(e==null||typeof e==`boolean`)return null;if(typeof e==`string`||typeof e==`number`)return e;if(Array.isArray(e))return e.map((e,n)=>m(e,t,r,`${i}.${n}`,a));if(n.t.isRef(e)){let a=n.t.resolve(e,r);return a.found?a.value===null||a.value===void 0||typeof a.value==`boolean`?null:typeof a.value==`string`||typeof a.value==`number`?a.value:(f(t,{code:`invalid-child`,path:i,message:`${n.t.pathOf(e)} is not text.`}),null):(f(t,{code:`unresolved-data`,path:i,message:`${n.t.pathOf(e)} resolved to nothing.`}),null)}return o.isPlainObject(e)?`type`in e?typeof e.type==`string`?e.type===``?null:g(e,t,r,i,a,e.type):(f(t,{code:`invalid-child`,path:i,message:"A node names its component in `type`."}),null):null:(f(t,{code:`invalid-child`,path:i,message:`A child is a node, text, or a reference to text.`}),null)}function h(e,t,r,i,a,o){let s=e.repeat;if(!n.t.isRef(s))return p(t,{code:`invalid-repeat`,path:`${i}.repeat`,component:o,message:`A repeat is a reference to an array.`});let c=n.t.resolve(s,r);if(!c.found)return f(t,{code:`unresolved-data`,path:`${i}.repeat`,component:o,message:`${n.t.pathOf(s)} resolved to nothing.`}),null;if(!Array.isArray(c.value))return p(t,{code:`invalid-repeat`,path:`${i}.repeat`,component:o,message:`${n.t.pathOf(s)} is not an array.`});let l=typeof e.key==`string`?e.key:i;return c.value.map((n,s)=>_(e,t,{data:r.data,item:n,index:s,repeating:!0},i,a,o,`${l}#${s}`))}function g(e,t,n,r,i,a){return i>t.maxDepth?p(t,{code:`too-deep`,path:r,component:a,message:`Nested deeper than the ${t.maxDepth} levels allowed.`}):e.repeat===void 0?_(e,t,n,r,i,a,typeof e.key==`string`?e.key:r):h(e,t,n,r,i,a)}function _(e,t,n,a,s,u,d){if(t.nodes+=1,t.nodes>t.maxNodes)return t.overflowed||(t.overflowed=!0,f(t,{code:`too-many-nodes`,path:a,component:u,message:`More than the ${t.maxNodes} nodes allowed.`})),null;let h=t.registry.get(u);if(!h)return p(t,{code:`unknown-component`,path:a,component:u,message:`${u} is not a component this app allows.`},d);let g=c.resolve(e,h,n,a),_={...g.props};t.issues.push(...g.issues);let v=t.memory?.get(`${a}:${u}`);if(g.missing.length){if(!(v&&g.missing.every(e=>e in v))){let e=g.missing.join(`, `);return p(t,{code:`missing-prop`,path:a,component:u,prop:g.missing[0],message:`${u} cannot render without ${e}.`},d)}for(let e of g.missing)_[e]=v[e]}if(t.memory){let e=h.props?.required??[];e.length&&t.memory.set(`${a}:${u}`,Object.fromEntries(e.map(e=>[e,_[e]])))}for(let[e,n]of Object.entries(g.actions))_[e]=(...r)=>t.onAction?.(n.action,{payload:n.payload,args:r,prop:e,component:u,path:a});if(e.children!==void 0&&(h.slots.includes(`default`)?_.children=m(e.children,t,n,`${a}.children`,s+1):f(t,{code:`unknown-slot`,path:`${a}.children`,component:u,message:`${u} takes no children.`})),o.isPlainObject(e.slots))for(let[r,i]of Object.entries(e.slots)){let e=`${a}.slots.${r}`;if(!o.isSafeKey(r)||!h.slots.includes(r)){f(t,{code:`unknown-slot`,path:e,component:u,message:`${u} has no ${r} slot.`});continue}_[r==="default"?`children`:r]=m(i,t,n,e,s+1)}return(0,i.jsx)(l,{path:a,component:u,resetKey:t.resetKey,fallback:t.fallback,onError:t.onError,children:(0,r.createElement)(h.component,_)},d)}function v(e,t){let n={registry:t.registry,data:t.data,onAction:t.onAction,onError:t.onError,fallback:t.fallback??(()=>null),maxNodes:t.maxNodes??u,maxDepth:t.maxDepth??d,resetKey:t.resetKey,memory:t.memory,issues:[],nodes:0,overflowed:!1};return{element:m(e,n,{data:t.data},`spec`,0),issues:n.issues}}function y(t){let n=(0,r.useRef)(t);return e.n(()=>{n.current=t}),n}function b({spec:e,onIssues:t,...n}){let[i]=(0,r.useState)(()=>new Map),{element:a,issues:o}=v(e,{...n,memory:i,resetKey:e,onError:e=>t?.([e])}),s=y(t),c=y(o),l=o.map(e=>`${e.code} ${e.path} ${e.message}`).join(`
2
+ `);return(0,r.useEffect)(()=>{s.current?.(c.current)},[l,c,s]),a}function x(e){return typeof e==`object`&&!!e&&`component`in e}function S(e,t,n){let r=x(t)?t:{component:t},i=n?.components[e];return{component:r.component,props:r.props??i?.props,slots:r.slots??i?.slots??[`default`],events:r.events??i?.events??[],description:r.description??i?.description}}function C(e){let t=Object.keys(e.components),n=new Map(t.map(t=>[t,S(t,e.components[t],e.catalog)]));return{names:t,components:Object.fromEntries([...n].map(([e,{component:t,...n}])=>[e,n])),get:e=>n.get(e)}}var w=o.matches;exports.SpecRenderer=b,exports.default=b,exports.createSpecRegistry=C,exports.matchesSchema=w,exports.renderSpec=v,exports.specSchema=t.t;
package/spec.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { default as SpecValidate } from './utils/spec/specValidate';
2
+ export { default, default as SpecRenderer } from './react/spec/specRenderer';
3
+ export type { SpecRendererProps } from './react/spec/specRenderer';
4
+ export { renderSpec } from './react/spec/renderSpec';
5
+ export type { SpecActionDetails, SpecActionHandler, SpecRenderOptions, SpecRenderResult } from './react/spec/renderSpec';
6
+ export { default as createSpecRegistry } from './react/spec/specRegistry';
7
+ export type { SpecComponent, SpecRegistry, SpecRegistryEntry, SpecRegistryOptions } from './react/spec/specRegistry';
8
+ export { default as specSchema } from './utils/spec/specSchema';
9
+ export type { SpecSchemaComponent, SpecSchemaOptions, SpecSchemaSource } from './utils/spec/specSchema';
10
+ export type { SpecAction, SpecChild, SpecDataRef, SpecIndexRef, SpecIssue, SpecIssueCode, SpecItemRef, SpecNode, SpecRef, SpecRules, } from './utils/spec/specTypes';
11
+ /** Whether a value is one a catalog schema allows: the check the renderer makes, for a host wanting it first. */
12
+ export declare const matchesSchema: typeof SpecValidate.matches;