@peripheries/renderer 0.22.0 → 0.23.1

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 (103) hide show
  1. package/README.md +125 -23
  2. package/dist/catalog.js +2 -6
  3. package/dist/chunks/forms-DxA9WzCt.js +336 -0
  4. package/dist/chunks/{load-CjPvISzH.js → load-b38vUiZM.js} +0 -4
  5. package/dist/chunks/{page-bundles-DLRIBP8g.js → page-bundles-CPj_vx-J.js} +1 -5
  6. package/dist/chunks/{persistence-DCMakM9H.js → persistence-hurkUUQ0.js} +0 -6
  7. package/dist/chunks/{projection-yvYKzMOS.js → projection-zhu32_KJ.js} +1 -15
  8. package/dist/chunks/{query-BPuCJC1C.js → query-D6xQbTFK.js} +166 -12
  9. package/dist/chunks/{refs-BLUokra5.js → refs-Csgh6rXq.js} +0 -4
  10. package/dist/chunks/{structure-o9pYoDjD.js → structure-CKBtQHJq.js} +1 -7
  11. package/dist/chunks/{validate-4LgH8QMJ.js → validate-BcB_PA1k.js} +20 -13
  12. package/dist/core.js +138 -47
  13. package/dist/forms.js +2 -0
  14. package/dist/print.js +0 -24
  15. package/dist/react.js +352 -22
  16. package/dist/runtime.js +2 -12
  17. package/dist/styles.css +9 -15
  18. package/dist/testing.js +214 -19
  19. package/dist/theme.js +0 -10
  20. package/dist/types/catalog/check.d.ts +0 -13
  21. package/dist/types/core/actions.d.ts +0 -24
  22. package/dist/types/core/bundles/load.d.ts +0 -23
  23. package/dist/types/core/bundles/page-bundles.d.ts +0 -9
  24. package/dist/types/core/bundles/refs.d.ts +1 -28
  25. package/dist/types/core/bundles/resolve.d.ts +0 -10
  26. package/dist/types/core/cell-props.d.ts +0 -20
  27. package/dist/types/core/cells.d.ts +0 -21
  28. package/dist/types/core/data-context.d.ts +4 -21
  29. package/dist/types/core/diagnostics.d.ts +20 -6
  30. package/dist/types/core/graph.d.ts +6 -44
  31. package/dist/types/core/index.d.ts +4 -3
  32. package/dist/types/core/layout.d.ts +0 -9
  33. package/dist/types/core/load-page.d.ts +30 -88
  34. package/dist/types/core/query.d.ts +6 -52
  35. package/dist/types/core/references.d.ts +0 -21
  36. package/dist/types/core/release.d.ts +11 -0
  37. package/dist/types/core/rules.d.ts +0 -1
  38. package/dist/types/core/selected.d.ts +0 -5
  39. package/dist/types/core/sources.d.ts +10 -63
  40. package/dist/types/core/store/create-store.d.ts +0 -6
  41. package/dist/types/core/store/persistence.d.ts +0 -1
  42. package/dist/types/core/store/projection.d.ts +0 -5
  43. package/dist/types/core/store/scope-keys.d.ts +0 -1
  44. package/dist/types/core/store/types.d.ts +0 -6
  45. package/dist/types/core/structure.d.ts +0 -33
  46. package/dist/types/core/types.d.ts +2 -13
  47. package/dist/types/core/validate.d.ts +0 -12
  48. package/dist/types/core/values.d.ts +0 -6
  49. package/dist/types/forms/conditions.d.ts +18 -0
  50. package/dist/types/forms/index.d.ts +3 -0
  51. package/dist/types/forms/model.d.ts +90 -0
  52. package/dist/types/forms/port.d.ts +47 -0
  53. package/dist/types/print/document-bindings.d.ts +0 -61
  54. package/dist/types/print/document-profile.d.ts +0 -137
  55. package/dist/types/print/map-staleness.d.ts +0 -66
  56. package/dist/types/print/page-map.d.ts +1 -334
  57. package/dist/types/print/paginate.d.ts +0 -190
  58. package/dist/types/print/print-layout.d.ts +0 -161
  59. package/dist/types/print/print-lint.d.ts +0 -109
  60. package/dist/types/print/print-policy.d.ts +0 -184
  61. package/dist/types/print/print-stylesheet.d.ts +0 -135
  62. package/dist/types/print/running-header.d.ts +0 -86
  63. package/dist/types/print/structure-print.d.ts +1 -243
  64. package/dist/types/react/cell-states.d.ts +0 -2
  65. package/dist/types/react/cell-view.d.ts +1 -15
  66. package/dist/types/react/index.d.ts +1 -0
  67. package/dist/types/react/page-renderer.d.ts +1 -33
  68. package/dist/types/react/structure-view.d.ts +0 -12
  69. package/dist/types/react/use-cell-store.d.ts +0 -5
  70. package/dist/types/react/use-container-size.d.ts +0 -5
  71. package/dist/types/react/use-forms-runtime.d.ts +147 -0
  72. package/dist/types/react/use-paging.d.ts +0 -9
  73. package/dist/types/runtime/boot.d.ts +0 -11
  74. package/dist/types/runtime/install.d.ts +0 -15
  75. package/dist/types/runtime/shims.d.ts +0 -8
  76. package/dist/types/runtime/store-export.d.ts +0 -7
  77. package/dist/types/testing/base-props.d.ts +0 -1
  78. package/dist/types/testing/components.d.ts +0 -8
  79. package/dist/types/testing/form-workbook.d.ts +103 -0
  80. package/dist/types/testing/golden.d.ts +0 -16
  81. package/dist/types/testing/ids.d.ts +0 -2
  82. package/dist/types/testing/index.d.ts +1 -0
  83. package/dist/types/testing/sources.d.ts +0 -7
  84. package/dist/types/theme/check.d.ts +0 -2
  85. package/dist/types/theme/color.d.ts +0 -12
  86. package/dist/types/theme/derive.d.ts +0 -12
  87. package/dist/types/theme/parse.d.ts +0 -7
  88. package/package.json +6 -2
  89. package/dist/catalog.js.map +0 -1
  90. package/dist/chunks/load-CjPvISzH.js.map +0 -1
  91. package/dist/chunks/page-bundles-DLRIBP8g.js.map +0 -1
  92. package/dist/chunks/persistence-DCMakM9H.js.map +0 -1
  93. package/dist/chunks/projection-yvYKzMOS.js.map +0 -1
  94. package/dist/chunks/query-BPuCJC1C.js.map +0 -1
  95. package/dist/chunks/refs-BLUokra5.js.map +0 -1
  96. package/dist/chunks/structure-o9pYoDjD.js.map +0 -1
  97. package/dist/chunks/validate-4LgH8QMJ.js.map +0 -1
  98. package/dist/core.js.map +0 -1
  99. package/dist/print.js.map +0 -1
  100. package/dist/react.js.map +0 -1
  101. package/dist/runtime.js.map +0 -1
  102. package/dist/testing.js.map +0 -1
  103. package/dist/theme.js.map +0 -1
