@braedonsaunders/appkit-forms 0.2.2

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 (120) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +74 -0
  3. package/canvas-editor.d.ts +24 -0
  4. package/canvas-editor.d.ts.map +1 -0
  5. package/canvas-editor.js +115 -0
  6. package/canvas-editor.js.map +1 -0
  7. package/canvas-runtime.d.ts +12 -0
  8. package/canvas-runtime.d.ts.map +1 -0
  9. package/canvas-runtime.js +54 -0
  10. package/canvas-runtime.js.map +1 -0
  11. package/element-preview.d.ts +9 -0
  12. package/element-preview.d.ts.map +1 -0
  13. package/element-preview.js +167 -0
  14. package/element-preview.js.map +1 -0
  15. package/form-designer.d.ts +29 -0
  16. package/form-designer.d.ts.map +1 -0
  17. package/form-designer.js +214 -0
  18. package/form-designer.js.map +1 -0
  19. package/form-flow-validation.d.ts +5 -0
  20. package/form-flow-validation.d.ts.map +1 -0
  21. package/form-flow-validation.js +154 -0
  22. package/form-flow-validation.js.map +1 -0
  23. package/form-renderer.d.ts +37 -0
  24. package/form-renderer.d.ts.map +1 -0
  25. package/form-renderer.js +282 -0
  26. package/form-renderer.js.map +1 -0
  27. package/formula-builder.d.ts +25 -0
  28. package/formula-builder.d.ts.map +1 -0
  29. package/formula-builder.js +310 -0
  30. package/formula-builder.js.map +1 -0
  31. package/generated-copy.d.ts +17 -0
  32. package/generated-copy.d.ts.map +1 -0
  33. package/generated-copy.js +396 -0
  34. package/generated-copy.js.map +1 -0
  35. package/index.d.ts +27 -0
  36. package/index.d.ts.map +1 -0
  37. package/index.js +25 -0
  38. package/index.js.map +1 -0
  39. package/logic-builder.d.ts +27 -0
  40. package/logic-builder.d.ts.map +1 -0
  41. package/logic-builder.js +106 -0
  42. package/logic-builder.js.map +1 -0
  43. package/package.json +54 -0
  44. package/photo-field-state.d.ts +9 -0
  45. package/photo-field-state.d.ts.map +1 -0
  46. package/photo-field-state.js +11 -0
  47. package/photo-field-state.js.map +1 -0
  48. package/production-designer-adapter.d.ts +100 -0
  49. package/production-designer-adapter.d.ts.map +1 -0
  50. package/production-designer-adapter.js +2 -0
  51. package/production-designer-adapter.js.map +1 -0
  52. package/production-file-upload.d.ts +27 -0
  53. package/production-file-upload.d.ts.map +1 -0
  54. package/production-file-upload.js +105 -0
  55. package/production-file-upload.js.map +1 -0
  56. package/production-form-designer.d.ts +3 -0
  57. package/production-form-designer.d.ts.map +1 -0
  58. package/production-form-designer.js +1124 -0
  59. package/production-form-designer.js.map +1 -0
  60. package/production-form-renderer.d.ts +16 -0
  61. package/production-form-renderer.d.ts.map +1 -0
  62. package/production-form-renderer.js +2134 -0
  63. package/production-form-renderer.js.map +1 -0
  64. package/production-runtime-adapter.d.ts +187 -0
  65. package/production-runtime-adapter.d.ts.map +1 -0
  66. package/production-runtime-adapter.js +2 -0
  67. package/production-runtime-adapter.js.map +1 -0
  68. package/production-runtime-context.d.ts +29 -0
  69. package/production-runtime-context.d.ts.map +1 -0
  70. package/production-runtime-context.js +35 -0
  71. package/production-runtime-context.js.map +1 -0
  72. package/production-runtime-layout.d.ts +21 -0
  73. package/production-runtime-layout.d.ts.map +1 -0
  74. package/production-runtime-layout.js +33 -0
  75. package/production-runtime-layout.js.map +1 -0
  76. package/properties.d.ts +36 -0
  77. package/properties.d.ts.map +1 -0
  78. package/properties.js +133 -0
  79. package/properties.js.map +1 -0
  80. package/record-actions-panel.d.ts +28 -0
  81. package/record-actions-panel.d.ts.map +1 -0
  82. package/record-actions-panel.js +136 -0
  83. package/record-actions-panel.js.map +1 -0
  84. package/record-behavior-panel.d.ts +15 -0
  85. package/record-behavior-panel.d.ts.map +1 -0
  86. package/record-behavior-panel.js +59 -0
  87. package/record-behavior-panel.js.map +1 -0
  88. package/record-config.d.ts +44 -0
  89. package/record-config.d.ts.map +1 -0
  90. package/record-config.js +2 -0
  91. package/record-config.js.map +1 -0
  92. package/record-list-panel.d.ts +16 -0
  93. package/record-list-panel.d.ts.map +1 -0
  94. package/record-list-panel.js +86 -0
  95. package/record-list-panel.js.map +1 -0
  96. package/risk-matrix.d.ts +93 -0
  97. package/risk-matrix.d.ts.map +1 -0
  98. package/risk-matrix.js +239 -0
  99. package/risk-matrix.js.map +1 -0
  100. package/runtime-controls.d.ts +19 -0
  101. package/runtime-controls.d.ts.map +1 -0
  102. package/runtime-controls.js +61 -0
  103. package/runtime-controls.js.map +1 -0
  104. package/sketch-pad-client.d.ts +4 -0
  105. package/sketch-pad-client.d.ts.map +1 -0
  106. package/sketch-pad-client.js +54 -0
  107. package/sketch-pad-client.js.map +1 -0
  108. package/sketch-pad.d.ts +13 -0
  109. package/sketch-pad.d.ts.map +1 -0
  110. package/sketch-pad.js +17 -0
  111. package/sketch-pad.js.map +1 -0
  112. package/styles.css +7 -0
  113. package/text.d.ts +5 -0
  114. package/text.d.ts.map +1 -0
  115. package/text.js +10 -0
  116. package/text.js.map +1 -0
  117. package/workflow-properties.d.ts +16 -0
  118. package/workflow-properties.d.ts.map +1 -0
  119. package/workflow-properties.js +31 -0
  120. package/workflow-properties.js.map +1 -0
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @braedonsaunders/appkit-forms
2
+
3
+ Production form design and filling for AppKit applications.
4
+ The package is optional and uses `@braedonsaunders/appkit-forms-core` as its portable schema and
5
+ validation contract.
6
+
7
+ ## Production runtime
8
+
9
+ `ProductionFormRenderer` is the source-parity fill surface. It includes:
10
+
11
+ - revision- and sequence-safe draft autosave, lazy draft creation, unload
12
+ beacons, inline field saves, resume state, and conflict handling;
13
+ - guided workflow steps, client and server validation, formulas, defaults,
14
+ conditional logic, read-only records, and submission handling;
15
+ - responsive grid, columns, and free-positioned canvas layouts;
16
+ - repeating sections and line tables, matrices, ratings, risk controls,
17
+ attestations, signatures, sketches, rich text, address and hierarchy fields;
18
+ - tenant data queries and aggregates, entity-attribute refresh, file/photo
19
+ upload, photo annotation, optional analysis, QR scanning, and camera input.
20
+
21
+ The runtime never reaches into an application's database, authorization layer,
22
+ storage, AI provider, or router. Supply those capabilities through
23
+ `ProductionFormRuntimeAdapter` and render through
24
+ `ProductionFormRuntimeProvider`. A database-free memory adapter is a valid
25
+ deployment; a production application can bind the same contract to RLS-scoped
26
+ services without changing the form component.
27
+
28
+ ```tsx
29
+ import { ProductionFormRenderer } from '@braedonsaunders/appkit-forms'
30
+ import '@braedonsaunders/appkit-forms/styles.css'
31
+
32
+ <ProductionFormRenderer adapter={adapter} {...input} />
33
+ ```
34
+
35
+ `input` carries the source-shaped template, sites, people, entity data, current
36
+ user, optional resumed response, and application-owned record/review links.
37
+ The built-in English copy catalogue is complete; pass `translateGenerated` to
38
+ the renderer to bind generated copy IDs to another catalogue.
39
+
40
+ ## Production designer
41
+
42
+ `ProductionFormDesigner` is the complete source authoring workspace. It ships
43
+ the production 1/3–2/3 editor composition with overview, field palette,
44
+ stacked and free-canvas layouts, tabs, sign-off steps, record behaviour,
45
+ configurable record lists, manual record actions, assignments, access control,
46
+ preview, immutable publishing, localized content, and the full properties
47
+ inspector. `formFlowProfile` and `lintFormFlowGraph` bind the same form schema
48
+ to `@braedonsaunders/appkit-workflows/react` without duplicating automation rules.
49
+
50
+ Application-owned persistence, routing, authorization, workflow storage, data
51
+ sources, and optional AI assistance enter through typed adapters and render
52
+ seams. The database-free playground uses browser-local adapters; a production
53
+ application can bind the same component to tenant-scoped services.
54
+
55
+ ```tsx
56
+ import { ProductionFormDesigner } from '@braedonsaunders/appkit-forms'
57
+
58
+ <ProductionFormDesigner
59
+ adapter={designerAdapter}
60
+ recordActionAdapter={recordActionAdapter}
61
+ renderFlows={(input) => <FlowsCanvas {...toWorkflowProps(input)} />}
62
+ {...template}
63
+ />
64
+ ```
65
+
66
+ ## Controlled surfaces
67
+
68
+ `FormRenderer` is a smaller controlled renderer for applications that want to
69
+ own every value and field callback. It is not the production-parity renderer.
70
+
71
+ `FormDesigner`, `LogicBuilder`, inspectors, canvas controls, and record/workflow
72
+ configuration remain available as smaller controlled compositions when an
73
+ application does not need the production shell. Do not substitute the smaller
74
+ `FormDesigner` for `ProductionFormDesigner` in a parity cutover.
@@ -0,0 +1,24 @@
1
+ import { type RefObject } from 'react';
2
+ import { type CanvasItem, type FieldType, type FormSection } from '@braedonsaunders/appkit-forms-core';
3
+ import type { AppLocale } from '@braedonsaunders/appkit-i18n';
4
+ export declare function defaultBox(type: FieldType): {
5
+ w: number;
6
+ h: number;
7
+ };
8
+ export declare function CanvasEditor({ section, locale, defaultLocale, selectedFieldId, dragTypeRef, onLayout, onAddWidget, onSelect, onDelete, }: {
9
+ section: FormSection;
10
+ locale: AppLocale;
11
+ defaultLocale: AppLocale;
12
+ selectedFieldId: string | null;
13
+ dragTypeRef: RefObject<FieldType | null>;
14
+ onLayout: (items: CanvasItem[]) => void;
15
+ onAddWidget: (type: FieldType, box: {
16
+ x: number;
17
+ y: number;
18
+ w: number;
19
+ h: number;
20
+ }) => void;
21
+ onSelect: (fieldId: string) => void;
22
+ onDelete: (fieldId: string) => void;
23
+ }): import("react").JSX.Element;
24
+ //# sourceMappingURL=canvas-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-editor.d.ts","sourceRoot":"","sources":["../src/canvas-editor.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAiBvD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAsB7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAGpE;AAoBD,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EACN,aAAa,EACb,eAAe,EACf,WAAW,EACX,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,EAAE,SAAS,CAAA;IACjB,aAAa,EAAE,SAAS,CAAA;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAE9B,WAAW,EAAE,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,CAAA;IACxC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAA;IACvC,WAAW,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;IAC3F,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,+BAkIA"}
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { GeneratedText, useGeneratedTranslations, GeneratedValue, useGeneratedValueTranslations, } from './generated-copy.js';
4
+ // The Appsmith / WordPress-style visual canvas for ONE section. Drag widgets
5
+ // from the palette onto a grid, move + resize them, click to select (the normal
6
+ // Field properties drawer opens), delete. Desktop-only authoring — the END
7
+ // PRODUCT renders mobile-first (see _lib/canvas.ts). Uses react-grid-layout v2,
8
+ // which needs an explicit width, so we self-measure with a ResizeObserver.
9
+ import { useMemo, useRef } from 'react';
10
+ // react-grid-layout v2 (current API): config-object props + the
11
+ // useContainerWidth hook (the modern WidthProvider replacement).
12
+ import { GridLayout, noCompactor, useContainerWidth, } from 'react-grid-layout';
13
+ // Free-form placement: no auto-compaction (elements stay exactly where you drop
14
+ // them), but collisions are BLOCKED rather than pushing neighbours away or
15
+ // allowing overlap. `noCompactor` alone leaves `preventCollision` off, so RGL
16
+ // shoves items far on collision (and can leave overlaps) — preventCollision
17
+ // turns "push neighbour away" into "snap back", fixing both.
18
+ const FREE_COMPACTOR = { ...noCompactor, preventCollision: true };
19
+ import { GripVertical, Trash2 } from 'lucide-react';
20
+ import { FIELD_TYPES, } from '@braedonsaunders/appkit-forms-core';
21
+ import { ElementPreview } from './element-preview.js';
22
+ // Curated widget palette with sensible default boxes (in grid units, 12 cols).
23
+ const PALETTE = [
24
+ { type: 'heading', w: 12, h: 1 },
25
+ { type: 'paragraph', w: 12, h: 2 },
26
+ { type: 'divider', w: 12, h: 1 },
27
+ { type: 'text', w: 4, h: 2 },
28
+ { type: 'long_text', w: 6, h: 3 },
29
+ { type: 'number', w: 3, h: 2 },
30
+ { type: 'date', w: 3, h: 2 },
31
+ { type: 'select', w: 4, h: 2 },
32
+ { type: 'radio', w: 4, h: 3 },
33
+ { type: 'checkbox_group', w: 4, h: 3 },
34
+ { type: 'yes_no_comment', w: 6, h: 3 },
35
+ { type: 'rating', w: 3, h: 2 },
36
+ { type: 'photo', w: 4, h: 4 },
37
+ { type: 'signature', w: 6, h: 3 },
38
+ { type: 'table', w: 12, h: 5 },
39
+ ];
40
+ export function defaultBox(type) {
41
+ const p = PALETTE.find((x) => x.type === type);
42
+ return { w: p?.w ?? 4, h: p?.h ?? 3 };
43
+ }
44
+ function toLayout(items) {
45
+ return items.map((it) => ({ i: it.i, x: it.x, y: it.y, w: it.w, h: it.h }));
46
+ }
47
+ function fromLayout(layout) {
48
+ return layout
49
+ .filter((l) => l.i !== '__dropping__')
50
+ .map((l) => ({ i: l.i, x: l.x, y: l.y, w: l.w, h: l.h }));
51
+ }
52
+ function sameItems(a, b) {
53
+ if (a.length !== b.length)
54
+ return false;
55
+ const m = new Map(a.map((it) => [it.i, it]));
56
+ for (const it of b) {
57
+ const o = m.get(it.i);
58
+ if (!o || o.x !== it.x || o.y !== it.y || o.w !== it.w || o.h !== it.h)
59
+ return false;
60
+ }
61
+ return true;
62
+ }
63
+ export function CanvasEditor({ section, locale, defaultLocale, selectedFieldId, dragTypeRef, onLayout, onAddWidget, onSelect, onDelete, }) {
64
+ const tGeneratedValue = useGeneratedValueTranslations();
65
+ const tGenerated = useGeneratedTranslations();
66
+ const canvas = section.canvas;
67
+ const { width, mounted, containerRef } = useContainerWidth({ measureBeforeMount: true });
68
+ // Tells a real "select" click apart from the click event that fires at the END
69
+ // of a drag/resize: a drag's release click lands far from where the press
70
+ // began, so only a near-stationary click selects. A pure distance check can
71
+ // never block a genuine (stationary) click, so selection always works.
72
+ const pressRef = useRef(null);
73
+ const fieldById = useMemo(() => new Map(section.fields.map((f) => [f.id, f])), [section.fields]);
74
+ // Only render items whose field still exists.
75
+ const items = useMemo(() => canvas.items.filter((it) => fieldById.has(it.i)), [canvas.items, fieldById]);
76
+ const layout = useMemo(() => toLayout(items), [items]);
77
+ return (_jsxs("div", { ref: containerRef, className: "relative min-h-[240px] rounded-md border border-border", style: {
78
+ backgroundImage: 'linear-gradient(to right, rgb(var(--ch-border-subtle)) 1px, transparent 1px), linear-gradient(to bottom, rgb(var(--ch-border-subtle)) 1px, transparent 1px)',
79
+ backgroundSize: `${width > 0 ? width / canvas.cols : 40}px ${canvas.rowHeight}px`,
80
+ }, children: [_jsx(GeneratedValue, { value: items.length === 0 ? (_jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: _jsx("span", { className: "rounded-md bg-white/80 px-3 py-1.5 text-xs text-fg-subtle", children: _jsx(GeneratedText, { id: "m_16c009976e0b86" }) }) })) : null }), _jsx(GeneratedValue, { value: mounted && width > 0 ? (_jsx(GridLayout, { width: width, layout: layout, gridConfig: { cols: canvas.cols, rowHeight: canvas.rowHeight, margin: [8, 8] }, dragConfig: { handle: '.cv-drag' }, dropConfig: {
81
+ enabled: true,
82
+ defaultItem: { w: 4, h: 3 },
83
+ onDragOver: () => ({ w: 4, h: 3 }),
84
+ }, compactor: FREE_COMPACTOR, droppingItem: { i: '__dropping__', x: 0, y: 0, w: 4, h: 3 }, onDrop: (_l, item) => {
85
+ const t = dragTypeRef.current;
86
+ if (t && item)
87
+ onAddWidget(t, { x: item.x, y: item.y, ...defaultBox(t) });
88
+ dragTypeRef.current = null;
89
+ }, onLayoutChange: (l) => {
90
+ const next = fromLayout(l);
91
+ if (!sameItems(items, next))
92
+ onLayout(next);
93
+ }, children: _jsx(GeneratedValue, { value: items.map((it) => {
94
+ const f = fieldById.get(it.i);
95
+ const sel = f.id === selectedFieldId;
96
+ return (_jsxs("div", { onPointerDown: (e) => {
97
+ pressRef.current = { x: e.clientX, y: e.clientY };
98
+ }, onClick: (e) => {
99
+ const d = pressRef.current;
100
+ pressRef.current = null;
101
+ // The click that ends a drag/resize lands far from the press —
102
+ // only a near-stationary click selects, so moving an element no
103
+ // longer opens the properties panel.
104
+ if (d && Math.hypot(e.clientX - d.x, e.clientY - d.y) > 5)
105
+ return;
106
+ onSelect(f.id);
107
+ }, title: tGeneratedValue(FIELD_TYPES[f.type]?.label ?? f.type), className: `group relative flex h-full cursor-pointer flex-col overflow-hidden rounded-lg border bg-white shadow-sm transition ${sel
108
+ ? 'border-primary ring-1 ring-ring'
109
+ : 'border-border hover:border-border-strong hover:shadow'}`, children: [_jsxs("div", { className: "absolute top-1 right-1 z-10 flex items-center gap-0.5 opacity-0 transition group-hover:opacity-100", children: [_jsx("span", { className: "cv-drag flex h-5 w-5 cursor-grab items-center justify-center rounded bg-white/90 text-fg-subtle shadow-sm ring-1 ring-ring hover:text-fg-muted active:cursor-grabbing", title: tGenerated('m_17cb3d26a75938'), children: _jsx(GripVertical, { size: 11 }) }), _jsx("button", { type: "button", title: tGenerated('m_0e6eb1d4f48e48'), onClick: (e) => {
110
+ e.stopPropagation();
111
+ onDelete(f.id);
112
+ }, className: "flex h-5 w-5 items-center justify-center rounded bg-white/90 text-fg-subtle shadow-sm ring-1 ring-ring hover:text-danger", children: _jsx(Trash2, { size: 11 }) })] }), _jsx("div", { className: "app-scroll min-h-0 flex-1 overflow-auto p-2.5", children: _jsx(ElementPreview, { field: f, locale: locale, defaultLocale: defaultLocale, compact: true }) })] }, it.i));
113
+ }) }) })) : null })] }));
114
+ }
115
+ //# sourceMappingURL=canvas-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-editor.js","sourceRoot":"","sources":["../src/canvas-editor.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport {\n GeneratedText,\n useGeneratedTranslations,\n GeneratedValue,\n useGeneratedValueTranslations,\n} from './generated-copy'\n\n// The Appsmith / WordPress-style visual canvas for ONE section. Drag widgets\n// from the palette onto a grid, move + resize them, click to select (the normal\n// Field properties drawer opens), delete. Desktop-only authoring — the END\n// PRODUCT renders mobile-first (see _lib/canvas.ts). Uses react-grid-layout v2,\n// which needs an explicit width, so we self-measure with a ResizeObserver.\n\nimport { useMemo, useRef, type RefObject } from 'react'\n// react-grid-layout v2 (current API): config-object props + the\n// useContainerWidth hook (the modern WidthProvider replacement).\nimport {\n GridLayout,\n noCompactor,\n useContainerWidth,\n type Layout,\n type LayoutItem,\n} from 'react-grid-layout'\n// Free-form placement: no auto-compaction (elements stay exactly where you drop\n// them), but collisions are BLOCKED rather than pushing neighbours away or\n// allowing overlap. `noCompactor` alone leaves `preventCollision` off, so RGL\n// shoves items far on collision (and can leave overlaps) — preventCollision\n// turns \"push neighbour away\" into \"snap back\", fixing both.\nconst FREE_COMPACTOR = { ...noCompactor, preventCollision: true }\nimport { GripVertical, Trash2 } from 'lucide-react'\nimport {\n FIELD_TYPES,\n type CanvasItem,\n type FieldType,\n type FormSection,\n} from '@braedonsaunders/appkit-forms-core'\nimport type { AppLocale } from '@braedonsaunders/appkit-i18n'\nimport { ElementPreview } from './element-preview'\n\n// Curated widget palette with sensible default boxes (in grid units, 12 cols).\nconst PALETTE: { type: FieldType; w: number; h: number }[] = [\n { type: 'heading', w: 12, h: 1 },\n { type: 'paragraph', w: 12, h: 2 },\n { type: 'divider', w: 12, h: 1 },\n { type: 'text', w: 4, h: 2 },\n { type: 'long_text', w: 6, h: 3 },\n { type: 'number', w: 3, h: 2 },\n { type: 'date', w: 3, h: 2 },\n { type: 'select', w: 4, h: 2 },\n { type: 'radio', w: 4, h: 3 },\n { type: 'checkbox_group', w: 4, h: 3 },\n { type: 'yes_no_comment', w: 6, h: 3 },\n { type: 'rating', w: 3, h: 2 },\n { type: 'photo', w: 4, h: 4 },\n { type: 'signature', w: 6, h: 3 },\n { type: 'table', w: 12, h: 5 },\n]\n\nexport function defaultBox(type: FieldType): { w: number; h: number } {\n const p = PALETTE.find((x) => x.type === type)\n return { w: p?.w ?? 4, h: p?.h ?? 3 }\n}\n\nfunction toLayout(items: CanvasItem[]): LayoutItem[] {\n return items.map((it) => ({ i: it.i, x: it.x, y: it.y, w: it.w, h: it.h }))\n}\nfunction fromLayout(layout: Layout): CanvasItem[] {\n return layout\n .filter((l) => l.i !== '__dropping__')\n .map((l) => ({ i: l.i, x: l.x, y: l.y, w: l.w, h: l.h }))\n}\nfunction sameItems(a: CanvasItem[], b: CanvasItem[]): boolean {\n if (a.length !== b.length) return false\n const m = new Map(a.map((it) => [it.i, it]))\n for (const it of b) {\n const o = m.get(it.i)\n if (!o || o.x !== it.x || o.y !== it.y || o.w !== it.w || o.h !== it.h) return false\n }\n return true\n}\n\nexport function CanvasEditor({\n section,\n locale,\n defaultLocale,\n selectedFieldId,\n dragTypeRef,\n onLayout,\n onAddWidget,\n onSelect,\n onDelete,\n}: {\n section: FormSection\n locale: AppLocale\n defaultLocale: AppLocale\n selectedFieldId: string | null\n // The element type currently being dragged from the left palette.\n dragTypeRef: RefObject<FieldType | null>\n onLayout: (items: CanvasItem[]) => void\n onAddWidget: (type: FieldType, box: { x: number; y: number; w: number; h: number }) => void\n onSelect: (fieldId: string) => void\n onDelete: (fieldId: string) => void\n}) {\n const tGeneratedValue = useGeneratedValueTranslations()\n const tGenerated = useGeneratedTranslations()\n const canvas = section.canvas!\n const { width, mounted, containerRef } = useContainerWidth({ measureBeforeMount: true })\n // Tells a real \"select\" click apart from the click event that fires at the END\n // of a drag/resize: a drag's release click lands far from where the press\n // began, so only a near-stationary click selects. A pure distance check can\n // never block a genuine (stationary) click, so selection always works.\n const pressRef = useRef<{ x: number; y: number } | null>(null)\n\n const fieldById = useMemo(() => new Map(section.fields.map((f) => [f.id, f])), [section.fields])\n // Only render items whose field still exists.\n const items = useMemo(\n () => canvas.items.filter((it) => fieldById.has(it.i)),\n [canvas.items, fieldById],\n )\n const layout = useMemo(() => toLayout(items), [items])\n\n return (\n <div\n ref={containerRef}\n className=\"relative min-h-[240px] rounded-md border border-border\"\n style={{\n backgroundImage:\n 'linear-gradient(to right, rgb(var(--ch-border-subtle)) 1px, transparent 1px), linear-gradient(to bottom, rgb(var(--ch-border-subtle)) 1px, transparent 1px)',\n backgroundSize: `${width > 0 ? width / canvas.cols : 40}px ${canvas.rowHeight}px`,\n }}\n >\n <GeneratedValue\n value={\n items.length === 0 ? (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <span className=\"rounded-md bg-white/80 px-3 py-1.5 text-xs text-fg-subtle\">\n <GeneratedText id=\"m_16c009976e0b86\" />\n </span>\n </div>\n ) : null\n }\n />\n <GeneratedValue\n value={\n mounted && width > 0 ? (\n <GridLayout\n width={width}\n layout={layout}\n gridConfig={{ cols: canvas.cols, rowHeight: canvas.rowHeight, margin: [8, 8] }}\n dragConfig={{ handle: '.cv-drag' }}\n dropConfig={{\n enabled: true,\n defaultItem: { w: 4, h: 3 },\n onDragOver: () => ({ w: 4, h: 3 }),\n }}\n compactor={FREE_COMPACTOR}\n droppingItem={{ i: '__dropping__', x: 0, y: 0, w: 4, h: 3 }}\n onDrop={(_l, item) => {\n const t = dragTypeRef.current\n if (t && item) onAddWidget(t, { x: item.x, y: item.y, ...defaultBox(t) })\n dragTypeRef.current = null\n }}\n onLayoutChange={(l) => {\n const next = fromLayout(l)\n if (!sameItems(items, next)) onLayout(next)\n }}\n >\n <GeneratedValue\n value={items.map((it) => {\n const f = fieldById.get(it.i)!\n const sel = f.id === selectedFieldId\n return (\n <div\n key={it.i}\n onPointerDown={(e) => {\n pressRef.current = { x: e.clientX, y: e.clientY }\n }}\n onClick={(e) => {\n const d = pressRef.current\n pressRef.current = null\n // The click that ends a drag/resize lands far from the press —\n // only a near-stationary click selects, so moving an element no\n // longer opens the properties panel.\n if (d && Math.hypot(e.clientX - d.x, e.clientY - d.y) > 5) return\n onSelect(f.id)\n }}\n title={tGeneratedValue(FIELD_TYPES[f.type]?.label ?? f.type)}\n className={`group relative flex h-full cursor-pointer flex-col overflow-hidden rounded-lg border bg-white shadow-sm transition ${\n sel\n ? 'border-primary ring-1 ring-ring'\n : 'border-border hover:border-border-strong hover:shadow'\n }`}\n >\n {/* Hover toolbar: drag handle + remove. The card body is a live\n preview of the element as it ships. */}\n <div className=\"absolute top-1 right-1 z-10 flex items-center gap-0.5 opacity-0 transition group-hover:opacity-100\">\n <span\n className=\"cv-drag flex h-5 w-5 cursor-grab items-center justify-center rounded bg-white/90 text-fg-subtle shadow-sm ring-1 ring-ring hover:text-fg-muted active:cursor-grabbing\"\n title={tGenerated('m_17cb3d26a75938')}\n >\n <GripVertical size={11} />\n </span>\n <button\n type=\"button\"\n title={tGenerated('m_0e6eb1d4f48e48')}\n onClick={(e) => {\n e.stopPropagation()\n onDelete(f.id)\n }}\n className=\"flex h-5 w-5 items-center justify-center rounded bg-white/90 text-fg-subtle shadow-sm ring-1 ring-ring hover:text-danger\"\n >\n <Trash2 size={11} />\n </button>\n </div>\n <div className=\"app-scroll min-h-0 flex-1 overflow-auto p-2.5\">\n <ElementPreview\n field={f}\n locale={locale}\n defaultLocale={defaultLocale}\n compact\n />\n </div>\n </div>\n )\n })}\n />\n </GridLayout>\n ) : null\n }\n />\n </div>\n )\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import type { CanvasItem } from '@braedonsaunders/appkit-forms-core';
2
+ export declare function gridClass(sectionId: string): string;
3
+ export declare function resolveCanvas(fieldIds: string[], items: CanvasItem[], cols: number): {
4
+ order: string[];
5
+ byId: Map<string, CanvasItem>;
6
+ };
7
+ export declare function columnsCss(cls: string, cols: number, spans: {
8
+ id: string;
9
+ span: number;
10
+ }[]): string;
11
+ export declare function canvasCss(cls: string, cols: number, rowHeight: number, byId: Map<string, CanvasItem>): string;
12
+ //# sourceMappingURL=canvas-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-runtime.d.ts","sourceRoot":"","sources":["../src/canvas-runtime.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAIpE,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAKD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAAE,EAClB,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAAE,CAgBpD;AAKD,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GACpC,MAAM,CAQR;AAGD,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAC5B,MAAM,CAaR"}
@@ -0,0 +1,54 @@
1
+ // Shared helpers for rendering a section's free-form canvas layout in the END
2
+ // PRODUCT (filler + viewer + PDF). MOBILE-FIRST: the default (no media query) is
3
+ // a single stacked column in (y,x) reading order; a scoped @media(min-width:640px)
4
+ // block applies the authored positioned grid. This keeps phones usable while
5
+ // desktops get the exact Appsmith/WordPress-style layout.
6
+ // Deterministic, collision-safe class derived from the section id (no useId, so
7
+ // it works identically in server + client render).
8
+ export function gridClass(sectionId) {
9
+ return 'cv_' + sectionId.replace(/[^a-zA-Z0-9_-]/g, '_');
10
+ }
11
+ // Resolve every field to a grid box, auto-placing any field that lacks one
12
+ // (appended below, sensible default size). Returns the mobile reading order +
13
+ // the box lookup used to emit desktop placements.
14
+ export function resolveCanvas(fieldIds, items, cols) {
15
+ const byId = new Map();
16
+ for (const it of items)
17
+ if (fieldIds.includes(it.i))
18
+ byId.set(it.i, it);
19
+ let nextY = items.reduce((m, it) => Math.max(m, it.y + it.h), 0);
20
+ for (const id of fieldIds) {
21
+ if (!byId.has(id)) {
22
+ byId.set(id, { i: id, x: 0, y: nextY, w: Math.min(cols, 6) || 6, h: 2 });
23
+ nextY += 2;
24
+ }
25
+ }
26
+ const order = [...fieldIds].sort((a, b) => {
27
+ const A = byId.get(a);
28
+ const B = byId.get(b);
29
+ return A.y - B.y || A.x - B.x;
30
+ });
31
+ return { order, byId };
32
+ }
33
+ // Scoped CSS for the simpler `section.layout.columns` grid: mobile single
34
+ // column; desktop N columns with per-field colSpan. Keeps the columns feature
35
+ // mobile-friendly too.
36
+ export function columnsCss(cls, cols, spans) {
37
+ const spanCss = spans
38
+ .map((s) => `.${cls}>[data-cs="${s.id}"]{grid-column:span ${Math.min(s.span, cols)}}`)
39
+ .join('');
40
+ return (`.${cls}{display:grid;grid-template-columns:1fr;gap:16px}` +
41
+ `@media(min-width:640px){.${cls}{grid-template-columns:repeat(${cols},minmax(0,1fr))}${spanCss}}`);
42
+ }
43
+ // Scoped CSS: mobile single column; desktop positioned grid.
44
+ export function canvasCss(cls, cols, rowHeight, byId) {
45
+ const placements = [];
46
+ for (const it of byId.values()) {
47
+ placements.push(`.${cls}>[data-ci="${it.i}"]{grid-column:${it.x + 1}/span ${it.w};grid-row:${it.y + 1}/span ${it.h};min-width:0}`);
48
+ }
49
+ // grid-auto-rows uses minmax(rowHeight, auto) so cells GROW to fit content
50
+ // (tall inputs never clip) while still honoring the authored row positions.
51
+ return (`.${cls}{display:grid;grid-template-columns:1fr;gap:12px}` +
52
+ `@media(min-width:640px){.${cls}{grid-template-columns:repeat(${cols},minmax(0,1fr));grid-auto-rows:minmax(${rowHeight}px,auto);align-items:start}${placements.join('')}}`);
53
+ }
54
+ //# sourceMappingURL=canvas-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-runtime.js","sourceRoot":"","sources":["../src/canvas-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// Shared helpers for rendering a section's free-form canvas layout in the END\n// PRODUCT (filler + viewer + PDF). MOBILE-FIRST: the default (no media query) is\n// a single stacked column in (y,x) reading order; a scoped @media(min-width:640px)\n// block applies the authored positioned grid. This keeps phones usable while\n// desktops get the exact Appsmith/WordPress-style layout.\n\nimport type { CanvasItem } from '@braedonsaunders/appkit-forms-core'\n\n// Deterministic, collision-safe class derived from the section id (no useId, so\n// it works identically in server + client render).\nexport function gridClass(sectionId: string): string {\n return 'cv_' + sectionId.replace(/[^a-zA-Z0-9_-]/g, '_')\n}\n\n// Resolve every field to a grid box, auto-placing any field that lacks one\n// (appended below, sensible default size). Returns the mobile reading order +\n// the box lookup used to emit desktop placements.\nexport function resolveCanvas(\n fieldIds: string[],\n items: CanvasItem[],\n cols: number,\n): { order: string[]; byId: Map<string, CanvasItem> } {\n const byId = new Map<string, CanvasItem>()\n for (const it of items) if (fieldIds.includes(it.i)) byId.set(it.i, it)\n let nextY = items.reduce((m, it) => Math.max(m, it.y + it.h), 0)\n for (const id of fieldIds) {\n if (!byId.has(id)) {\n byId.set(id, { i: id, x: 0, y: nextY, w: Math.min(cols, 6) || 6, h: 2 })\n nextY += 2\n }\n }\n const order = [...fieldIds].sort((a, b) => {\n const A = byId.get(a)!\n const B = byId.get(b)!\n return A.y - B.y || A.x - B.x\n })\n return { order, byId }\n}\n\n// Scoped CSS for the simpler `section.layout.columns` grid: mobile single\n// column; desktop N columns with per-field colSpan. Keeps the columns feature\n// mobile-friendly too.\nexport function columnsCss(\n cls: string,\n cols: number,\n spans: { id: string; span: number }[],\n): string {\n const spanCss = spans\n .map((s) => `.${cls}>[data-cs=\"${s.id}\"]{grid-column:span ${Math.min(s.span, cols)}}`)\n .join('')\n return (\n `.${cls}{display:grid;grid-template-columns:1fr;gap:16px}` +\n `@media(min-width:640px){.${cls}{grid-template-columns:repeat(${cols},minmax(0,1fr))}${spanCss}}`\n )\n}\n\n// Scoped CSS: mobile single column; desktop positioned grid.\nexport function canvasCss(\n cls: string,\n cols: number,\n rowHeight: number,\n byId: Map<string, CanvasItem>,\n): string {\n const placements: string[] = []\n for (const it of byId.values()) {\n placements.push(\n `.${cls}>[data-ci=\"${it.i}\"]{grid-column:${it.x + 1}/span ${it.w};grid-row:${it.y + 1}/span ${it.h};min-width:0}`,\n )\n }\n // grid-auto-rows uses minmax(rowHeight, auto) so cells GROW to fit content\n // (tall inputs never clip) while still honoring the authored row positions.\n return (\n `.${cls}{display:grid;grid-template-columns:1fr;gap:12px}` +\n `@media(min-width:640px){.${cls}{grid-template-columns:repeat(${cols},minmax(0,1fr));grid-auto-rows:minmax(${rowHeight}px,auto);align-items:start}${placements.join('')}}`\n )\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { FormField } from '@braedonsaunders/appkit-forms-core';
2
+ import { type AppLocale } from '@braedonsaunders/appkit-i18n';
3
+ export declare function ElementPreview({ field, locale, defaultLocale, compact, }: {
4
+ field: FormField;
5
+ locale: AppLocale;
6
+ defaultLocale: AppLocale;
7
+ compact?: boolean;
8
+ }): import("react").JSX.Element;
9
+ //# sourceMappingURL=element-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element-preview.d.ts","sourceRoot":"","sources":["../src/element-preview.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACnE,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAgB3E,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,MAAM,EACN,aAAa,EACb,OAAe,GAChB,EAAE;IACD,KAAK,EAAE,SAAS,CAAA;IAChB,MAAM,EAAE,SAAS,CAAA;IACjB,aAAa,EAAE,SAAS,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,+BAqCA"}
@@ -0,0 +1,167 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { GeneratedText, useGeneratedTranslations, GeneratedValue } from './generated-copy.js';
4
+ // A representative, read-only preview of ONE element — how it looks in the
5
+ // shipped product. Used by the canvas blocks (WYSIWYG building) and the Preview
6
+ // drawer. Uses the element's real label / options / config, not placeholders.
7
+ import { AlertTriangle, BarChart3, MapPin, ScanLine, Sparkles, Star } from 'lucide-react';
8
+ import { Input, Select, Textarea } from '@braedonsaunders/appkit-ui';
9
+ import { localizeText } from '@braedonsaunders/appkit-i18n';
10
+ function cfg(field) {
11
+ return field.config ?? {};
12
+ }
13
+ function optionLabels(field, locale, defaultLocale) {
14
+ const opts = field.validation?.options ??
15
+ cfg(field).options ??
16
+ [];
17
+ return opts.map((o) => localizeText(o.label, locale, o.value, defaultLocale));
18
+ }
19
+ export function ElementPreview({ field, locale, defaultLocale, compact = false, }) {
20
+ const label = localizeText(field.label, locale, field.id, defaultLocale);
21
+ const helpText = localizeText(field.helpText, locale, '', defaultLocale);
22
+ const t = field.type;
23
+ // Content elements ARE their content — no separate field label.
24
+ if (t === 'heading')
25
+ return (_jsx("h3", { className: "text-base font-semibold text-fg", children: _jsx(GeneratedValue, { value: label }) }));
26
+ if (t === 'paragraph')
27
+ return (_jsx("p", { className: "text-sm leading-snug text-fg-muted", children: _jsx(GeneratedValue, { value: label }) }));
28
+ if (t === 'divider')
29
+ return _jsx("hr", { className: "my-1 border-border" });
30
+ return (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "text-xs font-medium text-fg-muted", children: [_jsx(GeneratedValue, { value: label }), _jsx(GeneratedValue, { value: field.required ? _jsx("span", { className: "text-danger", children: " *" }) : null })] }), _jsx(ElementInput, { field: field, locale: locale, defaultLocale: defaultLocale, compact: compact }), _jsx(GeneratedValue, { value: helpText ? (_jsx("p", { className: "text-[10px] text-fg-subtle", children: _jsx(GeneratedValue, { value: helpText }) })) : null })] }));
31
+ }
32
+ function ElementInput({ field, locale, defaultLocale, compact, }) {
33
+ const tGenerated = useGeneratedTranslations();
34
+ const t = field.type;
35
+ const labels = optionLabels(field, locale, defaultLocale);
36
+ const choices = labels.length ? labels : ['Option A', 'Option B'];
37
+ switch (t) {
38
+ case 'long_text':
39
+ return _jsx(Textarea, { rows: compact ? 2 : 3, disabled: true, placeholder: "", className: "bg-white" });
40
+ case 'number':
41
+ return _jsx(Input, { type: "number", disabled: true, placeholder: "0", className: "bg-white" });
42
+ case 'date':
43
+ return _jsx(Input, { type: "date", disabled: true, className: "bg-white" });
44
+ case 'datetime':
45
+ return _jsx(Input, { type: "datetime-local", disabled: true, className: "bg-white" });
46
+ case 'time':
47
+ return _jsx(Input, { type: "time", disabled: true, className: "bg-white" });
48
+ case 'email':
49
+ return (_jsx(Input, { type: "email", disabled: true, placeholder: tGenerated('m_1b781270c0d2f5'), className: "bg-white" }));
50
+ case 'phone':
51
+ return _jsx(Input, { disabled: true, placeholder: "(555) 555-5555", className: "bg-white" });
52
+ case 'url':
53
+ return _jsx(Input, { disabled: true, placeholder: "https://", className: "bg-white" });
54
+ case 'select':
55
+ case 'customer_picker':
56
+ case 'site_picker':
57
+ case 'project_picker':
58
+ case 'area_picker':
59
+ case 'person_picker':
60
+ case 'multi_person_picker':
61
+ return (_jsx(Select, { disabled: true, className: "bg-white", children: _jsx("option", { children: labels[0] ?? 'Select…' }) }));
62
+ case 'radio':
63
+ return (_jsx("div", { className: "space-y-1", children: _jsx(GeneratedValue, { value: choices.slice(0, 4).map((l, i) => (_jsxs("label", { className: "flex items-center gap-2 text-xs text-fg-muted", children: [_jsx("input", { type: "radio", disabled: true }), " ", _jsx(GeneratedValue, { value: l })] }, i))) }) }));
64
+ case 'checkbox_group':
65
+ case 'multi_select':
66
+ return (_jsx("div", { className: "space-y-1", children: _jsx(GeneratedValue, { value: choices.slice(0, 4).map((l, i) => (_jsxs("label", { className: "flex items-center gap-2 text-xs text-fg-muted", children: [_jsx("input", { type: "checkbox", disabled: true }), " ", _jsx(GeneratedValue, { value: l })] }, i))) }) }));
67
+ case 'yes_no_comment':
68
+ return (_jsx("div", { className: "flex gap-2", children: _jsx(GeneratedValue, { value: ['Yes', 'No'].map((v) => (_jsx("span", { className: "rounded-md border border-border px-3 py-1 text-xs text-fg-muted", children: _jsx(GeneratedValue, { value: v }) }, v))) }) }));
69
+ case 'pass_fail_na':
70
+ return (_jsx("div", { className: "flex gap-1", children: _jsx(GeneratedValue, { value: ['PASS', 'FAIL', 'N/A'].map((v) => (_jsx("span", { className: "rounded border border-border px-2 py-1 text-[10px] text-fg-muted", children: _jsx(GeneratedValue, { value: v }) }, v))) }) }));
71
+ case 'traffic_light':
72
+ return (_jsx("div", { className: "flex gap-1.5", children: _jsx(GeneratedValue, { value: ['bg-success', 'bg-warning', 'bg-danger'].map((tone) => (_jsx("span", { className: `h-5 w-5 rounded-full ${tone}` }, tone))) }) }));
73
+ case 'rating':
74
+ return (_jsx("div", { className: "flex gap-0.5 text-warning", children: _jsx(GeneratedValue, { value: [1, 2, 3, 4, 5].map((i) => (_jsx(Star, { size: 16, fill: "currentColor", strokeWidth: 0 }, i))) }) }));
75
+ case 'signature':
76
+ return (_jsx("div", { className: "flex h-16 items-center justify-center rounded border border-dashed border-border-strong bg-bg-subtle text-xs text-fg-subtle italic", children: _jsx(GeneratedText, { id: "m_05a0e2d38bded7" }) }));
77
+ case 'photo':
78
+ case 'photo_upload':
79
+ case 'file':
80
+ case 'video':
81
+ case 'audio':
82
+ return (_jsx("div", { className: "flex h-16 items-center justify-center rounded border border-dashed border-border-strong bg-bg-subtle text-xs text-fg-subtle", children: _jsx(GeneratedText, { id: "m_10e50435ff2693" }) }));
83
+ case 'sketch':
84
+ return (_jsx("div", { className: "flex h-20 items-center justify-center rounded border border-dashed border-border-strong bg-[repeating-linear-gradient(45deg,transparent,transparent_9px,rgba(148,163,184,0.12)_9px,rgba(148,163,184,0.12)_10px)] text-xs text-fg-subtle italic", children: _jsx(GeneratedText, { id: "m_1abf555859c0a8" }) }));
85
+ case 'photo_ai':
86
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "flex h-14 items-center justify-center rounded border border-dashed border-border-strong bg-bg-subtle text-xs text-fg-subtle", children: _jsx(GeneratedText, { id: "m_18010438ac7907" }) }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-md border border-info bg-info-subtle px-2 py-1 text-[11px] text-info", children: [_jsx(Sparkles, { size: 12 }), " ", _jsx(GeneratedText, { id: "m_0fec93e95858fc" })] }), _jsxs("div", { className: "flex items-center gap-1 text-[10px] text-fg-subtle", children: [_jsx(AlertTriangle, { size: 11, className: "text-warning" }), ' ', _jsx(GeneratedText, { id: "m_0289ca8aa7b891" })] })] }));
87
+ case 'typed_attestation':
88
+ return _jsx(Input, { disabled: true, placeholder: tGenerated('m_0d0facbd71aa6a'), className: "bg-white" });
89
+ case 'formula':
90
+ return _jsx(Input, { disabled: true, value: "= computed", className: "bg-bg-subtle italic" });
91
+ case 'slider': {
92
+ const c = cfg(field);
93
+ const min = c.min ?? 0;
94
+ const max = c.max ?? 10;
95
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "range", min: min, max: max, defaultValue: (min + max) / 2, disabled: true, className: "h-2 flex-1 rounded-full bg-border accent-primary" }), _jsx("span", { className: "text-xs text-fg-subtle", children: _jsx(GeneratedValue, { value: Math.round((min + max) / 2) }) })] }));
96
+ }
97
+ case 'gps':
98
+ return (_jsxs("div", { className: "flex items-center gap-1.5 rounded border border-border bg-white px-2 py-1.5 text-xs text-fg-muted", children: [_jsx(MapPin, { size: 13 }), " ", _jsx(GeneratedText, { id: "m_07cd022cc38d60" })] }));
99
+ case 'matrix': {
100
+ const c = cfg(field);
101
+ const rows = c.rows ?? [
102
+ { key: 'r1', label: 'Item 1' },
103
+ { key: 'r2', label: 'Item 2' },
104
+ ];
105
+ const scale = c.scale ?? [
106
+ { value: '1', label: '1' },
107
+ { value: '2', label: '2' },
108
+ { value: '3', label: '3' },
109
+ ];
110
+ return (_jsxs("table", { className: "w-full text-[10px]", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", {}), _jsx(GeneratedValue, { value: scale.map((s) => (_jsx("th", { className: "px-1 text-center font-normal text-fg-subtle", children: _jsx(GeneratedValue, { value: s.label }) }, s.value))) })] }) }), _jsx("tbody", { children: _jsx(GeneratedValue, { value: rows.slice(0, 3).map((r) => (_jsxs("tr", { children: [_jsx("td", { className: "pr-1 text-fg-muted", children: _jsx(GeneratedValue, { value: r.label }) }), _jsx(GeneratedValue, { value: scale.map((s) => (_jsx("td", { className: "text-center", children: _jsx("input", { type: "radio", disabled: true }) }, s.value))) })] }, r.key))) }) })] }));
111
+ }
112
+ case 'risk_matrix':
113
+ return (_jsx("div", { className: "grid w-fit grid-cols-5 gap-0.5", children: _jsx(GeneratedValue, { value: Array.from({ length: 25 }).map((_, i) => {
114
+ const sev = (i % 5) + Math.floor(i / 5);
115
+ const tone = sev >= 6
116
+ ? 'bg-danger'
117
+ : sev >= 4
118
+ ? 'bg-warning'
119
+ : sev >= 2
120
+ ? 'bg-warning-subtle'
121
+ : 'bg-success-subtle';
122
+ return _jsx("span", { className: `h-3.5 w-3.5 rounded-sm ${tone}` }, i);
123
+ }) }) }));
124
+ case 'photo_annotated':
125
+ return (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "relative flex h-24 items-center justify-center rounded border border-dashed border-border-strong bg-bg-subtle text-xs text-fg-subtle", children: [_jsx(GeneratedText, { id: "m_18010438ac7907" }), _jsx("span", { className: "absolute top-1/3 left-1/3 flex h-5 w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-danger text-[10px] font-bold text-danger-fg ring-2 ring-surface", children: "1" }), _jsx("span", { className: "absolute top-1/2 left-2/3 flex h-5 w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-danger text-[10px] font-bold text-white ring-2 ring-white", children: "2" })] }), _jsx("div", { className: "text-[10px] text-fg-subtle", children: _jsx(GeneratedText, { id: "m_18c006b02858b9" }) })] }));
126
+ case 'qr_scanner':
127
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { disabled: true, placeholder: tGenerated('m_0a43fb83c91fdf'), className: "flex-1 bg-white" }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded border border-border px-1.5 py-1 text-[11px] text-fg-muted", children: [_jsx(ScanLine, { size: 12 }), " ", _jsx(GeneratedText, { id: "m_198b8dba5a829c" })] })] }));
128
+ case 'ranking': {
129
+ const ls = optionLabels(field, locale, defaultLocale);
130
+ const itemsR = ls.length ? ls : ['Option A', 'Option B', 'Option C'];
131
+ return (_jsx("ol", { className: "space-y-1", children: _jsx(GeneratedValue, { value: itemsR.slice(0, 4).map((l, i) => (_jsxs("li", { className: "flex items-center gap-2 rounded border border-border bg-white px-2 py-1 text-xs", children: [_jsx("span", { className: "w-4 text-center font-semibold text-fg-subtle", children: _jsx(GeneratedValue, { value: i + 1 }) }), _jsx("span", { className: "flex-1 truncate text-fg-muted", children: _jsx(GeneratedValue, { value: l }) }), _jsx("span", { className: "text-fg-subtle", children: "\u21C5" })] }, i))) }) }));
132
+ }
133
+ case 'rich_text':
134
+ return (_jsxs("div", { className: "rounded border border-border", children: [_jsxs("div", { className: "flex gap-2 border-b border-border bg-bg-subtle px-1.5 py-1 text-[11px] text-fg-subtle", children: [_jsx("span", { className: "font-bold", children: _jsx(GeneratedText, { id: "m_03d716fddcdd92" }) }), _jsx("span", { className: "italic", children: _jsx(GeneratedText, { id: "m_01f47383f19f8c" }) }), _jsx("span", { children: _jsx(GeneratedText, { id: "m_1287c1049195af" }) })] }), _jsxs("div", { className: "space-y-1 p-2", children: [_jsx("div", { className: "h-1.5 w-3/4 rounded bg-surface-hover" }), _jsx("div", { className: "h-1.5 w-1/2 rounded bg-surface-hover" })] })] }));
135
+ case 'address':
136
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center gap-1 rounded border border-border bg-white px-2 py-1.5 text-xs text-fg-subtle", children: [_jsx(MapPin, { size: 12 }), " ", _jsx(GeneratedText, { id: "m_178de0c94da150" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [_jsx("div", { className: "col-span-2 h-7 rounded border border-border bg-white" }), _jsx("div", { className: "h-7 rounded border border-border bg-white" }), _jsx("div", { className: "h-7 rounded border border-border bg-white" })] })] }));
137
+ case 'table':
138
+ return _jsx(MiniTable, { field: field });
139
+ case 'lookup':
140
+ return (_jsxs("div", { className: "space-y-1", children: [_jsx(Select, { disabled: true, className: "bg-white", children: _jsx("option", { children: 'Select…' }) }), _jsx(GeneratedValue, { value: field.binding?.autofill?.length ? (_jsxs("p", { className: "text-[10px] text-info", children: [_jsx(GeneratedText, { id: "m_011434db59926f" }), ' ', _jsx(GeneratedValue, { value: field.binding.autofill.length }), ' ', _jsx(GeneratedText, { id: "m_1d6aa8702d3fac" }), _jsx(GeneratedValue, { value: field.binding.autofill.length === 1 ? ('') : (_jsx(GeneratedText, { id: "m_00ded356f0f424" })) })] })) : null })] }));
141
+ case 'data_table': {
142
+ const keys = field.binding?.columns ?? [];
143
+ const headers = keys.length ? keys : ['Column A', 'Column B', 'Column C'];
144
+ const sel = field.binding?.selectable;
145
+ return (_jsxs("div", { className: "overflow-hidden rounded border border-border text-[10px]", children: [_jsx("div", { className: "flex bg-bg-subtle", children: _jsx(GeneratedValue, { value: headers.slice(0, 5).map((h, i) => (_jsx("div", { className: "flex-1 truncate border-r border-border px-1.5 py-1 font-medium text-fg-muted last:border-0", children: _jsx(GeneratedValue, { value: h }) }, i))) }) }), _jsx(GeneratedValue, { value: [0, 1, 2].map((r) => (_jsx("div", { className: "flex border-t border-border-subtle", children: _jsx(GeneratedValue, { value: headers.slice(0, 5).map((_, i) => (_jsx("div", { className: "flex-1 border-r border-border-subtle px-1.5 py-1 text-fg-subtle last:border-0", children: "\u2014" }, i))) }) }, r))) }), _jsx(GeneratedValue, { value: sel && sel !== 'none' ? (_jsx("div", { className: "bg-bg-subtle px-1.5 py-0.5 text-[9px] text-fg-subtle", children: _jsx(GeneratedValue, { value: sel === 'single' ? (_jsx(GeneratedText, { id: "m_1d33f094c621dc" })) : (_jsx(GeneratedText, { id: "m_1a1dcc7d0b9e8a" })) }) })) : null })] }));
146
+ }
147
+ case 'metric': {
148
+ const agg = field.binding?.aggregate;
149
+ const grouped = !!agg?.groupBy || (!!field.binding?.display && field.binding.display !== 'number');
150
+ if (grouped) {
151
+ return (_jsx("div", { className: "flex h-14 items-end gap-1", children: _jsx(GeneratedValue, { value: [8, 14, 10, 16, 6].map((h, i) => (_jsx("span", { className: "w-3 rounded-t bg-primary", style: { height: h * 3 } }, i))) }) }));
152
+ }
153
+ return (_jsxs("div", { className: "inline-flex items-center gap-2 rounded-lg border border-border bg-white px-3 py-2", children: [_jsx(BarChart3, { size: 18, className: "text-primary" }), _jsxs("div", { children: [_jsx("div", { className: "text-2xl leading-none font-semibold text-fg", children: "\u2014" }), _jsxs("div", { className: "mt-0.5 text-[10px] tracking-wide text-fg-subtle uppercase", children: [_jsx(GeneratedValue, { value: agg?.fn ?? _jsx(GeneratedText, { id: "m_15f748343d3956" }) }), _jsx(GeneratedValue, { value: agg?.column ? ` · ${agg.column}` : '' })] })] })] }));
154
+ }
155
+ default:
156
+ return _jsx(Input, { disabled: true, placeholder: "", className: "bg-white" });
157
+ }
158
+ }
159
+ function MiniTable({ field }) {
160
+ const cols = cfg(field).columns ?? [];
161
+ if (cols.length === 0) {
162
+ return (_jsx("div", { className: "rounded border border-dashed border-border-strong p-2 text-center text-[11px] text-fg-subtle", children: _jsx(GeneratedText, { id: "m_0d90d1eceb566b" }) }));
163
+ }
164
+ const shown = cols.slice(0, 5);
165
+ return (_jsxs("div", { className: "overflow-hidden rounded border border-border text-[10px]", children: [_jsx("div", { className: "flex bg-bg-subtle", children: _jsx(GeneratedValue, { value: shown.map((c) => (_jsx("div", { className: "flex-1 truncate border-r border-border px-1.5 py-1 font-medium text-fg-muted last:border-0", children: _jsx(GeneratedValue, { value: c.label || c.key }) }, c.key))) }) }), _jsx(GeneratedValue, { value: [0, 1].map((r) => (_jsx("div", { className: "flex border-t border-border-subtle", children: _jsx(GeneratedValue, { value: shown.map((c) => (_jsx("div", { className: "flex-1 border-r border-border-subtle px-1.5 py-1 text-fg-subtle last:border-0", children: "\u2014" }, c.key))) }) }, r))) })] }));
166
+ }
167
+ //# sourceMappingURL=element-preview.js.map