@peripheries/renderer 0.23.1 → 0.25.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.
package/README.md CHANGED
@@ -6,13 +6,13 @@ the same in each.
6
6
 
7
7
  | Entry | Holds |
8
8
  |---|---|
9
- | `@peripheries/renderer/core` | Framework-free: cells and thin-ref hydration, references and conditions, the data context, walks and state names, cell props, layout, document structure, bundle refs and resolution, the shared store, `loadPageData(page, source)`, validation, `DIAGNOSTICS` and `PAGE_RULES` |
9
+ | `@peripheries/renderer/core` | Framework-free: cells and thin-ref hydration, references and conditions, the data context, walks and state names (`layerName`, the layer catalog `LAYER_ROLES`), cell props, layout, document structure, bundle refs and resolution, the shared store, `loadPageData(page, source)`, validation, `DIAGNOSTICS` and `PAGE_RULES` |
10
10
  | `@peripheries/renderer/catalog` | `checkCatalog`: what a component repo runs on the knowledge it emits. The only entry with a JSON Schema validator (ajv) |
11
11
  | `@peripheries/renderer/react` | `PageRenderer`, `CellView`, `StructureView`, the cell states, `useContainerSize`, `useFormsRuntime` and its `FormSlotProps` and `FormAnswer` |
12
12
  | `@peripheries/renderer/runtime` | What a page boots with: the import map and boot script, the shims, `installRuntime`, the store bundles import |
13
13
  | `@peripheries/renderer/theme` | The org theme pipeline: `parseTheme` (version 1 or 2 in, version 2 out, with diagnostics), `themeTokens` (semantic overrides derived in OKLCH), `themeCss` (the `theme` layer), `checkTheme` (WCAG contrast) |