package/dist/react.js CHANGED
@@ -1,10 +1,10 @@
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-yvYKzMOS.js";
2
- import { C as resolveReferences, S as passesConditions, _ as EACH_DEFAULT_AS, s as pageVariables, v as cellContext, w as settled, x as pageContext, y as cellResolutionContext } from "./chunks/query-BPuCJC1C.js";
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-o9pYoDjD.js";
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";
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
+ 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
+ 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";
4
5
  import { Component, Fragment, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
5
6
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
7
  import { useSelector } from "@tanstack/react-store";
7
- //#region packages/renderer/src/react/cell-states.tsx
8
8
  function CellPlaceholder({ state }) {
9
9
  return jsx("div", {
10
10
  className: "cell-placeholder",
@@ -42,8 +42,6 @@ var CellErrorBoundary = class extends Component {
42
42
  }) : this.props.children;
43
43
  }
44
44
  };
45
- //#endregion
46
- //#region packages/renderer/src/react/use-cell-store.ts
47
45
  function useCellStore(cell, scope, api) {
48
46
  const selections = useSelector(api.store, (s) => projectSelections(cell.selections, s, scope), { compare: shallowEqualBundle });
49
47
  const viewState = useSelector(api.store, (s) => projectViewState(cell.viewState, s, scope), { compare: shallowEqualBundle });
@@ -80,8 +78,6 @@ function useCellStore(cell, scope, api) {
80
78
  scope.projectId
81
79
  ]);
82
80
  }
