@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.
- package/LICENSE +661 -0
- package/README.md +74 -0
- package/canvas-editor.d.ts +24 -0
- package/canvas-editor.d.ts.map +1 -0
- package/canvas-editor.js +115 -0
- package/canvas-editor.js.map +1 -0
- package/canvas-runtime.d.ts +12 -0
- package/canvas-runtime.d.ts.map +1 -0
- package/canvas-runtime.js +54 -0
- package/canvas-runtime.js.map +1 -0
- package/element-preview.d.ts +9 -0
- package/element-preview.d.ts.map +1 -0
- package/element-preview.js +167 -0
- package/element-preview.js.map +1 -0
- package/form-designer.d.ts +29 -0
- package/form-designer.d.ts.map +1 -0
- package/form-designer.js +214 -0
- package/form-designer.js.map +1 -0
- package/form-flow-validation.d.ts +5 -0
- package/form-flow-validation.d.ts.map +1 -0
- package/form-flow-validation.js +154 -0
- package/form-flow-validation.js.map +1 -0
- package/form-renderer.d.ts +37 -0
- package/form-renderer.d.ts.map +1 -0
- package/form-renderer.js +282 -0
- package/form-renderer.js.map +1 -0
- package/formula-builder.d.ts +25 -0
- package/formula-builder.d.ts.map +1 -0
- package/formula-builder.js +310 -0
- package/formula-builder.js.map +1 -0
- package/generated-copy.d.ts +17 -0
- package/generated-copy.d.ts.map +1 -0
- package/generated-copy.js +396 -0
- package/generated-copy.js.map +1 -0
- package/index.d.ts +27 -0
- package/index.d.ts.map +1 -0
- package/index.js +25 -0
- package/index.js.map +1 -0
- package/logic-builder.d.ts +27 -0
- package/logic-builder.d.ts.map +1 -0
- package/logic-builder.js +106 -0
- package/logic-builder.js.map +1 -0
- package/package.json +54 -0
- package/photo-field-state.d.ts +9 -0
- package/photo-field-state.d.ts.map +1 -0
- package/photo-field-state.js +11 -0
- package/photo-field-state.js.map +1 -0
- package/production-designer-adapter.d.ts +100 -0
- package/production-designer-adapter.d.ts.map +1 -0
- package/production-designer-adapter.js +2 -0
- package/production-designer-adapter.js.map +1 -0
- package/production-file-upload.d.ts +27 -0
- package/production-file-upload.d.ts.map +1 -0
- package/production-file-upload.js +105 -0
- package/production-file-upload.js.map +1 -0
- package/production-form-designer.d.ts +3 -0
- package/production-form-designer.d.ts.map +1 -0
- package/production-form-designer.js +1124 -0
- package/production-form-designer.js.map +1 -0
- package/production-form-renderer.d.ts +16 -0
- package/production-form-renderer.d.ts.map +1 -0
- package/production-form-renderer.js +2134 -0
- package/production-form-renderer.js.map +1 -0
- package/production-runtime-adapter.d.ts +187 -0
- package/production-runtime-adapter.d.ts.map +1 -0
- package/production-runtime-adapter.js +2 -0
- package/production-runtime-adapter.js.map +1 -0
- package/production-runtime-context.d.ts +29 -0
- package/production-runtime-context.d.ts.map +1 -0
- package/production-runtime-context.js +35 -0
- package/production-runtime-context.js.map +1 -0
- package/production-runtime-layout.d.ts +21 -0
- package/production-runtime-layout.d.ts.map +1 -0
- package/production-runtime-layout.js +33 -0
- package/production-runtime-layout.js.map +1 -0
- package/properties.d.ts +36 -0
- package/properties.d.ts.map +1 -0
- package/properties.js +133 -0
- package/properties.js.map +1 -0
- package/record-actions-panel.d.ts +28 -0
- package/record-actions-panel.d.ts.map +1 -0
- package/record-actions-panel.js +136 -0
- package/record-actions-panel.js.map +1 -0
- package/record-behavior-panel.d.ts +15 -0
- package/record-behavior-panel.d.ts.map +1 -0
- package/record-behavior-panel.js +59 -0
- package/record-behavior-panel.js.map +1 -0
- package/record-config.d.ts +44 -0
- package/record-config.d.ts.map +1 -0
- package/record-config.js +2 -0
- package/record-config.js.map +1 -0
- package/record-list-panel.d.ts +16 -0
- package/record-list-panel.d.ts.map +1 -0
- package/record-list-panel.js +86 -0
- package/record-list-panel.js.map +1 -0
- package/risk-matrix.d.ts +93 -0
- package/risk-matrix.d.ts.map +1 -0
- package/risk-matrix.js +239 -0
- package/risk-matrix.js.map +1 -0
- package/runtime-controls.d.ts +19 -0
- package/runtime-controls.d.ts.map +1 -0
- package/runtime-controls.js +61 -0
- package/runtime-controls.js.map +1 -0
- package/sketch-pad-client.d.ts +4 -0
- package/sketch-pad-client.d.ts.map +1 -0
- package/sketch-pad-client.js +54 -0
- package/sketch-pad-client.js.map +1 -0
- package/sketch-pad.d.ts +13 -0
- package/sketch-pad.d.ts.map +1 -0
- package/sketch-pad.js +17 -0
- package/sketch-pad.js.map +1 -0
- package/styles.css +7 -0
- package/text.d.ts +5 -0
- package/text.d.ts.map +1 -0
- package/text.js +10 -0
- package/text.js.map +1 -0
- package/workflow-properties.d.ts +16 -0
- package/workflow-properties.d.ts.map +1 -0
- package/workflow-properties.js +31 -0
- 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"}
|
package/canvas-editor.js
ADDED
|
@@ -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
|