14
14
  | `@peripheries/renderer/print` | The print pipeline, framework-free: `collectBlocks` (the page map), `paginate`, `buildPrintLayout` (the packer's decision as CSS), `buildPageStylesheet` (`@page` geometry and margin boxes), running headers, `@printPolicy` parsing, print lint, `resolveDocumentProfile`, region bindings, map staleness, and the `layout.structure[].print` writer |
15
- | `@peripheries/renderer/forms` | The forms runtime, framework-free: the steps overlay and its model, conditions, modes, navigation, progress, deep links, and the persistence port (`FormsPort`); the React hook is `useFormsRuntime` in `/react` |
15
+ | `@peripheries/renderer/forms` | The forms runtime, framework-free: the steps overlay and its model, conditions, modes, navigation, progress, deep links, and the persistence port (`FormsPort`), and the form-section contract's type (`FormSection`, `FormContentBlock`); the React hook is `useFormsRuntime` in `/react` |
16
16
  | `@peripheries/renderer/testing` | The golden pages, synthetic fixtures, and sources that serve them |
17
17
  | `@peripheries/renderer/styles.css` | Page and cell containers, the responsive buckets, placeholders and notices, in the `renderer` layer |
18
18
 
@@ -51,6 +51,11 @@ const runtime = await loadPageData(page, source, { params, baseProps, viewer, co
51
51
  - `frameProps(cell, contract)`: extra attributes and style on a cell's frame,
52
52
  for what the host doesn't apply yet (a print policy, say).
53
53
  - `onDiagnostic(d)`: a page configuration problem.
54
+ - `showHiddenCells`: where a page is being built or reviewed (an editor, a
55
+ harness, a preview), a cell its `hide_when_empty` leaves out shows as a
56
+ small "hidden (empty)" placeholder instead of nothing.
57
+ - `showProblemCodes`: a cell's problem notice adds its diagnostic code, for
58
+ staff and editors; without it a viewer reads only the plain wording.
54
59
 
55
60
  A consumer with resolved values instead of a graph (the component repos'
56
61
  harness) builds its runtime with `pageRuntimeFromValues(page, { values,
@@ -70,6 +75,7 @@ names: 0.8.0 dropped the old client-prefixed twins and container names.
70
75
  | A cell's buckets | `data-hide-when`, `data-show-when`, `data-stack-when`, `data-truncate-when` |
71
76
  | A structure section | `data-section`, `data-section-level`, `data-section-kind`, `data-derived-ordinal` |
72
77
  | A missing component's notice | `data-missing-component` |
78
+ | A cell `hide_when_empty` left out, shown by `showHiddenCells` | `.cell-hidden`, `data-cell-state="hidden-empty"` |
73
79
 
74
80
  The store specifier and runtime global are in `@peripheries/runtime`'s README.
75
81
  The theme reaches a page as CSS, in the `theme` layer; the renderer ignores a
@@ -77,6 +83,46 @@ bundle's `ThemeProvider` export.
77
83
 
78
84
  ## Versions
79
85
 
86
+ - **0.25.0** (2026-10-24): `hide_when_empty`, a cell-level list of prop names.
87
+ - **A cell with `hide_when_empty: ["title", "items"]`** renders nothing, and leaves no gap,
88
+ when every prop it names is empty once bound: null, undefined, text that is blank once
89
+ trimmed, `[]` or `{}`. `0` and `false` are content. A slot or `children` filled by child
90
+ cells is content. Print follows the screen. The check runs before the bundles load, so a
91
+ server render and the browser agree.
92
+ - **A refused, failed or unresolved read is not empty.** A named prop that reads a label whose
93
+ read was refused or failed keeps the cell's error notice. One whose query never ran because
94
+ its own reference resolved to nothing (`QUERY_UNRESOLVED`) shows the same notice instead of
95
+ hiding (`hideWhenEmptyProblem`). A label still loading isn't decided yet (the cell renders).
96
+ - **`RendererEnvironment.showProblemCodes`**: a problem notice adds its diagnostic code
97
+ (`data-cell-code`, `.cell-notice__code`) for staff and editors; a client reads only "This
98
+ part of the page couldn't load." The host sets it in the platform environment and in a
99
+ proposal preview.
100
+ - **`RendererEnvironment.showHiddenCells`**: an editor, a harness or a preview shows a hidden
101
+ cell as a small "hidden (empty)" placeholder (`CellHidden`, `.cell-hidden`).
102
+ - **Validation:** `validatePage` reports `PROP_UNKNOWN` (warning) at
103
+ `content.cells[i].hide_when_empty[j]` for a name the component's `props_schema` doesn't
104
+ declare, and `HIDE_WHEN_EMPTY_INVALID` (error) for anything but a non-empty list of names.
105
+ - A ref cell's node owns `hide_when_empty` (`CONTENT_FIELDS`, via `RENDER_FIELDS`). `/core`
106
+ exports `hiddenWhenEmpty`, `hideWhenEmptyProblem` and `isEmptyPropValue`. `PAGE_RULES` names the key.
107
+ - The renderer's `PageCell` type adds `hide_when_empty?: string[]`; `@peripheries/runtime`'s
108
+ `PageCell` gains it at its next release.
109
+
110
+ - **0.24.0** (2026-10-20): one renderer batch (CX).
111
+ - **`layerName(system)` and `LAYER_ROLES`** in `/core`: a system's state name (its layer's role,
112
+ else a registered layer's own type, `question`, `answer`, …) from the layer catalog, which is
113
+ generated from `public.system_layer` (`npm run gen:layers`; CI checks it is current). A host
114
+ and BEA's preview resolve a page's state names alike. An unregistered type has no name.
115
+ - **`FormSection` and `FormContentBlock`** in `/forms`: a form section cell's `props.section`,
116
+ contract v1 (`contracts/form-section.schema.json`): the section node's `id`, its authored
117
+ `sectionId`, `title`, `description?`, `sectionType?`, its `questions` (`{ key, label, required,
118
+ definition }`, the definition the form-definition contract's question or null) and its
119
+ `content` blocks (the form-definition contract's content block). Static: visibility, answers
120
+ and totals stay runtime (`forms.*`, `FormAnswer`, unchanged). v1 is the target shape: the
121
+ component engine doesn't emit `props.section` yet; the cutover is in the stack's
122
+ `contracts/README.md`.
123
+ - **`FormQuestionDefinition`'s** `options`, `validation`, `description` and `helpText` may be
124
+ null, as the form-definition contract's question has them.
125
+
80
126
  - **0.23.1** (2026-10-15): the published files carry no comments and no
81
127
  source maps (declarations included); this README is the documentation.
82
128
  No API change.
package/dist/catalog.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { x as isEach } from "./chunks/query-D6xQbTFK.js";
2
- import { i as eachFormProblems, s as validatePage } from "./chunks/validate-BcB_PA1k.js";
2
+ import { i as eachFormProblems, s as validatePage } from "./chunks/validate-CrHGlwey.js";
3
3
  import { KNOWLEDGE_BUDGETS, KNOWLEDGE_KEY } from "@peripheries/runtime";
4
4
  import Ajv2020 from "ajv/dist/2020.js";
5
5
  var ref = (kind, key) => `${kind}:${key}`;
@@ -16,7 +16,12 @@ var STORE_FIELDS = [
16
16
  "viewState",
17
17
  "live"
18
18
  ];
19
- var CONTENT_FIELDS = [...HYDRATED_CELL_FIELDS.filter((f) => f !== "layout"), ...STORE_FIELDS];
19
+ var RENDER_FIELDS = ["hide_when_empty"];
20
+ var CONTENT_FIELDS = [
21
+ ...HYDRATED_CELL_FIELDS.filter((f) => f !== "layout"),
22
+ ...STORE_FIELDS,
23
+ ...RENDER_FIELDS
24
+ ];
20
25
  function normalizeCell(cell, path = "cell") {
21
26
  const diagnostics = [];
22
27
  if (cell.ref) return {
@@ -114,6 +119,39 @@ function failedLabels(cell, data) {
114
119
  return Boolean(entry?.error) && !entry?.unresolved?.length && !entry?.isLoading && (entry?.data === void 0 || entry?.data === null);
115
120
  });
116
121
  }
122
+ function isEmptyPropValue(value) {
123
+ if (value === null || value === void 0) return true;
124
+ if (typeof value === "string") return value.trim() === "";
125
+ if (Array.isArray(value)) return value.length === 0;
126
+ if (typeof value === "object") {
127
+ const proto = Object.getPrototypeOf(value);
128
+ return (proto === Object.prototype || proto === null) && Object.keys(value).length === 0;
129
+ }
130
+ return false;
131
+ }
132
+ var propLabels = (value) => [...JSON.stringify(value ?? null).matchAll(LABEL_REF)].map((m) => m[1]);
133
+ function hideWhenEmptyProblem(cell, data) {
134
+ const names = cell.hide_when_empty;
135
+ if (!Array.isArray(names)) return null;
136
+ for (const name of names) {
137
+ if (typeof name !== "string") continue;
138
+ for (const label of propLabels(cell.props?.[name])) if (data[label]?.unresolved?.length && !data[label]?.isLoading) return "QUERY_UNRESOLVED";
139
+ }
140
+ return null;
141
+ }
142
+ function hiddenWhenEmpty(cell, props, data) {
143
+ const names = cell.hide_when_empty;
144
+ if (!Array.isArray(names) || names.length === 0) return false;
145
+ for (const name of names) {
146
+ if (typeof name !== "string") return false;
147
+ for (const label of propLabels(cell.props?.[name])) {
148
+ const entry = data[label];
149
+ if (entry?.isLoading || entry?.error) return false;
150
+ }
151
+ if (!isEmptyPropValue(props[name])) return false;
152
+ }
153
+ return true;
154
+ }
117
155
  function assembleCellProps(i) {
118
156
  const { style: _fromConfig, ...resolved } = i.resolvedProps;
119
157
  const props = { ...resolved };
@@ -304,4 +342,4 @@ function collectScopeIds(ctx, selections, viewState) {
304
342
  }
305
343
  return [...ids];
306
344
  }
307
- export { normalizeCell as C, hasRef as S, rootCells as T, failedLabels as _, projectSelections as a, HYDRATED_CELL_FIELDS as b, pageScopeKey as c, resolveComponent as d, cellAttributes as f, assembleCellProps as g, pageContainerStyle as h, projectLive as i, projectScopeKey as l, documentProfile as m, attachViewStateSetters as n, projectViewState as o, cellPlacementStyle as p, collectScopeIds as r, shallowEqualBundle as s, attachSelectionSetters as t, resolveScopeId as u, projectCellData as v, resolveCell as w, STORE_FIELDS as x, CONTENT_FIELDS as y };
345
+ export { HYDRATED_CELL_FIELDS as C, normalizeCell as D, hasRef as E, resolveCell as O, CONTENT_FIELDS as S, STORE_FIELDS as T, failedLabels as _, projectSelections as a, isEmptyPropValue as b, pageScopeKey as c, resolveComponent as d, cellAttributes as f, assembleCellProps as g, pageContainerStyle as h, projectLive as i, rootCells as k, projectScopeKey as l, documentProfile as m, attachViewStateSetters as n, projectViewState as o, cellPlacementStyle as p, collectScopeIds as r, shallowEqualBundle as s, attachSelectionSetters as t, resolveScopeId as u, hiddenWhenEmpty as v, RENDER_FIELDS as w, projectCellData as x, hideWhenEmptyProblem as y };
@@ -641,6 +641,16 @@ function validateCell(cell, catalog, path = "cell", ids, options = {}) {
641
641
  message: `${cell.component} requires ${prop}`
642
642
  });
643
643
  }
644
+ const listed = schema?.properties;
645
+ if (listed && Array.isArray(cell.hide_when_empty)) cell.hide_when_empty.forEach((prop, i) => {
646
+ if (typeof prop === "string" && prop !== "children" && !(prop in listed)) push({
647
+ severity: "warning",
648
+ code: "PROP_UNKNOWN",
649
+ cellId,
650
+ path: `${path}.hide_when_empty[${i}]`,
651
+ message: `hide_when_empty names ${prop}, which ${cell.component} doesn’t declare`
652
+ });
653
+ });
644
654
  const declared = schema?.properties;
645
655
  if (declared && schema?.additionalProperties !== true && typeof schema?.additionalProperties !== "object") {
646
656
  for (const prop of Object.keys(cell.props ?? {})) if (prop !== "children" && prop !== "style" && !(prop in declared)) push({
@@ -674,6 +684,14 @@ function validateCell(cell, catalog, path = "cell", ids, options = {}) {
674
684
  path: `${path}.children`,
675
685
  message: `child cell "${child}" is not on the page`
676
686
  });
687
+ const hide = cell.hide_when_empty;
688
+ if (hide !== void 0 && (!Array.isArray(hide) || hide.length === 0 || hide.some((p) => typeof p !== "string" || !p))) push({
689
+ severity: "error",
690
+ code: "HIDE_WHEN_EMPTY_INVALID",
691
+ cellId,
692
+ path: `${path}.hide_when_empty`,
693
+ message: "hide_when_empty takes a non-empty list of prop names"
694
+ });
677
695
  checkBindings(cell.data?.queries, `${path}.data.queries`, push, cellId, options.template);
678
696
  checkActions(cell, path, push);
679
697
  checkProjections(cell.props, `${path}.props`, push, cellId);
package/dist/core.js CHANGED
@@ -1,6 +1,6 @@
1
- import { C as normalizeCell, S as hasRef, T as rootCells, a as projectSelections, b as HYDRATED_CELL_FIELDS, c as pageScopeKey, d as resolveComponent, f as cellAttributes, g as assembleCellProps, h as pageContainerStyle, i as projectLive, l as projectScopeKey, n as attachViewStateSetters, o as projectViewState, p as cellPlacementStyle, r as collectScopeIds, s as shallowEqualBundle, t as attachSelectionSetters, u as resolveScopeId, v as projectCellData, w as resolveCell, x as STORE_FIELDS, y as CONTENT_FIELDS } from "./chunks/projection-zhu32_KJ.js";
1
+ import { C as HYDRATED_CELL_FIELDS, D as normalizeCell, E as hasRef, O as resolveCell, S as CONTENT_FIELDS, T as STORE_FIELDS, a as projectSelections, b as isEmptyPropValue, c as pageScopeKey, d as resolveComponent, f as cellAttributes, g as assembleCellProps, h as pageContainerStyle, i as projectLive, k as rootCells, l as projectScopeKey, n as attachViewStateSetters, o as projectViewState, p as cellPlacementStyle, r as collectScopeIds, s as shallowEqualBundle, t as attachSelectionSetters, u as resolveScopeId, v as hiddenWhenEmpty, w as RENDER_FIELDS, x as projectCellData, y as hideWhenEmptyProblem } from "./chunks/projection-e8MHn6Bt.js";
2
2
  import { C as passesConditions, S as pageContext, T as settled, _ as EACH, a as VARIABLE_NAMESPACES, b as cellResolutionContext, c as pageVariables, d as resolveDescriptorVariables, f as selectionShape, g as variableReferences, h as variableLabels, i as UnsafeDocumentError, l as prepareDocument, m as validateDescriptor, n as RENDER_FUNCTIONS, o as descriptorShape, p as shapeAt, r as RENDER_FUNCTIONS_AHEAD, s as orderByVariable, t as PAGING_VARIABLES, u as queryKey, v as EACH_DEFAULT_AS, w as resolveReferences, x as isEach, y as cellContext } from "./chunks/query-D6xQbTFK.js";
3
- import { _ as stateValue, a as nearestNames, b as walkKey, c as selectionIssues, d as needsWalk, f as readField, g as stateNames, h as resolveWalkStart, i as eachFormProblems, l as RESERVED_LABELS, m as referencedLabels, n as STORE_SCOPES, o as validateCell, p as readWalk, r as WALK_DIRECTIONS, s as validatePage, t as RESPONSIVE_BUCKETS, u as mergeStates, v as systemsFromGraphs, x as walkRequest, y as systemsFromProcess } from "./chunks/validate-BcB_PA1k.js";
3
+ import { _ as stateValue, a as nearestNames, b as walkKey, c as selectionIssues, d as needsWalk, f as readField, g as stateNames, h as resolveWalkStart, i as eachFormProblems, l as RESERVED_LABELS, m as referencedLabels, n as STORE_SCOPES, o as validateCell, p as readWalk, r as WALK_DIRECTIONS, s as validatePage, t as RESPONSIVE_BUCKETS, u as mergeStates, v as systemsFromGraphs, x as walkRequest, y as systemsFromProcess } from "./chunks/validate-CrHGlwey.js";
4
4
  import { C as resolveAction, S as projectCellActions, _ as actionBindingKey, a as deriveSectionReferences, b as declaredActions, c as nodePrintStyle, d as referencesFor, f as sectionAnchor, g as usesStructureLayout, h as tableOfContents, i as deriveSectionNumbering, l as normalizeStructure, m as structureIssues, n as SECTION_KINDS, r as deriveElementReferences, s as mergeSectionReferences, t as PAGE_DOCUMENT, u as numberingFor, v as actionProblems, x as normalizeActionDeclaration, y as actionReferences } from "./chunks/structure-CKBtQHJq.js";
5
5
  import { a as loadBundlesSettled, i as loadBundles, n as isComponentExport, o as projectModule, r as loadBundle, s as runtimeReady, t as _clearBundleCache } from "./chunks/load-b38vUiZM.js";
6
6
  import { a as normalizeRef, i as bundleScope, n as MAIN, o as refKey, r as PUBLIC, t as CORE_SPECIFIER } from "./chunks/refs-Csgh6rXq.js";
@@ -90,6 +90,22 @@ function buildDataContext(input) {
90
90
  }
91
91
  return ctx;
92
92
  }
93
+ var LAYER_ROLES = {
94
+ "answer": null,
95
+ "appraisal-system": "appraisal",
96
+ "data-system": "data",
97
+ "edits": null,
98
+ "model-system": "model",
99
+ "organisation": null,
100
+ "process-system": "process",
101
+ "question": null,
102
+ "submission": null
103
+ };
104
+ function layerName(s) {
105
+ if (s.system_layer) return s.system_layer.role ?? s.system_type ?? null;
106
+ if (!s.system_type || !Object.hasOwn(LAYER_ROLES, s.system_type)) return null;
107
+ return LAYER_ROLES[s.system_type] ?? s.system_type;
108
+ }
93
109
  async function resolveSystems(source, page) {
94
110
  const systems = !page.process_id ? {} : source.graphSystems ? systemsFromGraphs(await source.graphSystems(page.process_id)) : systemsFromProcess(await source.processSystems?.(page.process_id));
95
111
  if (!systems.eng_data) {
@@ -785,6 +801,10 @@ var DIAGNOSTICS = {
785
801
  meaning: "A literal prop value fails its schema.",
786
802
  fix: "Write the value in the shape `props_schema` gives, or bind it with a whole-string `@label.path` reference."
787
803
  },
804
+ HIDE_WHEN_EMPTY_INVALID: {
805
+ meaning: "A cell’s `hide_when_empty` isn’t a list of prop names.",
806
+ fix: "Write `hide_when_empty: [\"title\", \"items\"]`: the props that, all empty once bound (null, blank text, `[]`, `{}`), leave the cell out."
807
+ },
788
808
  CELL_UNKNOWN_CHILD: {
789
809
  meaning: "`children` names a cell that is not on the page.",
790
810
  fix: "List only ids of cells on this page in `children`."
@@ -1038,7 +1058,7 @@ The host's rules for every page. Components, kits and patterns are in the catalo
1038
1058
  - Every cell has a unique \`id\`. Inline: \`{ id, component, props, layout? }\`. Managed by a cell node: \`{ id, ref: { graph_node_id }, layout? }\`; its \`component\` and \`props\` are the fallback.
1039
1059
  - \`children\` lists child cell ids, rendered inside the cell in place of \`props.children\`.
1040
1060
  - A child with \`slot\` fills that \`x-slot\` of its parent; others are \`children\`.
1041
- - \`conditions.showIf\` / \`hideIf\` test a value, usually a reference, for truthiness.
1061
+ - \`conditions.showIf\` / \`hideIf\` test a value for truthiness; \`hide_when_empty: [props]\` hides the cell if all are empty.
1042
1062
  - An embed cell \`{ id, embed: { page_id } | { page_type, project_id?, process_id? } }\` renders another published page, one level deep.
1043
1063
 
1044
1064
  ## Data and references
@@ -1116,4 +1136,4 @@ function manifestPageFrom(graphNodeId, data) {
1116
1136
  }
1117
1137
  };
1118
1138
  }
1119
- export { CONTENT_FIELDS, CORE_SPECIFIER, DIAGNOSTICS, EACH, EACH_DEFAULT_AS, HYDRATED_CELL_FIELDS, LAYOUT_TYPES, MAIN, PAGE_DOCUMENT, PAGE_RULES, PAGING_VARIABLES, PUBLIC, RENDER_FUNCTIONS, RENDER_FUNCTIONS_AHEAD, RESERVED_LABELS, RESPONSIVE_BUCKETS, SECTION_KINDS, STORE_FIELDS, STORE_SCOPES, STRUCTURE_FIELDS, TOOL_OWNED_KEYS, UnsafeDocumentError, VARIABLE_NAMESPACES, WALK_DIRECTIONS, _clearBundleCache, actionBindingKey, actionProblems, actionReferences, assembleCellProps, attachSelectionSetters, attachViewStateSetters, buildDataContext, bundleScope, cellAttributes, cellContext, cellPlacementStyle, cellPropSources, cellResolutionContext, clearScope, collectDeclarations, collectDescriptors, collectScopeIds, collectStatic, createStackStore, declaredActions, deriveElementReferences, deriveSectionNumbering, deriveSectionReferences, descriptorShape, eachFormProblems, editTarget, hasRef, isComponentExport, isEach, loadBundle, loadBundles, loadBundlesSettled, loadPageData, loadScope, manifestPageFrom, mergeSectionReferences, mergeStates, nearestNames, needsWalk, nodePrintStyle, normalizeActionDeclaration, normalizeCell, normalizeRef, normalizeStructure, numberingFor, orderByVariable, pageBundleRefs, pageBundles, pageContainerStyle, pageContext, pageRuntimeFromValues, pageScopeKey, pageVariables, passesConditions, patchAt, prepareDocument, projectCellActions, projectCellData, projectLive, projectModule, projectScopeKey, projectSelections, projectViewState, queryKey, readField, readWalk, refKey, referencedLabels, referencesFor, resolveAction, resolveCell, resolveComponent, resolveDataSystem, resolveDeclarations, resolveDescriptorVariables, resolveReferences, resolveScopeId, resolveSystems, resolveWalkStart, rootCells, runtimeReady, saveScope, sectionAnchor, selectionIssues, selectionShape, settled, shallowEqualBundle, shapeAt, stateNames, stateValue, structureIssues, systemsFromGraphs, systemsFromProcess, tableOfContents, usesStructureLayout, validateCell, validateDescriptor, validateManifest, validatePage, variableLabels, variableReferences, walkKey, walkRequest };
1139
+ export { CONTENT_FIELDS, CORE_SPECIFIER, DIAGNOSTICS, EACH, EACH_DEFAULT_AS, HYDRATED_CELL_FIELDS, LAYER_ROLES, LAYOUT_TYPES, MAIN, PAGE_DOCUMENT, PAGE_RULES, PAGING_VARIABLES, PUBLIC, RENDER_FIELDS, RENDER_FUNCTIONS, RENDER_FUNCTIONS_AHEAD, RESERVED_LABELS, RESPONSIVE_BUCKETS, SECTION_KINDS, STORE_FIELDS, STORE_SCOPES, STRUCTURE_FIELDS, TOOL_OWNED_KEYS, UnsafeDocumentError, VARIABLE_NAMESPACES, WALK_DIRECTIONS, _clearBundleCache, actionBindingKey, actionProblems, actionReferences, assembleCellProps, attachSelectionSetters, attachViewStateSetters, buildDataContext, bundleScope, cellAttributes, cellContext, cellPlacementStyle, cellPropSources, cellResolutionContext, clearScope, collectDeclarations, collectDescriptors, collectScopeIds, collectStatic, createStackStore, declaredActions, deriveElementReferences, deriveSectionNumbering, deriveSectionReferences, descriptorShape, eachFormProblems, editTarget, hasRef, hiddenWhenEmpty, hideWhenEmptyProblem, isComponentExport, isEach, isEmptyPropValue, layerName, loadBundle, loadBundles, loadBundlesSettled, loadPageData, loadScope, manifestPageFrom, mergeSectionReferences, mergeStates, nearestNames, needsWalk, nodePrintStyle, normalizeActionDeclaration, normalizeCell, normalizeRef, normalizeStructure, numberingFor, orderByVariable, pageBundleRefs, pageBundles, pageContainerStyle, pageContext, pageRuntimeFromValues, pageScopeKey, pageVariables, passesConditions, patchAt, prepareDocument, projectCellActions, projectCellData, projectLive, projectModule, projectScopeKey, projectSelections, projectViewState, queryKey, readField, readWalk, refKey, referencedLabels, referencesFor, resolveAction, resolveCell, resolveComponent, resolveDataSystem, resolveDeclarations, resolveDescriptorVariables, resolveReferences, resolveScopeId, resolveSystems, resolveWalkStart, rootCells, runtimeReady, saveScope, sectionAnchor, selectionIssues, selectionShape, settled, shallowEqualBundle, shapeAt, stateNames, stateValue, structureIssues, systemsFromGraphs, systemsFromProcess, tableOfContents, usesStructureLayout, validateCell, validateDescriptor, validateManifest, validatePage, variableLabels, variableReferences, walkKey, walkRequest };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { T as rootCells, _ as failedLabels, a as projectSelections, d as resolveComponent, f as cellAttributes, g as assembleCellProps, h as pageContainerStyle, i as projectLive, m as documentProfile, n as attachViewStateSetters, o as projectViewState, p as cellPlacementStyle, s as shallowEqualBundle, t as attachSelectionSetters, u as resolveScopeId, v as projectCellData } from "./chunks/projection-zhu32_KJ.js";
1
+ import { _ as failedLabels, a as projectSelections, d as resolveComponent, f as cellAttributes, g as assembleCellProps, h as pageContainerStyle, i as projectLive, k as rootCells, m as documentProfile, n as attachViewStateSetters, o as projectViewState, p as cellPlacementStyle, s as shallowEqualBundle, t as attachSelectionSetters, u as resolveScopeId, v as hiddenWhenEmpty, x as projectCellData, y as hideWhenEmptyProblem } from "./chunks/projection-e8MHn6Bt.js";
2
2
  import { C as passesConditions, S as pageContext, T as settled, b as cellResolutionContext, c as pageVariables, v as EACH_DEFAULT_AS, w as resolveReferences, y as cellContext } from "./chunks/query-D6xQbTFK.js";
3
3
  import { a as deriveSectionReferences, b as declaredActions, c as nodePrintStyle, d as referencesFor, f as sectionAnchor, g as usesStructureLayout, i as deriveSectionNumbering, l as normalizeStructure, m as structureIssues, o as isSectionNode, p as sortByOrder, r as deriveElementReferences, s as mergeSectionReferences, t as PAGE_DOCUMENT, u as numberingFor } from "./chunks/structure-CKBtQHJq.js";
4
4
  import { a as goToQuestion, c as missing, f as progress, g as visibleSteps, h as stepKind, i as goTo, l as move, m as settle, n as enterAdvances, o as isReadOnlyStatus, p as questionsOf, s as linkTo, t as defaultMode, u as positionFromLink } from "./chunks/forms-DxA9WzCt.js";
@@ -12,18 +12,34 @@ function CellPlaceholder({ state }) {
12
12
  "aria-busy": state === "loading"
13
13
  });
14
14
  }
15
- function CellNotice({ state, component, onRetry, children }) {
15
+ function CellHidden() {
16
+ return jsx("div", {
17
+ className: "cell-hidden",
18
+ "data-cell-state": "hidden-empty",
19
+ role: "note",
20
+ children: "hidden (empty)"
21
+ });
22
+ }
23
+ function CellNotice({ state, component, onRetry, code, showCode, children }) {
16
24
  return jsxs("div", {
17
25
  className: "cell-notice",
18
26
  "data-cell-state": state,
19
27
  role: state === "error" ? "alert" : "note",
20
28
  ...state === "not-found" && component ? { "data-missing-component": component } : {},
21
- children: [children, onRetry && jsxs(Fragment$1, { children: [" ", jsx("button", {
22
- type: "button",
23
- className: "cell-notice__retry",
24
- onClick: onRetry,
25
- children: "Try again"
26
- })] })]
29
+ children: [
30
+ children,
31
+ code && showCode && jsxs(Fragment$1, { children: [" ", jsx("code", {
32
+ className: "cell-notice__code",
33
+ "data-cell-code": code,
34
+ children: code
35
+ })] }),
36
+ onRetry && jsxs(Fragment$1, { children: [" ", jsx("button", {
37
+ type: "button",
38
+ className: "cell-notice__retry",
39
+ onClick: onRetry,
40
+ children: "Try again"
41
+ })] })
42
+ ]
27
43
  });
28
44
  }
29
45
  var CellErrorBoundary = class extends Component {
@@ -219,6 +235,23 @@ function mergeActions(a, b) {
219
235
  ...b
220
236
  } : void 0;
221
237
  }
238
+ function boundProps(cell, ctx, env, injectedProps) {
239
+ const out = { ...resolveReferences(cell.props ?? {}, ctx) };
240
+ for (const id of cell.children ?? []) {
241
+ const child = env.cellsById.get(id);
242
+ if (!child) continue;
243
+ const slot = typeof child.slot === "string" && child.slot ? child.slot : "children";
244
+ if (slot === "item" && cell.props?.each !== void 0) {
245
+ const each = out.each;
246
+ out[slot] = Array.isArray(each) ? each : out[slot];
247
+ } else out[slot] = [...Array.isArray(out[slot]) ? out[slot] : [], id];
248
+ }
249
+ return {
250
+ ...out,
251
+ ...injectedProps,
252
+ ...env.cellProps?.(cell)
253
+ };
254
+ }
222
255
  function CellView({ cell, env, nested = false, injectedProps }) {
223
256
  const frame = useRef(null);
224
257
  const store = useCellStore(cell, env.scope, env.store);
@@ -305,8 +338,19 @@ function CellView({ cell, env, nested = false, injectedProps }) {
305
338
  context
306
339
  ]);
307
340
  if (!passesConditions(cell.conditions, ctx)) return null;
341
+ const deciding = Boolean(cell.hide_when_empty?.length && cell.component && !cell.embed && !failedLabels(cell, ctx.data).length);
342
+ const problem = deciding ? hideWhenEmptyProblem(cell, ctx.data) : null;
343
+ const hidden = deciding && !problem && hiddenWhenEmpty(cell, boundProps(cell, ctx, env, injectedProps), ctx.data);
344
+ if (hidden && !env.showHiddenCells) return null;
308
345
  let body;
309
- if (cell.embed) body = env.renderEmbed?.(cell.id) ?? null;
346
+ if (hidden) body = jsx(CellHidden, {});
347
+ else if (problem) body = jsx(CellNotice, {
348
+ state: "error",
349
+ code: problem,
350
+ showCode: env.showProblemCodes,
351
+ children: "This part of the page couldn't load."
352
+ });
353
+ else if (cell.embed) body = env.renderEmbed?.(cell.id) ?? null;
310
354
  else if (!cell.component) body = jsx(CellPlaceholder, { state: "pending" });
311
355
  else if (failedLabels(cell, ctx.data).length) body = jsx(CellNotice, {
312
356
  state: "error",
@@ -528,7 +572,7 @@ var warn = (d) => console.warn(`[render] ${d.code}${d.cellId ? ` (cell ${d.cellI
528
572
  function PageRenderer({ runtime, bundles, params, baseProps, context, viewer, environment }) {
529
573
  const { page, cells } = runtime;
530
574
  const declared = useMemo(() => new Set(runtime.declared), [runtime.declared]);
531
- const { core, navigate, store, onDiagnostic = warn, actionsFor, runAction, loadPage, onCellError, onUnresolvedReference, onPropSources, frameProps, cellProps, retry } = environment;
575
+ const { core, navigate, store, onDiagnostic = warn, actionsFor, runAction, loadPage, onCellError, onUnresolvedReference, onPropSources, frameProps, cellProps, retry, showHiddenCells, showProblemCodes } = environment;
532
576
  useEffect(() => {
533
577
  for (const d of runtime.diagnostics) onDiagnostic(d);
534
578
  }, [runtime.diagnostics, onDiagnostic]);
@@ -556,6 +600,8 @@ function PageRenderer({ runtime, bundles, params, baseProps, context, viewer, en
556
600
  onDiagnostic,
557
601
  frameProps,
558
602
  cellProps,
603
+ showHiddenCells,
604
+ showProblemCodes,
559
605
  renderEmbed: (cellId) => {
560
606
  const embedded = runtime.embeds?.[cellId];
561
607
  if (!embedded) return jsx(CellNotice, {
@@ -607,6 +653,8 @@ function PageRenderer({ runtime, bundles, params, baseProps, context, viewer, en
607
653
  frameProps,
608
654
  cellProps,
609
655
  retry,
656
+ showHiddenCells,
657
+ showProblemCodes,
610
658
  environment
611
659
  ]);
612
660
  const structured = usesStructureLayout(page.layout);
@@ -662,7 +710,7 @@ function useFormsRuntime(o) {
662
710
  const steps = useMemo(() => visibleSteps(o.model, values), [o.model, values]);
663
711
  const [at, setAt] = useState(() => positionFromLink(visibleSteps(o.model, answeredOf(o.initial)), mode, o.link ?? {}));
664
712
  const here = settle(steps, mode, at);
665
- const readOnly = isReadOnlyStatus(status);
713
+ const readOnly = o.readOnly === true || isReadOnlyStatus(status);
666
714
  const opts = useRef(o);
667
715
  opts.current = o;
668
716
  const expected = useRef(new Map(Object.entries(o.initial).map(([k, a]) => [k, a.stateId ?? null])));
@@ -713,7 +761,7 @@ function useFormsRuntime(o) {
713
761
  return chain.current;
714
762
  }, []);
715
763
  const change = useCallback((key, value) => {
716
- if (isReadOnlyStatus(status)) return;
764
+ if (readOnly) return;
717
765
  setValues((v) => ({
718
766
  ...v,
719
767
  [key]: value
@@ -721,7 +769,7 @@ function useFormsRuntime(o) {
721
769
  pending.current.set(key, value);
722
770
  if (timer.current) clearTimeout(timer.current);
723
771
  timer.current = setTimeout(() => void flush(), FORM_SAVE_DEBOUNCE_MS);
724
- }, [flush, status]);
772
+ }, [flush, readOnly]);
725
773
  useEffect(() => {
726
774
  const onLeave = () => void flush();
727
775
  window.addEventListener("pagehide", onLeave);
@@ -841,7 +889,7 @@ function useFormsRuntime(o) {
841
889
  refusal: refusals[q.key] ?? null,
842
890
  inputId: answerInputId(q.key),
843
891
  ...port.upload ? { upload: async (file) => {
844
- if (isReadOnlyStatus(status)) return {
892
+ if (readOnly) return {
845
893
  ok: false,
846
894
  refusal: words("FORM_NOT_OPEN")
847
895
  };
@@ -919,7 +967,7 @@ function useFormsRuntime(o) {
919
967
  run: () => void runSubmit(),
920
968
  blocked: o.model.submitRequired === "block" && allMissing.length > 0
921
969
  } : null,
922
- reopen: status === "submitted" && o.model.submittable !== false ? {
970
+ reopen: status === "submitted" && !o.readOnly && o.model.submittable !== false ? {
923
971
  label: "Edit answers",
924
972
  run: () => void runReopen()
925
973
  } : null,
@@ -980,4 +1028,4 @@ function bindActions(v, path, latest) {
980
1028
  if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, bindActions(x, [...path, k], latest)]));
981
1029
  return v;
982
1030
  }
983
- export { CellErrorBoundary, CellNotice, CellPlaceholder, CellView, FORM_SAVE_DEBOUNCE_MS, PageRenderer, StructureView, answerInputId, cellLabels, useCellStore, useContainerSize, useFormsRuntime, usePaging, useStructureFacts };
1031
+ export { CellErrorBoundary, CellHidden, CellNotice, CellPlaceholder, CellView, FORM_SAVE_DEBOUNCE_MS, PageRenderer, StructureView, answerInputId, cellLabels, useCellStore, useContainerSize, useFormsRuntime, usePaging, useStructureFacts };
package/dist/styles.css CHANGED
@@ -6,6 +6,8 @@
6
6
  --cell-placeholder-bg: var(--color-surface-sunken, #f1f1f1);
7
7
  --cell-notice-bg: var(--color-warning-surface, #fff4d6);
8
8
  --cell-notice-fg: var(--color-warning, #6b4e00);
9
+ --cell-hidden-border: var(--color-border, #c8c8c8);
10
+ --cell-hidden-fg: var(--color-text-muted, #6b6b6b);
9
11
  }
10
12
 
11
13
  [data-page-container] { font-family: var(--font-body, system-ui, sans-serif); color: var(--color-text, inherit); }
@@ -17,7 +19,9 @@
17
19
  .cell-placeholder[aria-busy='true'] { animation: cell-pulse 1.5s ease-in-out infinite; }
18
20
  @keyframes cell-pulse { 50% { opacity: 0.55; } }
19
21
  @media (prefers-reduced-motion: reduce) { .cell-placeholder[aria-busy='true'] { animation: none; } }
22
+ .cell-hidden { padding: 0.125rem 0.5rem; border: 1px dashed var(--cell-hidden-border); border-radius: 0.375rem; color: var(--cell-hidden-fg); font-size: 0.75rem; line-height: 1.5; }
20
23
  .cell-notice { padding: 0.75rem 1rem; border-radius: 0.5rem; background: var(--cell-notice-bg); color: var(--cell-notice-fg); }
24
+ .cell-notice__code { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.75rem; }
21
25
 
22
26
 
23
27
  [data-show-when] { display: none; }
@@ -45,6 +49,8 @@
45
49
  --cell-placeholder-bg: #262626;
46
50
  --cell-notice-bg: #3a300f;
47
51
  --cell-notice-fg: #f5dc9a;
52
+ --cell-hidden-border: #4a4a4a;
53
+ --cell-hidden-fg: #a3a3a3;
48
54
  }
49
55
  }
50
56
 
@@ -9,6 +9,9 @@ export declare function projectCellData(cell: PageCell, ctx: DataContext): {
9
9
  status: CellStatus;
10
10
  } | null;
11
11
  export declare function failedLabels(cell: PageCell, data: DataContext): string[];
12
+ export declare function isEmptyPropValue(value: unknown): boolean;
13
+ export declare function hideWhenEmptyProblem(cell: Pick<PageCell, 'hide_when_empty' | 'props'>, data: DataContext): 'QUERY_UNRESOLVED' | null;
14
+ export declare function hiddenWhenEmpty(cell: Pick<PageCell, 'hide_when_empty' | 'props'>, props: Record<string, unknown>, data: DataContext): boolean;
12
15
  export type AssembledInputs = {
13
16
  resolvedProps: Record<string, unknown>;
14
17
  children: unknown;
@@ -2,7 +2,8 @@ import type { Diagnostic } from './graph.js';
2
2
  import type { PageCell } from './types.js';
3
3
  export declare const HYDRATED_CELL_FIELDS: readonly ["component", "props", "layout", "conditions", "children", "traversal", "data", "bundle_ref", "actions", "embed"];
4
4
  export declare const STORE_FIELDS: readonly ["selections", "viewState", "live"];
5
- export declare const CONTENT_FIELDS: readonly [...("data" | "component" | "props" | "conditions" | "children" | "traversal" | "bundle_ref" | "actions" | "embed")[], "selections", "viewState", "live"];
5
+ export declare const RENDER_FIELDS: readonly ["hide_when_empty"];
6
+ export declare const CONTENT_FIELDS: readonly [...("component" | "props" | "conditions" | "children" | "traversal" | "data" | "bundle_ref" | "actions" | "embed")[], "selections", "viewState", "live", "hide_when_empty"];
6
7
  export type CellSource = 'inline' | 'node' | 'fallback' | 'pending';
7
8
  export type ResolvedCell = PageCell & {
8
9
  source: CellSource;
@@ -51,6 +51,10 @@ export declare const DIAGNOSTICS: {
51
51
  readonly meaning: "A literal prop value fails its schema.";
52
52
  readonly fix: "Write the value in the shape `props_schema` gives, or bind it with a whole-string `@label.path` reference.";
53
53
  };
54
+ readonly HIDE_WHEN_EMPTY_INVALID: {
55
+ readonly meaning: "A cell’s `hide_when_empty` isn’t a list of prop names.";
56
+ readonly fix: "Write `hide_when_empty: [\"title\", \"items\"]`: the props that, all empty once bound (null, blank text, `[]`, `{}`), leave the cell out.";
57
+ };
54
58
  readonly CELL_UNKNOWN_CHILD: {
55
59
  readonly meaning: "`children` names a cell that is not on the page.";
56
60
  readonly fix: "List only ids of cells on this page in `children`.";
@@ -1,6 +1,7 @@
1
- export { CONTENT_FIELDS, hasRef, HYDRATED_CELL_FIELDS, normalizeCell, resolveCell, rootCells, STORE_FIELDS, type CellSource, type ResolvedCell } from './cells.js';
1
+ export { CONTENT_FIELDS, hasRef, HYDRATED_CELL_FIELDS, normalizeCell, resolveCell, RENDER_FIELDS, rootCells, STORE_FIELDS, type CellSource, type ResolvedCell } from './cells.js';
2
2
  export { buildDataContext, collectDeclarations, collectDescriptors, collectStatic, readField, resolveDeclarations, type ResolvedDeclaration, type StateRead } from './data-context.js';
3
3
  export { mergeStates, needsWalk, readWalk, referencedLabels, RESERVED_LABELS, resolveWalkStart, stateNames, stateValue, systemsFromGraphs, systemsFromProcess, walkKey, walkRequest, type Diagnostic, type GraphSystem, type ProcessSystems, type RawWalk, type WalkBinding, type WalkRequest, type WalkStart, } from './graph.js';
4
+ export { LAYER_ROLES, layerName, type LayerType } from './layers.js';
4
5
  export { loadPageData, resolveDataSystem, resolveSystems, type CellData, type CellWalk, type GraphSource, type NodeRecordRow, type PageRuntime, type PageScope, type QueryPage, type ReadContext, type StateRow } from './load-page.js';
5
6
  export { descriptorShape, orderByVariable, PAGING_VARIABLES, pageVariables, prepareDocument, queryKey, RENDER_FUNCTIONS, RENDER_FUNCTIONS_AHEAD, resolveDescriptorVariables, selectionShape, shapeAt, UnsafeDocumentError, validateDescriptor, VARIABLE_NAMESPACES, variableLabels, variableReferences, type PreparedDocument, type ResolvedVariables, type SelectionShape, } from './query.js';
6
7
  export { selectionIssues } from './selected.js';
@@ -13,7 +14,7 @@ export { pageRuntimeFromValues } from './values.js';
13
14
  export { actionBindingKey, actionProblems, actionReferences, declaredActions, normalizeActionDeclaration, projectCellActions, resolveAction, type RunAction } from './actions.js';
14
15
  export type { ActionDeclaration } from '@peripheries/runtime';
15
16
  export type * from './types.js';
16
- export { assembleCellProps, projectCellData, type CellStatus } from './cell-props.js';
17
+ export { assembleCellProps, hiddenWhenEmpty, hideWhenEmptyProblem, isEmptyPropValue, projectCellData, type CellStatus } from './cell-props.js';
17
18
  export { cellAttributes, cellPlacementStyle, pageContainerStyle } from './layout.js';
18
19
  export { deriveElementReferences, deriveSectionNumbering, deriveSectionReferences, mergeSectionReferences, nodePrintStyle, normalizeStructure, numberingFor, PAGE_DOCUMENT, referencesFor, SECTION_KINDS, sectionAnchor, structureIssues, tableOfContents, usesStructureLayout, type ContentsEntry, type LayoutNode, type SectionNumbering, type SectionReference, type SectionReferences, } from './structure.js';
19
20
  export { _clearBundleCache, isComponentExport, loadBundle, loadBundles, loadBundlesSettled, projectModule, runtimeReady, type BundleFiles, type BundleRow, type LoadedBundle, type LoadedBundles, } from './bundles/load.js';
@@ -0,0 +1,8 @@
1
+ import { LAYER_ROLES, type LayerType } from './layers.gen.js';
2
+ export { LAYER_ROLES, type LayerType };
3
+ export declare function layerName(s: {
4
+ system_type: string | null;
5
+ system_layer?: {
6
+ role: string | null;
7
+ } | null;
8
+ }): string | null;
@@ -0,0 +1,12 @@
1
+ export declare const LAYER_ROLES: {
2
+ readonly answer: null;
3
+ readonly "appraisal-system": "appraisal";
4
+ readonly "data-system": "data";
5
+ readonly edits: null;
6
+ readonly "model-system": "model";
7
+ readonly organisation: null;
8
+ readonly "process-system": "process";
9
+ readonly question: null;
10
+ readonly submission: null;
11
+ };
12
+ export type LayerType = keyof typeof LAYER_ROLES;
@@ -1,4 +1,4 @@
1
- import type { DeclaredBinding, PageCell, QueryDescriptor } from '@peripheries/runtime';
1
+ import type { DeclaredBinding, PageCell as RuntimePageCell, QueryDescriptor } from '@peripheries/runtime';
2
2
  export type DataPage = {
3
3
  hasMore: boolean;
4
4
  endCursor: string | null;
@@ -37,7 +37,10 @@ export type DataOrigin = {
37
37
  kind: 'reserved';
38
38
  };
39
39
  export type DataContext = Record<string, DataEntry>;
40
- export type { CellData, CellLayout, DeclaredBinding, PageCell, ResponsiveBucket } from '@peripheries/runtime';
40
+ export type { CellData, CellLayout, DeclaredBinding, ResponsiveBucket } from '@peripheries/runtime';
41
+ export type PageCell = RuntimePageCell & {
42
+ hide_when_empty?: string[];
43
+ };
41
44
  export type PageContent = {
42
45
  cells?: PageCell[];
43
46
  data?: {
@@ -1,3 +1,4 @@
1
1
  export { CONDITION_OPS, evaluateCondition, isConditionOp, normalizeCondition, normalizeOperator, type Condition, type ConditionClause, type ConditionOp } from './conditions.js';
2
2
  export { defaultMode, enterAdvances, fromPageSteps, goTo, goToQuestion, isReadOnlyStatus, linkTo, missing, move, positionFromLink, positionsOf, progress, questionsOf, settle, stepKind, visibleSteps, type DisplayMode, type FormChapter, type FormItem, type FormItemKind, type FormLink, type FormModel, type FormQuestion, type FormQuestionDefinition, type FormStep, type Position, type StepKind, type StepProgress, type SubmissionStatus, } from './model.js';
3
3
  export type { AnswerToSave, FileToUpload, FormsPort, FormsRefusal, FormsResult } from './port.js';
4
+ export type { FormContentBlock, FormSection } from './section.js';
@@ -10,10 +10,10 @@ export type FormQuestion = {
10
10
  };
11
11
  export type FormQuestionDefinition = {
12
12
  type: string;
13
- options?: unknown[];
14
- validation?: Record<string, unknown>;
15
- description?: string;
16
- helpText?: string;
13
+ options?: unknown[] | null;
14
+ validation?: Record<string, unknown> | null;
15
+ description?: string | null;
16
+ helpText?: string | null;
17
17
  };
18
18
  export type FormItemKind = 'question' | 'content' | 'document' | 'review' | 'upload' | 'other';
19
19
  export type FormItem = {
@@ -0,0 +1,22 @@
1
+ import type { Condition } from './conditions.js';
2
+ import type { FormQuestionDefinition } from './model.js';
3
+ export type FormContentBlock = {
4
+ id: string;
5
+ type: string;
6
+ when?: Condition | null;
7
+ [prop: string]: unknown;
8
+ };
9
+ export type FormSection = {
10
+ id: string;
11
+ sectionId: string;
12
+ title: string;
13
+ description?: string;
14
+ sectionType?: string;
15
+ questions: Array<{
16
+ key: string;
17
+ label: string;
18
+ required: boolean;
19
+ definition: FormQuestionDefinition | null;
20
+ }>;
21
+ content: FormContentBlock[];
22
+ };
@@ -2,10 +2,13 @@ import { Component, type ReactNode } from 'react';
2
2
  export declare function CellPlaceholder({ state }: {
3
3
  state: 'loading' | 'pending';
4
4
  }): import("react").JSX.Element;
5
- export declare function CellNotice({ state, component, onRetry, children }: {
5
+ export declare function CellHidden(): import("react").JSX.Element;
6
+ export declare function CellNotice({ state, component, onRetry, code, showCode, children }: {
6
7
  state: 'not-found' | 'error';
7
8
  component?: string;
8
9
  onRetry?: () => void;
10
+ code?: string;
11
+ showCode?: boolean;
9
12
  children: ReactNode;
10
13
  }): import("react").JSX.Element;
11
14
  export declare class CellErrorBoundary extends Component<{
@@ -38,6 +38,8 @@ export type CellEnvironment = {
38
38
  onDiagnostic?: (d: Diagnostic) => void;
39
39
  frameProps?: (cell: PageCell, contract: ComponentContract | undefined) => FrameProps | undefined;
40
40
  cellProps?: (cell: PageCell) => Record<string, unknown> | undefined;
41
+ showHiddenCells?: boolean;
42
+ showProblemCodes?: boolean;
41
43
  };
42
44
  export type FrameProps = {
43
45
  attributes?: Record<string, string>;
@@ -1,4 +1,4 @@
1
- export { CellErrorBoundary, CellNotice, CellPlaceholder } from './cell-states.js';
1
+ export { CellErrorBoundary, CellHidden, CellNotice, CellPlaceholder } from './cell-states.js';
2
2
  export { CellView, type CellEnvironment, type FrameProps } from './cell-view.js';
3
3
  export { PageRenderer, type PageRendererProps, type RendererEnvironment } from './page-renderer.js';
4
4
  export { StructureView, useStructureFacts } from './structure-view.js';
@@ -23,6 +23,8 @@ export type RendererEnvironment = {
23
23
  retry?: () => void;
24
24
  frameProps?: (cell: PageCell, contract: ComponentContract | undefined) => FrameProps | undefined;
25
25
  cellProps?: (cell: PageCell) => Record<string, unknown> | undefined;
26
+ showHiddenCells?: boolean;
27
+ showProblemCodes?: boolean;
26
28
  };
27
29
  export type PageRendererProps = {
28
30
  runtime: PageRuntime;
@@ -114,6 +114,7 @@ export type FormsRuntimeOptions = {
114
114
  stateId?: string | null;
115
115
  }>;
116
116
  status: SubmissionStatus | null;
117
+ readOnly?: boolean;
117
118
  visited?: string[];
118
119
  link?: FormLink;
119
120
  phone?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peripheries/renderer",
3
- "version": "0.23.1",
3
+ "version": "0.25.0",
4
4
  "description": "The one renderer for pages of federated component bundles: a framework-free core, a React renderer, the runtime a page boots with, and golden pages for testing.",
5
5
  "type": "module",
6
6
  "sideEffects": [