83
- //#endregion
84
- //#region packages/renderer/src/react/use-container-size.ts
85
81
  function useContainerSize(ref, enabled) {
86
82
  const [size, setSize] = useState();
87
83
  useEffect(() => {
@@ -106,8 +102,6 @@ function useContainerSize(ref, enabled) {
106
102
  }, [ref, enabled]);
107
103
  return size;
108
104
  }
109
- //#endregion
110
- //#region packages/renderer/src/react/use-paging.ts
111
105
  var LABEL_REF = /@([A-Za-z0-9_$-]+)/g;
112
106
  function cellLabels(cell) {
113
107
  const out = new Set(Object.keys(cell.data?.queries ?? {}));
@@ -207,8 +201,6 @@ function usePaging(cell, entries, loadPage) {
207
201
  loadPage
208
202
  ]);
209
203
  }
210
- //#endregion
211
- //#region packages/renderer/src/react/cell-view.tsx
212
204
  var REPEAT_SLOT = "item";
213
205
  function useSoftNavigation(navigate) {
214
206
  return useCallback((e) => {
@@ -399,7 +391,8 @@ function CellView({ cell, env, nested = false, injectedProps }) {
399
391
  resolvedProps: {
400
392
  ...resolvedProps,
401
393
  ...slots,
402
- ...injectedProps
394
+ ...injectedProps,
395
+ ...env.cellProps?.(cell)
403
396
  },
404
397
  children,
405
398
  baseProps: env.baseProps,
@@ -437,8 +430,6 @@ function CellView({ cell, env, nested = false, injectedProps }) {
437
430
  children: body
438
431
  });
439
432
  }
440
- //#endregion
441
- //#region packages/renderer/src/react/structure-view.tsx
442
433
  function useStructureFacts(structure, cells, env, documents) {
443
434
  return useMemo(() => {
444
435
  const numbering = deriveSectionNumbering(structure);
@@ -533,13 +524,11 @@ function StructureView({ nodes, env, facts, level = 0, documentId = PAGE_DOCUMEN
533
524
  }, `${node.sectionId ?? ""}\u0000${node.cellId ?? index}`);
534
525
  }) });
535
526
  }
536
- //#endregion
537
- //#region packages/renderer/src/react/page-renderer.tsx
538
527
  var warn = (d) => console.warn(`[render] ${d.code}${d.cellId ? ` (cell ${d.cellId})` : ""}: ${d.message}`);
