formlift 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -3
- package/README.md +206 -2
- package/dist/{chunk-5VHPTLLV.cjs → chunk-33XWO4KB.cjs} +156 -34
- package/dist/chunk-33XWO4KB.cjs.map +1 -0
- package/dist/chunk-7NZPYWL3.cjs.map +1 -1
- package/dist/{chunk-BQAVPA7Q.js → chunk-NUKLGHKS.js} +157 -35
- package/dist/chunk-NUKLGHKS.js.map +1 -0
- package/dist/filters-Yzd4tG8r.d.ts +201 -0
- package/dist/filters-q6gqilzw.d.cts +201 -0
- package/dist/index.cjs +24 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -56
- package/dist/index.d.ts +5 -56
- package/dist/index.js +27 -5
- package/dist/react/index.cjs +1052 -836
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +528 -45
- package/dist/react/index.d.ts +528 -45
- package/dist/react/index.js +1278 -1062
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-5VHPTLLV.cjs.map +0 -1
- package/dist/chunk-BQAVPA7Q.js.map +0 -1
- package/dist/layout-ExhBeO6b.d.cts +0 -27
- package/dist/layout-ExhBeO6b.d.ts +0 -27
package/dist/react/index.js
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ELEMENT_TOOLS,
|
|
3
|
-
NO_FILTER,
|
|
4
3
|
boxStyle,
|
|
5
|
-
|
|
4
|
+
builtinFilters,
|
|
6
5
|
deletePage,
|
|
7
6
|
detectDocument,
|
|
8
7
|
duplicate,
|
|
9
8
|
duplicateElement,
|
|
9
|
+
filterQuestions,
|
|
10
10
|
fitInPage,
|
|
11
11
|
fitTextWidth,
|
|
12
12
|
insertBlankPage,
|
|
13
|
-
matches,
|
|
14
13
|
moveBy,
|
|
15
14
|
movePage,
|
|
16
15
|
newElement,
|
|
17
16
|
newField,
|
|
18
|
-
pickedOf,
|
|
19
17
|
reorder,
|
|
20
18
|
replaceLine,
|
|
21
19
|
resizeBy,
|
|
20
|
+
resolveFilters,
|
|
22
21
|
snap,
|
|
23
22
|
toLines,
|
|
24
23
|
toPage,
|
|
25
24
|
toQuestions,
|
|
26
25
|
withField
|
|
27
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-NUKLGHKS.js";
|
|
28
27
|
import {
|
|
29
28
|
FIELD_TYPES,
|
|
30
29
|
FONTS,
|
|
@@ -56,14 +55,10 @@ import {
|
|
|
56
55
|
Undo2,
|
|
57
56
|
Wand2
|
|
58
57
|
} from "lucide-react";
|
|
59
|
-
import {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
useMemo,
|
|
64
|
-
useRef as useRef4,
|
|
65
|
-
useState as useState7
|
|
66
|
-
} from "react";
|
|
58
|
+
import { useEffect as useEffect5, useImperativeHandle, useRef as useRef6, useState as useState9 } from "react";
|
|
59
|
+
|
|
60
|
+
// src/react/ui/slots.tsx
|
|
61
|
+
import { createContext, useContext } from "react";
|
|
67
62
|
|
|
68
63
|
// src/react/ui/Button.tsx
|
|
69
64
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -103,10 +98,147 @@ function Button({
|
|
|
103
98
|
);
|
|
104
99
|
}
|
|
105
100
|
|
|
101
|
+
// src/react/ui/Checkbox.tsx
|
|
102
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
103
|
+
function Checkbox({
|
|
104
|
+
label,
|
|
105
|
+
className = "",
|
|
106
|
+
...rest
|
|
107
|
+
}) {
|
|
108
|
+
return /* @__PURE__ */ jsx2(
|
|
109
|
+
"input",
|
|
110
|
+
{
|
|
111
|
+
type: "checkbox",
|
|
112
|
+
"aria-label": label,
|
|
113
|
+
className: `h-[18px] w-[18px] flex-none cursor-pointer accent-primary ${className}`,
|
|
114
|
+
...rest
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/react/ui/Chip.tsx
|
|
120
|
+
import { X } from "lucide-react";
|
|
121
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
122
|
+
function Chip({
|
|
123
|
+
label,
|
|
124
|
+
count,
|
|
125
|
+
active,
|
|
126
|
+
onClick
|
|
127
|
+
}) {
|
|
128
|
+
return /* @__PURE__ */ jsxs2(
|
|
129
|
+
"button",
|
|
130
|
+
{
|
|
131
|
+
type: "button",
|
|
132
|
+
"aria-pressed": !!active,
|
|
133
|
+
onClick,
|
|
134
|
+
className: `inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1 text-[12.5px] whitespace-nowrap transition-colors ${active ? "border-primary bg-primary-tint font-semibold text-primary" : "border-line-strong bg-white text-ink-2 hover:border-faint hover:bg-surface"}`,
|
|
135
|
+
children: [
|
|
136
|
+
label,
|
|
137
|
+
count != null && /* @__PURE__ */ jsx3(
|
|
138
|
+
"span",
|
|
139
|
+
{
|
|
140
|
+
className: `rounded-full px-1.5 text-[11px] font-semibold ${active ? "bg-primary text-white" : "bg-hover text-muted"}`,
|
|
141
|
+
children: count
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function ChipGroup({
|
|
149
|
+
title,
|
|
150
|
+
options,
|
|
151
|
+
value,
|
|
152
|
+
onChange
|
|
153
|
+
}) {
|
|
154
|
+
return /* @__PURE__ */ jsxs2("div", { role: "group", "aria-label": title, className: "flex flex-wrap items-center gap-1.5", children: [
|
|
155
|
+
title && /* @__PURE__ */ jsx3("span", { className: "mr-1 min-w-28 text-[11.5px] max-sm:basis-full font-semibold tracking-wide text-muted uppercase", children: title }),
|
|
156
|
+
options.map((o) => /* @__PURE__ */ jsx3(
|
|
157
|
+
Chip,
|
|
158
|
+
{
|
|
159
|
+
label: o.label,
|
|
160
|
+
count: o.count,
|
|
161
|
+
active: value === o.value,
|
|
162
|
+
onClick: () => onChange(value === o.value ? null : o.value)
|
|
163
|
+
},
|
|
164
|
+
o.value
|
|
165
|
+
))
|
|
166
|
+
] });
|
|
167
|
+
}
|
|
168
|
+
function RemovableChip({ label, onRemove }) {
|
|
169
|
+
return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary-tint py-0.5 pr-1 pl-2.5 text-[12px] font-medium text-primary", children: [
|
|
170
|
+
label,
|
|
171
|
+
/* @__PURE__ */ jsx3(
|
|
172
|
+
"button",
|
|
173
|
+
{
|
|
174
|
+
type: "button",
|
|
175
|
+
"aria-label": `Remove ${label}`,
|
|
176
|
+
onClick: onRemove,
|
|
177
|
+
className: "inline-flex h-4.5 w-4.5 cursor-pointer items-center justify-center rounded-full hover:bg-primary hover:text-white",
|
|
178
|
+
children: /* @__PURE__ */ jsx3(X, { size: 12 })
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
] });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/react/ui/FormField.tsx
|
|
185
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
186
|
+
var inputClass = "w-full rounded-md border border-line-strong bg-white px-3 py-2 text-[13.5px] outline-none focus:border-primary";
|
|
187
|
+
function FormField({
|
|
188
|
+
label,
|
|
189
|
+
required,
|
|
190
|
+
error,
|
|
191
|
+
children
|
|
192
|
+
}) {
|
|
193
|
+
return /* @__PURE__ */ jsxs3("label", { className: "block text-[13.5px]", children: [
|
|
194
|
+
/* @__PURE__ */ jsxs3("span", { className: "mb-1 block font-semibold", children: [
|
|
195
|
+
label,
|
|
196
|
+
" ",
|
|
197
|
+
required && /* @__PURE__ */ jsx4("span", { className: "text-danger", children: "*" })
|
|
198
|
+
] }),
|
|
199
|
+
children,
|
|
200
|
+
error && /* @__PURE__ */ jsx4("small", { className: "text-danger", children: error })
|
|
201
|
+
] });
|
|
202
|
+
}
|
|
203
|
+
var TextInput = ({ className = "", ...rest }) => /* @__PURE__ */ jsx4("input", { className: `${inputClass} ${className}`, ...rest });
|
|
204
|
+
var Select = ({ className = "", ...rest }) => /* @__PURE__ */ jsx4(
|
|
205
|
+
"select",
|
|
206
|
+
{
|
|
207
|
+
className: `rounded-md border border-line-strong bg-white px-2.5 py-1.5 text-[13px] outline-none ${className}`,
|
|
208
|
+
...rest
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
var TextArea = ({ className = "", ...rest }) => /* @__PURE__ */ jsx4("textarea", { className: `${inputClass} resize-none ${className}`, ...rest });
|
|
212
|
+
|
|
213
|
+
// src/react/ui/IconButton.tsx
|
|
214
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
215
|
+
function IconButton({
|
|
216
|
+
title,
|
|
217
|
+
icon: Icon,
|
|
218
|
+
danger,
|
|
219
|
+
iconSize = 18,
|
|
220
|
+
light,
|
|
221
|
+
className = "",
|
|
222
|
+
...rest
|
|
223
|
+
}) {
|
|
224
|
+
const tone = light ? "text-white opacity-90 hover:bg-white/15 hover:opacity-100" : `text-muted hover:bg-line-soft ${danger ? "hover:text-danger" : "hover:text-primary"}`;
|
|
225
|
+
return /* @__PURE__ */ jsx5(
|
|
226
|
+
"button",
|
|
227
|
+
{
|
|
228
|
+
type: "button",
|
|
229
|
+
title,
|
|
230
|
+
"aria-label": title,
|
|
231
|
+
className: `inline-flex cursor-pointer items-center justify-center rounded p-1.5 transition disabled:cursor-not-allowed disabled:opacity-40 ${tone} ${className}`,
|
|
232
|
+
...rest,
|
|
233
|
+
children: /* @__PURE__ */ jsx5(Icon, { size: iconSize })
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
106
238
|
// src/react/ui/Modal.tsx
|
|
107
|
-
import { jsx as
|
|
239
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
108
240
|
function Modal({ children, onClose, width }) {
|
|
109
|
-
return /* @__PURE__ */
|
|
241
|
+
return /* @__PURE__ */ jsx6("div", { className: "fixed inset-0 z-[1200] flex items-center justify-center bg-[rgba(15,23,42,0.45)]", onClick: onClose, children: /* @__PURE__ */ jsx6(
|
|
110
242
|
"div",
|
|
111
243
|
{
|
|
112
244
|
role: "dialog",
|
|
@@ -121,9 +253,9 @@ var ModalHeader = ({
|
|
|
121
253
|
title,
|
|
122
254
|
onClose,
|
|
123
255
|
disabled
|
|
124
|
-
}) => /* @__PURE__ */
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
-
/* @__PURE__ */
|
|
256
|
+
}) => /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between border-b border-line px-5 py-4", children: [
|
|
257
|
+
/* @__PURE__ */ jsx6("h3", { className: "m-0 text-[17px] font-semibold", children: title }),
|
|
258
|
+
/* @__PURE__ */ jsx6(
|
|
127
259
|
"button",
|
|
128
260
|
{
|
|
129
261
|
type: "button",
|
|
@@ -135,8 +267,8 @@ var ModalHeader = ({
|
|
|
135
267
|
}
|
|
136
268
|
)
|
|
137
269
|
] });
|
|
138
|
-
var ModalBody = ({ children }) => /* @__PURE__ */
|
|
139
|
-
var ModalFooter = ({ children }) => /* @__PURE__ */
|
|
270
|
+
var ModalBody = ({ children }) => /* @__PURE__ */ jsx6("div", { className: "space-y-4 px-5 py-4 text-[13.5px]", children });
|
|
271
|
+
var ModalFooter = ({ children }) => /* @__PURE__ */ jsx6("div", { className: "flex justify-end gap-2 border-t border-line px-5 py-3", children });
|
|
140
272
|
function ConfirmModal({
|
|
141
273
|
children,
|
|
142
274
|
confirmLabel = "Yes",
|
|
@@ -145,57 +277,91 @@ function ConfirmModal({
|
|
|
145
277
|
onConfirm,
|
|
146
278
|
onCancel
|
|
147
279
|
}) {
|
|
148
|
-
return /* @__PURE__ */
|
|
149
|
-
/* @__PURE__ */
|
|
150
|
-
/* @__PURE__ */
|
|
151
|
-
/* @__PURE__ */
|
|
152
|
-
/* @__PURE__ */
|
|
280
|
+
return /* @__PURE__ */ jsxs4(Modal, { onClose: onCancel, width: "w-[420px]", children: [
|
|
281
|
+
/* @__PURE__ */ jsx6("div", { className: "px-6 pt-7 pb-6 text-sm", children }),
|
|
282
|
+
/* @__PURE__ */ jsxs4(ModalFooter, { children: [
|
|
283
|
+
/* @__PURE__ */ jsx6(Button, { variant: "secondary", onClick: onCancel, children: cancelLabel }),
|
|
284
|
+
/* @__PURE__ */ jsx6(Button, { variant: danger ? "danger" : "primary", onClick: onConfirm, children: confirmLabel })
|
|
153
285
|
] })
|
|
154
286
|
] });
|
|
155
287
|
}
|
|
156
288
|
|
|
157
|
-
// src/react/ui/IconButton.tsx
|
|
158
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
159
|
-
function IconButton({
|
|
160
|
-
title,
|
|
161
|
-
icon: Icon,
|
|
162
|
-
danger,
|
|
163
|
-
iconSize = 18,
|
|
164
|
-
light,
|
|
165
|
-
className = "",
|
|
166
|
-
...rest
|
|
167
|
-
}) {
|
|
168
|
-
const tone = light ? "text-white opacity-90 hover:bg-white/15 hover:opacity-100" : `text-muted hover:bg-line-soft ${danger ? "hover:text-danger" : "hover:text-primary"}`;
|
|
169
|
-
return /* @__PURE__ */ jsx3(
|
|
170
|
-
"button",
|
|
171
|
-
{
|
|
172
|
-
type: "button",
|
|
173
|
-
title,
|
|
174
|
-
"aria-label": title,
|
|
175
|
-
className: `inline-flex cursor-pointer items-center justify-center rounded p-1.5 transition disabled:cursor-not-allowed disabled:opacity-40 ${tone} ${className}`,
|
|
176
|
-
...rest,
|
|
177
|
-
children: /* @__PURE__ */ jsx3(Icon, { size: iconSize })
|
|
178
|
-
}
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
289
|
// src/react/ui/Panel.tsx
|
|
183
|
-
import { jsx as
|
|
184
|
-
var PanelHeading = ({ children, actions }) => /* @__PURE__ */
|
|
290
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
291
|
+
var PanelHeading = ({ children, actions }) => /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between bg-heading px-3.5 py-[11px] text-[13px] font-bold tracking-[0.02em] text-white", children: [
|
|
185
292
|
children,
|
|
186
|
-
actions && /* @__PURE__ */
|
|
293
|
+
actions && /* @__PURE__ */ jsx7("span", { className: "flex items-center gap-1 tracking-normal", children: actions })
|
|
187
294
|
] });
|
|
188
|
-
var EmptyText = ({ children, className = "" }) => /* @__PURE__ */
|
|
295
|
+
var EmptyText = ({ children, className = "" }) => /* @__PURE__ */ jsx7("div", { className: `m-0 px-3.5 py-3 text-[13px] text-muted ${className}`, children });
|
|
296
|
+
|
|
297
|
+
// src/react/ui/Popover.tsx
|
|
298
|
+
import { useRef, useState } from "react";
|
|
299
|
+
|
|
300
|
+
// src/react/ui/useOutsideClose.ts
|
|
301
|
+
import { useEffect } from "react";
|
|
302
|
+
function useOutsideClose(ref, open, onClose) {
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
if (!open) return;
|
|
305
|
+
const onClick = (e) => ref.current && e.composedPath().includes(ref.current) || onClose();
|
|
306
|
+
const onKey = (e) => e.key === "Escape" && onClose();
|
|
307
|
+
document.addEventListener("mousedown", onClick);
|
|
308
|
+
document.addEventListener("keydown", onKey);
|
|
309
|
+
return () => {
|
|
310
|
+
document.removeEventListener("mousedown", onClick);
|
|
311
|
+
document.removeEventListener("keydown", onKey);
|
|
312
|
+
};
|
|
313
|
+
}, [ref, open, onClose]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/react/ui/Popover.tsx
|
|
317
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
318
|
+
function Popover({
|
|
319
|
+
label,
|
|
320
|
+
icon: Icon,
|
|
321
|
+
badge,
|
|
322
|
+
align = "left",
|
|
323
|
+
children
|
|
324
|
+
}) {
|
|
325
|
+
const [open, setOpen] = useState(false);
|
|
326
|
+
const box2 = useRef(null);
|
|
327
|
+
useOutsideClose(box2, open, () => setOpen(false));
|
|
328
|
+
return /* @__PURE__ */ jsxs6("div", { ref: box2, className: "relative", children: [
|
|
329
|
+
/* @__PURE__ */ jsxs6(
|
|
330
|
+
"button",
|
|
331
|
+
{
|
|
332
|
+
type: "button",
|
|
333
|
+
"aria-expanded": open,
|
|
334
|
+
"aria-haspopup": "dialog",
|
|
335
|
+
onClick: () => setOpen((o) => !o),
|
|
336
|
+
className: `inline-flex cursor-pointer items-center gap-1.5 rounded-md border px-3 py-1.5 text-[13px] font-medium ${open || badge ? "border-primary bg-primary-soft text-primary" : "border-line-strong bg-white text-ink-2 hover:border-faint"}`,
|
|
337
|
+
children: [
|
|
338
|
+
Icon && /* @__PURE__ */ jsx8(Icon, { size: 15 }),
|
|
339
|
+
label,
|
|
340
|
+
!!badge && /* @__PURE__ */ jsx8("span", { className: "inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[11px] font-bold text-white", children: badge })
|
|
341
|
+
]
|
|
342
|
+
}
|
|
343
|
+
),
|
|
344
|
+
open && /* @__PURE__ */ jsx8(
|
|
345
|
+
"div",
|
|
346
|
+
{
|
|
347
|
+
role: "dialog",
|
|
348
|
+
"aria-label": label,
|
|
349
|
+
className: `absolute z-30 mt-1.5 w-[min(560px,calc(100vw-24px))] max-sm:fixed max-sm:inset-x-3 max-sm:max-h-[70dvh] max-sm:w-auto max-sm:overflow-y-auto rounded-lg border border-line bg-white p-4 shadow-xl ${align === "right" ? "right-0" : "left-0"}`,
|
|
350
|
+
children
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
] });
|
|
354
|
+
}
|
|
189
355
|
|
|
190
356
|
// src/react/ui/SegmentedTabs.tsx
|
|
191
|
-
import { jsx as
|
|
357
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
192
358
|
function SegmentedTabs({
|
|
193
359
|
options,
|
|
194
360
|
value,
|
|
195
361
|
onChange,
|
|
196
362
|
size = "md"
|
|
197
363
|
}) {
|
|
198
|
-
return /* @__PURE__ */
|
|
364
|
+
return /* @__PURE__ */ jsx9("div", { role: "tablist", className: "inline-flex rounded-full border border-brand p-0.5", children: options.map((o) => /* @__PURE__ */ jsx9(
|
|
199
365
|
"button",
|
|
200
366
|
{
|
|
201
367
|
type: "button",
|
|
@@ -209,59 +375,163 @@ function SegmentedTabs({
|
|
|
209
375
|
)) });
|
|
210
376
|
}
|
|
211
377
|
|
|
212
|
-
// src/react/ui/
|
|
213
|
-
import {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return /* @__PURE__ */ jsx6(
|
|
220
|
-
"input",
|
|
221
|
-
{
|
|
222
|
-
type: "checkbox",
|
|
223
|
-
"aria-label": label,
|
|
224
|
-
className: `h-[18px] w-[18px] flex-none cursor-pointer accent-primary ${className}`,
|
|
225
|
-
...rest
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// src/react/ui/FormField.tsx
|
|
231
|
-
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
232
|
-
var inputClass = "w-full rounded-md border border-line-strong bg-white px-3 py-2 text-[13.5px] outline-none focus:border-primary";
|
|
233
|
-
function FormField({
|
|
234
|
-
label,
|
|
235
|
-
required,
|
|
236
|
-
error,
|
|
237
|
-
children
|
|
378
|
+
// src/react/ui/SignaturePad.tsx
|
|
379
|
+
import { useRef as useRef2, useState as useState2 } from "react";
|
|
380
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
381
|
+
function SignaturePad({
|
|
382
|
+
onSave,
|
|
383
|
+
onCancel,
|
|
384
|
+
maxWidth = 600
|
|
238
385
|
}) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
386
|
+
const canvas = useRef2(null);
|
|
387
|
+
const last = useRef2(null);
|
|
388
|
+
const [empty, setEmpty] = useState2(true);
|
|
389
|
+
const point = (e) => {
|
|
390
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
391
|
+
return {
|
|
392
|
+
x: (e.clientX - r.left) / r.width * e.currentTarget.width,
|
|
393
|
+
y: (e.clientY - r.top) / r.height * e.currentTarget.height
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
const draw = (e) => {
|
|
397
|
+
const ctx = canvas.current?.getContext("2d");
|
|
398
|
+
if (!ctx || !last.current) return;
|
|
399
|
+
const p = point(e);
|
|
400
|
+
ctx.lineWidth = 3;
|
|
401
|
+
ctx.lineCap = "round";
|
|
402
|
+
ctx.strokeStyle = "#111827";
|
|
403
|
+
ctx.beginPath();
|
|
404
|
+
ctx.moveTo(last.current.x, last.current.y);
|
|
405
|
+
ctx.lineTo(p.x, p.y);
|
|
406
|
+
ctx.stroke();
|
|
407
|
+
last.current = p;
|
|
408
|
+
setEmpty(false);
|
|
409
|
+
};
|
|
410
|
+
const clear = () => {
|
|
411
|
+
const c = canvas.current;
|
|
412
|
+
c?.getContext("2d")?.clearRect(0, 0, c.width, c.height);
|
|
413
|
+
setEmpty(true);
|
|
414
|
+
};
|
|
415
|
+
const save = () => {
|
|
416
|
+
const c = canvas.current;
|
|
417
|
+
if (!c) return;
|
|
418
|
+
const scale = Math.min(1, maxWidth / c.width);
|
|
419
|
+
const out = document.createElement("canvas");
|
|
420
|
+
out.width = Math.round(c.width * scale);
|
|
421
|
+
out.height = Math.round(c.height * scale);
|
|
422
|
+
out.getContext("2d")?.drawImage(c, 0, 0, out.width, out.height);
|
|
423
|
+
onSave(out.toDataURL("image/png"));
|
|
424
|
+
};
|
|
425
|
+
return /* @__PURE__ */ jsxs7("div", { className: "space-y-3", children: [
|
|
426
|
+
/* @__PURE__ */ jsx10(
|
|
427
|
+
"canvas",
|
|
428
|
+
{
|
|
429
|
+
ref: canvas,
|
|
430
|
+
width: 800,
|
|
431
|
+
height: 260,
|
|
432
|
+
"aria-label": "Signature drawing area",
|
|
433
|
+
className: "h-[200px] w-full touch-none rounded-md border border-dashed border-line-strong bg-surface",
|
|
434
|
+
onPointerDown: (e) => {
|
|
435
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
436
|
+
last.current = point(e);
|
|
437
|
+
},
|
|
438
|
+
onPointerMove: draw,
|
|
439
|
+
onPointerUp: () => last.current = null
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex justify-end gap-2", children: [
|
|
443
|
+
/* @__PURE__ */ jsx10(Button, { variant: "secondary", onClick: clear, children: "Clear" }),
|
|
444
|
+
/* @__PURE__ */ jsx10(Button, { variant: "secondary", onClick: onCancel, children: "Cancel" }),
|
|
445
|
+
/* @__PURE__ */ jsx10(Button, { disabled: empty, onClick: save, children: "Use signature" })
|
|
446
|
+
] })
|
|
247
447
|
] });
|
|
248
448
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
449
|
+
|
|
450
|
+
// src/react/ui/Tag.tsx
|
|
451
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
452
|
+
var tones = {
|
|
453
|
+
success: "bg-success/15 text-success",
|
|
454
|
+
info: "bg-heading/20 text-primary",
|
|
455
|
+
muted: "bg-hover text-muted"
|
|
456
|
+
};
|
|
457
|
+
var Tag = ({ tone = "info", children }) => /* @__PURE__ */ jsx11(
|
|
458
|
+
"span",
|
|
252
459
|
{
|
|
253
|
-
className: `
|
|
254
|
-
|
|
460
|
+
className: `inline-block rounded-full px-2.5 py-0.5 text-[11px] font-semibold whitespace-nowrap ${tones[tone]}`,
|
|
461
|
+
children
|
|
255
462
|
}
|
|
256
463
|
);
|
|
257
|
-
|
|
464
|
+
|
|
465
|
+
// src/react/ui/slots.tsx
|
|
466
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
467
|
+
var base = {
|
|
468
|
+
Button,
|
|
469
|
+
IconButton,
|
|
470
|
+
Checkbox,
|
|
471
|
+
FormField,
|
|
472
|
+
TextInput,
|
|
473
|
+
Select,
|
|
474
|
+
TextArea,
|
|
475
|
+
Modal,
|
|
476
|
+
ModalHeader,
|
|
477
|
+
ModalBody,
|
|
478
|
+
ModalFooter,
|
|
479
|
+
ConfirmModal,
|
|
480
|
+
PanelHeading,
|
|
481
|
+
EmptyText,
|
|
482
|
+
SegmentedTabs,
|
|
483
|
+
Chip,
|
|
484
|
+
ChipGroup,
|
|
485
|
+
RemovableChip,
|
|
486
|
+
Popover,
|
|
487
|
+
Tag,
|
|
488
|
+
SignaturePad
|
|
489
|
+
};
|
|
490
|
+
var defaultComponents = base;
|
|
491
|
+
var UIContext = createContext({});
|
|
492
|
+
function FormliftProvider({
|
|
493
|
+
components,
|
|
494
|
+
children
|
|
495
|
+
}) {
|
|
496
|
+
const outer = useContext(UIContext);
|
|
497
|
+
return /* @__PURE__ */ jsx12(UIContext.Provider, { value: components ? { ...outer, ...components } : outer, children });
|
|
498
|
+
}
|
|
499
|
+
function slot(name) {
|
|
500
|
+
const Slot = (props) => {
|
|
501
|
+
const C = useContext(UIContext)[name] ?? base[name];
|
|
502
|
+
return /* @__PURE__ */ jsx12(C, { ...props });
|
|
503
|
+
};
|
|
504
|
+
Slot.displayName = `formlift.${name}`;
|
|
505
|
+
return Slot;
|
|
506
|
+
}
|
|
507
|
+
var Button2 = slot("Button");
|
|
508
|
+
var IconButton2 = slot("IconButton");
|
|
509
|
+
var Checkbox2 = slot("Checkbox");
|
|
510
|
+
var FormField2 = slot("FormField");
|
|
511
|
+
var TextInput2 = slot("TextInput");
|
|
512
|
+
var Select2 = slot("Select");
|
|
513
|
+
var TextArea2 = slot("TextArea");
|
|
514
|
+
var Modal2 = slot("Modal");
|
|
515
|
+
var ModalHeader2 = slot("ModalHeader");
|
|
516
|
+
var ModalBody2 = slot("ModalBody");
|
|
517
|
+
var ModalFooter2 = slot("ModalFooter");
|
|
518
|
+
var ConfirmModal2 = slot("ConfirmModal");
|
|
519
|
+
var PanelHeading2 = slot("PanelHeading");
|
|
520
|
+
var EmptyText2 = slot("EmptyText");
|
|
521
|
+
var SegmentedTabs2 = slot("SegmentedTabs");
|
|
522
|
+
var Chip2 = slot("Chip");
|
|
523
|
+
var ChipGroup2 = slot("ChipGroup");
|
|
524
|
+
var RemovableChip2 = slot("RemovableChip");
|
|
525
|
+
var Popover2 = slot("Popover");
|
|
526
|
+
var Tag2 = slot("Tag");
|
|
527
|
+
var SignaturePad2 = slot("SignaturePad");
|
|
258
528
|
|
|
259
529
|
// src/react/components/ElementProperties.tsx
|
|
260
|
-
import { Fragment, jsx as
|
|
530
|
+
import { Fragment, jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
261
531
|
var num = (s) => s.trim() === "" || !Number.isFinite(Number(s)) ? void 0 : Number(s);
|
|
262
532
|
var pct = (n) => Math.round(n * 1e3) / 10;
|
|
263
|
-
var Section = ({ title, children }) => /* @__PURE__ */
|
|
264
|
-
/* @__PURE__ */
|
|
533
|
+
var Section = ({ title, children }) => /* @__PURE__ */ jsxs8("section", { className: "space-y-3 border-b border-line px-3.5 py-3", children: [
|
|
534
|
+
/* @__PURE__ */ jsx13("h4", { className: "m-0 text-[12px] font-bold tracking-wide text-muted uppercase", children: title }),
|
|
265
535
|
children
|
|
266
536
|
] });
|
|
267
537
|
function ColorField({
|
|
@@ -270,8 +540,8 @@ function ColorField({
|
|
|
270
540
|
onChange,
|
|
271
541
|
allowNone
|
|
272
542
|
}) {
|
|
273
|
-
return /* @__PURE__ */
|
|
274
|
-
/* @__PURE__ */
|
|
543
|
+
return /* @__PURE__ */ jsx13(FormField2, { label, children: /* @__PURE__ */ jsxs8("span", { className: "flex items-center gap-2", children: [
|
|
544
|
+
/* @__PURE__ */ jsx13(
|
|
275
545
|
"input",
|
|
276
546
|
{
|
|
277
547
|
type: "color",
|
|
@@ -281,9 +551,9 @@ function ColorField({
|
|
|
281
551
|
onChange: (e) => onChange(e.target.value)
|
|
282
552
|
}
|
|
283
553
|
),
|
|
284
|
-
allowNone && /* @__PURE__ */
|
|
285
|
-
/* @__PURE__ */
|
|
286
|
-
|
|
554
|
+
allowNone && /* @__PURE__ */ jsxs8("label", { className: "inline-flex items-center gap-1.5 text-[12.5px]", children: [
|
|
555
|
+
/* @__PURE__ */ jsx13(
|
|
556
|
+
Checkbox2,
|
|
287
557
|
{
|
|
288
558
|
label: `No ${label.toLowerCase()}`,
|
|
289
559
|
checked: !value,
|
|
@@ -312,15 +582,15 @@ function ElementProperties({
|
|
|
312
582
|
set({ position: fitted });
|
|
313
583
|
}
|
|
314
584
|
};
|
|
315
|
-
return /* @__PURE__ */
|
|
316
|
-
e.type === "text" && /* @__PURE__ */
|
|
317
|
-
e.replaces != null && /* @__PURE__ */
|
|
585
|
+
return /* @__PURE__ */ jsxs8("div", { className: "text-[13px]", children: [
|
|
586
|
+
e.type === "text" && /* @__PURE__ */ jsxs8(Section, { title: "Text", children: [
|
|
587
|
+
e.replaces != null && /* @__PURE__ */ jsxs8("p", { className: "m-0 rounded bg-primary-soft px-2 py-1 text-[12px] text-ink-2", children: [
|
|
318
588
|
"Replaces the original: \u201C",
|
|
319
589
|
e.replaces,
|
|
320
590
|
"\u201D"
|
|
321
591
|
] }),
|
|
322
|
-
/* @__PURE__ */
|
|
323
|
-
|
|
592
|
+
/* @__PURE__ */ jsx13(
|
|
593
|
+
TextArea2,
|
|
324
594
|
{
|
|
325
595
|
rows: 4,
|
|
326
596
|
"aria-label": "Text",
|
|
@@ -330,18 +600,18 @@ function ElementProperties({
|
|
|
330
600
|
onChange: (ev) => set({ text: ev.target.value })
|
|
331
601
|
}
|
|
332
602
|
),
|
|
333
|
-
/* @__PURE__ */
|
|
334
|
-
/* @__PURE__ */
|
|
335
|
-
|
|
603
|
+
/* @__PURE__ */ jsxs8("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
604
|
+
/* @__PURE__ */ jsx13(FormField2, { label: "Font", children: /* @__PURE__ */ jsx13(
|
|
605
|
+
Select2,
|
|
336
606
|
{
|
|
337
607
|
className: "w-full",
|
|
338
608
|
value: e.font ?? "sans",
|
|
339
609
|
onChange: (ev) => set({ font: ev.target.value }),
|
|
340
|
-
children: FONTS.map((f) => /* @__PURE__ */
|
|
610
|
+
children: FONTS.map((f) => /* @__PURE__ */ jsx13("option", { value: f, children: { sans: "Sans (Helvetica)", serif: "Serif (Times)", mono: "Mono (Courier)" }[f] }, f))
|
|
341
611
|
}
|
|
342
612
|
) }),
|
|
343
|
-
/* @__PURE__ */
|
|
344
|
-
|
|
613
|
+
/* @__PURE__ */ jsx13(FormField2, { label: "Size (pt)", children: /* @__PURE__ */ jsx13(
|
|
614
|
+
TextInput2,
|
|
345
615
|
{
|
|
346
616
|
type: "number",
|
|
347
617
|
min: 3,
|
|
@@ -355,31 +625,31 @@ function ElementProperties({
|
|
|
355
625
|
}
|
|
356
626
|
) })
|
|
357
627
|
] }),
|
|
358
|
-
/* @__PURE__ */
|
|
359
|
-
["bold", "italic"].map((k) => /* @__PURE__ */
|
|
360
|
-
/* @__PURE__ */
|
|
628
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-4", children: [
|
|
629
|
+
["bold", "italic"].map((k) => /* @__PURE__ */ jsxs8("label", { className: "inline-flex items-center gap-1.5", children: [
|
|
630
|
+
/* @__PURE__ */ jsx13(Checkbox2, { label: k, checked: !!e[k], onChange: (ev) => set({ [k]: ev.target.checked || void 0 }) }),
|
|
361
631
|
k === "bold" ? "Bold" : "Italic"
|
|
362
632
|
] }, k)),
|
|
363
|
-
/* @__PURE__ */
|
|
364
|
-
|
|
633
|
+
/* @__PURE__ */ jsxs8(
|
|
634
|
+
Select2,
|
|
365
635
|
{
|
|
366
636
|
"aria-label": "Alignment",
|
|
367
637
|
value: e.align ?? "left",
|
|
368
638
|
onChange: (ev) => set({ align: ev.target.value }),
|
|
369
639
|
children: [
|
|
370
|
-
/* @__PURE__ */
|
|
371
|
-
/* @__PURE__ */
|
|
372
|
-
/* @__PURE__ */
|
|
640
|
+
/* @__PURE__ */ jsx13("option", { value: "left", children: "Left" }),
|
|
641
|
+
/* @__PURE__ */ jsx13("option", { value: "center", children: "Centre" }),
|
|
642
|
+
/* @__PURE__ */ jsx13("option", { value: "right", children: "Right" })
|
|
373
643
|
]
|
|
374
644
|
}
|
|
375
645
|
)
|
|
376
646
|
] }),
|
|
377
|
-
/* @__PURE__ */
|
|
647
|
+
/* @__PURE__ */ jsx13(ColorField, { label: "Text colour", value: e.color ?? "#000000", onChange: (v) => set({ color: v }) })
|
|
378
648
|
] }),
|
|
379
|
-
/* @__PURE__ */
|
|
380
|
-
e.type !== "line" && /* @__PURE__ */
|
|
381
|
-
e.type !== "text" && /* @__PURE__ */
|
|
382
|
-
/* @__PURE__ */
|
|
649
|
+
/* @__PURE__ */ jsxs8(Section, { title: "Colours", children: [
|
|
650
|
+
e.type !== "line" && /* @__PURE__ */ jsx13(ColorField, { label: "Background", allowNone: true, value: e.fill, onChange: (v) => set({ fill: v }) }),
|
|
651
|
+
e.type !== "text" && /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
652
|
+
/* @__PURE__ */ jsx13(
|
|
383
653
|
ColorField,
|
|
384
654
|
{
|
|
385
655
|
label: e.type === "line" ? "Line colour" : "Border",
|
|
@@ -388,8 +658,8 @@ function ElementProperties({
|
|
|
388
658
|
onChange: (v) => set({ stroke: v })
|
|
389
659
|
}
|
|
390
660
|
),
|
|
391
|
-
/* @__PURE__ */
|
|
392
|
-
|
|
661
|
+
/* @__PURE__ */ jsx13(FormField2, { label: "Line width (pt)", children: /* @__PURE__ */ jsx13(
|
|
662
|
+
TextInput2,
|
|
393
663
|
{
|
|
394
664
|
type: "number",
|
|
395
665
|
min: 0,
|
|
@@ -403,11 +673,11 @@ function ElementProperties({
|
|
|
403
673
|
}
|
|
404
674
|
) })
|
|
405
675
|
] }),
|
|
406
|
-
e.fill === "#ffffff" && /* @__PURE__ */
|
|
676
|
+
e.fill === "#ffffff" && /* @__PURE__ */ jsx13("p", { className: "m-0 text-[12px] text-muted", children: "A white background hides the original under it." })
|
|
407
677
|
] }),
|
|
408
|
-
/* @__PURE__ */
|
|
409
|
-
/* @__PURE__ */
|
|
410
|
-
|
|
678
|
+
/* @__PURE__ */ jsx13(Section, { title: "Position (% of page)", children: /* @__PURE__ */ jsxs8("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
679
|
+
/* @__PURE__ */ jsx13(FormField2, { label: "Page", children: /* @__PURE__ */ jsx13(
|
|
680
|
+
TextInput2,
|
|
411
681
|
{
|
|
412
682
|
type: "number",
|
|
413
683
|
min: 1,
|
|
@@ -419,9 +689,9 @@ function ElementProperties({
|
|
|
419
689
|
}
|
|
420
690
|
}
|
|
421
691
|
) }),
|
|
422
|
-
/* @__PURE__ */
|
|
423
|
-
["x", "y", "width", "height"].map((k) => /* @__PURE__ */
|
|
424
|
-
|
|
692
|
+
/* @__PURE__ */ jsx13("span", {}),
|
|
693
|
+
["x", "y", "width", "height"].map((k) => /* @__PURE__ */ jsx13(FormField2, { label: { x: "Left", y: "Top", width: "Width", height: "Height" }[k], children: /* @__PURE__ */ jsx13(
|
|
694
|
+
TextInput2,
|
|
425
695
|
{
|
|
426
696
|
type: "number",
|
|
427
697
|
step: 0.1,
|
|
@@ -435,82 +705,8 @@ function ElementProperties({
|
|
|
435
705
|
|
|
436
706
|
// src/react/components/FieldControl.tsx
|
|
437
707
|
import { Paperclip, PenLine } from "lucide-react";
|
|
438
|
-
import { useRef as
|
|
439
|
-
|
|
440
|
-
// src/react/ui/SignaturePad.tsx
|
|
441
|
-
import { useRef, useState } from "react";
|
|
442
|
-
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
443
|
-
function SignaturePad({
|
|
444
|
-
onSave,
|
|
445
|
-
onCancel,
|
|
446
|
-
maxWidth = 600
|
|
447
|
-
}) {
|
|
448
|
-
const canvas = useRef(null);
|
|
449
|
-
const last = useRef(null);
|
|
450
|
-
const [empty, setEmpty] = useState(true);
|
|
451
|
-
const point = (e) => {
|
|
452
|
-
const r = e.currentTarget.getBoundingClientRect();
|
|
453
|
-
return {
|
|
454
|
-
x: (e.clientX - r.left) / r.width * e.currentTarget.width,
|
|
455
|
-
y: (e.clientY - r.top) / r.height * e.currentTarget.height
|
|
456
|
-
};
|
|
457
|
-
};
|
|
458
|
-
const draw = (e) => {
|
|
459
|
-
const ctx = canvas.current?.getContext("2d");
|
|
460
|
-
if (!ctx || !last.current) return;
|
|
461
|
-
const p = point(e);
|
|
462
|
-
ctx.lineWidth = 3;
|
|
463
|
-
ctx.lineCap = "round";
|
|
464
|
-
ctx.strokeStyle = "#111827";
|
|
465
|
-
ctx.beginPath();
|
|
466
|
-
ctx.moveTo(last.current.x, last.current.y);
|
|
467
|
-
ctx.lineTo(p.x, p.y);
|
|
468
|
-
ctx.stroke();
|
|
469
|
-
last.current = p;
|
|
470
|
-
setEmpty(false);
|
|
471
|
-
};
|
|
472
|
-
const clear = () => {
|
|
473
|
-
const c = canvas.current;
|
|
474
|
-
c?.getContext("2d")?.clearRect(0, 0, c.width, c.height);
|
|
475
|
-
setEmpty(true);
|
|
476
|
-
};
|
|
477
|
-
const save = () => {
|
|
478
|
-
const c = canvas.current;
|
|
479
|
-
if (!c) return;
|
|
480
|
-
const scale = Math.min(1, maxWidth / c.width);
|
|
481
|
-
const out = document.createElement("canvas");
|
|
482
|
-
out.width = Math.round(c.width * scale);
|
|
483
|
-
out.height = Math.round(c.height * scale);
|
|
484
|
-
out.getContext("2d")?.drawImage(c, 0, 0, out.width, out.height);
|
|
485
|
-
onSave(out.toDataURL("image/png"));
|
|
486
|
-
};
|
|
487
|
-
return /* @__PURE__ */ jsxs6("div", { className: "space-y-3", children: [
|
|
488
|
-
/* @__PURE__ */ jsx9(
|
|
489
|
-
"canvas",
|
|
490
|
-
{
|
|
491
|
-
ref: canvas,
|
|
492
|
-
width: 800,
|
|
493
|
-
height: 260,
|
|
494
|
-
"aria-label": "Signature drawing area",
|
|
495
|
-
className: "h-[200px] w-full touch-none rounded-md border border-dashed border-line-strong bg-surface",
|
|
496
|
-
onPointerDown: (e) => {
|
|
497
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
498
|
-
last.current = point(e);
|
|
499
|
-
},
|
|
500
|
-
onPointerMove: draw,
|
|
501
|
-
onPointerUp: () => last.current = null
|
|
502
|
-
}
|
|
503
|
-
),
|
|
504
|
-
/* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-2", children: [
|
|
505
|
-
/* @__PURE__ */ jsx9(Button, { variant: "secondary", onClick: clear, children: "Clear" }),
|
|
506
|
-
/* @__PURE__ */ jsx9(Button, { variant: "secondary", onClick: onCancel, children: "Cancel" }),
|
|
507
|
-
/* @__PURE__ */ jsx9(Button, { disabled: empty, onClick: save, children: "Use signature" })
|
|
508
|
-
] })
|
|
509
|
-
] });
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// src/react/components/FieldControl.tsx
|
|
513
|
-
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
708
|
+
import { useRef as useRef3, useState as useState3 } from "react";
|
|
709
|
+
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
514
710
|
var box = "h-full w-full rounded-sm border bg-primary-soft/70 px-1 text-[clamp(8px,62cqh,15px)] text-navy outline-none focus:border-primary focus:bg-white disabled:cursor-not-allowed";
|
|
515
711
|
function pngFile(dataUrl, name) {
|
|
516
712
|
const bin = atob(dataUrl.slice(dataUrl.indexOf(",") + 1));
|
|
@@ -527,12 +723,12 @@ function SheetInput({
|
|
|
527
723
|
onDone
|
|
528
724
|
}) {
|
|
529
725
|
if (field.type === "radio")
|
|
530
|
-
return /* @__PURE__ */
|
|
726
|
+
return /* @__PURE__ */ jsx14("div", { role: "radiogroup", "aria-label": field.label || field.key, className: "flex flex-col gap-1", children: (field.options ?? []).map((o) => /* @__PURE__ */ jsxs9(
|
|
531
727
|
"label",
|
|
532
728
|
{
|
|
533
729
|
className: "flex min-h-11 cursor-pointer items-center gap-3 rounded-md border border-line px-3 text-base",
|
|
534
730
|
children: [
|
|
535
|
-
/* @__PURE__ */
|
|
731
|
+
/* @__PURE__ */ jsx14(
|
|
536
732
|
"input",
|
|
537
733
|
{
|
|
538
734
|
type: "radio",
|
|
@@ -551,7 +747,7 @@ function SheetInput({
|
|
|
551
747
|
o
|
|
552
748
|
)) });
|
|
553
749
|
if (field.type === "textarea")
|
|
554
|
-
return /* @__PURE__ */
|
|
750
|
+
return /* @__PURE__ */ jsx14(
|
|
555
751
|
"textarea",
|
|
556
752
|
{
|
|
557
753
|
autoFocus: true,
|
|
@@ -563,7 +759,7 @@ function SheetInput({
|
|
|
563
759
|
onChange: (e) => onChange(e.target.value)
|
|
564
760
|
}
|
|
565
761
|
);
|
|
566
|
-
return /* @__PURE__ */
|
|
762
|
+
return /* @__PURE__ */ jsx14(
|
|
567
763
|
"input",
|
|
568
764
|
{
|
|
569
765
|
autoFocus: true,
|
|
@@ -592,9 +788,9 @@ function FieldControl({
|
|
|
592
788
|
signatureSrc,
|
|
593
789
|
compact
|
|
594
790
|
}) {
|
|
595
|
-
const [signing, setSigning] =
|
|
596
|
-
const [editing, setEditing] =
|
|
597
|
-
const fileInput =
|
|
791
|
+
const [signing, setSigning] = useState3(false);
|
|
792
|
+
const [editing, setEditing] = useState3(false);
|
|
793
|
+
const fileInput = useRef3(null);
|
|
598
794
|
const border = error ? "border-danger" : "border-primary/40";
|
|
599
795
|
const common = {
|
|
600
796
|
"aria-label": field.label || field.key,
|
|
@@ -605,31 +801,31 @@ function FieldControl({
|
|
|
605
801
|
const text = typeof value === "string" ? value : "";
|
|
606
802
|
if (compact && !readOnly && SHEET_TYPES.includes(field.type)) {
|
|
607
803
|
const name = field.label || field.key;
|
|
608
|
-
return /* @__PURE__ */
|
|
609
|
-
/* @__PURE__ */
|
|
804
|
+
return /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
805
|
+
/* @__PURE__ */ jsx14(
|
|
610
806
|
"button",
|
|
611
807
|
{
|
|
612
808
|
type: "button",
|
|
613
809
|
...common,
|
|
614
810
|
onClick: () => setEditing(true),
|
|
615
811
|
className: `${box} ${border} flex cursor-pointer overflow-hidden text-left leading-tight ${field.type === "textarea" ? "items-start py-0.5 text-[clamp(8px,14cqh,15px)]" : "items-center"}`,
|
|
616
|
-
children: /* @__PURE__ */
|
|
812
|
+
children: /* @__PURE__ */ jsx14("span", { className: `min-w-0 ${field.type === "textarea" ? "line-clamp-[8] whitespace-pre-wrap" : "truncate"}`, children: text || /* @__PURE__ */ jsx14("span", { className: "text-faint", children: field.placeholder || "" }) })
|
|
617
813
|
}
|
|
618
814
|
),
|
|
619
|
-
editing && /* @__PURE__ */
|
|
620
|
-
/* @__PURE__ */
|
|
621
|
-
/* @__PURE__ */
|
|
622
|
-
/* @__PURE__ */
|
|
623
|
-
field.helpText && /* @__PURE__ */
|
|
624
|
-
error && /* @__PURE__ */
|
|
815
|
+
editing && /* @__PURE__ */ jsxs9(Modal2, { onClose: () => setEditing(false), width: "w-[480px]", children: [
|
|
816
|
+
/* @__PURE__ */ jsx14(ModalHeader2, { title: name, onClose: () => setEditing(false) }),
|
|
817
|
+
/* @__PURE__ */ jsxs9(ModalBody2, { children: [
|
|
818
|
+
/* @__PURE__ */ jsx14(SheetInput, { field, text, onChange, onDone: () => setEditing(false) }),
|
|
819
|
+
field.helpText && /* @__PURE__ */ jsx14("p", { className: "mt-2 mb-0 text-[13px] text-muted", children: field.helpText }),
|
|
820
|
+
error && /* @__PURE__ */ jsx14("p", { className: "mt-2 mb-0 text-[13px] text-danger", children: error })
|
|
625
821
|
] }),
|
|
626
|
-
/* @__PURE__ */
|
|
822
|
+
/* @__PURE__ */ jsx14(ModalFooter2, { children: /* @__PURE__ */ jsx14(Button2, { onClick: () => setEditing(false), children: "Done" }) })
|
|
627
823
|
] })
|
|
628
824
|
] });
|
|
629
825
|
}
|
|
630
826
|
switch (field.type) {
|
|
631
827
|
case "textarea":
|
|
632
|
-
return /* @__PURE__ */
|
|
828
|
+
return /* @__PURE__ */ jsx14(
|
|
633
829
|
"textarea",
|
|
634
830
|
{
|
|
635
831
|
...common,
|
|
@@ -641,19 +837,19 @@ function FieldControl({
|
|
|
641
837
|
}
|
|
642
838
|
);
|
|
643
839
|
case "dropdown":
|
|
644
|
-
return /* @__PURE__ */
|
|
645
|
-
/* @__PURE__ */
|
|
646
|
-
(field.options ?? []).map((o) => /* @__PURE__ */
|
|
840
|
+
return /* @__PURE__ */ jsxs9("select", { ...common, className: `${box} ${border}`, value: text, onChange: (e) => onChange(e.target.value), children: [
|
|
841
|
+
/* @__PURE__ */ jsx14("option", { value: "", children: field.placeholder || "" }),
|
|
842
|
+
(field.options ?? []).map((o) => /* @__PURE__ */ jsx14("option", { children: o }, o))
|
|
647
843
|
] });
|
|
648
844
|
case "radio":
|
|
649
|
-
return /* @__PURE__ */
|
|
845
|
+
return /* @__PURE__ */ jsx14(
|
|
650
846
|
"div",
|
|
651
847
|
{
|
|
652
848
|
role: "radiogroup",
|
|
653
849
|
...common,
|
|
654
850
|
className: `flex h-full w-full flex-wrap items-center gap-x-3 rounded-sm border px-1 text-[clamp(8px,55cqh,14px)] ${border} bg-primary-soft/50`,
|
|
655
|
-
children: (field.options ?? []).map((o) => /* @__PURE__ */
|
|
656
|
-
/* @__PURE__ */
|
|
851
|
+
children: (field.options ?? []).map((o) => /* @__PURE__ */ jsxs9("label", { className: "inline-flex cursor-pointer items-center gap-1", children: [
|
|
852
|
+
/* @__PURE__ */ jsx14(
|
|
657
853
|
"input",
|
|
658
854
|
{
|
|
659
855
|
type: "radio",
|
|
@@ -669,11 +865,11 @@ function FieldControl({
|
|
|
669
865
|
}
|
|
670
866
|
);
|
|
671
867
|
case "checkbox":
|
|
672
|
-
return /* @__PURE__ */
|
|
868
|
+
return /* @__PURE__ */ jsx14(
|
|
673
869
|
"label",
|
|
674
870
|
{
|
|
675
871
|
className: `flex h-full w-full items-center justify-center rounded-sm border ${border} bg-primary-soft/50`,
|
|
676
|
-
children: /* @__PURE__ */
|
|
872
|
+
children: /* @__PURE__ */ jsx14(
|
|
677
873
|
"input",
|
|
678
874
|
{
|
|
679
875
|
type: "checkbox",
|
|
@@ -686,31 +882,31 @@ function FieldControl({
|
|
|
686
882
|
}
|
|
687
883
|
);
|
|
688
884
|
case "signature":
|
|
689
|
-
return /* @__PURE__ */
|
|
690
|
-
/* @__PURE__ */
|
|
885
|
+
return /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
886
|
+
/* @__PURE__ */ jsx14(
|
|
691
887
|
"button",
|
|
692
888
|
{
|
|
693
889
|
type: "button",
|
|
694
890
|
...common,
|
|
695
891
|
onClick: () => setSigning(true),
|
|
696
892
|
className: `flex h-full w-full cursor-pointer items-center justify-center rounded-sm border ${border} bg-primary-soft/50 disabled:cursor-default`,
|
|
697
|
-
children: text ? /* @__PURE__ */
|
|
893
|
+
children: text ? /* @__PURE__ */ jsx14(
|
|
698
894
|
"img",
|
|
699
895
|
{
|
|
700
896
|
src: text.startsWith("data:") ? text : signatureSrc,
|
|
701
897
|
alt: "Signature",
|
|
702
898
|
className: "max-h-full max-w-full object-contain"
|
|
703
899
|
}
|
|
704
|
-
) : /* @__PURE__ */
|
|
705
|
-
/* @__PURE__ */
|
|
900
|
+
) : /* @__PURE__ */ jsxs9("span", { className: "inline-flex items-center gap-1 text-[clamp(8px,45cqh,13px)] text-primary", children: [
|
|
901
|
+
/* @__PURE__ */ jsx14(PenLine, { size: 12 }),
|
|
706
902
|
" Sign"
|
|
707
903
|
] })
|
|
708
904
|
}
|
|
709
905
|
),
|
|
710
|
-
signing && /* @__PURE__ */
|
|
711
|
-
/* @__PURE__ */
|
|
712
|
-
/* @__PURE__ */
|
|
713
|
-
|
|
906
|
+
signing && /* @__PURE__ */ jsxs9(Modal2, { onClose: () => setSigning(false), width: "w-[640px]", children: [
|
|
907
|
+
/* @__PURE__ */ jsx14(ModalHeader2, { title: `Sign: ${field.label || field.key}`, onClose: () => setSigning(false) }),
|
|
908
|
+
/* @__PURE__ */ jsx14(ModalBody2, { children: /* @__PURE__ */ jsx14(
|
|
909
|
+
SignaturePad2,
|
|
714
910
|
{
|
|
715
911
|
onCancel: () => setSigning(false),
|
|
716
912
|
onSave: (url) => {
|
|
@@ -723,8 +919,8 @@ function FieldControl({
|
|
|
723
919
|
] })
|
|
724
920
|
] });
|
|
725
921
|
case "file":
|
|
726
|
-
return /* @__PURE__ */
|
|
727
|
-
/* @__PURE__ */
|
|
922
|
+
return /* @__PURE__ */ jsxs9("div", { className: `flex h-full w-full items-center gap-1 rounded-sm border px-1 ${border} bg-primary-soft/50`, children: [
|
|
923
|
+
/* @__PURE__ */ jsx14(
|
|
728
924
|
"input",
|
|
729
925
|
{
|
|
730
926
|
ref: fileInput,
|
|
@@ -737,7 +933,7 @@ function FieldControl({
|
|
|
737
933
|
}
|
|
738
934
|
}
|
|
739
935
|
),
|
|
740
|
-
value && /* @__PURE__ */
|
|
936
|
+
value && /* @__PURE__ */ jsx14(
|
|
741
937
|
"button",
|
|
742
938
|
{
|
|
743
939
|
type: "button",
|
|
@@ -746,7 +942,7 @@ function FieldControl({
|
|
|
746
942
|
children: fileName || "Attached file"
|
|
747
943
|
}
|
|
748
944
|
),
|
|
749
|
-
!readOnly && /* @__PURE__ */
|
|
945
|
+
!readOnly && /* @__PURE__ */ jsxs9(
|
|
750
946
|
"button",
|
|
751
947
|
{
|
|
752
948
|
type: "button",
|
|
@@ -754,7 +950,7 @@ function FieldControl({
|
|
|
754
950
|
onClick: () => fileInput.current?.click(),
|
|
755
951
|
className: "inline-flex flex-none cursor-pointer items-center gap-1 text-[clamp(8px,50cqh,13px)] text-primary",
|
|
756
952
|
children: [
|
|
757
|
-
/* @__PURE__ */
|
|
953
|
+
/* @__PURE__ */ jsx14(Paperclip, { size: 12 }),
|
|
758
954
|
" ",
|
|
759
955
|
value ? "Replace" : "Attach"
|
|
760
956
|
]
|
|
@@ -763,7 +959,7 @@ function FieldControl({
|
|
|
763
959
|
] });
|
|
764
960
|
default: {
|
|
765
961
|
const type = INPUT_TYPE[field.type] ?? "text";
|
|
766
|
-
return /* @__PURE__ */
|
|
962
|
+
return /* @__PURE__ */ jsx14(
|
|
767
963
|
"input",
|
|
768
964
|
{
|
|
769
965
|
...common,
|
|
@@ -780,9 +976,13 @@ function FieldControl({
|
|
|
780
976
|
}
|
|
781
977
|
}
|
|
782
978
|
}
|
|
979
|
+
function AnswerControl({ components, ...props }) {
|
|
980
|
+
const C = components?.fields?.[props.field.type] ?? components?.FieldControl ?? FieldControl;
|
|
981
|
+
return /* @__PURE__ */ jsx14(C, { ...props });
|
|
982
|
+
}
|
|
783
983
|
|
|
784
984
|
// src/react/components/FieldProperties.tsx
|
|
785
|
-
import { useState as
|
|
985
|
+
import { useState as useState4 } from "react";
|
|
786
986
|
|
|
787
987
|
// src/react/constants.ts
|
|
788
988
|
var TYPE_LABEL = {
|
|
@@ -802,8 +1002,8 @@ var ZOOMS = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
|
802
1002
|
var BASE_SCALE = 96 / 72;
|
|
803
1003
|
|
|
804
1004
|
// src/react/components/ConditionBuilder.tsx
|
|
805
|
-
import { Plus, X } from "lucide-react";
|
|
806
|
-
import { jsx as
|
|
1005
|
+
import { Plus, X as X2 } from "lucide-react";
|
|
1006
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
807
1007
|
var OP_LABEL = {
|
|
808
1008
|
equals: "equals",
|
|
809
1009
|
notEquals: "does not equal",
|
|
@@ -826,50 +1026,50 @@ function ConditionBuilder({
|
|
|
826
1026
|
const setRule = (i, r) => onChange({ ...value, rules: value.rules.map((x, j) => j === i ? r : x) });
|
|
827
1027
|
const remove = (i) => onChange({ ...value, rules: value.rules.filter((_, j) => j !== i) });
|
|
828
1028
|
const newRule = () => ({ fieldKey: fields[0]?.key ?? "", op: "equals", value: "" });
|
|
829
|
-
return /* @__PURE__ */
|
|
830
|
-
/* @__PURE__ */
|
|
1029
|
+
return /* @__PURE__ */ jsxs10("div", { className: `space-y-2 ${depth ? "rounded-md border border-line bg-surface p-2" : ""}`, children: [
|
|
1030
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-[12px]", children: [
|
|
831
1031
|
"Match",
|
|
832
|
-
/* @__PURE__ */
|
|
833
|
-
|
|
1032
|
+
/* @__PURE__ */ jsxs10(
|
|
1033
|
+
Select2,
|
|
834
1034
|
{
|
|
835
1035
|
"aria-label": "Match all or any",
|
|
836
1036
|
value: value.logic,
|
|
837
1037
|
onChange: (e) => onChange({ ...value, logic: e.target.value }),
|
|
838
1038
|
children: [
|
|
839
|
-
/* @__PURE__ */
|
|
840
|
-
/* @__PURE__ */
|
|
1039
|
+
/* @__PURE__ */ jsx15("option", { value: "and", children: "ALL (AND)" }),
|
|
1040
|
+
/* @__PURE__ */ jsx15("option", { value: "or", children: "ANY (OR)" })
|
|
841
1041
|
]
|
|
842
1042
|
}
|
|
843
1043
|
)
|
|
844
1044
|
] }),
|
|
845
1045
|
value.rules.map(
|
|
846
|
-
(r, i) => isGroup(r) ? /* @__PURE__ */
|
|
847
|
-
/* @__PURE__ */
|
|
848
|
-
/* @__PURE__ */
|
|
849
|
-
] }, i) : /* @__PURE__ */
|
|
850
|
-
/* @__PURE__ */
|
|
851
|
-
|
|
1046
|
+
(r, i) => isGroup(r) ? /* @__PURE__ */ jsxs10("div", { className: "flex items-start gap-1", children: [
|
|
1047
|
+
/* @__PURE__ */ jsx15("div", { className: "flex-1", children: /* @__PURE__ */ jsx15(ConditionBuilder, { value: r, onChange: (c) => setRule(i, c), fields, depth: depth + 1 }) }),
|
|
1048
|
+
/* @__PURE__ */ jsx15(IconButton2, { icon: X2, title: "Remove group", danger: true, iconSize: 14, onClick: () => remove(i) })
|
|
1049
|
+
] }, i) : /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
1050
|
+
/* @__PURE__ */ jsxs10(
|
|
1051
|
+
Select2,
|
|
852
1052
|
{
|
|
853
1053
|
"aria-label": "Field",
|
|
854
1054
|
value: r.fieldKey,
|
|
855
1055
|
onChange: (e) => setRule(i, { ...r, fieldKey: e.target.value }),
|
|
856
1056
|
children: [
|
|
857
|
-
!fields.some((f) => f.key === r.fieldKey) && /* @__PURE__ */
|
|
858
|
-
fields.map((f) => /* @__PURE__ */
|
|
1057
|
+
!fields.some((f) => f.key === r.fieldKey) && /* @__PURE__ */ jsx15("option", { value: r.fieldKey, children: r.fieldKey || "\u2014" }),
|
|
1058
|
+
fields.map((f) => /* @__PURE__ */ jsx15("option", { value: f.key, children: f.label || f.key }, f.id))
|
|
859
1059
|
]
|
|
860
1060
|
}
|
|
861
1061
|
),
|
|
862
|
-
/* @__PURE__ */
|
|
863
|
-
|
|
1062
|
+
/* @__PURE__ */ jsx15(
|
|
1063
|
+
Select2,
|
|
864
1064
|
{
|
|
865
1065
|
"aria-label": "Operator",
|
|
866
1066
|
value: r.op,
|
|
867
1067
|
onChange: (e) => setRule(i, { ...r, op: e.target.value }),
|
|
868
|
-
children: OPERATORS.map((op) => /* @__PURE__ */
|
|
1068
|
+
children: OPERATORS.map((op) => /* @__PURE__ */ jsx15("option", { value: op, children: OP_LABEL[op] }, op))
|
|
869
1069
|
}
|
|
870
1070
|
),
|
|
871
|
-
r.op !== "isEmpty" && r.op !== "isNotEmpty" && /* @__PURE__ */
|
|
872
|
-
|
|
1071
|
+
r.op !== "isEmpty" && r.op !== "isNotEmpty" && /* @__PURE__ */ jsx15(
|
|
1072
|
+
TextInput2,
|
|
873
1073
|
{
|
|
874
1074
|
"aria-label": "Value",
|
|
875
1075
|
className: "!w-24 !py-1",
|
|
@@ -877,8 +1077,8 @@ function ConditionBuilder({
|
|
|
877
1077
|
onChange: (e) => setRule(i, { ...r, value: e.target.value })
|
|
878
1078
|
}
|
|
879
1079
|
),
|
|
880
|
-
r.op === "between" && /* @__PURE__ */
|
|
881
|
-
|
|
1080
|
+
r.op === "between" && /* @__PURE__ */ jsx15(
|
|
1081
|
+
TextInput2,
|
|
882
1082
|
{
|
|
883
1083
|
"aria-label": "Upper value",
|
|
884
1084
|
className: "!w-24 !py-1",
|
|
@@ -886,12 +1086,12 @@ function ConditionBuilder({
|
|
|
886
1086
|
onChange: (e) => setRule(i, { ...r, value2: e.target.value })
|
|
887
1087
|
}
|
|
888
1088
|
),
|
|
889
|
-
/* @__PURE__ */
|
|
1089
|
+
/* @__PURE__ */ jsx15(IconButton2, { icon: X2, title: "Remove rule", danger: true, iconSize: 14, onClick: () => remove(i) })
|
|
890
1090
|
] }, i)
|
|
891
1091
|
),
|
|
892
|
-
/* @__PURE__ */
|
|
893
|
-
/* @__PURE__ */
|
|
894
|
-
|
|
1092
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex gap-2", children: [
|
|
1093
|
+
/* @__PURE__ */ jsx15(
|
|
1094
|
+
Button2,
|
|
895
1095
|
{
|
|
896
1096
|
size: "sm",
|
|
897
1097
|
variant: "secondary",
|
|
@@ -901,8 +1101,8 @@ function ConditionBuilder({
|
|
|
901
1101
|
children: "Rule"
|
|
902
1102
|
}
|
|
903
1103
|
),
|
|
904
|
-
depth < 2 && /* @__PURE__ */
|
|
905
|
-
|
|
1104
|
+
depth < 2 && /* @__PURE__ */ jsx15(
|
|
1105
|
+
Button2,
|
|
906
1106
|
{
|
|
907
1107
|
size: "sm",
|
|
908
1108
|
variant: "secondary",
|
|
@@ -917,11 +1117,11 @@ function ConditionBuilder({
|
|
|
917
1117
|
}
|
|
918
1118
|
|
|
919
1119
|
// src/react/components/FieldProperties.tsx
|
|
920
|
-
import { Fragment as Fragment3, jsx as
|
|
1120
|
+
import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
921
1121
|
var num2 = (s) => s.trim() === "" || !Number.isFinite(Number(s)) ? void 0 : Number(s);
|
|
922
1122
|
var pct2 = (n) => Math.round(n * 1e3) / 10;
|
|
923
|
-
var Section2 = ({ title, children }) => /* @__PURE__ */
|
|
924
|
-
/* @__PURE__ */
|
|
1123
|
+
var Section2 = ({ title, children }) => /* @__PURE__ */ jsxs11("section", { className: "space-y-3 border-b border-line px-3.5 py-3", children: [
|
|
1124
|
+
/* @__PURE__ */ jsx16("h4", { className: "m-0 text-[12px] font-bold tracking-wide text-muted uppercase", children: title }),
|
|
925
1125
|
children
|
|
926
1126
|
] });
|
|
927
1127
|
var CONDITIONS = [
|
|
@@ -937,56 +1137,56 @@ function FieldProperties({
|
|
|
937
1137
|
}) {
|
|
938
1138
|
const set = (patch) => onChange({ ...field, ...patch });
|
|
939
1139
|
const others = fields.filter((f) => f.id !== field.id);
|
|
940
|
-
const [optionsText, setOptionsText] =
|
|
1140
|
+
const [optionsText, setOptionsText] = useState4((field.options ?? []).join("\n"));
|
|
941
1141
|
const hasOptions = field.type === "dropdown" || field.type === "radio";
|
|
942
1142
|
const textual = !["checkbox", "file", "signature", "dropdown", "radio"].includes(field.type);
|
|
943
1143
|
const setPos = (k, v) => {
|
|
944
1144
|
const n = num2(v);
|
|
945
1145
|
if (n != null) set({ position: fitInPage({ ...field.position, [k]: n / 100 }) });
|
|
946
1146
|
};
|
|
947
|
-
return /* @__PURE__ */
|
|
948
|
-
/* @__PURE__ */
|
|
949
|
-
field.source === "ai" && /* @__PURE__ */
|
|
1147
|
+
return /* @__PURE__ */ jsxs11("div", { className: "text-[13px]", children: [
|
|
1148
|
+
/* @__PURE__ */ jsxs11(Section2, { title: "Field", children: [
|
|
1149
|
+
field.source === "ai" && /* @__PURE__ */ jsxs11("p", { className: "m-0 rounded bg-folder/15 px-2 py-1 text-[12px]", children: [
|
|
950
1150
|
"Suggested automatically",
|
|
951
1151
|
field.confidence != null && ` (${Math.round(field.confidence * 100)}% sure)`,
|
|
952
1152
|
". Check it."
|
|
953
1153
|
] }),
|
|
954
|
-
/* @__PURE__ */
|
|
1154
|
+
/* @__PURE__ */ jsxs11("p", { className: "m-0 font-mono text-[11px] text-faint", children: [
|
|
955
1155
|
"ID ",
|
|
956
1156
|
field.id
|
|
957
1157
|
] }),
|
|
958
|
-
/* @__PURE__ */
|
|
959
|
-
/* @__PURE__ */
|
|
960
|
-
/* @__PURE__ */
|
|
961
|
-
textual && /* @__PURE__ */
|
|
962
|
-
|
|
1158
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Label", children: /* @__PURE__ */ jsx16(TextInput2, { value: field.label, onChange: (e) => set({ label: e.target.value }) }) }),
|
|
1159
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Key (automatic, from the label)", children: /* @__PURE__ */ jsx16(TextInput2, { value: field.key, readOnly: true, className: "text-faint" }) }),
|
|
1160
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Type", children: /* @__PURE__ */ jsx16(Select2, { className: "w-full", value: field.type, onChange: (e) => set({ type: e.target.value }), children: FIELD_TYPES.map((t) => /* @__PURE__ */ jsx16("option", { value: t, children: TYPE_LABEL[t] }, t)) }) }),
|
|
1161
|
+
textual && /* @__PURE__ */ jsx16(FormField2, { label: "Placeholder", children: /* @__PURE__ */ jsx16(
|
|
1162
|
+
TextInput2,
|
|
963
1163
|
{
|
|
964
1164
|
value: field.placeholder ?? "",
|
|
965
1165
|
onChange: (e) => set({ placeholder: e.target.value || void 0 })
|
|
966
1166
|
}
|
|
967
1167
|
) }),
|
|
968
|
-
field.type !== "file" && field.type !== "signature" && /* @__PURE__ */
|
|
969
|
-
|
|
1168
|
+
field.type !== "file" && field.type !== "signature" && /* @__PURE__ */ jsx16(FormField2, { label: field.type === "checkbox" ? "Checked by default" : "Default value", children: field.type === "checkbox" ? /* @__PURE__ */ jsx16(
|
|
1169
|
+
Checkbox2,
|
|
970
1170
|
{
|
|
971
1171
|
label: "Checked by default",
|
|
972
1172
|
checked: field.defaultValue === "true",
|
|
973
1173
|
onChange: (e) => set({ defaultValue: e.target.checked ? "true" : void 0 })
|
|
974
1174
|
}
|
|
975
|
-
) : /* @__PURE__ */
|
|
976
|
-
|
|
1175
|
+
) : /* @__PURE__ */ jsx16(
|
|
1176
|
+
TextInput2,
|
|
977
1177
|
{
|
|
978
1178
|
value: field.defaultValue ?? "",
|
|
979
1179
|
onChange: (e) => set({ defaultValue: e.target.value || void 0 })
|
|
980
1180
|
}
|
|
981
1181
|
) }),
|
|
982
|
-
/* @__PURE__ */
|
|
983
|
-
/* @__PURE__ */
|
|
984
|
-
/* @__PURE__ */
|
|
1182
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Help text (shown as tooltip)", children: /* @__PURE__ */ jsx16(TextInput2, { value: field.helpText ?? "", onChange: (e) => set({ helpText: e.target.value || void 0 }) }) }),
|
|
1183
|
+
/* @__PURE__ */ jsx16("div", { className: "flex flex-wrap gap-4", children: ["required", "readOnly", "hidden"].map((k) => /* @__PURE__ */ jsxs11("label", { className: "inline-flex items-center gap-1.5", children: [
|
|
1184
|
+
/* @__PURE__ */ jsx16(Checkbox2, { label: k, checked: !!field[k], onChange: (e) => set({ [k]: e.target.checked || void 0 }) }),
|
|
985
1185
|
{ required: "Required", readOnly: "Read-only", hidden: "Hidden" }[k]
|
|
986
1186
|
] }, k)) })
|
|
987
1187
|
] }),
|
|
988
|
-
hasOptions && /* @__PURE__ */
|
|
989
|
-
|
|
1188
|
+
hasOptions && /* @__PURE__ */ jsx16(Section2, { title: "Options (one per line)", children: /* @__PURE__ */ jsx16(
|
|
1189
|
+
TextArea2,
|
|
990
1190
|
{
|
|
991
1191
|
rows: 4,
|
|
992
1192
|
value: optionsText,
|
|
@@ -1002,10 +1202,10 @@ function FieldProperties({
|
|
|
1002
1202
|
}
|
|
1003
1203
|
}
|
|
1004
1204
|
) }),
|
|
1005
|
-
textual && /* @__PURE__ */
|
|
1006
|
-
/* @__PURE__ */
|
|
1007
|
-
/* @__PURE__ */
|
|
1008
|
-
|
|
1205
|
+
textual && /* @__PURE__ */ jsxs11(Section2, { title: "Validation", children: [
|
|
1206
|
+
/* @__PURE__ */ jsxs11("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
1207
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Min length", children: /* @__PURE__ */ jsx16(
|
|
1208
|
+
TextInput2,
|
|
1009
1209
|
{
|
|
1010
1210
|
type: "number",
|
|
1011
1211
|
min: 0,
|
|
@@ -1013,8 +1213,8 @@ function FieldProperties({
|
|
|
1013
1213
|
onChange: (e) => set({ minLength: num2(e.target.value) })
|
|
1014
1214
|
}
|
|
1015
1215
|
) }),
|
|
1016
|
-
/* @__PURE__ */
|
|
1017
|
-
|
|
1216
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Max length", children: /* @__PURE__ */ jsx16(
|
|
1217
|
+
TextInput2,
|
|
1018
1218
|
{
|
|
1019
1219
|
type: "number",
|
|
1020
1220
|
min: 0,
|
|
@@ -1022,17 +1222,17 @@ function FieldProperties({
|
|
|
1022
1222
|
onChange: (e) => set({ maxLength: num2(e.target.value) })
|
|
1023
1223
|
}
|
|
1024
1224
|
) }),
|
|
1025
|
-
field.type === "number" && /* @__PURE__ */
|
|
1026
|
-
/* @__PURE__ */
|
|
1027
|
-
|
|
1225
|
+
field.type === "number" && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
1226
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Min value", children: /* @__PURE__ */ jsx16(
|
|
1227
|
+
TextInput2,
|
|
1028
1228
|
{
|
|
1029
1229
|
type: "number",
|
|
1030
1230
|
value: field.min ?? "",
|
|
1031
1231
|
onChange: (e) => set({ min: num2(e.target.value) })
|
|
1032
1232
|
}
|
|
1033
1233
|
) }),
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
|
|
1234
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Max value", children: /* @__PURE__ */ jsx16(
|
|
1235
|
+
TextInput2,
|
|
1036
1236
|
{
|
|
1037
1237
|
type: "number",
|
|
1038
1238
|
value: field.max ?? "",
|
|
@@ -1041,8 +1241,8 @@ function FieldProperties({
|
|
|
1041
1241
|
) })
|
|
1042
1242
|
] })
|
|
1043
1243
|
] }),
|
|
1044
|
-
/* @__PURE__ */
|
|
1045
|
-
|
|
1244
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Regex (whole value must match)", children: /* @__PURE__ */ jsx16(
|
|
1245
|
+
TextInput2,
|
|
1046
1246
|
{
|
|
1047
1247
|
className: "font-mono",
|
|
1048
1248
|
value: field.pattern ?? "",
|
|
@@ -1052,14 +1252,14 @@ function FieldProperties({
|
|
|
1052
1252
|
] }),
|
|
1053
1253
|
CONDITIONS.map(([key, title]) => {
|
|
1054
1254
|
const cond = field[key];
|
|
1055
|
-
return /* @__PURE__ */
|
|
1255
|
+
return /* @__PURE__ */ jsx16(Section2, { title, children: cond ? /* @__PURE__ */ jsx16(
|
|
1056
1256
|
ConditionBuilder,
|
|
1057
1257
|
{
|
|
1058
1258
|
value: cond,
|
|
1059
1259
|
fields: others,
|
|
1060
1260
|
onChange: (c) => set({ [key]: c.rules.length ? c : void 0 })
|
|
1061
1261
|
}
|
|
1062
|
-
) : /* @__PURE__ */
|
|
1262
|
+
) : /* @__PURE__ */ jsx16(
|
|
1063
1263
|
"button",
|
|
1064
1264
|
{
|
|
1065
1265
|
type: "button",
|
|
@@ -1070,9 +1270,9 @@ function FieldProperties({
|
|
|
1070
1270
|
}
|
|
1071
1271
|
) }, key);
|
|
1072
1272
|
}),
|
|
1073
|
-
/* @__PURE__ */
|
|
1074
|
-
/* @__PURE__ */
|
|
1075
|
-
|
|
1273
|
+
/* @__PURE__ */ jsx16(Section2, { title: "Position (% of page)", children: /* @__PURE__ */ jsxs11("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
1274
|
+
/* @__PURE__ */ jsx16(FormField2, { label: "Page", children: /* @__PURE__ */ jsx16(
|
|
1275
|
+
TextInput2,
|
|
1076
1276
|
{
|
|
1077
1277
|
type: "number",
|
|
1078
1278
|
min: 1,
|
|
@@ -1085,9 +1285,9 @@ function FieldProperties({
|
|
|
1085
1285
|
}
|
|
1086
1286
|
}
|
|
1087
1287
|
) }),
|
|
1088
|
-
/* @__PURE__ */
|
|
1089
|
-
["x", "y", "width", "height"].map((k) => /* @__PURE__ */
|
|
1090
|
-
|
|
1288
|
+
/* @__PURE__ */ jsx16("span", {}),
|
|
1289
|
+
["x", "y", "width", "height"].map((k) => /* @__PURE__ */ jsx16(FormField2, { label: { x: "Left", y: "Top", width: "Width", height: "Height" }[k], children: /* @__PURE__ */ jsx16(
|
|
1290
|
+
TextInput2,
|
|
1091
1291
|
{
|
|
1092
1292
|
type: "number",
|
|
1093
1293
|
step: 0.1,
|
|
@@ -1100,7 +1300,7 @@ function FieldProperties({
|
|
|
1100
1300
|
}
|
|
1101
1301
|
|
|
1102
1302
|
// src/react/components/LayoutElementView.tsx
|
|
1103
|
-
import { jsx as
|
|
1303
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1104
1304
|
var FAMILY = {
|
|
1105
1305
|
sans: "Helvetica, Arial, sans-serif",
|
|
1106
1306
|
serif: '"Times New Roman", Times, serif',
|
|
@@ -1114,9 +1314,9 @@ function LayoutElementView({
|
|
|
1114
1314
|
style
|
|
1115
1315
|
}) {
|
|
1116
1316
|
const stroke = ptToCqh(e.strokeWidth ?? 1, pageHeight);
|
|
1117
|
-
const
|
|
1317
|
+
const base2 = { ...boxStyle({ page: e.page, ...e.position }), ...style };
|
|
1118
1318
|
if (e.type === "line")
|
|
1119
|
-
return /* @__PURE__ */
|
|
1319
|
+
return /* @__PURE__ */ jsx17("svg", { className: `absolute overflow-visible ${className}`, style: base2, "aria-hidden": true, children: /* @__PURE__ */ jsx17(
|
|
1120
1320
|
"line",
|
|
1121
1321
|
{
|
|
1122
1322
|
x1: "0",
|
|
@@ -1127,12 +1327,12 @@ function LayoutElementView({
|
|
|
1127
1327
|
style: { strokeWidth: stroke }
|
|
1128
1328
|
}
|
|
1129
1329
|
) });
|
|
1130
|
-
return /* @__PURE__ */
|
|
1330
|
+
return /* @__PURE__ */ jsx17(
|
|
1131
1331
|
"div",
|
|
1132
1332
|
{
|
|
1133
1333
|
className: `absolute overflow-hidden whitespace-pre-wrap ${className}`,
|
|
1134
1334
|
style: {
|
|
1135
|
-
...
|
|
1335
|
+
...base2,
|
|
1136
1336
|
background: e.fill,
|
|
1137
1337
|
border: e.type === "box" && e.stroke ? `${stroke} solid ${e.stroke}` : void 0,
|
|
1138
1338
|
color: e.color ?? "#000000",
|
|
@@ -1151,7 +1351,7 @@ function LayoutElementView({
|
|
|
1151
1351
|
|
|
1152
1352
|
// src/react/components/PagesPanel.tsx
|
|
1153
1353
|
import { ArrowDown, ArrowUp, FilePlus2, Trash2 } from "lucide-react";
|
|
1154
|
-
import { jsx as
|
|
1354
|
+
import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1155
1355
|
function PagesPanel({
|
|
1156
1356
|
pages,
|
|
1157
1357
|
counts,
|
|
@@ -1160,36 +1360,36 @@ function PagesPanel({
|
|
|
1160
1360
|
onInsertBlank,
|
|
1161
1361
|
onGoTo
|
|
1162
1362
|
}) {
|
|
1163
|
-
return /* @__PURE__ */
|
|
1164
|
-
/* @__PURE__ */
|
|
1363
|
+
return /* @__PURE__ */ jsxs12("div", { className: "text-[12.5px]", children: [
|
|
1364
|
+
/* @__PURE__ */ jsxs12(
|
|
1165
1365
|
"button",
|
|
1166
1366
|
{
|
|
1167
1367
|
type: "button",
|
|
1168
1368
|
className: "flex w-full cursor-pointer items-center gap-1.5 border-b border-line-soft px-3 py-2 text-primary hover:bg-primary-soft",
|
|
1169
1369
|
onClick: () => onInsertBlank(0),
|
|
1170
1370
|
children: [
|
|
1171
|
-
/* @__PURE__ */
|
|
1371
|
+
/* @__PURE__ */ jsx18(FilePlus2, { size: 14 }),
|
|
1172
1372
|
" Blank page at the start"
|
|
1173
1373
|
]
|
|
1174
1374
|
}
|
|
1175
1375
|
),
|
|
1176
|
-
/* @__PURE__ */
|
|
1376
|
+
/* @__PURE__ */ jsx18("ol", { className: "m-0 list-none p-0", children: pages.map((p, i) => {
|
|
1177
1377
|
const n = i + 1;
|
|
1178
|
-
return /* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1378
|
+
return /* @__PURE__ */ jsxs12("li", { className: "flex items-center gap-1 border-b border-line-soft px-2 py-1", children: [
|
|
1379
|
+
/* @__PURE__ */ jsxs12(
|
|
1180
1380
|
"button",
|
|
1181
1381
|
{
|
|
1182
1382
|
type: "button",
|
|
1183
1383
|
className: "min-w-0 flex-1 cursor-pointer truncate text-left",
|
|
1184
1384
|
onClick: () => onGoTo(n),
|
|
1185
1385
|
children: [
|
|
1186
|
-
/* @__PURE__ */
|
|
1386
|
+
/* @__PURE__ */ jsxs12("b", { children: [
|
|
1187
1387
|
"Page ",
|
|
1188
1388
|
n
|
|
1189
1389
|
] }),
|
|
1190
1390
|
" ",
|
|
1191
|
-
/* @__PURE__ */
|
|
1192
|
-
counts[n] ? /* @__PURE__ */
|
|
1391
|
+
/* @__PURE__ */ jsx18("span", { className: "text-muted", children: p.source ? `\xB7 original p.${p.source}` : "\xB7 blank" }),
|
|
1392
|
+
counts[n] ? /* @__PURE__ */ jsxs12("span", { className: "text-faint", children: [
|
|
1193
1393
|
" \xB7 ",
|
|
1194
1394
|
counts[n],
|
|
1195
1395
|
" items"
|
|
@@ -1197,8 +1397,8 @@ function PagesPanel({
|
|
|
1197
1397
|
]
|
|
1198
1398
|
}
|
|
1199
1399
|
),
|
|
1200
|
-
/* @__PURE__ */
|
|
1201
|
-
|
|
1400
|
+
/* @__PURE__ */ jsx18(
|
|
1401
|
+
IconButton2,
|
|
1202
1402
|
{
|
|
1203
1403
|
icon: ArrowUp,
|
|
1204
1404
|
title: "Move page up",
|
|
@@ -1207,8 +1407,8 @@ function PagesPanel({
|
|
|
1207
1407
|
onClick: () => onMove(n, -1)
|
|
1208
1408
|
}
|
|
1209
1409
|
),
|
|
1210
|
-
/* @__PURE__ */
|
|
1211
|
-
|
|
1410
|
+
/* @__PURE__ */ jsx18(
|
|
1411
|
+
IconButton2,
|
|
1212
1412
|
{
|
|
1213
1413
|
icon: ArrowDown,
|
|
1214
1414
|
title: "Move page down",
|
|
@@ -1217,8 +1417,8 @@ function PagesPanel({
|
|
|
1217
1417
|
onClick: () => onMove(n, 1)
|
|
1218
1418
|
}
|
|
1219
1419
|
),
|
|
1220
|
-
/* @__PURE__ */
|
|
1221
|
-
|
|
1420
|
+
/* @__PURE__ */ jsx18(
|
|
1421
|
+
IconButton2,
|
|
1222
1422
|
{
|
|
1223
1423
|
icon: FilePlus2,
|
|
1224
1424
|
title: "Add blank page after",
|
|
@@ -1226,8 +1426,8 @@ function PagesPanel({
|
|
|
1226
1426
|
onClick: () => onInsertBlank(n)
|
|
1227
1427
|
}
|
|
1228
1428
|
),
|
|
1229
|
-
/* @__PURE__ */
|
|
1230
|
-
|
|
1429
|
+
/* @__PURE__ */ jsx18(
|
|
1430
|
+
IconButton2,
|
|
1231
1431
|
{
|
|
1232
1432
|
icon: Trash2,
|
|
1233
1433
|
title: "Delete page",
|
|
@@ -1243,7 +1443,7 @@ function PagesPanel({
|
|
|
1243
1443
|
}
|
|
1244
1444
|
|
|
1245
1445
|
// src/react/components/PdfPages.tsx
|
|
1246
|
-
import { useEffect, useRef as
|
|
1446
|
+
import { useEffect as useEffect2, useRef as useRef4, useState as useState5 } from "react";
|
|
1247
1447
|
|
|
1248
1448
|
// src/react/pdfjs.ts
|
|
1249
1449
|
var workerSrc;
|
|
@@ -1263,7 +1463,7 @@ var loadPdfJs = () => pdfjs ??= import("pdfjs-dist").then((lib) => {
|
|
|
1263
1463
|
});
|
|
1264
1464
|
|
|
1265
1465
|
// src/react/components/PdfPages.tsx
|
|
1266
|
-
import { jsx as
|
|
1466
|
+
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1267
1467
|
async function readPdf(src) {
|
|
1268
1468
|
const v = typeof src === "function" ? await src() : src;
|
|
1269
1469
|
if (typeof v === "string" || v instanceof URL) {
|
|
@@ -1282,14 +1482,14 @@ function PdfPages({
|
|
|
1282
1482
|
onLoaded,
|
|
1283
1483
|
onError
|
|
1284
1484
|
}) {
|
|
1285
|
-
const [doc, setDoc] =
|
|
1286
|
-
const [sizes2, setSizes] =
|
|
1287
|
-
const loadRef =
|
|
1288
|
-
const callbacks =
|
|
1289
|
-
|
|
1485
|
+
const [doc, setDoc] = useState5(null);
|
|
1486
|
+
const [sizes2, setSizes] = useState5([]);
|
|
1487
|
+
const loadRef = useRef4(load);
|
|
1488
|
+
const callbacks = useRef4({ onLoaded, onError });
|
|
1489
|
+
useEffect2(() => {
|
|
1290
1490
|
callbacks.current = { onLoaded, onError };
|
|
1291
1491
|
});
|
|
1292
|
-
|
|
1492
|
+
useEffect2(() => {
|
|
1293
1493
|
let live = true;
|
|
1294
1494
|
let opened = null;
|
|
1295
1495
|
Promise.all([loadPdfJs(), readPdf(loadRef.current)]).then(([lib, data]) => lib.getDocument({ data, ...{ isEvalSupported: false } }).promise).then(async (pdf) => {
|
|
@@ -1309,11 +1509,11 @@ function PdfPages({
|
|
|
1309
1509
|
void opened?.destroy();
|
|
1310
1510
|
};
|
|
1311
1511
|
}, []);
|
|
1312
|
-
if (!doc) return /* @__PURE__ */
|
|
1512
|
+
if (!doc) return /* @__PURE__ */ jsx19("p", { className: "p-6 text-center text-[13px] text-muted", children: "Loading document\u2026" });
|
|
1313
1513
|
const shown = pages ?? sizes2.map((s, i) => ({ source: i + 1, ...s }));
|
|
1314
1514
|
return (
|
|
1315
1515
|
// min-w-full + w-max: centred when narrower than the panel, fully scrollable (left edge too) when wider.
|
|
1316
|
-
/* @__PURE__ */
|
|
1516
|
+
/* @__PURE__ */ jsx19("div", { className: "flex w-max min-w-full flex-col gap-4 p-4", children: shown.map((p, i) => /* @__PURE__ */ jsx19(
|
|
1317
1517
|
PdfPage,
|
|
1318
1518
|
{
|
|
1319
1519
|
doc,
|
|
@@ -1337,20 +1537,20 @@ function PdfPage({
|
|
|
1337
1537
|
withText,
|
|
1338
1538
|
overlay
|
|
1339
1539
|
}) {
|
|
1340
|
-
const box2 =
|
|
1341
|
-
const canvas =
|
|
1342
|
-
const [visible, setVisible] =
|
|
1343
|
-
const [lines, setLines] =
|
|
1540
|
+
const box2 = useRef4(null);
|
|
1541
|
+
const canvas = useRef4(null);
|
|
1542
|
+
const [visible, setVisible] = useState5(false);
|
|
1543
|
+
const [lines, setLines] = useState5(null);
|
|
1344
1544
|
const width = size.width * BASE_SCALE * zoom;
|
|
1345
1545
|
const height = size.height * BASE_SCALE * zoom;
|
|
1346
|
-
|
|
1546
|
+
useEffect2(() => {
|
|
1347
1547
|
const el = box2.current;
|
|
1348
1548
|
if (!el || typeof IntersectionObserver === "undefined") return setVisible(true);
|
|
1349
1549
|
const io = new IntersectionObserver(([e]) => e.isIntersecting && setVisible(true), { rootMargin: "600px" });
|
|
1350
1550
|
io.observe(el);
|
|
1351
1551
|
return () => io.disconnect();
|
|
1352
1552
|
}, []);
|
|
1353
|
-
|
|
1553
|
+
useEffect2(() => {
|
|
1354
1554
|
if (!visible || !source || !canvas.current) return;
|
|
1355
1555
|
let task = null;
|
|
1356
1556
|
let live = true;
|
|
@@ -1370,7 +1570,7 @@ function PdfPage({
|
|
|
1370
1570
|
task?.cancel();
|
|
1371
1571
|
};
|
|
1372
1572
|
}, [doc, source, visible, zoom]);
|
|
1373
|
-
|
|
1573
|
+
useEffect2(() => {
|
|
1374
1574
|
if (!withText || !visible || !source || lines?.source === source) return;
|
|
1375
1575
|
let live = true;
|
|
1376
1576
|
void doc.getPage(source).then(async (page) => {
|
|
@@ -1388,7 +1588,7 @@ function PdfPage({
|
|
|
1388
1588
|
live = false;
|
|
1389
1589
|
};
|
|
1390
1590
|
}, [doc, source, visible, withText, lines]);
|
|
1391
|
-
return /* @__PURE__ */
|
|
1591
|
+
return /* @__PURE__ */ jsxs13(
|
|
1392
1592
|
"div",
|
|
1393
1593
|
{
|
|
1394
1594
|
ref: box2,
|
|
@@ -1396,8 +1596,8 @@ function PdfPage({
|
|
|
1396
1596
|
className: "relative mx-auto flex-none bg-white shadow-[0_1px_6px_rgba(15,23,42,0.18)]",
|
|
1397
1597
|
style: { width, height },
|
|
1398
1598
|
children: [
|
|
1399
|
-
source && /* @__PURE__ */
|
|
1400
|
-
/* @__PURE__ */
|
|
1599
|
+
source && /* @__PURE__ */ jsx19("canvas", { ref: canvas, className: "absolute inset-0 h-full w-full", "aria-label": `Page ${pageNo}` }),
|
|
1600
|
+
/* @__PURE__ */ jsx19("div", { className: "absolute inset-0 [container-type:size]", children: overlay?.(pageNo, withText && lines?.source === source ? lines.lines : void 0) })
|
|
1401
1601
|
]
|
|
1402
1602
|
}
|
|
1403
1603
|
);
|
|
@@ -1405,28 +1605,28 @@ function PdfPage({
|
|
|
1405
1605
|
|
|
1406
1606
|
// src/react/components/ZoomControls.tsx
|
|
1407
1607
|
import { Minus, Plus as Plus2 } from "lucide-react";
|
|
1408
|
-
import { jsx as
|
|
1608
|
+
import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1409
1609
|
function ZoomControls({ zoom, onChange }) {
|
|
1410
1610
|
const lower = [...ZOOMS].reverse().find((z) => z < zoom - 0.01);
|
|
1411
1611
|
const higher = ZOOMS.find((z) => z > zoom + 0.01);
|
|
1412
|
-
return /* @__PURE__ */
|
|
1413
|
-
/* @__PURE__ */
|
|
1414
|
-
/* @__PURE__ */
|
|
1612
|
+
return /* @__PURE__ */ jsxs14("div", { className: "inline-flex items-center gap-1 text-[13px]", children: [
|
|
1613
|
+
/* @__PURE__ */ jsx20(IconButton2, { icon: Minus, title: "Zoom out", disabled: lower == null, onClick: () => onChange(lower) }),
|
|
1614
|
+
/* @__PURE__ */ jsxs14("span", { className: "w-12 text-center tabular-nums", children: [
|
|
1415
1615
|
Math.round(zoom * 100),
|
|
1416
1616
|
"%"
|
|
1417
1617
|
] }),
|
|
1418
|
-
/* @__PURE__ */
|
|
1618
|
+
/* @__PURE__ */ jsx20(IconButton2, { icon: Plus2, title: "Zoom in", disabled: higher == null, onClick: () => onChange(higher) })
|
|
1419
1619
|
] });
|
|
1420
1620
|
}
|
|
1421
1621
|
|
|
1422
1622
|
// src/react/hooks/useFitZoom.ts
|
|
1423
|
-
import { useEffect as
|
|
1623
|
+
import { useEffect as useEffect3, useState as useState6 } from "react";
|
|
1424
1624
|
var PADDING = 32;
|
|
1425
1625
|
function useFitZoom(pageWidthPt) {
|
|
1426
|
-
const [el, ref] =
|
|
1427
|
-
const [fit, setFit] =
|
|
1428
|
-
const [chosen, setZoom] =
|
|
1429
|
-
|
|
1626
|
+
const [el, ref] = useState6(null);
|
|
1627
|
+
const [fit, setFit] = useState6(1);
|
|
1628
|
+
const [chosen, setZoom] = useState6(null);
|
|
1629
|
+
useEffect3(() => {
|
|
1430
1630
|
if (!el) return;
|
|
1431
1631
|
const measure = () => {
|
|
1432
1632
|
const room = el.clientWidth - PADDING;
|
|
@@ -1443,8 +1643,8 @@ function useFitZoom(pageWidthPt) {
|
|
|
1443
1643
|
function useCompact() {
|
|
1444
1644
|
const query = "(max-width: 767px), (pointer: coarse)";
|
|
1445
1645
|
const get = () => typeof window !== "undefined" && !!window.matchMedia?.(query).matches;
|
|
1446
|
-
const [compact, setCompact] =
|
|
1447
|
-
|
|
1646
|
+
const [compact, setCompact] = useState6(get);
|
|
1647
|
+
useEffect3(() => {
|
|
1448
1648
|
const m = window.matchMedia?.(query);
|
|
1449
1649
|
if (!m) return;
|
|
1450
1650
|
const on = () => setCompact(m.matches);
|
|
@@ -1454,11 +1654,14 @@ function useCompact() {
|
|
|
1454
1654
|
return compact;
|
|
1455
1655
|
}
|
|
1456
1656
|
|
|
1657
|
+
// src/react/hooks/useFormEditor.ts
|
|
1658
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useMemo, useRef as useRef5, useState as useState8 } from "react";
|
|
1659
|
+
|
|
1457
1660
|
// src/react/hooks/useUndo.ts
|
|
1458
|
-
import { useCallback, useState as
|
|
1661
|
+
import { useCallback, useState as useState7 } from "react";
|
|
1459
1662
|
var LIMIT = 100;
|
|
1460
1663
|
function useUndo(initial) {
|
|
1461
|
-
const [state, setState] =
|
|
1664
|
+
const [state, setState] = useState7({ past: [], present: initial, future: [] });
|
|
1462
1665
|
const set = useCallback((next, record = true) => {
|
|
1463
1666
|
setState((s) => {
|
|
1464
1667
|
const value = typeof next === "function" ? next(s.present) : next;
|
|
@@ -1495,51 +1698,39 @@ function useUndo(initial) {
|
|
|
1495
1698
|
};
|
|
1496
1699
|
}
|
|
1497
1700
|
|
|
1498
|
-
// src/react/
|
|
1499
|
-
import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1701
|
+
// src/react/hooks/useFormEditor.ts
|
|
1500
1702
|
var GRID = 5e-3;
|
|
1501
|
-
var DRAG_TYPE = "application/x-doc-item";
|
|
1502
1703
|
var EMPTY = { fields: [], layout: null };
|
|
1503
1704
|
var isDefault = (l, sizes2) => !l.elements.length && l.pages.length === sizes2.length && l.pages.every((p, i) => p.source === i + 1);
|
|
1504
|
-
function
|
|
1505
|
-
pdf: pdfSource,
|
|
1705
|
+
function useFormEditor({
|
|
1506
1706
|
initialValue = EMPTY,
|
|
1507
1707
|
onSave,
|
|
1508
1708
|
onChange,
|
|
1509
|
-
title,
|
|
1510
|
-
onBack,
|
|
1511
|
-
toolbar,
|
|
1512
|
-
banner,
|
|
1513
|
-
tabs = [],
|
|
1514
1709
|
autoDetect,
|
|
1710
|
+
onDetected,
|
|
1711
|
+
keyboard = true,
|
|
1712
|
+
warnUnsaved = true,
|
|
1713
|
+
fieldTypes = FIELD_TYPES,
|
|
1714
|
+
elementTools = Object.keys(ELEMENT_TOOLS),
|
|
1515
1715
|
notify = () => {
|
|
1516
1716
|
},
|
|
1517
|
-
onError: notifyError = (e) => console.error(e)
|
|
1518
|
-
className = "h-full",
|
|
1519
|
-
ref
|
|
1717
|
+
onError: notifyError = (e) => console.error(e)
|
|
1520
1718
|
}) {
|
|
1521
|
-
const [sizes2, setSizes] =
|
|
1719
|
+
const [sizes2, setSizes] = useState8(null);
|
|
1522
1720
|
const history = useUndo(initialValue);
|
|
1523
1721
|
const { value: doc, set } = history;
|
|
1524
|
-
const [saved, setSaved] =
|
|
1525
|
-
const [selection, setSelection] =
|
|
1526
|
-
const [
|
|
1527
|
-
const [
|
|
1528
|
-
const [
|
|
1529
|
-
const
|
|
1530
|
-
const [
|
|
1531
|
-
const [
|
|
1532
|
-
const [
|
|
1533
|
-
const [
|
|
1534
|
-
const
|
|
1535
|
-
const
|
|
1536
|
-
const [loadError, setLoadError] = useState7("");
|
|
1537
|
-
const [pdf, setPdf] = useState7(null);
|
|
1538
|
-
const [detecting, setDetecting] = useState7(null);
|
|
1539
|
-
const autoDetected = useRef4(false);
|
|
1540
|
-
const drag = useRef4(null);
|
|
1541
|
-
const scroller = useRef4(null);
|
|
1542
|
-
const { zoom, setZoom, ref: fitRef } = useFitZoom(Math.max(...(sizes2 ?? [{ width: 595 }]).map((p) => p.width)));
|
|
1722
|
+
const [saved, setSaved] = useState8(initialValue);
|
|
1723
|
+
const [selection, setSelection] = useState8(null);
|
|
1724
|
+
const [armed, setArmed] = useState8(null);
|
|
1725
|
+
const [snapOn, setSnapOn] = useState8(true);
|
|
1726
|
+
const [editText, setEditText] = useState8(false);
|
|
1727
|
+
const [preview, setPreviewState] = useState8(false);
|
|
1728
|
+
const [previewAnswers, setPreviewAnswers] = useState8({});
|
|
1729
|
+
const [busy, setBusy] = useState8(false);
|
|
1730
|
+
const [pdf, setPdf] = useState8(null);
|
|
1731
|
+
const [detecting, setDetecting] = useState8(null);
|
|
1732
|
+
const autoDetected = useRef5(false);
|
|
1733
|
+
const drag = useRef5(null);
|
|
1543
1734
|
const { fields } = doc;
|
|
1544
1735
|
const layout = doc.layout ?? (sizes2 ? defaultLayout(sizes2) : null);
|
|
1545
1736
|
const pageCount = layout?.pages.length ?? 1;
|
|
@@ -1551,20 +1742,20 @@ function FormEditor({
|
|
|
1551
1742
|
() => sizes2 ? [...doc.layout ? validateLayout(doc.layout, sizes2.length) : [], ...validateFields(fields, pageCount)] : [],
|
|
1552
1743
|
[sizes2, doc.layout, fields, pageCount]
|
|
1553
1744
|
);
|
|
1554
|
-
const onChangeRef =
|
|
1555
|
-
|
|
1745
|
+
const onChangeRef = useRef5(onChange);
|
|
1746
|
+
useEffect4(() => {
|
|
1556
1747
|
onChangeRef.current = onChange;
|
|
1557
1748
|
});
|
|
1558
|
-
const first =
|
|
1559
|
-
|
|
1749
|
+
const first = useRef5(true);
|
|
1750
|
+
useEffect4(() => {
|
|
1560
1751
|
if (first.current) first.current = false;
|
|
1561
1752
|
else onChangeRef.current?.(doc);
|
|
1562
1753
|
}, [doc]);
|
|
1563
1754
|
const withDoc = useCallback2(
|
|
1564
1755
|
(fn, record = true) => set((cur) => {
|
|
1565
|
-
const
|
|
1566
|
-
if (!
|
|
1567
|
-
return fn({ fields: cur.fields, layout:
|
|
1756
|
+
const base2 = cur.layout ?? (sizes2 ? defaultLayout(sizes2) : null);
|
|
1757
|
+
if (!base2) return cur;
|
|
1758
|
+
return fn({ fields: cur.fields, layout: base2 });
|
|
1568
1759
|
}, record),
|
|
1569
1760
|
[set, sizes2]
|
|
1570
1761
|
);
|
|
@@ -1576,10 +1767,6 @@ function FormEditor({
|
|
|
1576
1767
|
(fn, record = true) => withDoc((d) => ({ ...d, layout: { ...d.layout, elements: fn(d.layout.elements) } }), record),
|
|
1577
1768
|
[withDoc]
|
|
1578
1769
|
);
|
|
1579
|
-
const select = (s) => {
|
|
1580
|
-
setSelection(s);
|
|
1581
|
-
if (s) setTab("props");
|
|
1582
|
-
};
|
|
1583
1770
|
const updateField = useCallback2((f) => setFields((l) => withField(l, f)), [setFields]);
|
|
1584
1771
|
const updateElement = useCallback2(
|
|
1585
1772
|
(e) => {
|
|
@@ -1605,7 +1792,19 @@ function FormEditor({
|
|
|
1605
1792
|
setSelection({ kind: "element", id: copy.id });
|
|
1606
1793
|
}
|
|
1607
1794
|
}, [selectedField, selectedElement, fields, setFields, setElements]);
|
|
1608
|
-
|
|
1795
|
+
const nudge = useCallback2(
|
|
1796
|
+
(dx, dy) => {
|
|
1797
|
+
if (selectedField) updateField({ ...selectedField, position: moveBy(selectedField.position, dx, dy) });
|
|
1798
|
+
if (selectedElement) {
|
|
1799
|
+
const { page: _p, ...position } = moveBy({ page: selectedElement.page, ...selectedElement.position }, dx, dy);
|
|
1800
|
+
void _p;
|
|
1801
|
+
updateElement({ ...selectedElement, position });
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
[selectedField, selectedElement, updateField, updateElement]
|
|
1805
|
+
);
|
|
1806
|
+
useEffect4(() => {
|
|
1807
|
+
if (!keyboard) return;
|
|
1609
1808
|
const onKey = (e) => {
|
|
1610
1809
|
const t = e.target;
|
|
1611
1810
|
if (preview || t.closest("input, textarea, select, [contenteditable]")) return;
|
|
@@ -1627,42 +1826,27 @@ function FormEditor({
|
|
|
1627
1826
|
else if (e.key.startsWith("Arrow")) {
|
|
1628
1827
|
e.preventDefault();
|
|
1629
1828
|
const step = e.shiftKey ? 0.01 : 2e-3;
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
const { page: _p, ...position } = moveBy({ page: selectedElement.page, ...selectedElement.position }, dx, dy);
|
|
1635
|
-
void _p;
|
|
1636
|
-
updateElement({ ...selectedElement, position });
|
|
1637
|
-
}
|
|
1829
|
+
nudge(
|
|
1830
|
+
e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0,
|
|
1831
|
+
e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0
|
|
1832
|
+
);
|
|
1638
1833
|
}
|
|
1639
1834
|
};
|
|
1640
1835
|
window.addEventListener("keydown", onKey);
|
|
1641
1836
|
return () => window.removeEventListener("keydown", onKey);
|
|
1642
|
-
}, [
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
selection,
|
|
1646
|
-
selectedField,
|
|
1647
|
-
selectedElement,
|
|
1648
|
-
history,
|
|
1649
|
-
removeSelected,
|
|
1650
|
-
duplicateSelected,
|
|
1651
|
-
updateField,
|
|
1652
|
-
updateElement
|
|
1653
|
-
]);
|
|
1654
|
-
useEffect3(() => {
|
|
1655
|
-
if (!dirty) return;
|
|
1837
|
+
}, [keyboard, preview, armed, selection, history, removeSelected, duplicateSelected, nudge]);
|
|
1838
|
+
useEffect4(() => {
|
|
1839
|
+
if (!dirty || !warnUnsaved) return;
|
|
1656
1840
|
const warn = (e) => e.preventDefault();
|
|
1657
1841
|
window.addEventListener("beforeunload", warn);
|
|
1658
1842
|
return () => window.removeEventListener("beforeunload", warn);
|
|
1659
|
-
}, [dirty]);
|
|
1843
|
+
}, [dirty, warnUnsaved]);
|
|
1660
1844
|
function startDrag(e, sel, start, mode) {
|
|
1661
1845
|
if (e.button !== 0) return;
|
|
1662
1846
|
e.stopPropagation();
|
|
1663
1847
|
const pageEl = e.currentTarget.closest("[data-page]");
|
|
1664
1848
|
if (!pageEl) return;
|
|
1665
|
-
|
|
1849
|
+
setSelection(sel);
|
|
1666
1850
|
history.checkpoint();
|
|
1667
1851
|
drag.current = { sel, mode, x: e.clientX, y: e.clientY, start, rect: pageEl.getBoundingClientRect() };
|
|
1668
1852
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
@@ -1698,31 +1882,35 @@ function FormEditor({
|
|
|
1698
1882
|
false
|
|
1699
1883
|
);
|
|
1700
1884
|
}
|
|
1701
|
-
const endDrag = () =>
|
|
1885
|
+
const endDrag = () => {
|
|
1886
|
+
drag.current = null;
|
|
1887
|
+
};
|
|
1702
1888
|
function placeNew(key, page, clientX, clientY, rect) {
|
|
1703
1889
|
const [kind, what] = key.split(":");
|
|
1704
1890
|
const { x, y } = toPage(clientX, clientY, rect);
|
|
1705
|
-
if (kind === "field" &&
|
|
1891
|
+
if (kind === "field" && fieldTypes.includes(what)) {
|
|
1706
1892
|
const f = newField(what, page, x, y, fields);
|
|
1707
1893
|
setFields((l) => [...l, f]);
|
|
1708
|
-
|
|
1894
|
+
setSelection({ kind: "field", id: f.id });
|
|
1709
1895
|
return true;
|
|
1710
1896
|
}
|
|
1711
|
-
if (kind === "element" && what
|
|
1897
|
+
if (kind === "element" && elementTools.includes(what)) {
|
|
1712
1898
|
const el = newElement(what, page, x, y);
|
|
1713
1899
|
setElements((l) => [...l, el]);
|
|
1714
|
-
|
|
1900
|
+
setSelection({ kind: "element", id: el.id });
|
|
1715
1901
|
return true;
|
|
1716
1902
|
}
|
|
1717
1903
|
return false;
|
|
1718
1904
|
}
|
|
1719
|
-
function
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
function goToPage(n) {
|
|
1724
|
-
scroller.current?.querySelector(`[data-page="${n}"]`)?.scrollIntoView({ block: "start" });
|
|
1905
|
+
function editLine(line, page) {
|
|
1906
|
+
const el = replaceLine(line, page);
|
|
1907
|
+
setElements((l) => [...l, el]);
|
|
1908
|
+
setSelection({ kind: "element", id: el.id });
|
|
1725
1909
|
}
|
|
1910
|
+
const onDetectedRef = useRef5(onDetected);
|
|
1911
|
+
useEffect4(() => {
|
|
1912
|
+
onDetectedRef.current = onDetected;
|
|
1913
|
+
});
|
|
1726
1914
|
const detect = useCallback2(
|
|
1727
1915
|
async (auto = false) => {
|
|
1728
1916
|
if (!pdf || !layout || detecting) return;
|
|
@@ -1740,7 +1928,7 @@ function FormEditor({
|
|
|
1740
1928
|
);
|
|
1741
1929
|
if (found.length) {
|
|
1742
1930
|
setFields((l) => [...l, ...found]);
|
|
1743
|
-
|
|
1931
|
+
onDetectedRef.current?.(found);
|
|
1744
1932
|
}
|
|
1745
1933
|
notify(
|
|
1746
1934
|
found.length ? `${found.length} fields suggested${stop.signal.aborted ? " (stopped early)" : ""}. Check the dashed ones, then Save.` : auto ? "No fields found automatically. Drag them in from the left." : "No new fields found"
|
|
@@ -1753,7 +1941,7 @@ function FormEditor({
|
|
|
1753
1941
|
},
|
|
1754
1942
|
[pdf, layout, detecting, fields, setFields, notify, notifyError]
|
|
1755
1943
|
);
|
|
1756
|
-
|
|
1944
|
+
useEffect4(() => {
|
|
1757
1945
|
if (autoDetected.current || !pdf || !layout) return;
|
|
1758
1946
|
const fresh = autoDetect ?? (!initialValue.fields.length && !initialValue.layout);
|
|
1759
1947
|
if (!fresh) {
|
|
@@ -1786,33 +1974,152 @@ function FormEditor({
|
|
|
1786
1974
|
setBusy(false);
|
|
1787
1975
|
}
|
|
1788
1976
|
}
|
|
1789
|
-
const
|
|
1790
|
-
|
|
1791
|
-
|
|
1977
|
+
const counts = {};
|
|
1978
|
+
for (const f of fields) counts[f.position.page] = (counts[f.position.page] ?? 0) + 1;
|
|
1979
|
+
for (const el of layout?.elements ?? []) counts[el.page] = (counts[el.page] ?? 0) + 1;
|
|
1980
|
+
return {
|
|
1981
|
+
doc,
|
|
1982
|
+
fields,
|
|
1983
|
+
/** The layout as shown (the original pages until changed); null until the PDF has loaded. */
|
|
1984
|
+
layout,
|
|
1985
|
+
pageCount,
|
|
1986
|
+
/** Page size in PDF points, for drawing layout text at the right size. */
|
|
1987
|
+
pageHeight: (page) => layout?.pages[page - 1]?.height ?? 842,
|
|
1988
|
+
/** Fields + layout items per page. */
|
|
1989
|
+
counts,
|
|
1990
|
+
dirty,
|
|
1991
|
+
/** Validation messages; save() refuses while there are any. */
|
|
1992
|
+
problems,
|
|
1993
|
+
busy,
|
|
1994
|
+
save,
|
|
1995
|
+
undo: history.undo,
|
|
1996
|
+
redo: history.redo,
|
|
1997
|
+
canUndo: history.canUndo,
|
|
1998
|
+
canRedo: history.canRedo,
|
|
1999
|
+
/** Changes the document; `record: false` = no undo step (live drags). */
|
|
2000
|
+
setFields,
|
|
2001
|
+
setElements,
|
|
2002
|
+
updateField,
|
|
2003
|
+
updateElement,
|
|
2004
|
+
reorderField: (id, step) => setFields((l) => reorder(l, id, step)),
|
|
2005
|
+
movePage: (n, step) => withDoc((d) => movePage(d, n, step)),
|
|
2006
|
+
insertBlankPage: (n) => withDoc((d) => insertBlankPage(d, n)),
|
|
2007
|
+
deletePage: (n) => {
|
|
2008
|
+
setSelection(null);
|
|
2009
|
+
withDoc((d) => deletePage(d, n));
|
|
2010
|
+
},
|
|
2011
|
+
selection,
|
|
2012
|
+
setSelection,
|
|
2013
|
+
selectedField,
|
|
2014
|
+
selectedElement,
|
|
2015
|
+
removeSelected,
|
|
2016
|
+
duplicateSelected,
|
|
2017
|
+
nudge,
|
|
2018
|
+
startDrag,
|
|
2019
|
+
onDrag,
|
|
2020
|
+
endDrag,
|
|
2021
|
+
/** Tools for a palette: drag with `dataTransfer` / tap to arm, then placeNew. */
|
|
2022
|
+
fieldTools: fieldTypes.map((t) => `field:${t}`),
|
|
2023
|
+
elementTools: elementTools.map((t) => `element:${t}`),
|
|
2024
|
+
armed,
|
|
2025
|
+
setArmed,
|
|
2026
|
+
placeNew,
|
|
2027
|
+
editText,
|
|
2028
|
+
setEditText,
|
|
2029
|
+
editLine,
|
|
2030
|
+
snapOn,
|
|
2031
|
+
setSnapOn,
|
|
2032
|
+
preview,
|
|
2033
|
+
/** Preview starts with empty answers; leaving it ends Edit text. */
|
|
2034
|
+
setPreview: (on) => {
|
|
2035
|
+
setPreviewState(on);
|
|
2036
|
+
setPreviewAnswers({});
|
|
2037
|
+
setEditText(false);
|
|
2038
|
+
},
|
|
2039
|
+
previewAnswers,
|
|
2040
|
+
setPreviewAnswers,
|
|
2041
|
+
previewStates: fieldStates(fields, previewAnswers),
|
|
2042
|
+
pdf,
|
|
2043
|
+
/** Pass to PdfPages' onLoaded. */
|
|
2044
|
+
onPdfLoaded: (s, d) => {
|
|
2045
|
+
setSizes(s);
|
|
2046
|
+
setPdf(d);
|
|
2047
|
+
},
|
|
2048
|
+
pageSizes: sizes2,
|
|
2049
|
+
detect,
|
|
2050
|
+
/** Progress while detecting; `stop.abort()` keeps what was found so far. */
|
|
2051
|
+
detecting
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
// src/react/FormEditor.tsx
|
|
2056
|
+
import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2057
|
+
var DRAG_TYPE = "application/x-doc-item";
|
|
2058
|
+
function FormEditor({
|
|
2059
|
+
pdf: pdfSource,
|
|
2060
|
+
title,
|
|
2061
|
+
onBack,
|
|
2062
|
+
toolbar,
|
|
2063
|
+
banner,
|
|
2064
|
+
tabs = [],
|
|
2065
|
+
components,
|
|
2066
|
+
hide = [],
|
|
2067
|
+
className = "h-full",
|
|
2068
|
+
ref,
|
|
2069
|
+
...options
|
|
2070
|
+
}) {
|
|
2071
|
+
const shows = (p) => !hide.includes(p);
|
|
2072
|
+
const panelTabs = [
|
|
2073
|
+
...shows("propertiesTab") ? ["props"] : [],
|
|
2074
|
+
...shows("fieldsTab") ? ["fields"] : [],
|
|
2075
|
+
...shows("pagesTab") ? ["pages"] : [],
|
|
2076
|
+
...tabs.map((t) => t.id)
|
|
2077
|
+
];
|
|
2078
|
+
const [tab, setTabState] = useState9(panelTabs[0] ?? "props");
|
|
2079
|
+
const setTab = (t) => panelTabs.includes(t) && setTabState(t);
|
|
2080
|
+
const ed = useFormEditor({
|
|
2081
|
+
...options,
|
|
2082
|
+
onDetected: (found) => {
|
|
2083
|
+
setTab("fields");
|
|
2084
|
+
options.onDetected?.(found);
|
|
2085
|
+
}
|
|
2086
|
+
});
|
|
2087
|
+
const { fields, layout, pageCount, selection, selectedField, selectedElement, preview, armed, snapOn, editText } = ed;
|
|
2088
|
+
const [drawer, setDrawer] = useState9(null);
|
|
2089
|
+
const compact = useCompact();
|
|
2090
|
+
const [confirmDeletePage, setConfirmDeletePage] = useState9(null);
|
|
2091
|
+
const [loadError, setLoadError] = useState9("");
|
|
2092
|
+
const scroller = useRef6(null);
|
|
2093
|
+
const { zoom, setZoom, ref: fitRef } = useFitZoom(Math.max(...(ed.pageSizes ?? [{ width: 595 }]).map((p) => p.width)));
|
|
2094
|
+
useEffect5(() => {
|
|
2095
|
+
if (selection) setTab("props");
|
|
2096
|
+
}, [selection]);
|
|
2097
|
+
function goToPage(n) {
|
|
2098
|
+
scroller.current?.querySelector(`[data-page="${n}"]`)?.scrollIntoView({ block: "start" });
|
|
2099
|
+
}
|
|
2100
|
+
const saveRef = useRef6(ed.save);
|
|
2101
|
+
useEffect5(() => {
|
|
2102
|
+
saveRef.current = ed.save;
|
|
1792
2103
|
});
|
|
1793
2104
|
useImperativeHandle(
|
|
1794
2105
|
ref,
|
|
1795
2106
|
() => ({
|
|
1796
|
-
save: () => dirty ? saveRef.current() : Promise.resolve(problems.length === 0),
|
|
1797
|
-
dirty,
|
|
1798
|
-
value: doc
|
|
2107
|
+
save: () => ed.dirty ? saveRef.current() : Promise.resolve(ed.problems.length === 0),
|
|
2108
|
+
dirty: ed.dirty,
|
|
2109
|
+
value: ed.doc
|
|
1799
2110
|
}),
|
|
1800
|
-
[dirty, doc, problems]
|
|
2111
|
+
[ed.dirty, ed.doc, ed.problems]
|
|
1801
2112
|
);
|
|
1802
|
-
if (loadError) return /* @__PURE__ */
|
|
1803
|
-
const previewStates
|
|
1804
|
-
const
|
|
1805
|
-
for (const f of fields) counts[f.position.page] = (counts[f.position.page] ?? 0) + 1;
|
|
1806
|
-
for (const el of layout?.elements ?? []) counts[el.page] = (counts[el.page] ?? 0) + 1;
|
|
1807
|
-
const pageHeight = (page) => layout?.pages[page - 1]?.height ?? 842;
|
|
1808
|
-
const toolItem = (key, label) => /* @__PURE__ */ jsx17(
|
|
2113
|
+
if (loadError) return /* @__PURE__ */ jsx21("p", { className: "fl-root p-6 text-center text-sm text-danger", children: loadError });
|
|
2114
|
+
const { previewStates, previewAnswers, counts, pageHeight } = ed;
|
|
2115
|
+
const toolItem = (key, label) => /* @__PURE__ */ jsx21(
|
|
1809
2116
|
"button",
|
|
1810
2117
|
{
|
|
1811
2118
|
type: "button",
|
|
1812
2119
|
draggable: true,
|
|
1813
2120
|
onDragStart: (e) => e.dataTransfer.setData(DRAG_TYPE, key),
|
|
1814
2121
|
onClick: () => {
|
|
1815
|
-
setArmed(
|
|
2122
|
+
ed.setArmed(armed === key ? null : key);
|
|
1816
2123
|
setDrawer(null);
|
|
1817
2124
|
},
|
|
1818
2125
|
"aria-pressed": armed === key,
|
|
@@ -1822,11 +2129,11 @@ function FormEditor({
|
|
|
1822
2129
|
key
|
|
1823
2130
|
);
|
|
1824
2131
|
const armedLabel = armed ? armed.startsWith("field:") ? TYPE_LABEL[armed.slice(6)] : ELEMENT_TOOLS[armed.slice(8)]?.label : "";
|
|
1825
|
-
return /* @__PURE__ */
|
|
1826
|
-
/* @__PURE__ */
|
|
1827
|
-
onBack && /* @__PURE__ */
|
|
1828
|
-
!preview && /* @__PURE__ */
|
|
1829
|
-
|
|
2132
|
+
return /* @__PURE__ */ jsx21(FormliftProvider, { components, children: /* @__PURE__ */ jsxs15("div", { className: `fl-root flex min-h-[640px] flex-col bg-white text-ink ${className}`, children: [
|
|
2133
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-2 border-b border-line bg-white px-4 py-2.5", children: [
|
|
2134
|
+
onBack && shows("back") && /* @__PURE__ */ jsx21(Button2, { variant: "secondary", size: "sm", icon: ArrowLeft, onClick: onBack, children: "Back" }),
|
|
2135
|
+
!preview && shows("tools") && /* @__PURE__ */ jsx21(
|
|
2136
|
+
Button2,
|
|
1830
2137
|
{
|
|
1831
2138
|
variant: drawer === "tools" ? "primary" : "secondary",
|
|
1832
2139
|
size: "sm",
|
|
@@ -1836,8 +2143,8 @@ function FormEditor({
|
|
|
1836
2143
|
children: "Add"
|
|
1837
2144
|
}
|
|
1838
2145
|
),
|
|
1839
|
-
/* @__PURE__ */
|
|
1840
|
-
|
|
2146
|
+
shows("panel") && /* @__PURE__ */ jsx21(
|
|
2147
|
+
Button2,
|
|
1841
2148
|
{
|
|
1842
2149
|
variant: drawer === "panel" ? "primary" : "secondary",
|
|
1843
2150
|
size: "sm",
|
|
@@ -1847,127 +2154,119 @@ function FormEditor({
|
|
|
1847
2154
|
children: selection ? "Properties" : "Panel"
|
|
1848
2155
|
}
|
|
1849
2156
|
),
|
|
1850
|
-
title != null && /* @__PURE__ */
|
|
1851
|
-
/* @__PURE__ */
|
|
1852
|
-
/* @__PURE__ */
|
|
1853
|
-
/* @__PURE__ */
|
|
1854
|
-
|
|
1855
|
-
{
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
onClick: history.undo
|
|
1860
|
-
}
|
|
1861
|
-
),
|
|
1862
|
-
/* @__PURE__ */ jsx17(
|
|
1863
|
-
IconButton,
|
|
1864
|
-
{
|
|
1865
|
-
icon: Redo2,
|
|
1866
|
-
title: "Redo (Ctrl+Y)",
|
|
1867
|
-
disabled: !history.canRedo || preview,
|
|
1868
|
-
onClick: history.redo
|
|
1869
|
-
}
|
|
1870
|
-
),
|
|
1871
|
-
/* @__PURE__ */ jsx17(
|
|
1872
|
-
IconButton,
|
|
2157
|
+
title != null && shows("title") && /* @__PURE__ */ jsx21("h2", { className: "m-0 min-w-0 truncate text-[17px] font-semibold", children: title }),
|
|
2158
|
+
shows("status") && /* @__PURE__ */ jsx21("span", { className: "text-[12px] text-muted", children: ed.dirty ? "Unsaved changes" : "All changes saved" }),
|
|
2159
|
+
/* @__PURE__ */ jsxs15("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
|
|
2160
|
+
shows("undo") && /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
2161
|
+
/* @__PURE__ */ jsx21(IconButton2, { icon: Undo2, title: "Undo (Ctrl+Z)", disabled: !ed.canUndo || preview, onClick: ed.undo }),
|
|
2162
|
+
/* @__PURE__ */ jsx21(IconButton2, { icon: Redo2, title: "Redo (Ctrl+Y)", disabled: !ed.canRedo || preview, onClick: ed.redo })
|
|
2163
|
+
] }),
|
|
2164
|
+
shows("snap") && /* @__PURE__ */ jsx21(
|
|
2165
|
+
IconButton2,
|
|
1873
2166
|
{
|
|
1874
2167
|
icon: Grid3x3,
|
|
1875
2168
|
title: snapOn ? "Snap to grid: on" : "Snap to grid: off",
|
|
1876
2169
|
className: snapOn ? "!text-primary" : "",
|
|
1877
|
-
onClick: () => setSnapOn(
|
|
2170
|
+
onClick: () => ed.setSnapOn(!snapOn)
|
|
1878
2171
|
}
|
|
1879
2172
|
),
|
|
1880
|
-
/* @__PURE__ */
|
|
1881
|
-
!preview && /* @__PURE__ */
|
|
1882
|
-
|
|
2173
|
+
shows("zoom") && /* @__PURE__ */ jsx21(ZoomControls, { zoom, onChange: setZoom }),
|
|
2174
|
+
!preview && shows("editText") && /* @__PURE__ */ jsx21(
|
|
2175
|
+
Button2,
|
|
1883
2176
|
{
|
|
1884
2177
|
variant: editText ? "primary" : "secondary",
|
|
1885
2178
|
size: "sm",
|
|
1886
2179
|
icon: Type,
|
|
1887
2180
|
disabled: !layout,
|
|
1888
2181
|
title: "Click any line of the document to change its text",
|
|
1889
|
-
onClick: () => setEditText(
|
|
1890
|
-
children: /* @__PURE__ */
|
|
2182
|
+
onClick: () => ed.setEditText(!editText),
|
|
2183
|
+
children: /* @__PURE__ */ jsx21("span", { className: "max-sm:hidden", children: "Edit text" })
|
|
1891
2184
|
}
|
|
1892
2185
|
),
|
|
1893
|
-
!preview && /* @__PURE__ */
|
|
1894
|
-
|
|
2186
|
+
!preview && shows("autoDetect") && /* @__PURE__ */ jsx21(
|
|
2187
|
+
Button2,
|
|
1895
2188
|
{
|
|
1896
2189
|
variant: "secondary",
|
|
1897
2190
|
size: "sm",
|
|
1898
2191
|
icon: Wand2,
|
|
1899
|
-
disabled: !pdf || !layout || !!detecting,
|
|
2192
|
+
disabled: !ed.pdf || !layout || !!ed.detecting,
|
|
1900
2193
|
title: "Find answer lines, checkboxes and table cells and suggest fields for them",
|
|
1901
|
-
onClick: () => void detect(),
|
|
1902
|
-
children: /* @__PURE__ */
|
|
2194
|
+
onClick: () => void ed.detect(),
|
|
2195
|
+
children: /* @__PURE__ */ jsx21("span", { className: "max-sm:hidden", children: "Auto-detect" })
|
|
1903
2196
|
}
|
|
1904
2197
|
),
|
|
1905
|
-
/* @__PURE__ */
|
|
1906
|
-
|
|
2198
|
+
shows("preview") && /* @__PURE__ */ jsx21(
|
|
2199
|
+
Button2,
|
|
1907
2200
|
{
|
|
1908
2201
|
variant: "secondary",
|
|
1909
2202
|
size: "sm",
|
|
1910
2203
|
icon: preview ? Pencil : Eye,
|
|
1911
2204
|
title: preview ? "Back to editing" : "Preview",
|
|
1912
|
-
onClick: () =>
|
|
1913
|
-
|
|
1914
|
-
setPreviewAnswers({});
|
|
1915
|
-
setEditText(false);
|
|
1916
|
-
},
|
|
1917
|
-
children: /* @__PURE__ */ jsx17("span", { className: "max-sm:hidden", children: preview ? "Edit" : "Preview" })
|
|
2205
|
+
onClick: () => ed.setPreview(!preview),
|
|
2206
|
+
children: /* @__PURE__ */ jsx21("span", { className: "max-sm:hidden", children: preview ? "Edit" : "Preview" })
|
|
1918
2207
|
}
|
|
1919
2208
|
),
|
|
1920
|
-
/* @__PURE__ */
|
|
2209
|
+
shows("save") && /* @__PURE__ */ jsx21(Button2, { size: "sm", icon: Save, title: "Save", disabled: !ed.dirty || ed.busy, onClick: () => void ed.save(), children: /* @__PURE__ */ jsx21("span", { className: "max-sm:hidden", children: "Save" }) }),
|
|
1921
2210
|
toolbar
|
|
1922
2211
|
] })
|
|
1923
2212
|
] }),
|
|
1924
2213
|
banner,
|
|
1925
|
-
detecting && /* @__PURE__ */
|
|
2214
|
+
ed.detecting && /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-3 bg-folder/15 px-4 py-1.5 text-[12.5px] text-ink-2", role: "status", children: [
|
|
1926
2215
|
"Detecting fields\u2026 page ",
|
|
1927
|
-
detecting.done,
|
|
2216
|
+
ed.detecting.done,
|
|
1928
2217
|
" of ",
|
|
1929
|
-
detecting.total,
|
|
1930
|
-
/* @__PURE__ */
|
|
2218
|
+
ed.detecting.total,
|
|
2219
|
+
/* @__PURE__ */ jsx21("div", { className: "h-1.5 w-40 overflow-hidden rounded bg-white", children: /* @__PURE__ */ jsx21(
|
|
1931
2220
|
"div",
|
|
1932
2221
|
{
|
|
1933
2222
|
className: "h-full bg-folder",
|
|
1934
|
-
style: { width: `${100 * detecting.done / Math.max(1, detecting.total)}%` }
|
|
2223
|
+
style: { width: `${100 * ed.detecting.done / Math.max(1, ed.detecting.total)}%` }
|
|
1935
2224
|
}
|
|
1936
2225
|
) }),
|
|
1937
|
-
/* @__PURE__ */
|
|
2226
|
+
/* @__PURE__ */ jsx21(
|
|
1938
2227
|
"button",
|
|
1939
2228
|
{
|
|
1940
2229
|
type: "button",
|
|
1941
2230
|
className: "cursor-pointer text-primary hover:underline",
|
|
1942
|
-
onClick: () => detecting
|
|
2231
|
+
onClick: () => ed.detecting?.stop.abort(),
|
|
1943
2232
|
children: "Stop"
|
|
1944
2233
|
}
|
|
1945
2234
|
)
|
|
1946
2235
|
] }),
|
|
1947
|
-
armed && !preview && /* @__PURE__ */
|
|
2236
|
+
armed && !preview && /* @__PURE__ */ jsxs15("p", { className: "m-0 flex items-center gap-3 bg-primary-soft px-4 py-1.5 text-[12.5px] text-ink-2", role: "status", children: [
|
|
1948
2237
|
"Tap the page where the ",
|
|
1949
2238
|
armedLabel,
|
|
1950
2239
|
" should go.",
|
|
1951
|
-
/* @__PURE__ */
|
|
2240
|
+
/* @__PURE__ */ jsx21(
|
|
2241
|
+
"button",
|
|
2242
|
+
{
|
|
2243
|
+
type: "button",
|
|
2244
|
+
className: "cursor-pointer text-primary hover:underline",
|
|
2245
|
+
onClick: () => ed.setArmed(null),
|
|
2246
|
+
children: "Cancel"
|
|
2247
|
+
}
|
|
2248
|
+
)
|
|
1952
2249
|
] }),
|
|
1953
|
-
editText && /* @__PURE__ */
|
|
1954
|
-
/* @__PURE__ */
|
|
1955
|
-
!preview && /* @__PURE__ */
|
|
2250
|
+
editText && /* @__PURE__ */ jsx21("p", { className: "m-0 bg-folder/15 px-4 py-1.5 text-[12.5px] text-ink-2", children: "Edit text: click a line of the document to replace it with an editable copy (same place and size). The uploaded file itself never changes." }),
|
|
2251
|
+
/* @__PURE__ */ jsxs15("div", { className: "relative flex min-h-0 flex-1", children: [
|
|
2252
|
+
!preview && shows("tools") && /* @__PURE__ */ jsxs15(
|
|
1956
2253
|
"aside",
|
|
1957
2254
|
{
|
|
1958
2255
|
className: `w-[170px] flex-none overflow-y-auto border-r border-line bg-white max-lg:absolute max-lg:inset-y-0 max-lg:left-0 max-lg:z-30 max-lg:w-[220px] max-lg:shadow-xl ${drawer === "tools" ? "" : "max-lg:hidden"}`,
|
|
1959
2256
|
children: [
|
|
1960
|
-
/* @__PURE__ */
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2257
|
+
shows("fieldTools") && ed.fieldTools.length > 0 && /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
2258
|
+
/* @__PURE__ */ jsx21(PanelHeading2, { children: "FIELDS" }),
|
|
2259
|
+
/* @__PURE__ */ jsx21("p", { className: "m-0 px-3 pt-2 text-[11.5px] text-muted", children: "Drag onto the document, or tap one, then the page" }),
|
|
2260
|
+
/* @__PURE__ */ jsx21("div", { className: "space-y-1 p-2", children: ed.fieldTools.map((k) => toolItem(k, TYPE_LABEL[k.slice(6)])) })
|
|
2261
|
+
] }),
|
|
2262
|
+
shows("layoutTools") && ed.elementTools.length > 0 && /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
2263
|
+
/* @__PURE__ */ jsx21(PanelHeading2, { children: "LAYOUT" }),
|
|
2264
|
+
/* @__PURE__ */ jsx21("div", { className: "space-y-1 p-2", children: ed.elementTools.map((k) => toolItem(k, ELEMENT_TOOLS[k.slice(8)].label)) })
|
|
2265
|
+
] })
|
|
1967
2266
|
]
|
|
1968
2267
|
}
|
|
1969
2268
|
),
|
|
1970
|
-
/* @__PURE__ */
|
|
2269
|
+
/* @__PURE__ */ jsx21(
|
|
1971
2270
|
"div",
|
|
1972
2271
|
{
|
|
1973
2272
|
ref: (n) => {
|
|
@@ -1976,22 +2275,19 @@ function FormEditor({
|
|
|
1976
2275
|
},
|
|
1977
2276
|
className: "min-w-0 flex-1 overflow-auto bg-hover",
|
|
1978
2277
|
onPointerDown: () => {
|
|
1979
|
-
if (!preview) setSelection(null);
|
|
2278
|
+
if (!preview) ed.setSelection(null);
|
|
1980
2279
|
setDrawer(null);
|
|
1981
2280
|
},
|
|
1982
|
-
children: /* @__PURE__ */
|
|
2281
|
+
children: /* @__PURE__ */ jsx21(
|
|
1983
2282
|
PdfPages,
|
|
1984
2283
|
{
|
|
1985
2284
|
load: pdfSource,
|
|
1986
2285
|
zoom,
|
|
1987
2286
|
pages: layout?.pages,
|
|
1988
2287
|
withText: editText && !preview,
|
|
1989
|
-
onLoaded:
|
|
1990
|
-
setSizes(s);
|
|
1991
|
-
setPdf(doc2);
|
|
1992
|
-
},
|
|
2288
|
+
onLoaded: ed.onPdfLoaded,
|
|
1993
2289
|
onError: () => setLoadError("The document could not be shown"),
|
|
1994
|
-
overlay: (page, lines) => /* @__PURE__ */
|
|
2290
|
+
overlay: (page, lines) => /* @__PURE__ */ jsxs15(
|
|
1995
2291
|
"div",
|
|
1996
2292
|
{
|
|
1997
2293
|
className: "absolute inset-0",
|
|
@@ -2000,14 +2296,18 @@ function FormEditor({
|
|
|
2000
2296
|
backgroundSize: "2% 2%"
|
|
2001
2297
|
} : void 0,
|
|
2002
2298
|
onDragOver: (e) => !preview && e.preventDefault(),
|
|
2003
|
-
onDrop: (e) =>
|
|
2299
|
+
onDrop: (e) => {
|
|
2300
|
+
const key = e.dataTransfer.getData(DRAG_TYPE);
|
|
2301
|
+
if (ed.placeNew(key, page, e.clientX, e.clientY, e.currentTarget.getBoundingClientRect()))
|
|
2302
|
+
e.preventDefault();
|
|
2303
|
+
},
|
|
2004
2304
|
onClick: (e) => {
|
|
2005
2305
|
if (!armed || preview) return;
|
|
2006
|
-
placeNew(armed, page, e.clientX, e.clientY, e.currentTarget.getBoundingClientRect());
|
|
2007
|
-
setArmed(null);
|
|
2306
|
+
ed.placeNew(armed, page, e.clientX, e.clientY, e.currentTarget.getBoundingClientRect());
|
|
2307
|
+
ed.setArmed(null);
|
|
2008
2308
|
},
|
|
2009
2309
|
children: [
|
|
2010
|
-
lines?.map((line, i) => /* @__PURE__ */
|
|
2310
|
+
lines?.map((line, i) => /* @__PURE__ */ jsx21(
|
|
2011
2311
|
"button",
|
|
2012
2312
|
{
|
|
2013
2313
|
type: "button",
|
|
@@ -2015,16 +2315,12 @@ function FormEditor({
|
|
|
2015
2315
|
className: "absolute cursor-text rounded-[1px] outline-1 outline-folder/60 hover:bg-folder/25 hover:outline-folder",
|
|
2016
2316
|
style: boxStyle({ page, ...line }),
|
|
2017
2317
|
onPointerDown: (e) => e.stopPropagation(),
|
|
2018
|
-
onClick: () =>
|
|
2019
|
-
const el = replaceLine(line, page);
|
|
2020
|
-
setElements((l) => [...l, el]);
|
|
2021
|
-
select({ kind: "element", id: el.id });
|
|
2022
|
-
}
|
|
2318
|
+
onClick: () => ed.editLine(line, page)
|
|
2023
2319
|
},
|
|
2024
2320
|
i
|
|
2025
2321
|
)),
|
|
2026
2322
|
layout?.elements.filter((el) => el.page === page).map(
|
|
2027
|
-
(el) => preview ? /* @__PURE__ */
|
|
2323
|
+
(el) => preview ? /* @__PURE__ */ jsx21(LayoutElementView, { element: el, pageHeight: pageHeight(page) }, el.id) : /* @__PURE__ */ jsxs15(
|
|
2028
2324
|
"div",
|
|
2029
2325
|
{
|
|
2030
2326
|
role: "button",
|
|
@@ -2032,12 +2328,12 @@ function FormEditor({
|
|
|
2032
2328
|
"aria-label": `${el.type} ${el.text ?? ""}`,
|
|
2033
2329
|
className: `absolute cursor-move touch-none select-none ${selection?.id === el.id ? "z-10 outline-2 outline-primary" : "hover:outline-1 hover:outline-primary/50"}`,
|
|
2034
2330
|
style: boxStyle({ page, ...el.position, height: Math.max(el.position.height, 4e-3) }),
|
|
2035
|
-
onPointerDown: (e) => startDrag(e, { kind: "element", id: el.id }, { page, ...el.position }, "move"),
|
|
2036
|
-
onPointerMove: onDrag,
|
|
2037
|
-
onPointerUp: endDrag,
|
|
2331
|
+
onPointerDown: (e) => ed.startDrag(e, { kind: "element", id: el.id }, { page, ...el.position }, "move"),
|
|
2332
|
+
onPointerMove: ed.onDrag,
|
|
2333
|
+
onPointerUp: ed.endDrag,
|
|
2038
2334
|
onClick: (e) => e.stopPropagation(),
|
|
2039
2335
|
children: [
|
|
2040
|
-
/* @__PURE__ */
|
|
2336
|
+
/* @__PURE__ */ jsx21(
|
|
2041
2337
|
LayoutElementView,
|
|
2042
2338
|
{
|
|
2043
2339
|
element: el,
|
|
@@ -2045,14 +2341,14 @@ function FormEditor({
|
|
|
2045
2341
|
style: { left: 0, top: 0, width: "100%", height: "100%" }
|
|
2046
2342
|
}
|
|
2047
2343
|
),
|
|
2048
|
-
selection?.id === el.id && /* @__PURE__ */
|
|
2344
|
+
selection?.id === el.id && /* @__PURE__ */ jsx21(
|
|
2049
2345
|
"span",
|
|
2050
2346
|
{
|
|
2051
2347
|
"aria-label": "Resize",
|
|
2052
2348
|
className: "absolute -right-1 -bottom-1 h-2.5 w-2.5 cursor-se-resize rounded-sm border border-white bg-primary",
|
|
2053
|
-
onPointerDown: (e) => startDrag(e, { kind: "element", id: el.id }, { page, ...el.position }, "resize"),
|
|
2054
|
-
onPointerMove: onDrag,
|
|
2055
|
-
onPointerUp: endDrag
|
|
2349
|
+
onPointerDown: (e) => ed.startDrag(e, { kind: "element", id: el.id }, { page, ...el.position }, "resize"),
|
|
2350
|
+
onPointerMove: ed.onDrag,
|
|
2351
|
+
onPointerUp: ed.endDrag
|
|
2056
2352
|
}
|
|
2057
2353
|
)
|
|
2058
2354
|
]
|
|
@@ -2061,16 +2357,17 @@ function FormEditor({
|
|
|
2061
2357
|
)
|
|
2062
2358
|
),
|
|
2063
2359
|
fields.filter((f) => f.position.page === page).map(
|
|
2064
|
-
(f) => preview ? previewStates[f.key]?.visible && /* @__PURE__ */
|
|
2065
|
-
|
|
2360
|
+
(f) => preview ? previewStates[f.key]?.visible && /* @__PURE__ */ jsx21("div", { className: "absolute [container-type:size]", style: boxStyle(f.position), children: /* @__PURE__ */ jsx21(
|
|
2361
|
+
AnswerControl,
|
|
2066
2362
|
{
|
|
2363
|
+
components,
|
|
2067
2364
|
field: f,
|
|
2068
2365
|
value: previewAnswers[f.key],
|
|
2069
2366
|
readOnly: previewStates[f.key].readOnly,
|
|
2070
2367
|
compact,
|
|
2071
|
-
onChange: (v) => setPreviewAnswers((a) => ({ ...a, [f.key]: v }))
|
|
2368
|
+
onChange: (v) => ed.setPreviewAnswers((a) => ({ ...a, [f.key]: v }))
|
|
2072
2369
|
}
|
|
2073
|
-
) }, f.id) : /* @__PURE__ */
|
|
2370
|
+
) }, f.id) : /* @__PURE__ */ jsxs15(
|
|
2074
2371
|
"div",
|
|
2075
2372
|
{
|
|
2076
2373
|
role: "button",
|
|
@@ -2079,23 +2376,23 @@ function FormEditor({
|
|
|
2079
2376
|
title: `${f.label || f.key} (${TYPE_LABEL[f.type]})`,
|
|
2080
2377
|
className: `absolute cursor-move touch-none rounded-sm border text-[10px] select-none [container-type:size] ${selection?.id === f.id ? "z-10 border-primary bg-primary/20 ring-2 ring-primary/40" : f.source === "ai" ? "border-dashed border-folder bg-folder/15" : "border-primary/60 bg-primary/10 hover:bg-primary/15"}`,
|
|
2081
2378
|
style: boxStyle(f.position),
|
|
2082
|
-
onPointerDown: (e) => startDrag(e, { kind: "field", id: f.id }, f.position, "move"),
|
|
2083
|
-
onPointerMove: onDrag,
|
|
2084
|
-
onPointerUp: endDrag,
|
|
2379
|
+
onPointerDown: (e) => ed.startDrag(e, { kind: "field", id: f.id }, f.position, "move"),
|
|
2380
|
+
onPointerMove: ed.onDrag,
|
|
2381
|
+
onPointerUp: ed.endDrag,
|
|
2085
2382
|
onClick: (e) => e.stopPropagation(),
|
|
2086
2383
|
children: [
|
|
2087
|
-
/* @__PURE__ */
|
|
2384
|
+
/* @__PURE__ */ jsxs15("span", { className: "pointer-events-none block truncate px-0.5 text-[clamp(7px,55cqh,12px)] leading-tight text-primary", children: [
|
|
2088
2385
|
f.label || f.key,
|
|
2089
|
-
f.required && /* @__PURE__ */
|
|
2386
|
+
f.required && /* @__PURE__ */ jsx21("b", { className: "text-danger", children: " *" })
|
|
2090
2387
|
] }),
|
|
2091
|
-
selection?.id === f.id && /* @__PURE__ */
|
|
2388
|
+
selection?.id === f.id && /* @__PURE__ */ jsx21(
|
|
2092
2389
|
"span",
|
|
2093
2390
|
{
|
|
2094
2391
|
"aria-label": "Resize",
|
|
2095
2392
|
className: "absolute -right-1 -bottom-1 h-2.5 w-2.5 cursor-se-resize rounded-sm border border-white bg-primary",
|
|
2096
|
-
onPointerDown: (e) => startDrag(e, { kind: "field", id: f.id }, f.position, "resize"),
|
|
2097
|
-
onPointerMove: onDrag,
|
|
2098
|
-
onPointerUp: endDrag
|
|
2393
|
+
onPointerDown: (e) => ed.startDrag(e, { kind: "field", id: f.id }, f.position, "resize"),
|
|
2394
|
+
onPointerMove: ed.onDrag,
|
|
2395
|
+
onPointerUp: ed.endDrag
|
|
2099
2396
|
}
|
|
2100
2397
|
)
|
|
2101
2398
|
]
|
|
@@ -2110,121 +2407,130 @@ function FormEditor({
|
|
|
2110
2407
|
)
|
|
2111
2408
|
}
|
|
2112
2409
|
),
|
|
2113
|
-
/* @__PURE__ */
|
|
2410
|
+
shows("panel") && panelTabs.length > 0 && /* @__PURE__ */ jsxs15(
|
|
2114
2411
|
"aside",
|
|
2115
2412
|
{
|
|
2116
2413
|
className: `flex w-[350px] flex-none flex-col border-l border-line bg-white max-lg:absolute max-lg:inset-y-0 max-lg:right-0 max-lg:z-30 max-lg:w-[min(350px,92vw)] max-lg:shadow-xl ${drawer === "panel" ? "" : "max-lg:hidden"}`,
|
|
2117
2414
|
children: [
|
|
2118
|
-
/* @__PURE__ */
|
|
2119
|
-
|
|
2415
|
+
/* @__PURE__ */ jsx21("div", { className: "overflow-x-auto border-b border-line p-2 [&_button]:px-2.5", children: /* @__PURE__ */ jsx21(
|
|
2416
|
+
SegmentedTabs2,
|
|
2120
2417
|
{
|
|
2121
2418
|
options: [
|
|
2122
2419
|
{ value: "props", label: "Properties" },
|
|
2123
2420
|
{ value: "fields", label: `Fields (${fields.length})` },
|
|
2124
2421
|
{ value: "pages", label: `Pages (${pageCount})` },
|
|
2125
2422
|
...tabs.map((t) => ({ value: t.id, label: t.label }))
|
|
2126
|
-
],
|
|
2423
|
+
].filter((o) => panelTabs.includes(o.value)),
|
|
2127
2424
|
value: tab,
|
|
2128
2425
|
onChange: setTab
|
|
2129
2426
|
}
|
|
2130
2427
|
) }),
|
|
2131
|
-
problems.length > 0 && /* @__PURE__ */
|
|
2132
|
-
/* @__PURE__ */
|
|
2133
|
-
tab === "props" && /* @__PURE__ */
|
|
2134
|
-
/* @__PURE__ */
|
|
2135
|
-
|
|
2428
|
+
ed.problems.length > 0 && shows("problems") && /* @__PURE__ */ jsx21("ul", { className: "m-0 border-b border-danger/30 bg-danger/5 py-2 pr-3 pl-7 text-[12px] text-danger", children: ed.problems.slice(0, 6).map((p) => /* @__PURE__ */ jsx21("li", { children: p }, p)) }),
|
|
2429
|
+
/* @__PURE__ */ jsxs15("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
|
|
2430
|
+
tab === "props" && /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
2431
|
+
/* @__PURE__ */ jsx21(
|
|
2432
|
+
PanelHeading2,
|
|
2136
2433
|
{
|
|
2137
|
-
actions: selection && /* @__PURE__ */
|
|
2138
|
-
/* @__PURE__ */
|
|
2139
|
-
|
|
2434
|
+
actions: selection && /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
2435
|
+
/* @__PURE__ */ jsx21(
|
|
2436
|
+
IconButton2,
|
|
2140
2437
|
{
|
|
2141
2438
|
icon: Copy,
|
|
2142
2439
|
title: "Duplicate (Ctrl+D)",
|
|
2143
2440
|
light: true,
|
|
2144
2441
|
iconSize: 16,
|
|
2145
|
-
onClick: duplicateSelected
|
|
2442
|
+
onClick: ed.duplicateSelected
|
|
2146
2443
|
}
|
|
2147
2444
|
),
|
|
2148
|
-
/* @__PURE__ */
|
|
2445
|
+
/* @__PURE__ */ jsx21(
|
|
2446
|
+
IconButton2,
|
|
2447
|
+
{
|
|
2448
|
+
icon: Trash22,
|
|
2449
|
+
title: "Delete (Del)",
|
|
2450
|
+
light: true,
|
|
2451
|
+
iconSize: 16,
|
|
2452
|
+
onClick: ed.removeSelected
|
|
2453
|
+
}
|
|
2454
|
+
)
|
|
2149
2455
|
] }),
|
|
2150
2456
|
children: selectedField ? "FIELD" : selectedElement ? selectedElement.type.toUpperCase() : "PROPERTIES"
|
|
2151
2457
|
}
|
|
2152
2458
|
),
|
|
2153
|
-
selectedField ? /* @__PURE__ */
|
|
2459
|
+
selectedField ? /* @__PURE__ */ jsx21(
|
|
2154
2460
|
FieldProperties,
|
|
2155
2461
|
{
|
|
2156
2462
|
field: selectedField,
|
|
2157
2463
|
fields,
|
|
2158
2464
|
pageCount,
|
|
2159
|
-
onChange: updateField
|
|
2465
|
+
onChange: ed.updateField
|
|
2160
2466
|
},
|
|
2161
2467
|
selectedField.id
|
|
2162
|
-
) : selectedElement ? /* @__PURE__ */
|
|
2468
|
+
) : selectedElement ? /* @__PURE__ */ jsx21(
|
|
2163
2469
|
ElementProperties,
|
|
2164
2470
|
{
|
|
2165
2471
|
element: selectedElement,
|
|
2166
2472
|
pageCount,
|
|
2167
|
-
onChange: updateElement
|
|
2473
|
+
onChange: ed.updateElement
|
|
2168
2474
|
},
|
|
2169
2475
|
selectedElement.id
|
|
2170
|
-
) : /* @__PURE__ */
|
|
2476
|
+
) : /* @__PURE__ */ jsxs15(EmptyText2, { children: [
|
|
2171
2477
|
"Select something on the document, drag a field or layout item in from the left, or use",
|
|
2172
2478
|
" ",
|
|
2173
|
-
/* @__PURE__ */
|
|
2479
|
+
/* @__PURE__ */ jsx21("b", { children: "Edit text" }),
|
|
2174
2480
|
" to change the document's own text."
|
|
2175
2481
|
] })
|
|
2176
2482
|
] }),
|
|
2177
|
-
tab === "fields" && /* @__PURE__ */
|
|
2178
|
-
/* @__PURE__ */
|
|
2179
|
-
!fields.length && /* @__PURE__ */
|
|
2180
|
-
/* @__PURE__ */
|
|
2483
|
+
tab === "fields" && /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
2484
|
+
/* @__PURE__ */ jsx21(PanelHeading2, { children: "FILL ORDER" }),
|
|
2485
|
+
!fields.length && /* @__PURE__ */ jsx21(EmptyText2, { children: "No fields yet." }),
|
|
2486
|
+
/* @__PURE__ */ jsx21("ol", { className: "m-0 list-none p-0", children: fields.map((f, i) => /* @__PURE__ */ jsxs15(
|
|
2181
2487
|
"li",
|
|
2182
2488
|
{
|
|
2183
2489
|
className: `flex items-center gap-1 border-b border-line-soft px-2 py-1 text-[12.5px] ${selection?.id === f.id ? "bg-primary-tint" : ""}`,
|
|
2184
2490
|
children: [
|
|
2185
|
-
/* @__PURE__ */
|
|
2491
|
+
/* @__PURE__ */ jsxs15(
|
|
2186
2492
|
"button",
|
|
2187
2493
|
{
|
|
2188
2494
|
type: "button",
|
|
2189
2495
|
className: "min-w-0 flex-1 cursor-pointer truncate text-left",
|
|
2190
2496
|
onClick: () => {
|
|
2191
|
-
|
|
2497
|
+
ed.setSelection({ kind: "field", id: f.id });
|
|
2192
2498
|
goToPage(f.position.page);
|
|
2193
2499
|
},
|
|
2194
2500
|
children: [
|
|
2195
|
-
/* @__PURE__ */
|
|
2501
|
+
/* @__PURE__ */ jsxs15("span", { className: "text-faint", children: [
|
|
2196
2502
|
"p",
|
|
2197
2503
|
f.position.page
|
|
2198
2504
|
] }),
|
|
2199
2505
|
" ",
|
|
2200
2506
|
f.label || f.key,
|
|
2201
2507
|
" ",
|
|
2202
|
-
/* @__PURE__ */
|
|
2508
|
+
/* @__PURE__ */ jsxs15("span", { className: "text-muted", children: [
|
|
2203
2509
|
"\xB7 ",
|
|
2204
2510
|
TYPE_LABEL[f.type]
|
|
2205
2511
|
] }),
|
|
2206
|
-
f.source === "ai" && /* @__PURE__ */
|
|
2512
|
+
f.source === "ai" && /* @__PURE__ */ jsx21("span", { className: "text-folder", children: " \xB7 AI" })
|
|
2207
2513
|
]
|
|
2208
2514
|
}
|
|
2209
2515
|
),
|
|
2210
|
-
/* @__PURE__ */
|
|
2211
|
-
|
|
2516
|
+
/* @__PURE__ */ jsx21(
|
|
2517
|
+
IconButton2,
|
|
2212
2518
|
{
|
|
2213
2519
|
icon: ArrowUp2,
|
|
2214
2520
|
title: "Move up",
|
|
2215
2521
|
iconSize: 13,
|
|
2216
2522
|
disabled: i === 0,
|
|
2217
|
-
onClick: () =>
|
|
2523
|
+
onClick: () => ed.reorderField(f.id, -1)
|
|
2218
2524
|
}
|
|
2219
2525
|
),
|
|
2220
|
-
/* @__PURE__ */
|
|
2221
|
-
|
|
2526
|
+
/* @__PURE__ */ jsx21(
|
|
2527
|
+
IconButton2,
|
|
2222
2528
|
{
|
|
2223
2529
|
icon: ArrowDown2,
|
|
2224
2530
|
title: "Move down",
|
|
2225
2531
|
iconSize: 13,
|
|
2226
2532
|
disabled: i === fields.length - 1,
|
|
2227
|
-
onClick: () =>
|
|
2533
|
+
onClick: () => ed.reorderField(f.id, 1)
|
|
2228
2534
|
}
|
|
2229
2535
|
)
|
|
2230
2536
|
]
|
|
@@ -2232,25 +2538,25 @@ function FormEditor({
|
|
|
2232
2538
|
f.id
|
|
2233
2539
|
)) })
|
|
2234
2540
|
] }),
|
|
2235
|
-
tab === "pages" && (layout ? /* @__PURE__ */
|
|
2541
|
+
tab === "pages" && (layout ? /* @__PURE__ */ jsx21(
|
|
2236
2542
|
PagesPanel,
|
|
2237
2543
|
{
|
|
2238
2544
|
pages: layout.pages,
|
|
2239
2545
|
counts,
|
|
2240
2546
|
onGoTo: goToPage,
|
|
2241
|
-
onMove:
|
|
2242
|
-
onInsertBlank:
|
|
2243
|
-
onDelete: (n) => counts[n] ? setConfirmDeletePage(n) :
|
|
2547
|
+
onMove: ed.movePage,
|
|
2548
|
+
onInsertBlank: ed.insertBlankPage,
|
|
2549
|
+
onDelete: (n) => counts[n] ? setConfirmDeletePage(n) : ed.deletePage(n)
|
|
2244
2550
|
}
|
|
2245
|
-
) : /* @__PURE__ */
|
|
2551
|
+
) : /* @__PURE__ */ jsx21(EmptyText2, { children: "Loading pages\u2026" })),
|
|
2246
2552
|
tabs.find((t) => t.id === tab)?.content
|
|
2247
2553
|
] })
|
|
2248
2554
|
]
|
|
2249
2555
|
}
|
|
2250
2556
|
)
|
|
2251
2557
|
] }),
|
|
2252
|
-
confirmDeletePage != null && /* @__PURE__ */
|
|
2253
|
-
|
|
2558
|
+
confirmDeletePage != null && /* @__PURE__ */ jsxs15(
|
|
2559
|
+
ConfirmModal2,
|
|
2254
2560
|
{
|
|
2255
2561
|
danger: true,
|
|
2256
2562
|
confirmLabel: "Delete page",
|
|
@@ -2258,8 +2564,7 @@ function FormEditor({
|
|
|
2258
2564
|
onConfirm: () => {
|
|
2259
2565
|
const n = confirmDeletePage;
|
|
2260
2566
|
setConfirmDeletePage(null);
|
|
2261
|
-
|
|
2262
|
-
withDoc((d) => deletePage(d, n));
|
|
2567
|
+
ed.deletePage(n);
|
|
2263
2568
|
},
|
|
2264
2569
|
children: [
|
|
2265
2570
|
"Delete page ",
|
|
@@ -2270,158 +2575,88 @@ function FormEditor({
|
|
|
2270
2575
|
]
|
|
2271
2576
|
}
|
|
2272
2577
|
)
|
|
2273
|
-
] });
|
|
2578
|
+
] }) });
|
|
2274
2579
|
}
|
|
2275
2580
|
|
|
2276
2581
|
// src/react/FormFiller.tsx
|
|
2277
2582
|
import { ArrowLeft as ArrowLeft2, Download, Paperclip as Paperclip2, Save as Save2, Send } from "lucide-react";
|
|
2278
|
-
import {
|
|
2583
|
+
import { useEffect as useEffect8, useState as useState13 } from "react";
|
|
2279
2584
|
|
|
2280
2585
|
// src/react/components/QuestionList.tsx
|
|
2281
2586
|
import { Search, SlidersHorizontal } from "lucide-react";
|
|
2282
|
-
import { Fragment as Fragment5,
|
|
2587
|
+
import { Fragment as Fragment5, useEffect as useEffect6, useState as useState11 } from "react";
|
|
2283
2588
|
|
|
2284
|
-
// src/react/
|
|
2285
|
-
import {
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
})
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2589
|
+
// src/react/hooks/useQuestionFilter.ts
|
|
2590
|
+
import { useMemo as useMemo2, useState as useState10 } from "react";
|
|
2591
|
+
var defaultFilters = builtinFilters({ type: (t) => TYPE_LABEL[t] });
|
|
2592
|
+
var byId = (id) => {
|
|
2593
|
+
if (id === "tags") return defaultFilters.find((s) => typeof s === "function");
|
|
2594
|
+
return defaultFilters.find((s) => typeof s !== "function" && s.id === id);
|
|
2595
|
+
};
|
|
2596
|
+
function useQuestionFilter(questions, answers, { filters = defaultFilters, search: searchOn = true, initial, predicate, onChange } = {}) {
|
|
2597
|
+
const [values, setValues] = useState10(initial?.values ?? {});
|
|
2598
|
+
const [search, setSearchState] = useState10(initial?.search ?? "");
|
|
2599
|
+
const [seen, setSeen] = useState10(answers);
|
|
2600
|
+
const [version, setVersion] = useState10(0);
|
|
2601
|
+
const update = (nextSearch, nextValues) => {
|
|
2602
|
+
setSearchState(nextSearch);
|
|
2603
|
+
setValues(nextValues);
|
|
2604
|
+
setSeen(answers);
|
|
2605
|
+
setVersion((v) => v + 1);
|
|
2606
|
+
onChange?.({ search: nextSearch, values: nextValues });
|
|
2607
|
+
};
|
|
2608
|
+
const key = filters.map((f) => typeof f === "string" ? f : typeof f === "function" ? "fn" : f.id).join();
|
|
2609
|
+
const defs = useMemo2(
|
|
2610
|
+
() => resolveFilters(
|
|
2611
|
+
filters.map((f) => typeof f === "string" ? byId(f) : f).filter((f) => !!f),
|
|
2612
|
+
questions
|
|
2613
|
+
),
|
|
2614
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- re-resolve when the list's ids change, not on every render
|
|
2615
|
+
[key, questions]
|
|
2311
2616
|
);
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
active: value === o.value,
|
|
2327
|
-
onClick: () => onChange(value === o.value ? null : o.value)
|
|
2328
|
-
},
|
|
2329
|
-
o.value
|
|
2330
|
-
))
|
|
2331
|
-
] });
|
|
2332
|
-
}
|
|
2333
|
-
function RemovableChip({ label, onRemove }) {
|
|
2334
|
-
return /* @__PURE__ */ jsxs14("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary-tint py-0.5 pr-1 pl-2.5 text-[12px] font-medium text-primary", children: [
|
|
2335
|
-
label,
|
|
2336
|
-
/* @__PURE__ */ jsx18(
|
|
2337
|
-
"button",
|
|
2338
|
-
{
|
|
2339
|
-
type: "button",
|
|
2340
|
-
"aria-label": `Remove ${label}`,
|
|
2341
|
-
onClick: onRemove,
|
|
2342
|
-
className: "inline-flex h-4.5 w-4.5 cursor-pointer items-center justify-center rounded-full hover:bg-primary hover:text-white",
|
|
2343
|
-
children: /* @__PURE__ */ jsx18(X2, { size: 12 })
|
|
2344
|
-
}
|
|
2345
|
-
)
|
|
2346
|
-
] });
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
// src/react/ui/Popover.tsx
|
|
2350
|
-
import { useRef as useRef5, useState as useState8 } from "react";
|
|
2351
|
-
|
|
2352
|
-
// src/react/ui/useOutsideClose.ts
|
|
2353
|
-
import { useEffect as useEffect4 } from "react";
|
|
2354
|
-
function useOutsideClose(ref, open, onClose) {
|
|
2355
|
-
useEffect4(() => {
|
|
2356
|
-
if (!open) return;
|
|
2357
|
-
const onClick = (e) => ref.current && e.composedPath().includes(ref.current) || onClose();
|
|
2358
|
-
const onKey = (e) => e.key === "Escape" && onClose();
|
|
2359
|
-
document.addEventListener("mousedown", onClick);
|
|
2360
|
-
document.addEventListener("keydown", onKey);
|
|
2361
|
-
return () => {
|
|
2362
|
-
document.removeEventListener("mousedown", onClick);
|
|
2363
|
-
document.removeEventListener("keydown", onKey);
|
|
2617
|
+
const list = useMemo2(() => {
|
|
2618
|
+
const shown = filterQuestions(questions, seen, defs, values, searchOn ? search : "");
|
|
2619
|
+
return predicate ? shown.filter((q) => predicate(q, seen)) : shown;
|
|
2620
|
+
}, [questions, seen, defs, values, search, searchOn, predicate]);
|
|
2621
|
+
const groups = defs.map((d) => {
|
|
2622
|
+
const options = d.options(questions, answers);
|
|
2623
|
+
return {
|
|
2624
|
+
id: d.id,
|
|
2625
|
+
title: d.title,
|
|
2626
|
+
section: d.section,
|
|
2627
|
+
options,
|
|
2628
|
+
available: options.length > 0,
|
|
2629
|
+
value: values[d.id] ?? null,
|
|
2630
|
+
set: (v) => update(search, { ...values, [d.id]: v })
|
|
2364
2631
|
};
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
children: [
|
|
2390
|
-
Icon && /* @__PURE__ */ jsx19(Icon, { size: 15 }),
|
|
2391
|
-
label,
|
|
2392
|
-
!!badge && /* @__PURE__ */ jsx19("span", { className: "inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[11px] font-bold text-white", children: badge })
|
|
2393
|
-
]
|
|
2394
|
-
}
|
|
2395
|
-
),
|
|
2396
|
-
open && /* @__PURE__ */ jsx19(
|
|
2397
|
-
"div",
|
|
2398
|
-
{
|
|
2399
|
-
role: "dialog",
|
|
2400
|
-
"aria-label": label,
|
|
2401
|
-
className: `absolute z-30 mt-1.5 w-[min(560px,calc(100vw-24px))] max-sm:fixed max-sm:inset-x-3 max-sm:max-h-[70dvh] max-sm:w-auto max-sm:overflow-y-auto rounded-lg border border-line bg-white p-4 shadow-xl ${align === "right" ? "right-0" : "left-0"}`,
|
|
2402
|
-
children
|
|
2403
|
-
}
|
|
2404
|
-
)
|
|
2405
|
-
] });
|
|
2632
|
+
});
|
|
2633
|
+
const picked = groups.filter((g) => g.value != null);
|
|
2634
|
+
return {
|
|
2635
|
+
groups,
|
|
2636
|
+
/** One group by id (undefined when not in `filters`). */
|
|
2637
|
+
group: (id) => groups.find((g) => g.id === id),
|
|
2638
|
+
/** The search text; null when search is off. */
|
|
2639
|
+
search: searchOn ? search : null,
|
|
2640
|
+
setSearch: (text) => update(text, values),
|
|
2641
|
+
values,
|
|
2642
|
+
/** Filters in use, as chips to take off one by one. */
|
|
2643
|
+
inUse: picked.map((g) => ({
|
|
2644
|
+
id: g.id,
|
|
2645
|
+
label: `${g.title}: ${g.options.find((o) => o.value === g.value)?.label ?? g.value}`,
|
|
2646
|
+
clear: () => g.set(null)
|
|
2647
|
+
})),
|
|
2648
|
+
active: !!search || picked.length > 0,
|
|
2649
|
+
clear: () => update("", {}),
|
|
2650
|
+
/** The questions that match, in form order. */
|
|
2651
|
+
list,
|
|
2652
|
+
total: questions.length,
|
|
2653
|
+
/** Bumps on every change (reset paging / scroll on it). */
|
|
2654
|
+
version
|
|
2655
|
+
};
|
|
2406
2656
|
}
|
|
2407
2657
|
|
|
2408
|
-
// src/react/ui/Tag.tsx
|
|
2409
|
-
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
2410
|
-
var tones = {
|
|
2411
|
-
success: "bg-success/15 text-success",
|
|
2412
|
-
info: "bg-heading/20 text-primary",
|
|
2413
|
-
muted: "bg-hover text-muted"
|
|
2414
|
-
};
|
|
2415
|
-
var Tag = ({ tone = "info", children }) => /* @__PURE__ */ jsx20(
|
|
2416
|
-
"span",
|
|
2417
|
-
{
|
|
2418
|
-
className: `inline-block rounded-full px-2.5 py-0.5 text-[11px] font-semibold whitespace-nowrap ${tones[tone]}`,
|
|
2419
|
-
children
|
|
2420
|
-
}
|
|
2421
|
-
);
|
|
2422
|
-
|
|
2423
2658
|
// src/react/components/QuestionList.tsx
|
|
2424
|
-
import { Fragment as Fragment6, jsx as
|
|
2659
|
+
import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2425
2660
|
var PAGE = 100;
|
|
2426
2661
|
var SIZE = {
|
|
2427
2662
|
checkbox: "h-7 w-7 lg:h-8 lg:w-8",
|
|
@@ -2435,216 +2670,103 @@ var wide = (t) => t === "textarea" || t === "radio" || t === "file";
|
|
|
2435
2670
|
function QuestionList({
|
|
2436
2671
|
questions,
|
|
2437
2672
|
answers,
|
|
2438
|
-
control
|
|
2673
|
+
control,
|
|
2674
|
+
filter: external,
|
|
2675
|
+
filters,
|
|
2676
|
+
search,
|
|
2677
|
+
hideUnavailable = true,
|
|
2678
|
+
toolbar = true,
|
|
2679
|
+
renderToolbar
|
|
2439
2680
|
}) {
|
|
2440
|
-
const
|
|
2441
|
-
const
|
|
2442
|
-
const
|
|
2443
|
-
const
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
const
|
|
2449
|
-
const count = (f) => questions.filter(f).length;
|
|
2450
|
-
const types = useMemo2(() => [...new Set(questions.flatMap((q) => q.parts.map((p) => p.field.type)))], [questions]);
|
|
2451
|
-
const categories = useMemo2(
|
|
2452
|
-
() => [...new Set(questions.map((q) => q.category).filter((c) => !!c))],
|
|
2453
|
-
[questions]
|
|
2454
|
-
);
|
|
2455
|
-
const tagValues = useMemo2(() => {
|
|
2456
|
-
const m = /* @__PURE__ */ new Map();
|
|
2457
|
-
for (const q of questions)
|
|
2458
|
-
for (const [k, v] of Object.entries(q.tags ?? {})) m.set(k, (m.get(k) ?? /* @__PURE__ */ new Set()).add(v));
|
|
2459
|
-
return [...m].map(
|
|
2460
|
-
([k, vs]) => [k, [...vs].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))]
|
|
2461
|
-
);
|
|
2462
|
-
}, [questions]);
|
|
2463
|
-
const has = (re) => questions.some((q) => q.parts.some((p) => re.test(p.name) || re.test(p.field.label)));
|
|
2464
|
-
const progress = [
|
|
2465
|
-
["answered", "Answered", "Answered", "Not answered"],
|
|
2466
|
-
["remarks", "Remarks", "Written", "Empty"],
|
|
2467
|
-
["comments", "Comments", "Written", "Empty"]
|
|
2468
|
-
].filter(([key]) => key === "answered" || has(key === "remarks" ? /remark/i : /comment/i));
|
|
2469
|
-
const setTag = (key, value) => {
|
|
2470
|
-
const tags = { ...filter.tags };
|
|
2471
|
-
if (value == null) delete tags[key];
|
|
2472
|
-
else tags[key] = value;
|
|
2473
|
-
set({ tags });
|
|
2474
|
-
};
|
|
2475
|
-
const choiceCount = useMemo2(() => {
|
|
2476
|
-
const m = /* @__PURE__ */ new Map();
|
|
2477
|
-
for (const q of questions) for (const c of new Set(choicesOf(q))) m.set(c, (m.get(c) ?? 0) + 1);
|
|
2478
|
-
return [...m].filter(([, n]) => n >= 2).slice(0, 40);
|
|
2479
|
-
}, [questions]);
|
|
2480
|
-
const [choiceMode, setChoiceMode] = useState9("offered");
|
|
2481
|
-
const choiceValue = choiceMode === "picked" ? filter.picked : filter.choices;
|
|
2482
|
-
const pickedCount = useMemo2(() => {
|
|
2483
|
-
const m = /* @__PURE__ */ new Map();
|
|
2484
|
-
for (const q of questions) for (const c of new Set(pickedOf(q, answers))) m.set(c, (m.get(c) ?? 0) + 1);
|
|
2485
|
-
return m;
|
|
2486
|
-
}, [questions, answers]);
|
|
2487
|
-
const inUse = [
|
|
2488
|
-
...progress.filter(([key]) => filter[key] !== "all").map(([key, title, yes, no]) => ({
|
|
2489
|
-
label: `${title}: ${filter[key] === "filled" ? yes : no}`,
|
|
2490
|
-
clear: () => set({ [key]: "all" })
|
|
2491
|
-
})),
|
|
2492
|
-
...filter.type !== "all" ? [{ label: `Type: ${TYPE_LABEL[filter.type]}`, clear: () => set({ type: "all" }) }] : [],
|
|
2493
|
-
...Object.entries(filter.tags).map(([k, v]) => ({ label: `${k}: ${v}`, clear: () => setTag(k, null) })),
|
|
2494
|
-
...filter.choices !== "all" ? [{ label: `Has option: ${filter.choices}`, clear: () => set({ choices: "all" }) }] : [],
|
|
2495
|
-
...filter.picked !== "all" ? [{ label: `Answered: ${filter.picked}`, clear: () => set({ picked: "all" }) }] : []
|
|
2496
|
-
];
|
|
2497
|
-
const active = JSON.stringify(filter) !== JSON.stringify(NO_FILTER);
|
|
2681
|
+
const own = useQuestionFilter(questions, answers, { filters, search });
|
|
2682
|
+
const fs = external ?? own;
|
|
2683
|
+
const { list } = fs;
|
|
2684
|
+
const [shown, setShown] = useState11(PAGE);
|
|
2685
|
+
useEffect6(() => setShown(PAGE), [fs.version]);
|
|
2686
|
+
const groups = fs.groups.filter((g) => g.available || !hideUnavailable);
|
|
2687
|
+
const bar = groups.filter((g) => g.section === "bar");
|
|
2688
|
+
const menu = groups.filter((g) => g.section !== "bar");
|
|
2689
|
+
const sections = [...new Set(menu.map((g) => g.section ?? ""))];
|
|
2498
2690
|
return /* @__PURE__ */ jsxs16("div", { className: "w-full p-3 sm:p-4 lg:px-6", children: [
|
|
2499
|
-
/* @__PURE__ */ jsxs16("div", { className: "sticky top-0 z-10 -mx-3 mb-3 flex flex-col gap-2 border-b border-line bg-white/95 px-3 py-2.5 shadow-sm backdrop-blur sm:-mx-4 sm:px-4 lg:-mx-6 lg:px-6", children: [
|
|
2691
|
+
renderToolbar ? renderToolbar(fs) : toolbar && /* @__PURE__ */ jsxs16("div", { className: "sticky top-0 z-10 -mx-3 mb-3 flex flex-col gap-2 border-b border-line bg-white/95 px-3 py-2.5 shadow-sm backdrop-blur sm:-mx-4 sm:px-4 lg:-mx-6 lg:px-6", children: [
|
|
2500
2692
|
/* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2501
|
-
/* @__PURE__ */ jsxs16("label", { className: "relative min-w-0 flex-1 basis-full sm:basis-72", children: [
|
|
2502
|
-
/* @__PURE__ */
|
|
2503
|
-
/* @__PURE__ */
|
|
2693
|
+
fs.search != null && /* @__PURE__ */ jsxs16("label", { className: "relative min-w-0 flex-1 basis-full sm:basis-72", children: [
|
|
2694
|
+
/* @__PURE__ */ jsx22(Search, { size: 15, className: "absolute top-1/2 left-3 -translate-y-1/2 text-muted" }),
|
|
2695
|
+
/* @__PURE__ */ jsx22(
|
|
2504
2696
|
"input",
|
|
2505
2697
|
{
|
|
2506
2698
|
type: "search",
|
|
2507
2699
|
"aria-label": "Search questions",
|
|
2508
2700
|
placeholder: "Search questions, sections, answers\u2026",
|
|
2509
|
-
value:
|
|
2510
|
-
onChange: (e) =>
|
|
2701
|
+
value: fs.search,
|
|
2702
|
+
onChange: (e) => fs.setSearch(e.target.value),
|
|
2511
2703
|
className: "w-full rounded-full border border-line-strong bg-surface py-1.5 pr-3 pl-9 text-[13px] outline-none focus:border-primary focus:bg-white max-sm:text-base"
|
|
2512
2704
|
}
|
|
2513
2705
|
)
|
|
2514
2706
|
] }),
|
|
2515
|
-
/* @__PURE__ */
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
]
|
|
2526
|
-
},
|
|
2527
|
-
key
|
|
2528
|
-
)) }),
|
|
2529
|
-
choiceCount.length > 0 && /* @__PURE__ */ jsx21(
|
|
2530
|
-
FilterSection,
|
|
2531
|
-
{
|
|
2532
|
-
title: "Answers",
|
|
2533
|
-
action: /* @__PURE__ */ jsx21(
|
|
2534
|
-
SegmentedTabs,
|
|
2535
|
-
{
|
|
2536
|
-
size: "sm",
|
|
2537
|
-
value: choiceMode,
|
|
2538
|
-
onChange: (m) => {
|
|
2539
|
-
setChoiceMode(m);
|
|
2540
|
-
const c = filter.choices !== "all" ? filter.choices : filter.picked;
|
|
2541
|
-
if (c !== "all")
|
|
2542
|
-
set(m === "picked" ? { picked: c, choices: "all" } : { choices: c, picked: "all" });
|
|
2543
|
-
},
|
|
2544
|
-
options: [
|
|
2545
|
-
{ value: "offered", label: "Has option" },
|
|
2546
|
-
{ value: "picked", label: "Answered" }
|
|
2547
|
-
]
|
|
2548
|
-
}
|
|
2549
|
-
),
|
|
2550
|
-
children: /* @__PURE__ */ jsx21(
|
|
2551
|
-
ChipGroup,
|
|
2552
|
-
{
|
|
2553
|
-
value: choiceValue === "all" ? null : choiceValue,
|
|
2554
|
-
onChange: (v) => set(choiceMode === "picked" ? { picked: v ?? "all" } : { choices: v ?? "all" }),
|
|
2555
|
-
options: choiceCount.map(([c, n]) => ({
|
|
2556
|
-
value: c,
|
|
2557
|
-
label: c,
|
|
2558
|
-
count: choiceMode === "picked" ? pickedCount.get(c) ?? 0 : n
|
|
2559
|
-
}))
|
|
2560
|
-
}
|
|
2561
|
-
)
|
|
2562
|
-
}
|
|
2563
|
-
),
|
|
2564
|
-
tagValues.length > 0 && /* @__PURE__ */ jsx21(FilterSection, { title: "Question details", children: tagValues.map(([key, values]) => /* @__PURE__ */ jsx21(
|
|
2565
|
-
ChipGroup,
|
|
2566
|
-
{
|
|
2567
|
-
title: key,
|
|
2568
|
-
value: filter.tags[key] ?? null,
|
|
2569
|
-
onChange: (v) => setTag(key, v),
|
|
2570
|
-
options: values.map((v) => ({ value: v, label: v, count: count((q) => q.tags?.[key] === v) }))
|
|
2571
|
-
},
|
|
2572
|
-
key
|
|
2573
|
-
)) }),
|
|
2574
|
-
types.length > 1 && /* @__PURE__ */ jsx21(FilterSection, { title: "Answer type", children: /* @__PURE__ */ jsx21(
|
|
2575
|
-
ChipGroup,
|
|
2576
|
-
{
|
|
2577
|
-
value: filter.type === "all" ? null : filter.type,
|
|
2578
|
-
onChange: (v) => set({ type: v ?? "all" }),
|
|
2579
|
-
options: types.map((t) => ({
|
|
2580
|
-
value: t,
|
|
2581
|
-
label: TYPE_LABEL[t],
|
|
2582
|
-
count: count((q) => q.parts.some((p) => p.field.type === t))
|
|
2583
|
-
}))
|
|
2584
|
-
}
|
|
2585
|
-
) })
|
|
2586
|
-
] }) }),
|
|
2707
|
+
menu.length > 0 && /* @__PURE__ */ jsx22(Popover2, { label: "Filters", icon: SlidersHorizontal, badge: fs.inUse.length, align: "right", children: /* @__PURE__ */ jsx22("div", { className: "flex flex-col gap-4", children: sections.map((sec) => /* @__PURE__ */ jsx22(FilterSection, { title: sec, children: menu.filter((g) => (g.section ?? "") === sec).map((g) => /* @__PURE__ */ jsx22(
|
|
2708
|
+
ChipGroup2,
|
|
2709
|
+
{
|
|
2710
|
+
title: g.title,
|
|
2711
|
+
value: g.value,
|
|
2712
|
+
onChange: g.set,
|
|
2713
|
+
options: g.options
|
|
2714
|
+
},
|
|
2715
|
+
g.id
|
|
2716
|
+
)) }, sec)) }) }),
|
|
2587
2717
|
/* @__PURE__ */ jsxs16("span", { className: "text-[12.5px] text-muted sm:ml-auto", "aria-live": "polite", children: [
|
|
2588
|
-
/* @__PURE__ */
|
|
2718
|
+
/* @__PURE__ */ jsx22("b", { className: "font-semibold text-ink-2", children: list.length }),
|
|
2589
2719
|
" of ",
|
|
2590
|
-
|
|
2720
|
+
fs.total,
|
|
2591
2721
|
" questions"
|
|
2592
2722
|
] }),
|
|
2593
|
-
active && /* @__PURE__ */
|
|
2723
|
+
fs.active && /* @__PURE__ */ jsx22(Button2, { variant: "secondary", size: "sm", onClick: fs.clear, children: "Clear all" })
|
|
2594
2724
|
] }),
|
|
2595
|
-
|
|
2596
|
-
/* @__PURE__ */
|
|
2597
|
-
/* @__PURE__ */
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
label: "All",
|
|
2601
|
-
count: questions.length,
|
|
2602
|
-
active: filter.category === "all",
|
|
2603
|
-
onClick: () => set({ category: "all" })
|
|
2604
|
-
}
|
|
2605
|
-
),
|
|
2606
|
-
categories.map((c) => /* @__PURE__ */ jsx21(
|
|
2607
|
-
Chip,
|
|
2725
|
+
bar.map((g) => /* @__PURE__ */ jsxs16("div", { role: "group", "aria-label": g.title, className: "flex flex-wrap items-center gap-1.5", children: [
|
|
2726
|
+
/* @__PURE__ */ jsx22("span", { className: "mr-1 text-[11.5px] font-semibold tracking-wide text-muted uppercase", children: g.title }),
|
|
2727
|
+
/* @__PURE__ */ jsx22(Chip2, { label: "All", count: fs.total, active: g.value == null, onClick: () => g.set(null) }),
|
|
2728
|
+
g.options.map((o) => /* @__PURE__ */ jsx22(
|
|
2729
|
+
Chip2,
|
|
2608
2730
|
{
|
|
2609
|
-
label:
|
|
2610
|
-
count: count
|
|
2611
|
-
active:
|
|
2612
|
-
onClick: () => set(
|
|
2731
|
+
label: o.label,
|
|
2732
|
+
count: o.count,
|
|
2733
|
+
active: g.value === o.value,
|
|
2734
|
+
onClick: () => g.set(g.value === o.value ? null : o.value)
|
|
2613
2735
|
},
|
|
2614
|
-
|
|
2736
|
+
o.value
|
|
2615
2737
|
))
|
|
2616
|
-
] }),
|
|
2617
|
-
inUse.length > 0 && /* @__PURE__ */
|
|
2738
|
+
] }, g.id)),
|
|
2739
|
+
fs.inUse.length > 0 && /* @__PURE__ */ jsx22("div", { className: "flex flex-wrap items-center gap-1.5", children: fs.inUse.map((f) => /* @__PURE__ */ jsx22(RemovableChip2, { label: f.label, onRemove: f.clear }, f.id)) })
|
|
2618
2740
|
] }),
|
|
2619
|
-
list.length === 0 && /* @__PURE__ */
|
|
2620
|
-
/* @__PURE__ */
|
|
2741
|
+
list.length === 0 && /* @__PURE__ */ jsx22(EmptyText2, { children: "No question matches these filters." }),
|
|
2742
|
+
/* @__PURE__ */ jsx22("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: list.slice(0, shown).map((q, i, all) => /* @__PURE__ */ jsxs16(Fragment5, { children: [
|
|
2621
2743
|
q.section && q.section !== all[i - 1]?.section && /* @__PURE__ */ jsxs16("li", { className: "mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 border-b border-line pb-1", children: [
|
|
2622
|
-
/* @__PURE__ */
|
|
2623
|
-
Object.entries(q.tags ?? {}).map(([k, v]) => /* @__PURE__ */ jsxs16(
|
|
2744
|
+
/* @__PURE__ */ jsx22("span", { className: "text-[13.5px] font-semibold text-primary lg:text-[15px]", children: q.section }),
|
|
2745
|
+
Object.entries(q.tags ?? {}).map(([k, v]) => /* @__PURE__ */ jsxs16(Tag2, { tone: "muted", children: [
|
|
2624
2746
|
k,
|
|
2625
2747
|
": ",
|
|
2626
2748
|
v
|
|
2627
2749
|
] }, k))
|
|
2628
2750
|
] }),
|
|
2629
|
-
q.intro && (q.intro !== all[i - 1]?.intro || q.section !== all[i - 1]?.section) && /* @__PURE__ */
|
|
2751
|
+
q.intro && (q.intro !== all[i - 1]?.intro || q.section !== all[i - 1]?.section) && /* @__PURE__ */ jsx22("li", { className: "rounded-md border-l-4 border-primary bg-primary-soft px-3 py-2 text-[13px] font-medium text-ink-2 lg:text-[14px]", children: q.intro }),
|
|
2630
2752
|
/* @__PURE__ */ jsxs16("li", { className: "rounded-lg border border-line bg-white p-3", children: [
|
|
2631
2753
|
/* @__PURE__ */ jsxs16("div", { className: "mb-2 flex items-start gap-2", children: [
|
|
2632
|
-
/* @__PURE__ */
|
|
2633
|
-
q.category && /* @__PURE__ */
|
|
2634
|
-
/* @__PURE__ */ jsxs16(
|
|
2754
|
+
/* @__PURE__ */ jsx22("p", { className: "m-0 min-w-0 flex-1 text-[13.5px] font-semibold whitespace-pre-line text-ink-2 lg:text-[15px]", children: q.title }),
|
|
2755
|
+
q.category && /* @__PURE__ */ jsx22(Tag2, { tone: "muted", children: q.category }),
|
|
2756
|
+
/* @__PURE__ */ jsxs16(Tag2, { tone: "muted", children: [
|
|
2635
2757
|
"p.",
|
|
2636
2758
|
q.page
|
|
2637
2759
|
] })
|
|
2638
2760
|
] }),
|
|
2639
|
-
q.columns ? /* @__PURE__ */
|
|
2640
|
-
q.rows && /* @__PURE__ */
|
|
2641
|
-
q.parts.length > gridSize(q) && /* @__PURE__ */
|
|
2761
|
+
q.columns ? /* @__PURE__ */ jsx22(TableGrid, { q, control }) : /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
2762
|
+
q.rows && /* @__PURE__ */ jsx22(OptionGrid, { rows: q.rows, parts: q.parts.slice(0, gridSize(q)), control }),
|
|
2763
|
+
q.parts.length > gridSize(q) && /* @__PURE__ */ jsx22("div", { className: `flex flex-wrap items-end gap-x-4 gap-y-2 ${q.rows ? "mt-3" : ""}`, children: q.parts.slice(gridSize(q)).map(({ field, name }) => /* @__PURE__ */ jsxs16(
|
|
2642
2764
|
"div",
|
|
2643
2765
|
{
|
|
2644
2766
|
className: `flex min-w-0 flex-col gap-1 ${wide(field.type) ? "basis-full" : ""} ${field.type === "checkbox" ? "flex-row-reverse items-center justify-end gap-2" : "max-sm:basis-full"}`,
|
|
2645
2767
|
children: [
|
|
2646
|
-
name && /* @__PURE__ */
|
|
2647
|
-
/* @__PURE__ */
|
|
2768
|
+
name && /* @__PURE__ */ jsx22("span", { className: "text-[12px] text-muted lg:text-[13.5px]", children: name }),
|
|
2769
|
+
/* @__PURE__ */ jsx22(
|
|
2648
2770
|
"div",
|
|
2649
2771
|
{
|
|
2650
2772
|
className: `relative [container-type:size] ${SIZE[field.type] ?? "h-9 w-full sm:w-80 lg:h-10 lg:w-96"}`,
|
|
@@ -2658,7 +2780,7 @@ function QuestionList({
|
|
|
2658
2780
|
] })
|
|
2659
2781
|
] })
|
|
2660
2782
|
] }, q.id)) }),
|
|
2661
|
-
list.length > shown && /* @__PURE__ */
|
|
2783
|
+
list.length > shown && /* @__PURE__ */ jsx22("div", { className: "mt-3 text-center", children: /* @__PURE__ */ jsxs16(Button2, { variant: "secondary", size: "sm", onClick: () => setShown((n) => n + PAGE), children: [
|
|
2662
2784
|
"Show more (",
|
|
2663
2785
|
list.length - shown,
|
|
2664
2786
|
" left)"
|
|
@@ -2671,7 +2793,7 @@ function OptionGrid({
|
|
|
2671
2793
|
control
|
|
2672
2794
|
}) {
|
|
2673
2795
|
const starts = new Set(rows.map((_, i) => rows.slice(0, i).reduce((a, b) => a + b, 0)));
|
|
2674
|
-
return /* @__PURE__ */
|
|
2796
|
+
return /* @__PURE__ */ jsx22(
|
|
2675
2797
|
"div",
|
|
2676
2798
|
{
|
|
2677
2799
|
className: "grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-[repeat(var(--cols),minmax(0,1fr))]",
|
|
@@ -2681,8 +2803,8 @@ function OptionGrid({
|
|
|
2681
2803
|
{
|
|
2682
2804
|
className: `flex min-w-0 items-center gap-2 text-[13px] text-ink-2 lg:text-[14.5px] ${starts.has(i) ? "sm:col-start-1" : ""}`,
|
|
2683
2805
|
children: [
|
|
2684
|
-
/* @__PURE__ */
|
|
2685
|
-
/* @__PURE__ */
|
|
2806
|
+
/* @__PURE__ */ jsx22("div", { className: "relative h-6 w-6 shrink-0 [container-type:size] lg:h-7 lg:w-7", children: control(field) }),
|
|
2807
|
+
/* @__PURE__ */ jsx22(
|
|
2686
2808
|
"span",
|
|
2687
2809
|
{
|
|
2688
2810
|
className: "min-w-0 cursor-pointer break-words",
|
|
@@ -2699,13 +2821,13 @@ function OptionGrid({
|
|
|
2699
2821
|
}
|
|
2700
2822
|
function TableGrid({ q, control }) {
|
|
2701
2823
|
const rows = q.rows.map((n, i) => q.parts.slice(q.rows.slice(0, i).reduce((a, b) => a + b, 0)).slice(0, n));
|
|
2702
|
-
return /* @__PURE__ */
|
|
2703
|
-
/* @__PURE__ */
|
|
2704
|
-
q.rowNames && /* @__PURE__ */
|
|
2705
|
-
q.columns.map((c, i) => /* @__PURE__ */
|
|
2824
|
+
return /* @__PURE__ */ jsx22("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs16("table", { className: "w-full border-separate border-spacing-x-2 border-spacing-y-1.5 text-left", children: [
|
|
2825
|
+
/* @__PURE__ */ jsx22("thead", { className: q.columns.some(Boolean) ? "" : "sr-only", children: /* @__PURE__ */ jsxs16("tr", { children: [
|
|
2826
|
+
q.rowNames && /* @__PURE__ */ jsx22("th", {}),
|
|
2827
|
+
q.columns.map((c, i) => /* @__PURE__ */ jsx22("th", { scope: "col", className: "min-w-32 px-0 text-[12px] font-medium text-muted lg:text-[13.5px]", children: c }, i))
|
|
2706
2828
|
] }) }),
|
|
2707
|
-
/* @__PURE__ */
|
|
2708
|
-
q.rowNames && /* @__PURE__ */
|
|
2829
|
+
/* @__PURE__ */ jsx22("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsxs16("tr", { children: [
|
|
2830
|
+
q.rowNames && /* @__PURE__ */ jsx22(
|
|
2709
2831
|
"th",
|
|
2710
2832
|
{
|
|
2711
2833
|
scope: "row",
|
|
@@ -2713,7 +2835,7 @@ function TableGrid({ q, control }) {
|
|
|
2713
2835
|
children: q.rowNames[r]
|
|
2714
2836
|
}
|
|
2715
2837
|
),
|
|
2716
|
-
row.map(({ field }) => /* @__PURE__ */
|
|
2838
|
+
row.map(({ field }) => /* @__PURE__ */ jsx22("td", { className: "p-0 align-middle", children: /* @__PURE__ */ jsx22(
|
|
2717
2839
|
"div",
|
|
2718
2840
|
{
|
|
2719
2841
|
className: `relative [container-type:size] ${field.type === "checkbox" ? "h-7 w-7" : `${field.type === "textarea" ? "h-16" : "h-9 lg:h-10"} w-full min-w-32`}`,
|
|
@@ -2726,25 +2848,15 @@ function TableGrid({ q, control }) {
|
|
|
2726
2848
|
function FilterSection({ title, action, children }) {
|
|
2727
2849
|
return /* @__PURE__ */ jsxs16("section", { className: "flex flex-col gap-2 border-b border-line-soft pb-3 last:border-0 last:pb-0", children: [
|
|
2728
2850
|
/* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
2729
|
-
/* @__PURE__ */
|
|
2851
|
+
/* @__PURE__ */ jsx22("h4", { className: "m-0 text-[13px] font-semibold text-ink", children: title }),
|
|
2730
2852
|
action
|
|
2731
2853
|
] }),
|
|
2732
2854
|
children
|
|
2733
2855
|
] });
|
|
2734
2856
|
}
|
|
2735
2857
|
|
|
2736
|
-
// src/react/
|
|
2737
|
-
import {
|
|
2738
|
-
var humanSize = (n) => {
|
|
2739
|
-
if (!n) return "";
|
|
2740
|
-
const units = ["B", "KB", "MB", "GB"];
|
|
2741
|
-
let i = 0;
|
|
2742
|
-
while (n >= 1024 && i < units.length - 1) {
|
|
2743
|
-
n /= 1024;
|
|
2744
|
-
i++;
|
|
2745
|
-
}
|
|
2746
|
-
return `${n.toFixed(i ? 1 : 0)} ${units[i]}`;
|
|
2747
|
-
};
|
|
2858
|
+
// src/react/hooks/useFormFiller.ts
|
|
2859
|
+
import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef7, useState as useState12 } from "react";
|
|
2748
2860
|
function download(blob, name) {
|
|
2749
2861
|
const url = URL.createObjectURL(blob);
|
|
2750
2862
|
const a = document.createElement("a");
|
|
@@ -2753,11 +2865,9 @@ function download(blob, name) {
|
|
|
2753
2865
|
a.click();
|
|
2754
2866
|
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
2755
2867
|
}
|
|
2756
|
-
function
|
|
2757
|
-
pdf: pdfSource,
|
|
2868
|
+
function useFormFiller({
|
|
2758
2869
|
fields,
|
|
2759
2870
|
layout = null,
|
|
2760
|
-
title,
|
|
2761
2871
|
initialAnswers,
|
|
2762
2872
|
files: initialFiles = [],
|
|
2763
2873
|
readOnly = false,
|
|
@@ -2766,36 +2876,29 @@ function FormFiller({
|
|
|
2766
2876
|
autosave = 2500,
|
|
2767
2877
|
onUpload,
|
|
2768
2878
|
loadFile,
|
|
2769
|
-
|
|
2770
|
-
toolbar,
|
|
2879
|
+
toQuestions: toQuestions2 = toQuestions,
|
|
2771
2880
|
notify = () => {
|
|
2772
2881
|
},
|
|
2773
|
-
onError: notifyError = (e) => console.error(e)
|
|
2774
|
-
className = "h-full"
|
|
2882
|
+
onError: notifyError = (e) => console.error(e)
|
|
2775
2883
|
}) {
|
|
2776
|
-
const [answers, setAnswers] =
|
|
2777
|
-
const [files, setFiles] =
|
|
2778
|
-
const [signatures, setSignatures] =
|
|
2779
|
-
const [errors, setErrors] =
|
|
2780
|
-
const [
|
|
2781
|
-
const
|
|
2782
|
-
const
|
|
2783
|
-
const
|
|
2784
|
-
const
|
|
2785
|
-
const
|
|
2786
|
-
|
|
2787
|
-
const [loadError, setLoadError] = useState10("");
|
|
2788
|
-
const queue = useRef6(Promise.resolve());
|
|
2789
|
-
const latest = useRef6(answers);
|
|
2790
|
-
useEffect5(() => {
|
|
2884
|
+
const [answers, setAnswers] = useState12(() => initialAnswers ?? defaultAnswers(fields));
|
|
2885
|
+
const [files, setFiles] = useState12(initialFiles);
|
|
2886
|
+
const [signatures, setSignatures] = useState12({});
|
|
2887
|
+
const [errors, setErrors] = useState12({});
|
|
2888
|
+
const [saveState, setSaveState] = useState12("saved");
|
|
2889
|
+
const [hasSaved, setHasSaved] = useState12(!!initialAnswers);
|
|
2890
|
+
const [pdf, setPdf] = useState12(null);
|
|
2891
|
+
const [pdfSizes, setPdfSizes] = useState12([]);
|
|
2892
|
+
const queue = useRef7(Promise.resolve());
|
|
2893
|
+
const latest = useRef7(answers);
|
|
2894
|
+
useEffect7(() => {
|
|
2791
2895
|
latest.current = answers;
|
|
2792
2896
|
}, [answers]);
|
|
2793
2897
|
const states = useMemo3(() => fieldStates(fields, answers), [fields, answers]);
|
|
2794
|
-
const [
|
|
2795
|
-
const [
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
if (view !== "list" || !pdf || pageText) return;
|
|
2898
|
+
const [wantText, setWantText] = useState12(false);
|
|
2899
|
+
const [pageText, setPageText] = useState12(null);
|
|
2900
|
+
useEffect7(() => {
|
|
2901
|
+
if (!wantText || !pdf || pageText) return;
|
|
2799
2902
|
let live = true;
|
|
2800
2903
|
(async () => {
|
|
2801
2904
|
const out = {};
|
|
@@ -2808,7 +2911,7 @@ function FormFiller({
|
|
|
2808
2911
|
return () => {
|
|
2809
2912
|
live = false;
|
|
2810
2913
|
};
|
|
2811
|
-
}, [
|
|
2914
|
+
}, [wantText, pdf, pageText]);
|
|
2812
2915
|
const visibleKeys = fields.filter((f) => states[f.key]?.visible).map((f) => f.key).join(",");
|
|
2813
2916
|
const questions = useMemo3(() => {
|
|
2814
2917
|
const keys = new Set(visibleKeys.split(","));
|
|
@@ -2816,13 +2919,13 @@ function FormFiller({
|
|
|
2816
2919
|
const source = layout ? layout.pages[page - 1]?.source : page;
|
|
2817
2920
|
return source ? pageText?.[source] : void 0;
|
|
2818
2921
|
};
|
|
2819
|
-
return
|
|
2922
|
+
return toQuestions2(
|
|
2820
2923
|
fields.filter((f) => keys.has(f.key)),
|
|
2821
2924
|
textOf
|
|
2822
2925
|
);
|
|
2823
|
-
}, [fields, layout, visibleKeys, pageText]);
|
|
2824
|
-
const loading =
|
|
2825
|
-
|
|
2926
|
+
}, [fields, layout, visibleKeys, pageText, toQuestions2]);
|
|
2927
|
+
const loading = useRef7(/* @__PURE__ */ new Set());
|
|
2928
|
+
useEffect7(() => {
|
|
2826
2929
|
if (!loadFile) return;
|
|
2827
2930
|
for (const f of fields) {
|
|
2828
2931
|
const v = answers[f.key];
|
|
@@ -2832,11 +2935,11 @@ function FormFiller({
|
|
|
2832
2935
|
loadFile(v).then((blob) => setSignatures((m) => ({ ...m, [v]: URL.createObjectURL(blob) }))).catch(() => loading.current.delete(v));
|
|
2833
2936
|
}
|
|
2834
2937
|
}, [fields, answers, signatures, loadFile]);
|
|
2835
|
-
const urls =
|
|
2836
|
-
|
|
2938
|
+
const urls = useRef7(signatures);
|
|
2939
|
+
useEffect7(() => {
|
|
2837
2940
|
urls.current = signatures;
|
|
2838
2941
|
}, [signatures]);
|
|
2839
|
-
|
|
2942
|
+
useEffect7(() => () => Object.values(urls.current).forEach((u) => URL.revokeObjectURL(u)), []);
|
|
2840
2943
|
const save = useCallback3(
|
|
2841
2944
|
(submit2 = false) => {
|
|
2842
2945
|
if (!onSave) return Promise.resolve(true);
|
|
@@ -2860,7 +2963,7 @@ function FormFiller({
|
|
|
2860
2963
|
},
|
|
2861
2964
|
[onSave, notifyError]
|
|
2862
2965
|
);
|
|
2863
|
-
|
|
2966
|
+
useEffect7(() => {
|
|
2864
2967
|
if (readOnly || autosave === false || !onSave || saveState !== "dirty") return;
|
|
2865
2968
|
const t = setTimeout(() => void save(), autosave);
|
|
2866
2969
|
return () => clearTimeout(t);
|
|
@@ -2889,135 +2992,248 @@ function FormFiller({
|
|
|
2889
2992
|
}
|
|
2890
2993
|
}
|
|
2891
2994
|
const fetchFile = (id, then) => loadFile?.(id).then(then, () => notifyError("Unable to open the file"));
|
|
2995
|
+
const openFile = (id) => fetchFile(id, (blob) => {
|
|
2996
|
+
const url = URL.createObjectURL(blob);
|
|
2997
|
+
window.open(url, "_blank");
|
|
2998
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
2999
|
+
});
|
|
3000
|
+
const downloadFile = (id, name) => fetchFile(id, (blob) => download(blob, name));
|
|
2892
3001
|
async function submit() {
|
|
2893
|
-
setConfirmSubmit(false);
|
|
2894
3002
|
const found = validateAnswers(fields, answers);
|
|
2895
3003
|
setErrors(found);
|
|
2896
|
-
if (Object.keys(found).length)
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
onSubmitted?.(latest.current);
|
|
3004
|
+
if (Object.keys(found).length) {
|
|
3005
|
+
notifyError("Some answers need attention");
|
|
3006
|
+
return false;
|
|
2900
3007
|
}
|
|
3008
|
+
if (!await save(true)) return false;
|
|
3009
|
+
notify("Submitted");
|
|
3010
|
+
onSubmitted?.(latest.current);
|
|
3011
|
+
return true;
|
|
2901
3012
|
}
|
|
2902
|
-
if (loadError) return /* @__PURE__ */ jsx22("p", { className: "fl-root p-6 text-center text-sm text-danger", children: loadError });
|
|
2903
3013
|
const fileOf = (id) => files.find((x) => x.id === id);
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
3014
|
+
return {
|
|
3015
|
+
answers,
|
|
3016
|
+
/** Visible / required / readOnly per field key, from the conditions and current answers. */
|
|
3017
|
+
states,
|
|
3018
|
+
/** Field key → message. */
|
|
3019
|
+
errors,
|
|
3020
|
+
setErrors,
|
|
3021
|
+
/** Fields with an error, in form order. */
|
|
3022
|
+
errorList: fields.filter((f) => errors[f.key]),
|
|
3023
|
+
change,
|
|
3024
|
+
save,
|
|
3025
|
+
submit,
|
|
3026
|
+
saveState,
|
|
3027
|
+
hasSaved,
|
|
3028
|
+
readOnly,
|
|
3029
|
+
files,
|
|
3030
|
+
fileOf,
|
|
3031
|
+
/** Files of upload fields that are answers now. */
|
|
3032
|
+
attachments: files.filter(
|
|
3033
|
+
(x) => fields.some((f) => f.type === "file" && f.key === x.fieldKey && answers[f.key] === x.id)
|
|
3034
|
+
),
|
|
3035
|
+
attach,
|
|
3036
|
+
canUpload: !!onUpload,
|
|
3037
|
+
canLoadFiles: !!loadFile,
|
|
3038
|
+
openFile,
|
|
3039
|
+
downloadFile,
|
|
3040
|
+
/** Image src of a signature answer (stored file or data URL). */
|
|
3041
|
+
signatureSrc: (f) => signatures[String(answers[f.key])],
|
|
3042
|
+
/** Visible fields grouped as questions (tables, option groups), titled from the PDF text once loaded. */
|
|
3043
|
+
questions,
|
|
3044
|
+
/** Reads the PDF text so `questions` get real titles (call when you first show them). */
|
|
3045
|
+
loadPageText: () => setWantText(true),
|
|
3046
|
+
pdf,
|
|
3047
|
+
pdfSizes,
|
|
3048
|
+
/** Pass to PdfPages' onLoaded. */
|
|
3049
|
+
onPdfLoaded: (sizes2, doc) => {
|
|
3050
|
+
setPdf(doc);
|
|
3051
|
+
setPdfSizes(sizes2);
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
// src/react/FormFiller.tsx
|
|
3057
|
+
import { Fragment as Fragment7, jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3058
|
+
var humanSize = (n) => {
|
|
3059
|
+
if (!n) return "";
|
|
3060
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
3061
|
+
let i = 0;
|
|
3062
|
+
while (n >= 1024 && i < units.length - 1) {
|
|
3063
|
+
n /= 1024;
|
|
3064
|
+
i++;
|
|
3065
|
+
}
|
|
3066
|
+
return `${n.toFixed(i ? 1 : 0)} ${units[i]}`;
|
|
3067
|
+
};
|
|
3068
|
+
function FormFiller({
|
|
3069
|
+
pdf: pdfSource,
|
|
3070
|
+
title,
|
|
3071
|
+
onBack,
|
|
3072
|
+
toolbar,
|
|
3073
|
+
components,
|
|
3074
|
+
hide = [],
|
|
3075
|
+
views = ["document", "list"],
|
|
3076
|
+
listFilters,
|
|
3077
|
+
listSearch,
|
|
3078
|
+
hideUnavailableFilters,
|
|
3079
|
+
renderFilters,
|
|
3080
|
+
confirmSubmit: askFirst = true,
|
|
3081
|
+
className = "h-full",
|
|
3082
|
+
...options
|
|
3083
|
+
}) {
|
|
3084
|
+
const shows = (p) => !hide.includes(p);
|
|
3085
|
+
const ff = useFormFiller(options);
|
|
3086
|
+
const { fields, readOnly = false, onSave } = options;
|
|
3087
|
+
const layout = options.layout ?? null;
|
|
3088
|
+
const { answers, states, errors } = ff;
|
|
3089
|
+
const pdfWidth = ff.pdfSizes.length ? Math.max(...ff.pdfSizes.map((p) => p.width)) : 595;
|
|
3090
|
+
const { zoom, setZoom, ref: fitRef } = useFitZoom(Math.max(...layout?.pages.map((p) => p.width) ?? [pdfWidth]));
|
|
3091
|
+
const compact = useCompact();
|
|
3092
|
+
const [confirmSubmit, setConfirmSubmit] = useState13(false);
|
|
3093
|
+
const [loadError, setLoadError] = useState13("");
|
|
3094
|
+
const [view, setView] = useState13(views[0] ?? "document");
|
|
3095
|
+
const { loadPageText } = ff;
|
|
3096
|
+
useEffect8(() => {
|
|
3097
|
+
if (view === "list") loadPageText();
|
|
3098
|
+
}, [view, loadPageText]);
|
|
3099
|
+
if (loadError) return /* @__PURE__ */ jsx23("p", { className: "fl-root p-6 text-center text-sm text-danger", children: loadError });
|
|
2908
3100
|
const control = (f) => /* @__PURE__ */ jsxs17(Fragment7, { children: [
|
|
2909
|
-
/* @__PURE__ */
|
|
2910
|
-
|
|
3101
|
+
/* @__PURE__ */ jsx23(
|
|
3102
|
+
AnswerControl,
|
|
2911
3103
|
{
|
|
3104
|
+
components,
|
|
2912
3105
|
field: f,
|
|
2913
3106
|
value: answers[f.key],
|
|
2914
|
-
readOnly: readOnly || states[f.key].readOnly || f.type === "file" && !
|
|
3107
|
+
readOnly: readOnly || states[f.key].readOnly || f.type === "file" && !ff.canUpload,
|
|
2915
3108
|
compact,
|
|
2916
3109
|
error: errors[f.key],
|
|
2917
|
-
onChange: (v) => change(f, v),
|
|
2918
|
-
fileName: fileOf(answers[f.key])?.fileName,
|
|
2919
|
-
signatureSrc:
|
|
2920
|
-
onFile:
|
|
2921
|
-
onOpenFile: () =>
|
|
2922
|
-
const url = URL.createObjectURL(blob);
|
|
2923
|
-
window.open(url, "_blank");
|
|
2924
|
-
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
2925
|
-
})
|
|
3110
|
+
onChange: (v) => ff.change(f, v),
|
|
3111
|
+
fileName: ff.fileOf(answers[f.key])?.fileName,
|
|
3112
|
+
signatureSrc: ff.signatureSrc(f),
|
|
3113
|
+
onFile: ff.canUpload ? (file) => void ff.attach(f, file) : void 0,
|
|
3114
|
+
onOpenFile: () => ff.openFile(String(answers[f.key]))
|
|
2926
3115
|
}
|
|
2927
3116
|
),
|
|
2928
|
-
states[f.key].required && !readOnly && /* @__PURE__ */
|
|
3117
|
+
states[f.key].required && !readOnly && shows("requiredMark") && /* @__PURE__ */ jsx23("span", { className: "pointer-events-none absolute -top-1.5 -right-1.5 text-[12px] font-bold text-danger", children: "*" })
|
|
2929
3118
|
] });
|
|
2930
|
-
return /* @__PURE__ */ jsxs17("div", { className: `fl-root flex min-h-[min(600px,100dvh)] flex-col bg-white text-ink ${className}`, children: [
|
|
3119
|
+
return /* @__PURE__ */ jsx23(FormliftProvider, { components, children: /* @__PURE__ */ jsxs17("div", { className: `fl-root flex min-h-[min(600px,100dvh)] flex-col bg-white text-ink ${className}`, children: [
|
|
2931
3120
|
/* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-center gap-3 border-b border-line bg-white px-4 py-2.5", children: [
|
|
2932
|
-
onBack && /* @__PURE__ */
|
|
2933
|
-
title != null && /* @__PURE__ */
|
|
2934
|
-
/* @__PURE__ */
|
|
2935
|
-
|
|
3121
|
+
onBack && shows("back") && /* @__PURE__ */ jsx23(Button2, { variant: "secondary", size: "sm", icon: ArrowLeft2, onClick: onBack, children: "Back" }),
|
|
3122
|
+
title != null && shows("title") && /* @__PURE__ */ jsx23("h2", { className: "m-0 min-w-0 flex-1 truncate text-[17px] font-semibold max-sm:text-[15px] sm:flex-none", children: title }),
|
|
3123
|
+
views.length > 1 && shows("views") && /* @__PURE__ */ jsx23(
|
|
3124
|
+
SegmentedTabs2,
|
|
2936
3125
|
{
|
|
2937
|
-
options:
|
|
2938
|
-
{ value: "document", label: "Document" },
|
|
2939
|
-
{ value: "list", label: "List" }
|
|
2940
|
-
],
|
|
3126
|
+
options: views.map((v) => ({ value: v, label: v === "document" ? "Document" : "List" })),
|
|
2941
3127
|
value: view,
|
|
2942
3128
|
onChange: setView
|
|
2943
3129
|
}
|
|
2944
3130
|
),
|
|
2945
3131
|
/* @__PURE__ */ jsxs17("div", { className: "ml-auto flex flex-wrap items-center gap-2 max-sm:w-full max-sm:justify-end", children: [
|
|
2946
|
-
view === "document" && /* @__PURE__ */
|
|
3132
|
+
view === "document" && shows("zoom") && /* @__PURE__ */ jsx23(ZoomControls, { zoom, onChange: setZoom }),
|
|
2947
3133
|
!readOnly && onSave && /* @__PURE__ */ jsxs17(Fragment7, { children: [
|
|
2948
|
-
/* @__PURE__ */
|
|
2949
|
-
saved: hasSaved ? "Draft saved" : "",
|
|
3134
|
+
shows("saveStatus") && /* @__PURE__ */ jsx23("span", { className: "text-right text-[12px] text-muted sm:w-24", "aria-live": "polite", children: {
|
|
3135
|
+
saved: ff.hasSaved ? "Draft saved" : "",
|
|
2950
3136
|
dirty: "Unsaved",
|
|
2951
3137
|
saving: "Saving\u2026",
|
|
2952
3138
|
error: "Not saved"
|
|
2953
|
-
}[saveState] }),
|
|
2954
|
-
/* @__PURE__ */
|
|
2955
|
-
/* @__PURE__ */
|
|
3139
|
+
}[ff.saveState] }),
|
|
3140
|
+
shows("saveDraft") && /* @__PURE__ */ jsx23(Button2, { variant: "secondary", size: "sm", icon: Save2, title: "Save draft", onClick: () => void ff.save(), children: /* @__PURE__ */ jsx23("span", { className: "max-sm:hidden", children: "Save draft" }) }),
|
|
3141
|
+
shows("submit") && /* @__PURE__ */ jsx23(Button2, { size: "sm", icon: Send, onClick: () => askFirst ? setConfirmSubmit(true) : void ff.submit(), children: "Submit" })
|
|
2956
3142
|
] }),
|
|
2957
3143
|
toolbar
|
|
2958
3144
|
] })
|
|
2959
3145
|
] }),
|
|
2960
|
-
attachments.length > 0 && /* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 border-b border-line bg-white px-4 py-1.5 text-[12.5px]", children: [
|
|
3146
|
+
ff.attachments.length > 0 && shows("attachments") && /* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 border-b border-line bg-white px-4 py-1.5 text-[12.5px]", children: [
|
|
2961
3147
|
/* @__PURE__ */ jsxs17("span", { className: "inline-flex items-center gap-1 font-semibold text-ink-2", children: [
|
|
2962
|
-
/* @__PURE__ */
|
|
3148
|
+
/* @__PURE__ */ jsx23(Paperclip2, { size: 13 }),
|
|
2963
3149
|
" Attachments"
|
|
2964
3150
|
] }),
|
|
2965
|
-
attachments.map((x) => /* @__PURE__ */ jsxs17(
|
|
3151
|
+
ff.attachments.map((x) => /* @__PURE__ */ jsxs17(
|
|
2966
3152
|
"button",
|
|
2967
3153
|
{
|
|
2968
3154
|
type: "button",
|
|
2969
3155
|
title: `Download ${x.fileName}`,
|
|
2970
|
-
disabled: !
|
|
3156
|
+
disabled: !ff.canLoadFiles,
|
|
2971
3157
|
className: "inline-flex max-w-full cursor-pointer items-center gap-1 text-primary hover:underline disabled:cursor-default",
|
|
2972
|
-
onClick: () =>
|
|
3158
|
+
onClick: () => ff.downloadFile(x.id, x.fileName),
|
|
2973
3159
|
children: [
|
|
2974
|
-
/* @__PURE__ */
|
|
3160
|
+
/* @__PURE__ */ jsx23("span", { className: "truncate", children: x.fileName }),
|
|
2975
3161
|
x.fileSize ? /* @__PURE__ */ jsxs17("span", { className: "text-muted", children: [
|
|
2976
3162
|
"(",
|
|
2977
3163
|
humanSize(x.fileSize),
|
|
2978
3164
|
")"
|
|
2979
3165
|
] }) : null,
|
|
2980
|
-
|
|
3166
|
+
ff.canLoadFiles && /* @__PURE__ */ jsx23(Download, { size: 12 })
|
|
2981
3167
|
]
|
|
2982
3168
|
},
|
|
2983
3169
|
x.id
|
|
2984
3170
|
))
|
|
2985
3171
|
] }),
|
|
2986
|
-
errorList.length > 0 && /* @__PURE__ */
|
|
3172
|
+
ff.errorList.length > 0 && shows("errors") && /* @__PURE__ */ jsx23("ul", { className: "m-0 border-b border-danger/30 bg-danger/5 px-6 py-2 text-[13px] text-danger", children: ff.errorList.map((f) => /* @__PURE__ */ jsxs17("li", { children: [
|
|
2987
3173
|
"Page ",
|
|
2988
3174
|
f.position.page,
|
|
2989
3175
|
": ",
|
|
2990
3176
|
errors[f.key]
|
|
2991
3177
|
] }, f.key)) }),
|
|
2992
|
-
view === "list" && /* @__PURE__ */
|
|
2993
|
-
|
|
3178
|
+
view === "list" && /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-auto bg-hover", children: /* @__PURE__ */ jsx23(
|
|
3179
|
+
QuestionList,
|
|
3180
|
+
{
|
|
3181
|
+
questions: ff.questions,
|
|
3182
|
+
answers,
|
|
3183
|
+
control,
|
|
3184
|
+
filters: listFilters,
|
|
3185
|
+
search: listSearch,
|
|
3186
|
+
hideUnavailable: hideUnavailableFilters,
|
|
3187
|
+
renderToolbar: renderFilters
|
|
3188
|
+
}
|
|
3189
|
+
) }),
|
|
3190
|
+
/* @__PURE__ */ jsx23("div", { ref: fitRef, className: `flex-1 overflow-auto bg-hover ${view === "list" ? "hidden" : ""}`, children: /* @__PURE__ */ jsx23(
|
|
2994
3191
|
PdfPages,
|
|
2995
3192
|
{
|
|
2996
3193
|
load: pdfSource,
|
|
2997
3194
|
zoom,
|
|
2998
3195
|
pages: layout?.pages,
|
|
2999
|
-
onLoaded:
|
|
3000
|
-
setPdf(doc);
|
|
3001
|
-
if (sizes2.length) setPdfWidth(Math.max(...sizes2.map((p) => p.width)));
|
|
3002
|
-
},
|
|
3196
|
+
onLoaded: ff.onPdfLoaded,
|
|
3003
3197
|
onError: () => setLoadError("The document could not be shown"),
|
|
3004
3198
|
overlay: (page) => [
|
|
3005
3199
|
// The admin's text / boxes / lines first, the answer controls on top.
|
|
3006
|
-
...(layout?.elements ?? []).filter((el) => el.page === page).map((el) => /* @__PURE__ */
|
|
3007
|
-
...fields.filter((f) => f.position.page === page && states[f.key]?.visible).map((f) => /* @__PURE__ */
|
|
3200
|
+
...(layout?.elements ?? []).filter((el) => el.page === page).map((el) => /* @__PURE__ */ jsx23(LayoutElementView, { element: el, pageHeight: layout.pages[page - 1]?.height ?? 842 }, el.id)),
|
|
3201
|
+
...fields.filter((f) => f.position.page === page && states[f.key]?.visible).map((f) => /* @__PURE__ */ jsx23("div", { className: "absolute [container-type:size]", style: boxStyle(f.position), children: control(f) }, f.id))
|
|
3008
3202
|
]
|
|
3009
3203
|
}
|
|
3010
3204
|
) }),
|
|
3011
|
-
confirmSubmit && /* @__PURE__ */
|
|
3012
|
-
|
|
3205
|
+
confirmSubmit && /* @__PURE__ */ jsx23(
|
|
3206
|
+
ConfirmModal2,
|
|
3207
|
+
{
|
|
3208
|
+
confirmLabel: "Submit",
|
|
3209
|
+
onConfirm: () => {
|
|
3210
|
+
setConfirmSubmit(false);
|
|
3211
|
+
void ff.submit();
|
|
3212
|
+
},
|
|
3213
|
+
onCancel: () => setConfirmSubmit(false),
|
|
3214
|
+
children: "Submit this form? You cannot change the answers afterwards."
|
|
3215
|
+
}
|
|
3216
|
+
)
|
|
3217
|
+
] }) });
|
|
3013
3218
|
}
|
|
3014
3219
|
export {
|
|
3220
|
+
AnswerControl,
|
|
3015
3221
|
FieldControl,
|
|
3016
3222
|
FormEditor,
|
|
3017
3223
|
FormFiller,
|
|
3224
|
+
FormliftProvider,
|
|
3225
|
+
LayoutElementView,
|
|
3018
3226
|
PdfPages,
|
|
3227
|
+
QuestionList,
|
|
3019
3228
|
TYPE_LABEL,
|
|
3229
|
+
defaultComponents,
|
|
3230
|
+
defaultFilters,
|
|
3020
3231
|
readPdf,
|
|
3021
|
-
setPdfWorker
|
|
3232
|
+
setPdfWorker,
|
|
3233
|
+
useFitZoom,
|
|
3234
|
+
useFormEditor,
|
|
3235
|
+
useFormFiller,
|
|
3236
|
+
useQuestionFilter,
|
|
3237
|
+
useUndo
|
|
3022
3238
|
};
|
|
3023
3239
|
//# sourceMappingURL=index.js.map
|