539
528
  function PageRenderer({ runtime, bundles, params, baseProps, context, viewer, environment }) {
540
529
  const { page, cells } = runtime;
541
530
  const declared = useMemo(() => new Set(runtime.declared), [runtime.declared]);
542
- const { core, navigate, store, onDiagnostic = warn, actionsFor, runAction, loadPage, onCellError, onUnresolvedReference, onPropSources, frameProps, retry } = environment;
531
+ const { core, navigate, store, onDiagnostic = warn, actionsFor, runAction, loadPage, onCellError, onUnresolvedReference, onPropSources, frameProps, cellProps, retry } = environment;
543
532
  useEffect(() => {
544
533
  for (const d of runtime.diagnostics) onDiagnostic(d);
545
534
  }, [runtime.diagnostics, onDiagnostic]);
@@ -566,6 +555,7 @@ function PageRenderer({ runtime, bundles, params, baseProps, context, viewer, en
566
555
  onPropSources,
567
556
  onDiagnostic,
568
557
  frameProps,
558
+ cellProps,
569
559
  renderEmbed: (cellId) => {
570
560
  const embedded = runtime.embeds?.[cellId];
571
561
  if (!embedded) return jsx(CellNotice, {
@@ -615,6 +605,7 @@ function PageRenderer({ runtime, bundles, params, baseProps, context, viewer, en
615
605
  onPropSources,
616
606
  onDiagnostic,
617
607
  frameProps,
608
+ cellProps,
618
609
  retry,
619
610
  environment
620
611
  ]);
@@ -647,7 +638,346 @@ function PageRenderer({ runtime, bundles, params, baseProps, context, viewer, en
647
638
  }, cell.id))
648
639
  });
649
640
  }
650
- //#endregion
651
- export { CellErrorBoundary, CellNotice, CellPlaceholder, CellView, PageRenderer, StructureView, cellLabels, useCellStore, useContainerSize, usePaging, useStructureFacts };
652
-
653
- //# sourceMappingURL=react.js.map
641
+ var FORM_SAVE_DEBOUNCE_MS = 3e3;
642
+ var answerInputId = (key) => `form-answer-${key.replace(/[^\w-]/g, "_")}`;
643
+ var reviewOf = (notes) => {
644
+ const last = [...notes ?? []].reverse().find((n) => n.status);
645
+ return last ? {
646
+ status: last.status,
647
+ note: last.text,
648
+ at: last.at,
649
+ by: last.by ?? null
650
+ } : null;
651
+ };
652
+ var answeredOf = (v) => Object.fromEntries(Object.entries(v).map(([k, a]) => [k, a.value]));
653
+ function useFormsRuntime(o) {
654
+ const [values, setValues] = useState(() => answeredOf(o.initial));
655
+ const [mode, setMode] = useState(() => o.link?.mode === "question" ? "question" : defaultMode(!!o.phone));
656
+ const [status, setStatus] = useState(o.status);
657
+ const [visited, setVisited] = useState(() => new Set(o.visited ?? []));
658
+ const [save, setSave] = useState({ state: "idle" });
659
+ const [refusals, setRefusals] = useState({});
660
+ const [submitTried, setSubmitTried] = useState(false);
661
+ const focusKey = useRef(null);
662
+ const steps = useMemo(() => visibleSteps(o.model, values), [o.model, values]);
663
+ const [at, setAt] = useState(() => positionFromLink(visibleSteps(o.model, answeredOf(o.initial)), mode, o.link ?? {}));
664
+ const here = settle(steps, mode, at);
665
+ const readOnly = isReadOnlyStatus(status);
666
+ const opts = useRef(o);
667
+ opts.current = o;
668
+ const expected = useRef(new Map(Object.entries(o.initial).map(([k, a]) => [k, a.stateId ?? null])));
669
+ const pending = useRef(new Map());
670
+ const timer = useRef(null);
671
+ const chain = useRef(Promise.resolve());
672
+ const words = (code, message) => opts.current.describe?.(code) ?? message ?? code;
673
+ const flush = useCallback(() => {
674
+ if (timer.current) clearTimeout(timer.current);
675
+ timer.current = null;
676
+ const batch = [...pending.current];
677
+ pending.current.clear();
678
+ if (!batch.length) return chain.current;
679
+ chain.current = chain.current.then(async () => {
680
+ setSave({ state: "saving" });
681
+ for (const [key, value] of batch) {
682
+ const q = opts.current.model.steps.flatMap(questionsOf).find((x) => x.key === key);
683
+ const r = await opts.current.port.save({
684
+ key,
685
+ value,
686
+ expectedStateId: expected.current.get(key) ?? null,
687
+ ...q?.node ? { node: q.node } : {}
688
+ }).catch(() => ({
689
+ ok: false,
690
+ code: "NETWORK"
691
+ }));
692
+ if (!r.ok) {
693
+ if (!pending.current.has(key)) pending.current.set(key, value);
694
+ const said = words(r.code, "message" in r ? r.message : void 0);
695
+ setRefusals((x) => ({
696
+ ...x,
697
+ [key]: said
698
+ }));
699
+ setSave({
700
+ state: "error",
701
+ error: said
702
+ });
703
+ return;
704
+ }
705
+ expected.current.set(key, r.stateId ?? null);
706
+ setRefusals((x) => key in x ? Object.fromEntries(Object.entries(x).filter(([k]) => k !== key)) : x);
707
+ }
708
+ setSave({
709
+ state: "saved",
710
+ at: new Date().toISOString()
711
+ });
712
+ });
713
+ return chain.current;
714
+ }, []);
715
+ const change = useCallback((key, value) => {
716
+ if (isReadOnlyStatus(status)) return;
717
+ setValues((v) => ({
718
+ ...v,
719
+ [key]: value
720
+ }));
721
+ pending.current.set(key, value);
722
+ if (timer.current) clearTimeout(timer.current);
723
+ timer.current = setTimeout(() => void flush(), FORM_SAVE_DEBOUNCE_MS);
724
+ }, [flush, status]);
725
+ useEffect(() => {
726
+ const onLeave = () => void flush();
727
+ window.addEventListener("pagehide", onLeave);
728
+ return () => {
729
+ window.removeEventListener("pagehide", onLeave);
730
+ flush();
731
+ };
732
+ }, [flush]);
733
+ const headingRef = useRef(null);
734
+ const contentRef = useRef(null);
735
+ const go = useCallback((to) => {
736
+ if (!to) return;
737
+ flush();
738
+ const stepChanged = to.step !== here?.step;
739
+ setAt(to);
740
+ opts.current.onNavigate?.(linkTo(steps, mode, to), { replace: !stepChanged });
741
+ }, [
742
+ flush,
743
+ here?.step,
744
+ steps,
745
+ mode
746
+ ]);
747
+ useEffect(() => {
748
+ if (!here) return;
749
+ contentRef.current?.scrollTo?.({ top: 0 });
750
+ headingRef.current?.focus({ preventScroll: true });
751
+ if (!visited.has(here.step)) {
752
+ setVisited((v) => new Set(v).add(here.step));
753
+ opts.current.port.visit(here.step).catch(() => void 0);
754
+ }
755
+ }, [here?.step]);
756
+ useEffect(() => {
757
+ const key = focusKey.current;
758
+ if (!key) return;
759
+ const el = typeof document === "undefined" ? null : document.getElementById(answerInputId(key));
760
+ if (el) {
761
+ focusKey.current = null;
762
+ el.focus({ preventScroll: false });
763
+ }
764
+ });
765
+ const index = here ? steps.findIndex((s) => s.id === here.step) : -1;
766
+ const step = steps[index];
767
+ const isLast = index === steps.length - 1;
768
+ const nextPos = here ? move(steps, mode, here, 1) : null;
769
+ const p = progress(steps, values, visited);
770
+ const allMissing = missing(steps, values);
771
+ const advance = useCallback(() => go(nextPos), [go, nextPos]);
772
+ const back = useCallback(() => here && go(move(steps, mode, here, -1)), [
773
+ go,
774
+ here,
775
+ steps,
776
+ mode
777
+ ]);
778
+ const runSubmit = useCallback(async () => {
779
+ setSubmitTried(true);
780
+ if (o.model.submitRequired === "block" && allMissing.length) return;
781
+ await flush();
782
+ const r = await opts.current.port.submit({ missing: allMissing.map((q) => q.key) }).catch(() => ({
783
+ ok: false,
784
+ code: "NETWORK"
785
+ }));
786
+ if (r.ok) setStatus("submitted");
787
+ else setSave({
788
+ state: "error",
789
+ error: words(r.code, "message" in r ? r.message : void 0)
790
+ });
791
+ }, [
792
+ o.model.submitRequired,
793
+ allMissing,
794
+ flush
795
+ ]);
796
+ const runReopen = useCallback(async () => {
797
+ const r = await opts.current.port.reopen().catch(() => ({
798
+ ok: false,
799
+ code: "NETWORK"
800
+ }));
801
+ if (r.ok) setStatus("in_progress");
802
+ else setSave({
803
+ state: "error",
804
+ error: words(r.code, "message" in r ? r.message : void 0)
805
+ });
806
+ }, []);
807
+ const onKeyDown = useCallback((e) => {
808
+ if (e.key !== "Enter" || e.shiftKey || e.nativeEvent.isComposing || !step) return;
809
+ if (e.target.tagName === "TEXTAREA") return;
810
+ if (!enterAdvances(step, mode, here?.question)) return;
811
+ e.preventDefault();
812
+ advance();
813
+ }, [
814
+ step,
815
+ mode,
816
+ here?.question,
817
+ advance
818
+ ]);
819
+ const nextStep = nextPos && nextPos.step !== here?.step ? steps.find((s) => s.id === nextPos.step) : null;
820
+ const stepQuestions = step ? questionsOf(step) : [];
821
+ const qIndex = mode === "question" && here?.question ? stepQuestions.findIndex((q) => q.key === here.question) : -1;
822
+ const questionHere = qIndex >= 0 ? {
823
+ index: qIndex,
824
+ count: stepQuestions.length,
825
+ key: stepQuestions[qIndex].key,
826
+ next: stepQuestions[qIndex + 1] ? { label: stepQuestions[qIndex + 1].label } : null
827
+ } : null;
828
+ const passed = new Set(steps.slice(0, Math.max(index, 0)).map((s) => s.id).filter((id) => visited.has(id)));
829
+ const missingKeys = new Set(allMissing.map((q) => q.key));
830
+ const answerOf = (q, stepId) => {
831
+ const port = o.port;
832
+ return {
833
+ key: q.key,
834
+ label: q.label,
835
+ question: q.definition ?? null,
836
+ value: values[q.key],
837
+ change: (v) => change(q.key, v),
838
+ readOnly,
839
+ required: q.required,
840
+ missing: missingKeys.has(q.key) && (submitTried || passed.has(stepId)),
841
+ refusal: refusals[q.key] ?? null,
842
+ inputId: answerInputId(q.key),
843
+ ...port.upload ? { upload: async (file) => {
844
+ if (isReadOnlyStatus(status)) return {
845
+ ok: false,
846
+ refusal: words("FORM_NOT_OPEN")
847
+ };
848
+ const r = await port.upload({
849
+ key: q.key,
850
+ file,
851
+ ...q.node ? { node: q.node } : {}
852
+ }).catch(() => ({
853
+ ok: false,
854
+ code: "NETWORK"
855
+ }));
856
+ if (!r.ok) return {
857
+ ok: false,
858
+ refusal: words(r.code, "message" in r ? r.message : void 0)
859
+ };
860
+ change(q.key, r.answer);
861
+ await flush();
862
+ return { ok: true };
863
+ } } : {},
864
+ ...port.openFile ? { openFile: async () => {
865
+ const v = values[q.key];
866
+ if (!v?.file) return null;
867
+ const r = await port.openFile(v.file).catch(() => null);
868
+ return r && r.ok ? r.url : null;
869
+ } } : {}
870
+ };
871
+ };
872
+ const questionAnswer = questionHere && step ? stepQuestions.find((q) => q.key === questionHere.key) : void 0;
873
+ const stable = useStableSlots(step ? {
874
+ title: o.model.title,
875
+ chapter: step.chapter && o.model.chapters.find((c) => c.id === step.chapter) || null,
876
+ step: {
877
+ id: step.id,
878
+ title: step.title,
879
+ index,
880
+ count: steps.length,
881
+ kind: stepKind(step, isLast),
882
+ ...step.label ? { label: step.label } : {}
883
+ },
884
+ sections: steps.map((s, i) => ({
885
+ id: s.id,
886
+ title: s.title,
887
+ ...s.chapter ? { chapter: s.chapter } : {},
888
+ kind: stepKind(s, i === steps.length - 1),
889
+ ...s.label ? { label: s.label } : {},
890
+ ...p.perStep[s.id],
891
+ current: s.id === step.id
892
+ })),
893
+ progress: {
894
+ answered: p.answered,
895
+ required: p.required,
896
+ ratio: p.ratio,
897
+ total: p.total
898
+ },
899
+ question: questionHere && questionAnswer ? {
900
+ ...questionHere,
901
+ answer: answerOf(questionAnswer, step.id)
902
+ } : questionHere,
903
+ goToQuestion: (key) => {
904
+ focusKey.current = key;
905
+ go(goToQuestion(steps, mode, key));
906
+ },
907
+ displayMode: mode,
908
+ setDisplayMode: (m) => {
909
+ setMode(m);
910
+ if (here) setAt(settle(steps, m, here));
911
+ },
912
+ goToSection: (id) => go(goTo(steps, mode, id, values)),
913
+ advance,
914
+ back,
915
+ canBack: o.model.allowBack && index > 0,
916
+ next: nextStep ? { title: nextStep.title } : null,
917
+ submit: isLast && !readOnly && o.model.submittable !== false ? {
918
+ label: "Submit",
919
+ run: () => void runSubmit(),
920
+ blocked: o.model.submitRequired === "block" && allMissing.length > 0
921
+ } : null,
922
+ reopen: status === "submitted" && o.model.submittable !== false ? {
923
+ label: "Edit answers",
924
+ run: () => void runReopen()
925
+ } : null,
926
+ save,
927
+ missing: missing(steps, values, step.id).map((q) => ({
928
+ key: q.key,
929
+ label: q.label
930
+ })),
931
+ chapters: o.model.chapters.map((c) => ({
932
+ id: c.id,
933
+ title: c.title
934
+ })),
935
+ status,
936
+ readOnly,
937
+ review: reviewOf(o.notes)
938
+ } : null);
939
+ const cellProps = (cell) => {
940
+ for (const s of steps) {
941
+ const item = s.items.find((i) => i.id === cell.id);
942
+ if (!item) continue;
943
+ const answers = Object.fromEntries(item.questions.map((q) => [q.key, answerOf(q, s.id)]));
944
+ const list = Object.values(answers);
945
+ return {
946
+ form: stable,
947
+ answers,
948
+ ...list.length === 1 ? { answer: list[0] } : {}
949
+ };
950
+ }
951
+ };
952
+ return {
953
+ slots: stable,
954
+ cellProps,
955
+ steps,
956
+ step: step ?? null,
957
+ position: here,
958
+ values,
959
+ change,
960
+ flush,
961
+ readOnly,
962
+ status,
963
+ headingRef,
964
+ contentRef,
965
+ onKeyDown
966
+ };
967
+ }
968
+ function useStableSlots(slots) {
969
+ const latest = useRef(slots);
970
+ latest.current = slots;
971
+ const shown = JSON.stringify(slots);
972
+ return useMemo(() => slots ? bindActions(slots, [], latest) : null, [shown]);
973
+ }
974
+ function bindActions(v, path, latest) {
975
+ if (typeof v === "function") return (...args) => {
976
+ const fn = path.reduce((o, k) => o?.[k], latest.current);
977
+ return typeof fn === "function" ? fn(...args) : void 0;
978
+ };
979
+ if (Array.isArray(v)) return v.map((x, i) => bindActions(x, [...path, String(i)], latest));
980
+ if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, bindActions(x, [...path, k], latest)]));
981
+ return v;
982
+ }
983
+ export { CellErrorBoundary, CellNotice, CellPlaceholder, CellView, FORM_SAVE_DEBOUNCE_MS, PageRenderer, StructureView, answerInputId, cellLabels, useCellStore, useContainerSize, useFormsRuntime, usePaging, useStructureFacts };
package/dist/runtime.js CHANGED
@@ -1,5 +1,5 @@
1
- import { s as runtimeReady } from "./chunks/load-CjPvISzH.js";
2
- import { a as createStackStore, t as clearAllScopes } from "./chunks/persistence-DCMakM9H.js";
1
+ import { s as runtimeReady } from "./chunks/load-b38vUiZM.js";
2
+ import { a as createStackStore, t as clearAllScopes } from "./chunks/persistence-hurkUUQ0.js";
3
3
  import { PINNED_RUNTIMES, READY_GLOBAL, RESOLVE_GLOBAL, RUNTIME_GLOBAL, buildImportMap } from "@peripheries/runtime";
4
4
  import * as React from "react";
5
5
  import * as JsxRuntime from "react/jsx-runtime";
@@ -8,7 +8,6 @@ import * as ReactDOM from "react-dom";
8
8
  import * as ReactDOMClient from "react-dom/client";
9
9
  import * as JsxDevRuntime from "react/jsx-dev-runtime";
10
10
  import * as zustand from "zustand";
11
- //#region packages/renderer/src/runtime/boot.ts
12
11
  var RUNTIME_BOOT_SCRIPT = `(function(g){if(g.${READY_GLOBAL})return;g.${READY_GLOBAL}=new Promise(function(r){g.${RESOLVE_GLOBAL}=r;});})(globalThis);`;
13
12
  function importMapFor(shared = []) {
14
13
  return { imports: buildImportMap(shared).imports };
@@ -17,8 +16,6 @@ function importMapJson(shared = []) {
17
16
  return JSON.stringify(importMapFor(shared)).replace(/</g, "\\u003c");
18
17
  }
19
18
  var IMPORT_MAP_JSON = importMapJson();
20
- //#endregion
21
- //#region packages/renderer/src/runtime/store-export.ts
22
19
  var singleton = null;
23
20
  function getStackStore() {
24
21
  return singleton ??= createStackStore();
@@ -43,8 +40,6 @@ function buildStoreRuntime() {
43
40
  selectors
44
41
  };
45
42
  }
46
- //#endregion
47
- //#region packages/renderer/src/runtime/install.ts
48
43
  function buildRuntime(extra = {}) {
49
44
  return {
50
45
  react: React,
@@ -68,8 +63,6 @@ function installRuntime(g = globalThis, extra = {}) {
68
63
  g[RESOLVE_GLOBAL]?.();
69
64
  return runtime;
70
65
  }
71
- //#endregion
72
- //#region packages/renderer/src/runtime/shims.ts
73
66
  function shimFileName(specifier) {
74
67
  const base = PINNED_RUNTIMES[specifier].url.split("/").pop();
75
68
  if (!base?.endsWith(".js")) throw new Error(`[runtime] ${specifier}: url is not a .js file`);
@@ -92,7 +85,4 @@ function renderShim(specifier, names, hasDefault) {
92
85
  }
93
86
  return lines.join("\n") + "\n";
94
87
  }
95
- //#endregion
96
88
  export { IMPORT_MAP_JSON, RUNTIME_BOOT_SCRIPT, buildRuntime, getStackStore, importMapFor, importMapJson, installRuntime, renderShim, resetStackStore, runtimeReady, selectors, shimFileName, useStackStore };
97
-
98
- //# sourceMappingURL=runtime.js.map
package/dist/styles.css CHANGED
@@ -1,23 +1,17 @@
1
- /* @peripheries/renderer/styles.css: what the renderer's markup needs (design/renderer.md §2).
2
- Import it once per app. Everything is in the `renderer` layer (design/core-and-tokens.md §7.2):
3
- above the reset and the tokens, below every bundle and the org's theme. */
1
+
4
2
 
5
3
  @layer renderer {
6
- /* ── Page cells (design/core.md §3–4) ─────────────────────────────────────
7
- The page and every cell are size containers, so layout responds to the
8
- space a component actually has, not the viewport. */
4
+
9
5
  :root {
10
6
  --cell-placeholder-bg: var(--color-surface-sunken, #f1f1f1);
11
7
  --cell-notice-bg: var(--color-warning-surface, #fff4d6);
12
8
  --cell-notice-fg: var(--color-warning, #6b4e00);
13
9
  }
14
- /* A page reads the theme's body font and ink, so a cell that sets neither still matches it. */
10
+
15
11
  [data-page-container] { font-family: var(--font-body, system-ui, sans-serif); color: var(--color-text, inherit); }
16
12
  [data-page-container] { container: page / inline-size; }
17
13
  .cell { container: cell / inline-size; min-width: 0; }
18
- /* A child cell sizes to what it holds: inside its parent's flex or grid parts (a Card's header, an
19
- Inline) an inline-size container would have no width of its own and collapse to nothing. Its
20
- `@container cell` queries read the parent cell's width instead. */
14
+
21
15
  .cell[data-nested] { container: none; }
22
16
  .cell-placeholder { min-height: 6rem; border-radius: 0.5rem; background: var(--cell-placeholder-bg); }
23
17
  .cell-placeholder[aria-busy='true'] { animation: cell-pulse 1.5s ease-in-out infinite; }
@@ -25,24 +19,24 @@
25
19
  @media (prefers-reduced-motion: reduce) { .cell-placeholder[aria-busy='true'] { animation: none; } }
26
20
  .cell-notice { padding: 0.75rem 1rem; border-radius: 0.5rem; background: var(--cell-notice-bg); color: var(--cell-notice-fg); }
27
21
 
28
- /* Responsive buckets: each fires at or below its width (narrow 480, compact 720, wide 1024). */
22
+
29
23
  [data-show-when] { display: none; }
30
24
  @container page (max-width: 1024px) {
31
25
  [data-hide-when='wide'] { display: none; }
32
26
  [data-show-when='wide'] { display: revert; }
33
- [data-stack-when='wide'] { grid-column: 1 / -1 !important; } /* over the inline span */
27
+ [data-stack-when='wide'] { grid-column: 1 / -1 !important; }
34
28
  [data-truncate-when='wide'] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
35
29
  }
36
30
  @container page (max-width: 720px) {
37
31
  [data-hide-when='compact'] { display: none; }
38
32
  [data-show-when='compact'] { display: revert; }
39
- [data-stack-when='compact'] { grid-column: 1 / -1 !important; } /* over the inline span */
33
+ [data-stack-when='compact'] { grid-column: 1 / -1 !important; }
40
34
  [data-truncate-when='compact'] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
41
35
  }
42
36
  @container page (max-width: 480px) {
43
37
  [data-hide-when='narrow'] { display: none; }
44
38
  [data-show-when='narrow'] { display: revert; }
45
- [data-stack-when='narrow'] { grid-column: 1 / -1 !important; } /* over the inline span */
39
+ [data-stack-when='narrow'] { grid-column: 1 / -1 !important; }
46
40
  [data-truncate-when='narrow'] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
47
41
  }
48
42
 
@@ -54,6 +48,6 @@
54
48
  }
55
49
  }
56
50
 
57
- /* Document structure: sections span a grid page; inside, content stacks. */
51
+
58
52
  .structure-section { grid-column: 1 / -1; display: flex; flex-direction: column; gap: inherit; }
59
53
  }