@nextlyhq/plugin-form-builder 0.0.2-alpha.35 → 0.0.2-alpha.39
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/dist/admin/index.d.ts +31 -22
- package/dist/admin/index.js +178 -120
- package/dist/admin/index.js.map +1 -1
- package/dist/{chunk-I4VFGPNR.js → chunk-WVI7N3QO.js} +54 -1
- package/dist/chunk-WVI7N3QO.js.map +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +24 -41
- package/dist/index.js.map +1 -1
- package/dist/{types-C-DcuYGF.d.ts → types-r8uBeB0u.d.ts} +37 -4
- package/package.json +11 -11
- package/dist/chunk-I4VFGPNR.js.map +0 -1
package/dist/admin/index.js
CHANGED
|
@@ -2,8 +2,9 @@ import {
|
|
|
2
2
|
DEFAULT_FORM_SETTINGS,
|
|
3
3
|
evaluateConditions,
|
|
4
4
|
formatExportValue,
|
|
5
|
+
isKnownFormField,
|
|
5
6
|
normalizeFormSettings
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-WVI7N3QO.js";
|
|
7
8
|
|
|
8
9
|
// src/admin/index.ts
|
|
9
10
|
import {
|
|
@@ -265,7 +266,8 @@ function SubmissionSheet({
|
|
|
265
266
|
] })
|
|
266
267
|
] }),
|
|
267
268
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-6", children: [
|
|
268
|
-
submission.status === "spam" &&
|
|
269
|
+
submission.status === "spam" && // Full-strength destructive border so the spam banner boundary is perceivable over its tinted fill.
|
|
270
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-destructive bg-destructive/5 p-3 text-sm text-destructive", children: [
|
|
269
271
|
/* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
270
272
|
"Flagged as spam",
|
|
271
273
|
submission.spamReason ? ` (${submission.spamReason})` : "",
|
|
@@ -371,7 +373,7 @@ function SubmissionSheet({
|
|
|
371
373
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
372
374
|
"IP: ",
|
|
373
375
|
submission.ipAddress || "\u2014",
|
|
374
|
-
/* @__PURE__ */ jsx("span", { className: "mx-1.5 text-muted-foreground
|
|
376
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1.5 text-muted-foreground", children: "\xB7" }),
|
|
375
377
|
"ID: ",
|
|
376
378
|
/* @__PURE__ */ jsx("span", { className: "font-mono", children: submission.id })
|
|
377
379
|
] }),
|
|
@@ -444,7 +446,8 @@ function StatusBadge({ status }) {
|
|
|
444
446
|
Badge2,
|
|
445
447
|
{
|
|
446
448
|
variant: "outline",
|
|
447
|
-
className: `rounded-none px-1.5 py-0 text-[10px] font-semibold uppercase tracking-wider ${
|
|
449
|
+
className: `rounded-none px-1.5 py-0 text-[10px] font-semibold uppercase tracking-wider ${// Full-strength status borders (destructive/primary) so each badge boundary is perceivable; the default falls back to border-border, keeping the states distinct.
|
|
450
|
+
status === "spam" ? "border-destructive text-destructive" : status === "new" ? "border-primary text-primary" : "border-border text-muted-foreground"}`,
|
|
448
451
|
children: label
|
|
449
452
|
}
|
|
450
453
|
);
|
|
@@ -908,6 +911,7 @@ import {
|
|
|
908
911
|
verticalListSortingStrategy
|
|
909
912
|
} from "@dnd-kit/sortable";
|
|
910
913
|
import { CSS } from "@dnd-kit/utilities";
|
|
914
|
+
import { usePluginFieldTypeEntries } from "@nextlyhq/plugin-sdk/admin";
|
|
911
915
|
import {
|
|
912
916
|
Button as Button5,
|
|
913
917
|
DropdownMenu as DropdownMenu2,
|
|
@@ -994,15 +998,18 @@ function createNotification() {
|
|
|
994
998
|
}
|
|
995
999
|
var FormBuilderContext = createContext(null);
|
|
996
1000
|
function generateFieldName(type, existingNames = []) {
|
|
1001
|
+
const base = type.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+/, "").replace(/^([0-9])/, "field_$1").replace(/_+$/, "") || "field";
|
|
997
1002
|
const taken = new Set(existingNames);
|
|
998
|
-
if (!taken.has(
|
|
1003
|
+
if (!taken.has(base)) return base;
|
|
999
1004
|
let suffix = 2;
|
|
1000
|
-
while (taken.has(`${
|
|
1001
|
-
return `${
|
|
1005
|
+
while (taken.has(`${base}_${suffix}`)) suffix += 1;
|
|
1006
|
+
return `${base}_${suffix}`;
|
|
1002
1007
|
}
|
|
1003
1008
|
function generateFieldLabel(type) {
|
|
1004
1009
|
const entry = FORM_FIELD_TYPE_CATALOG.find((row) => row.type === type);
|
|
1005
|
-
return entry
|
|
1010
|
+
if (entry) return entry.label;
|
|
1011
|
+
const titleCased = type.split(/[-_\s]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1012
|
+
return titleCased || "New Field";
|
|
1006
1013
|
}
|
|
1007
1014
|
function createFieldFromType(type, existingNames = []) {
|
|
1008
1015
|
const name = generateFieldName(type, existingNames);
|
|
@@ -1054,7 +1061,7 @@ function createFieldFromType(type, existingNames = []) {
|
|
|
1054
1061
|
case "hidden":
|
|
1055
1062
|
return { ...baseField, type: "hidden" };
|
|
1056
1063
|
default:
|
|
1057
|
-
return { ...baseField, type
|
|
1064
|
+
return { ...baseField, type };
|
|
1058
1065
|
}
|
|
1059
1066
|
}
|
|
1060
1067
|
function FormBuilderProvider({
|
|
@@ -1099,9 +1106,7 @@ function FormBuilderProvider({
|
|
|
1099
1106
|
const updateField = useCallback2(
|
|
1100
1107
|
(fieldName, updates) => {
|
|
1101
1108
|
setFieldsState(
|
|
1102
|
-
(prev) => prev.map(
|
|
1103
|
-
(f) => f.name === fieldName ? { ...f, ...updates } : f
|
|
1104
|
-
)
|
|
1109
|
+
(prev) => prev.map((f) => f.name === fieldName ? { ...f, ...updates } : f)
|
|
1105
1110
|
);
|
|
1106
1111
|
if (updates.name && updates.name !== fieldName) {
|
|
1107
1112
|
setSelectedFieldId(updates.name);
|
|
@@ -1341,7 +1346,8 @@ function AddFieldDialog({
|
|
|
1341
1346
|
|
|
1342
1347
|
// src/admin/components/builder/FieldEditor.tsx
|
|
1343
1348
|
import {
|
|
1344
|
-
FieldOptionsEditor
|
|
1349
|
+
FieldOptionsEditor,
|
|
1350
|
+
withOptionIds
|
|
1345
1351
|
} from "@nextlyhq/plugin-sdk/admin";
|
|
1346
1352
|
import {
|
|
1347
1353
|
FormLabelWithTooltip as FormLabelWithTooltip2,
|
|
@@ -1463,7 +1469,8 @@ function ConditionalLogicEditor({
|
|
|
1463
1469
|
}
|
|
1464
1470
|
)
|
|
1465
1471
|
] }) }),
|
|
1466
|
-
logic.enabled &&
|
|
1472
|
+
logic.enabled && // Semantic border token so this top divider is visible at the 3:1 UI minimum.
|
|
1473
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-4 pt-4 border-t border-border", children: [
|
|
1467
1474
|
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1468
1475
|
/* @__PURE__ */ jsxs4(
|
|
1469
1476
|
Select3,
|
|
@@ -1496,66 +1503,72 @@ function ConditionalLogicEditor({
|
|
|
1496
1503
|
),
|
|
1497
1504
|
/* @__PURE__ */ jsx5("span", { children: "of the following conditions are met:" })
|
|
1498
1505
|
] }),
|
|
1499
|
-
/* @__PURE__ */ jsx5("div", { className: "space-y-3 pt-4", children: logic.conditions.length === 0 ?
|
|
1500
|
-
|
|
1501
|
-
{
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
children: [
|
|
1511
|
-
/* @__PURE__ */ jsx5(SelectTrigger3, { className: "w-full bg-transparent border-input dark:bg-muted/50", children: /* @__PURE__ */ jsx5(SelectValue3, { placeholder: "Select field" }) }),
|
|
1512
|
-
/* @__PURE__ */ jsx5(SelectContent3, { children: availableFields.map((f) => /* @__PURE__ */ jsx5(SelectItem3, { value: f.name, children: f.label || f.name }, f.name)) })
|
|
1513
|
-
]
|
|
1514
|
-
}
|
|
1515
|
-
),
|
|
1516
|
-
/* @__PURE__ */ jsxs4("div", { className: "flex gap-2", children: [
|
|
1506
|
+
/* @__PURE__ */ jsx5("div", { className: "space-y-3 pt-4", children: logic.conditions.length === 0 ? (
|
|
1507
|
+
// Semantic border token so this empty-state boundary is visible at the 3:1 UI minimum.
|
|
1508
|
+
/* @__PURE__ */ jsx5("div", { className: "p-4 bg-muted rounded-none border border-dashed border-border text-center text-xs text-muted-foreground", children: "No conditions defined. Add a condition to get started." })
|
|
1509
|
+
) : logic.conditions.map((condition, index) => (
|
|
1510
|
+
// Semantic border token so this condition card boundary is visible at the 3:1 UI minimum.
|
|
1511
|
+
/* @__PURE__ */ jsxs4(
|
|
1512
|
+
"div",
|
|
1513
|
+
{
|
|
1514
|
+
className: "flex flex-col gap-2 p-3 rounded-none bg-muted border border-border relative group",
|
|
1515
|
+
children: [
|
|
1516
|
+
/* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-1 gap-2", children: [
|
|
1517
1517
|
/* @__PURE__ */ jsxs4(
|
|
1518
1518
|
Select3,
|
|
1519
1519
|
{
|
|
1520
|
-
value: condition.
|
|
1521
|
-
onValueChange: (value) => handleUpdateCondition(index, {
|
|
1522
|
-
comparison: value
|
|
1523
|
-
}),
|
|
1520
|
+
value: condition.field,
|
|
1521
|
+
onValueChange: (value) => handleUpdateCondition(index, { field: value }),
|
|
1524
1522
|
children: [
|
|
1525
|
-
/* @__PURE__ */ jsx5(SelectTrigger3, { className: "
|
|
1526
|
-
/* @__PURE__ */ jsx5(SelectContent3, { children:
|
|
1523
|
+
/* @__PURE__ */ jsx5(SelectTrigger3, { className: "w-full bg-transparent border-input dark:bg-muted/50", children: /* @__PURE__ */ jsx5(SelectValue3, { placeholder: "Select field" }) }),
|
|
1524
|
+
/* @__PURE__ */ jsx5(SelectContent3, { children: availableFields.map((f) => /* @__PURE__ */ jsx5(SelectItem3, { value: f.name, children: f.label || f.name }, f.name)) })
|
|
1527
1525
|
]
|
|
1528
1526
|
}
|
|
1529
1527
|
),
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1528
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex gap-2", children: [
|
|
1529
|
+
/* @__PURE__ */ jsxs4(
|
|
1530
|
+
Select3,
|
|
1531
|
+
{
|
|
1532
|
+
value: condition.comparison,
|
|
1533
|
+
onValueChange: (value) => handleUpdateCondition(index, {
|
|
1534
|
+
comparison: value
|
|
1535
|
+
}),
|
|
1536
|
+
children: [
|
|
1537
|
+
/* @__PURE__ */ jsx5(SelectTrigger3, { className: "flex-1 h-9 bg-transparent border-input dark:bg-muted/50", children: /* @__PURE__ */ jsx5(SelectValue3, { placeholder: "Compare operator" }) }),
|
|
1538
|
+
/* @__PURE__ */ jsx5(SelectContent3, { children: COMPARISON_OPERATORS.map((op) => /* @__PURE__ */ jsx5(SelectItem3, { value: op.value, children: op.label }, op.value)) })
|
|
1539
|
+
]
|
|
1540
|
+
}
|
|
1541
|
+
),
|
|
1542
|
+
needsValue(condition.comparison) && /* @__PURE__ */ jsx5(
|
|
1543
|
+
Input2,
|
|
1544
|
+
{
|
|
1545
|
+
type: "text",
|
|
1546
|
+
value: conditionValueToString(condition.value),
|
|
1547
|
+
onChange: (e) => handleUpdateCondition(index, {
|
|
1548
|
+
value: e.target.value
|
|
1549
|
+
}),
|
|
1550
|
+
placeholder: "Value",
|
|
1551
|
+
className: "flex-1 h-9 bg-transparent"
|
|
1552
|
+
}
|
|
1553
|
+
)
|
|
1554
|
+
] })
|
|
1555
|
+
] }),
|
|
1556
|
+
/* @__PURE__ */ jsx5(
|
|
1557
|
+
Button4,
|
|
1558
|
+
{
|
|
1559
|
+
type: "button",
|
|
1560
|
+
variant: "ghost",
|
|
1561
|
+
size: "icon-sm",
|
|
1562
|
+
onClick: () => handleRemoveCondition(index),
|
|
1563
|
+
className: "absolute -top-2 -right-2 h-6 w-6 rounded-full bg-background border border-border opacity-0 group-hover:opacity-100 transition-opacity",
|
|
1564
|
+
title: "Remove condition",
|
|
1565
|
+
children: /* @__PURE__ */ jsx5("span", { className: "text-xs", children: "\xD7" })
|
|
1566
|
+
}
|
|
1567
|
+
)
|
|
1568
|
+
]
|
|
1569
|
+
},
|
|
1570
|
+
index
|
|
1571
|
+
)
|
|
1559
1572
|
)) }),
|
|
1560
1573
|
availableFields.length > 0 ? /* @__PURE__ */ jsx5(
|
|
1561
1574
|
Button4,
|
|
@@ -1567,7 +1580,10 @@ function ConditionalLogicEditor({
|
|
|
1567
1580
|
className: "w-full mt-4 border-dashed",
|
|
1568
1581
|
children: "+ Add Condition"
|
|
1569
1582
|
}
|
|
1570
|
-
) :
|
|
1583
|
+
) : (
|
|
1584
|
+
// Full-strength warning border so this notice boundary is perceivable over its tinted fill.
|
|
1585
|
+
/* @__PURE__ */ jsx5("p", { className: "mt-4 text-center text-xs text-warning font-medium bg-warning/10 p-2 rounded-none border border-warning", children: "Add more fields to the form to create conditions." })
|
|
1586
|
+
)
|
|
1571
1587
|
] })
|
|
1572
1588
|
] });
|
|
1573
1589
|
}
|
|
@@ -1719,7 +1735,7 @@ function GeneralTab({
|
|
|
1719
1735
|
}
|
|
1720
1736
|
)
|
|
1721
1737
|
] }),
|
|
1722
|
-
/* @__PURE__ */ jsx6(TypeSpecificOptions, { field, onUpdate })
|
|
1738
|
+
isKnownFormField(field) && /* @__PURE__ */ jsx6(TypeSpecificOptions, { field, onUpdate })
|
|
1723
1739
|
] });
|
|
1724
1740
|
}
|
|
1725
1741
|
function TypeSpecificOptions({
|
|
@@ -1924,11 +1940,7 @@ function OptionsEditor({
|
|
|
1924
1940
|
}) {
|
|
1925
1941
|
const optionsField = field;
|
|
1926
1942
|
const [kitOptions, setKitOptions] = useState5(
|
|
1927
|
-
() => (optionsField.options || [])
|
|
1928
|
-
id: `opt_initial_${index}`,
|
|
1929
|
-
label: opt.label,
|
|
1930
|
-
value: opt.value
|
|
1931
|
-
}))
|
|
1943
|
+
() => withOptionIds(optionsField.options || [])
|
|
1932
1944
|
);
|
|
1933
1945
|
const handleOptionsChange = useCallback4(
|
|
1934
1946
|
(next) => {
|
|
@@ -1951,8 +1963,7 @@ function OptionsEditor({
|
|
|
1951
1963
|
FieldOptionsEditor,
|
|
1952
1964
|
{
|
|
1953
1965
|
options: kitOptions,
|
|
1954
|
-
onOptionsChange: handleOptionsChange
|
|
1955
|
-
fieldType: field.type === "radio" ? "radio" : "select"
|
|
1966
|
+
onOptionsChange: handleOptionsChange
|
|
1956
1967
|
}
|
|
1957
1968
|
)
|
|
1958
1969
|
] });
|
|
@@ -2135,7 +2146,8 @@ function ValidationTab({
|
|
|
2135
2146
|
}
|
|
2136
2147
|
)
|
|
2137
2148
|
] }),
|
|
2138
|
-
(field.type === "checkbox" || field.type === "hidden") &&
|
|
2149
|
+
(field.type === "checkbox" || field.type === "hidden") && // Semantic border token so this info-box boundary is visible at the 3:1 UI minimum.
|
|
2150
|
+
/* @__PURE__ */ jsx6("div", { className: "p-3 bg-muted rounded-none text-xs text-muted-foreground text-center border border-dashed border-border", children: "No additional validation options for this field type." })
|
|
2139
2151
|
] });
|
|
2140
2152
|
}
|
|
2141
2153
|
function FieldEditor({ field, allFields, onUpdate }) {
|
|
@@ -2147,7 +2159,7 @@ function FieldEditor({ field, allFields, onUpdate }) {
|
|
|
2147
2159
|
onValueChange: setActiveTab,
|
|
2148
2160
|
className: "flex-1 flex flex-col",
|
|
2149
2161
|
children: [
|
|
2150
|
-
/* @__PURE__ */ jsx6("div", { className: "border-b bg-muted border-
|
|
2162
|
+
/* @__PURE__ */ jsx6("div", { className: "border-b bg-muted border-border", children: /* @__PURE__ */ jsxs5(TabsList2, { className: "w-full justify-start gap-0", children: [
|
|
2151
2163
|
/* @__PURE__ */ jsx6(
|
|
2152
2164
|
TabsTrigger2,
|
|
2153
2165
|
{
|
|
@@ -2182,7 +2194,7 @@ function FieldEditor({ field, allFields, onUpdate }) {
|
|
|
2182
2194
|
onUpdate
|
|
2183
2195
|
}
|
|
2184
2196
|
) }),
|
|
2185
|
-
/* @__PURE__ */ jsx6(TabsContent, { value: "validation", className: "mt-0", children: /* @__PURE__ */ jsx6(ValidationTab, { field, onUpdate }) }),
|
|
2197
|
+
/* @__PURE__ */ jsx6(TabsContent, { value: "validation", className: "mt-0", children: isKnownFormField(field) ? /* @__PURE__ */ jsx6(ValidationTab, { field, onUpdate }) : /* @__PURE__ */ jsx6("p", { className: "py-4 text-sm text-muted-foreground", children: "This field type manages its own validation." }) }),
|
|
2186
2198
|
/* @__PURE__ */ jsx6(TabsContent, { value: "conditional", className: "mt-0", children: /* @__PURE__ */ jsx6(
|
|
2187
2199
|
ConditionalLogicEditor,
|
|
2188
2200
|
{
|
|
@@ -2239,7 +2251,10 @@ function FieldCard({
|
|
|
2239
2251
|
{
|
|
2240
2252
|
ref: setNodeRef,
|
|
2241
2253
|
style: { transform: CSS.Transform.toString(transform), transition },
|
|
2242
|
-
className:
|
|
2254
|
+
className: (
|
|
2255
|
+
// Dragging uses full-strength border-primary so the lifted card stands out; the resting state uses border-border, keeping the two distinct.
|
|
2256
|
+
isDragging ? "border border-primary bg-card opacity-80 relative z-10" : "border border-border bg-card"
|
|
2257
|
+
),
|
|
2243
2258
|
children: [
|
|
2244
2259
|
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 px-3 py-2.5", children: [
|
|
2245
2260
|
/* @__PURE__ */ jsx7(
|
|
@@ -2249,7 +2264,7 @@ function FieldCard({
|
|
|
2249
2264
|
ref: setActivatorNodeRef,
|
|
2250
2265
|
...attributes,
|
|
2251
2266
|
...listeners,
|
|
2252
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-none text-muted-foreground
|
|
2267
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-none text-muted-foreground hover:text-foreground hover:bg-primary/5 cursor-grab active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
2253
2268
|
"aria-label": `Reorder ${field.label || field.name}. Press Space to lift, arrow keys to move, Space to drop.`,
|
|
2254
2269
|
children: /* @__PURE__ */ jsx7(Lucide.GripVertical, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
2255
2270
|
}
|
|
@@ -2262,14 +2277,14 @@ function FieldCard({
|
|
|
2262
2277
|
"aria-expanded": expanded,
|
|
2263
2278
|
"aria-controls": bodyId,
|
|
2264
2279
|
id: headerId,
|
|
2265
|
-
className: "flex min-w-0 flex-1 items-center gap-3 rounded-none py-1 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary
|
|
2280
|
+
className: "flex min-w-0 flex-1 items-center gap-3 rounded-none py-1 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
2266
2281
|
children: [
|
|
2267
2282
|
/* @__PURE__ */ jsx7("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center bg-primary/5 text-primary", children: /* @__PURE__ */ jsx7(CatalogIcon, { name: entry?.icon, className: "h-3.5 w-3.5" }) }),
|
|
2268
2283
|
/* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1", children: [
|
|
2269
2284
|
/* @__PURE__ */ jsx7("span", { className: "block truncate text-sm font-medium text-foreground", children: field.label || field.name }),
|
|
2270
2285
|
/* @__PURE__ */ jsxs6("span", { className: "block truncate text-xs text-muted-foreground", children: [
|
|
2271
2286
|
entry?.label ?? field.type,
|
|
2272
|
-
/* @__PURE__ */ jsx7("span", { className: "mx-1.5 text-muted-foreground
|
|
2287
|
+
/* @__PURE__ */ jsx7("span", { className: "mx-1.5 text-muted-foreground", children: "\xB7" }),
|
|
2273
2288
|
/* @__PURE__ */ jsx7("span", { className: "font-mono", children: field.name })
|
|
2274
2289
|
] })
|
|
2275
2290
|
] }),
|
|
@@ -2392,7 +2407,11 @@ function FieldCard({
|
|
|
2392
2407
|
}
|
|
2393
2408
|
);
|
|
2394
2409
|
}
|
|
2395
|
-
function FieldCards({
|
|
2410
|
+
function FieldCards({
|
|
2411
|
+
enabledTypes,
|
|
2412
|
+
disabledFieldTypes,
|
|
2413
|
+
onAddField
|
|
2414
|
+
}) {
|
|
2396
2415
|
const {
|
|
2397
2416
|
fields,
|
|
2398
2417
|
notifications,
|
|
@@ -2408,11 +2427,22 @@ function FieldCards({ enabledTypes, onAddField }) {
|
|
|
2408
2427
|
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
2409
2428
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
2410
2429
|
);
|
|
2430
|
+
const pluginEntries = usePluginFieldTypeEntries("forms");
|
|
2411
2431
|
const entries = useMemo5(() => {
|
|
2412
2432
|
if (enabledTypes === null) return null;
|
|
2413
2433
|
const allowed = new Set(enabledTypes);
|
|
2414
|
-
|
|
2415
|
-
|
|
2434
|
+
const builtinTypes = new Set(
|
|
2435
|
+
FORM_FIELD_TYPE_CATALOG2.map((entry) => entry.type)
|
|
2436
|
+
);
|
|
2437
|
+
const builtins = FORM_FIELD_TYPE_CATALOG2.filter((entry) => allowed.has(entry.type));
|
|
2438
|
+
return [
|
|
2439
|
+
...builtins,
|
|
2440
|
+
// A plugin type a built-in shadows, or one the host disabled, is excluded.
|
|
2441
|
+
...pluginEntries.filter(
|
|
2442
|
+
(entry) => !builtinTypes.has(entry.type) && !disabledFieldTypes?.has(entry.type)
|
|
2443
|
+
)
|
|
2444
|
+
];
|
|
2445
|
+
}, [enabledTypes, pluginEntries, disabledFieldTypes]);
|
|
2416
2446
|
const referenceMap = useMemo5(
|
|
2417
2447
|
() => buildFieldReferenceMap(fields, notifications),
|
|
2418
2448
|
[fields, notifications]
|
|
@@ -2642,7 +2672,7 @@ function NotificationCard({
|
|
|
2642
2672
|
{
|
|
2643
2673
|
type: "button",
|
|
2644
2674
|
onClick: onEdit,
|
|
2645
|
-
className: "flex min-w-0 flex-1 items-center gap-3 rounded-none py-1 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary
|
|
2675
|
+
className: "flex min-w-0 flex-1 items-center gap-3 rounded-none py-1 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
2646
2676
|
"aria-label": `Edit notification ${notification.name}`,
|
|
2647
2677
|
children: [
|
|
2648
2678
|
/* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1", children: [
|
|
@@ -2663,12 +2693,12 @@ function NotificationCard({
|
|
|
2663
2693
|
/* @__PURE__ */ jsxs7("span", { className: "block truncate text-xs text-muted-foreground", children: [
|
|
2664
2694
|
/* @__PURE__ */ jsx8("span", { className: recipient.missing ? "text-destructive" : "", children: recipient.text }),
|
|
2665
2695
|
ccCount > 0 && /* @__PURE__ */ jsxs7("span", { children: [
|
|
2666
|
-
/* @__PURE__ */ jsx8("span", { className: "mx-1.5 text-muted-foreground
|
|
2696
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-1.5 text-muted-foreground", children: "\xB7" }),
|
|
2667
2697
|
"+",
|
|
2668
2698
|
ccCount,
|
|
2669
2699
|
" cc/bcc"
|
|
2670
2700
|
] }),
|
|
2671
|
-
/* @__PURE__ */ jsx8("span", { className: "mx-1.5 text-muted-foreground
|
|
2701
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-1.5 text-muted-foreground", children: "\xB7" }),
|
|
2672
2702
|
providerName
|
|
2673
2703
|
] })
|
|
2674
2704
|
] }),
|
|
@@ -2780,7 +2810,7 @@ function AddressChipList({
|
|
|
2780
2810
|
"button",
|
|
2781
2811
|
{
|
|
2782
2812
|
type: "button",
|
|
2783
|
-
className: "text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary
|
|
2813
|
+
className: "text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary",
|
|
2784
2814
|
onClick: () => onChange(addresses.filter((_, i) => i !== index)),
|
|
2785
2815
|
"aria-label": `Remove ${email}`,
|
|
2786
2816
|
children: /* @__PURE__ */ jsx8(X, { className: "h-3 w-3", "aria-hidden": "true" })
|
|
@@ -3525,6 +3555,20 @@ function PreviewFieldInput({
|
|
|
3525
3555
|
);
|
|
3526
3556
|
}
|
|
3527
3557
|
}
|
|
3558
|
+
function PluginFieldPreview({ field }) {
|
|
3559
|
+
return /* @__PURE__ */ jsxs8("div", { className: "space-y-1.5", children: [
|
|
3560
|
+
/* @__PURE__ */ jsxs8(Label3, { htmlFor: `preview-${field.name}`, children: [
|
|
3561
|
+
field.label || field.name,
|
|
3562
|
+
field.required && /* @__PURE__ */ jsx9("span", { className: "ml-1 text-destructive", children: "*" })
|
|
3563
|
+
] }),
|
|
3564
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground", children: [
|
|
3565
|
+
"Custom field (",
|
|
3566
|
+
field.type,
|
|
3567
|
+
") \u2014 renders from its plugin component in the live form."
|
|
3568
|
+
] }),
|
|
3569
|
+
field.helpText && /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: field.helpText })
|
|
3570
|
+
] });
|
|
3571
|
+
}
|
|
3528
3572
|
function FormPreview({ fields, formData }) {
|
|
3529
3573
|
const { settings } = useFormBuilder();
|
|
3530
3574
|
const [values, setValues] = useState8({});
|
|
@@ -3558,6 +3602,7 @@ function FormPreview({ fields, formData }) {
|
|
|
3558
3602
|
const missing = new Set(
|
|
3559
3603
|
visibleFields.filter((field) => {
|
|
3560
3604
|
if (!field.required || field.type === "file") return false;
|
|
3605
|
+
if (!isKnownFormField(field)) return false;
|
|
3561
3606
|
const value = values[field.name];
|
|
3562
3607
|
return value === void 0 || value === "" || value === false;
|
|
3563
3608
|
}).map((field) => field.name)
|
|
@@ -3614,38 +3659,40 @@ function FormPreview({ fields, formData }) {
|
|
|
3614
3659
|
/* @__PURE__ */ jsx9(Button7, { type: "button", variant: "outline", onClick: reset, children: "Fill again" })
|
|
3615
3660
|
] }) : /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
3616
3661
|
visibleFields.length === 0 && /* @__PURE__ */ jsx9("p", { className: "text-sm text-muted-foreground", children: "No visible fields \u2014 add fields in the Builder tab." }),
|
|
3617
|
-
visibleFields.map(
|
|
3618
|
-
field
|
|
3619
|
-
field.
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
/* @__PURE__ */
|
|
3662
|
+
visibleFields.map(
|
|
3663
|
+
(field) => !isKnownFormField(field) ? /* @__PURE__ */ jsx9(PluginFieldPreview, { field }, field.name) : /* @__PURE__ */ jsxs8("div", { className: "space-y-1.5", children: [
|
|
3664
|
+
field.type !== "checkbox" && /* @__PURE__ */ jsxs8(Label3, { htmlFor: `preview-${field.name}`, children: [
|
|
3665
|
+
field.label || field.name,
|
|
3666
|
+
field.required && /* @__PURE__ */ jsx9("span", { className: "ml-1 text-destructive", children: "*" })
|
|
3667
|
+
] }),
|
|
3668
|
+
field.type === "checkbox" ? /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
|
|
3669
|
+
/* @__PURE__ */ jsx9(
|
|
3670
|
+
PreviewFieldInput,
|
|
3671
|
+
{
|
|
3672
|
+
field,
|
|
3673
|
+
value: values[field.name],
|
|
3674
|
+
onChange: (value) => setValues((prev) => ({
|
|
3675
|
+
...prev,
|
|
3676
|
+
[field.name]: value
|
|
3677
|
+
}))
|
|
3678
|
+
}
|
|
3679
|
+
),
|
|
3680
|
+
/* @__PURE__ */ jsxs8(Label3, { htmlFor: `preview-${field.name}`, children: [
|
|
3681
|
+
field.label || field.name,
|
|
3682
|
+
field.required && /* @__PURE__ */ jsx9("span", { className: "ml-1 text-destructive", children: "*" })
|
|
3683
|
+
] })
|
|
3684
|
+
] }) : /* @__PURE__ */ jsx9(
|
|
3624
3685
|
PreviewFieldInput,
|
|
3625
3686
|
{
|
|
3626
3687
|
field,
|
|
3627
3688
|
value: values[field.name],
|
|
3628
|
-
onChange: (value) => setValues((prev) => ({
|
|
3629
|
-
...prev,
|
|
3630
|
-
[field.name]: value
|
|
3631
|
-
}))
|
|
3689
|
+
onChange: (value) => setValues((prev) => ({ ...prev, [field.name]: value }))
|
|
3632
3690
|
}
|
|
3633
3691
|
),
|
|
3634
|
-
/* @__PURE__ */
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
] }) : /* @__PURE__ */ jsx9(
|
|
3639
|
-
PreviewFieldInput,
|
|
3640
|
-
{
|
|
3641
|
-
field,
|
|
3642
|
-
value: values[field.name],
|
|
3643
|
-
onChange: (value) => setValues((prev) => ({ ...prev, [field.name]: value }))
|
|
3644
|
-
}
|
|
3645
|
-
),
|
|
3646
|
-
field.helpText && /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: field.helpText }),
|
|
3647
|
-
missingRequired.has(field.name) && /* @__PURE__ */ jsx9("p", { className: "text-xs text-destructive", children: "This field is required." })
|
|
3648
|
-
] }, field.name)),
|
|
3692
|
+
field.helpText && /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: field.helpText }),
|
|
3693
|
+
missingRequired.has(field.name) && /* @__PURE__ */ jsx9("p", { className: "text-xs text-destructive", children: "This field is required." })
|
|
3694
|
+
] }, field.name)
|
|
3695
|
+
),
|
|
3649
3696
|
/* @__PURE__ */ jsx9(
|
|
3650
3697
|
Button7,
|
|
3651
3698
|
{
|
|
@@ -3945,6 +3992,9 @@ function FormBuilderViewInner({
|
|
|
3945
3992
|
const [isSaving, setIsSaving] = useState9(false);
|
|
3946
3993
|
const [saveError, setSaveError] = useState9(null);
|
|
3947
3994
|
const [enabledTypes, setEnabledTypes] = useState9(null);
|
|
3995
|
+
const [disabledFieldTypes, setDisabledFieldTypes] = useState9(
|
|
3996
|
+
() => /* @__PURE__ */ new Set()
|
|
3997
|
+
);
|
|
3948
3998
|
const [notificationDefaults, setNotificationDefaults] = useState9(null);
|
|
3949
3999
|
const [spamDefaults, setSpamDefaults] = useState9(null);
|
|
3950
4000
|
useEffect4(() => {
|
|
@@ -3959,6 +4009,11 @@ function FormBuilderViewInner({
|
|
|
3959
4009
|
setEnabledTypes(
|
|
3960
4010
|
config?.fields ? allTypes.filter((type) => config.fields?.[type] !== false) : allTypes
|
|
3961
4011
|
);
|
|
4012
|
+
setDisabledFieldTypes(
|
|
4013
|
+
new Set(
|
|
4014
|
+
Object.entries(config?.fields ?? {}).filter(([, enabled]) => enabled === false).map(([type]) => type)
|
|
4015
|
+
)
|
|
4016
|
+
);
|
|
3962
4017
|
setNotificationDefaults(config?.notifications ?? {});
|
|
3963
4018
|
setSpamDefaults(config?.spamProtection ?? {});
|
|
3964
4019
|
}
|
|
@@ -4103,7 +4158,8 @@ function FormBuilderViewInner({
|
|
|
4103
4158
|
/* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground mt-1", children: isCreating ? "Design a new form with drag-and-drop fields." : "Modify your form fields and settings." })
|
|
4104
4159
|
] }),
|
|
4105
4160
|
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4106
|
-
isDirty &&
|
|
4161
|
+
isDirty && // Full-strength warning border so the unsaved-changes badge boundary is perceivable over its tinted fill.
|
|
4162
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs font-medium bg-warning-100 text-warning-700 dark:bg-warning-900 dark:text-warning-100 border border-warning px-2.5 py-1 rounded-none whitespace-nowrap", children: "Unsaved changes" }),
|
|
4107
4163
|
/* @__PURE__ */ jsx11(
|
|
4108
4164
|
Button8,
|
|
4109
4165
|
{
|
|
@@ -4202,7 +4258,7 @@ function FormBuilderViewInner({
|
|
|
4202
4258
|
value: formData.slug || "",
|
|
4203
4259
|
onChange: (e) => updateFormData({ slug: e.target.value }),
|
|
4204
4260
|
placeholder: "e.g., contact-form",
|
|
4205
|
-
className: "bg-transparent placeholder:text-muted-foreground
|
|
4261
|
+
className: "bg-transparent placeholder:text-muted-foreground"
|
|
4206
4262
|
}
|
|
4207
4263
|
)
|
|
4208
4264
|
] }),
|
|
@@ -4246,7 +4302,7 @@ function FormBuilderViewInner({
|
|
|
4246
4302
|
style: {
|
|
4247
4303
|
borderBottomColor: activeTab === tab.value ? "var(--nx-primary)" : "transparent"
|
|
4248
4304
|
},
|
|
4249
|
-
className: "shrink-0 whitespace-nowrap border-b-2 relative -mb-0.5 data-[state=active]:text-primary data-[state=inactive]:text-muted-foreground hover:text-primary hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
|
|
4305
|
+
className: "shrink-0 whitespace-nowrap border-b-2 relative -mb-0.5 data-[state=active]:text-primary data-[state=inactive]:text-muted-foreground hover:text-primary hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer",
|
|
4250
4306
|
children: [
|
|
4251
4307
|
tab.label,
|
|
4252
4308
|
tab.count !== null && /* @__PURE__ */ jsx11(
|
|
@@ -4267,13 +4323,15 @@ function FormBuilderViewInner({
|
|
|
4267
4323
|
FieldCards,
|
|
4268
4324
|
{
|
|
4269
4325
|
enabledTypes,
|
|
4326
|
+
disabledFieldTypes,
|
|
4270
4327
|
onAddField: handleAddField
|
|
4271
4328
|
}
|
|
4272
4329
|
),
|
|
4273
4330
|
activeTab === "preview" && /* @__PURE__ */ jsx11(FormPreview, { fields, formData }),
|
|
4274
4331
|
activeTab === "settings" && /* @__PURE__ */ jsx11("div", { className: "w-full", children: /* @__PURE__ */ jsx11(FormSettingsTab, { spamDefaults }) }),
|
|
4275
4332
|
activeTab === "notifications" && /* @__PURE__ */ jsx11("div", { className: "w-full", children: /* @__PURE__ */ jsx11(FormNotificationsTab, { defaults: notificationDefaults }) }),
|
|
4276
|
-
saveError &&
|
|
4333
|
+
saveError && // Full-strength destructive border so the error box boundary is perceivable over its tinted fill.
|
|
4334
|
+
/* @__PURE__ */ jsx11("div", { className: "p-3 text-sm text-destructive bg-destructive/10 border border-destructive rounded-none", children: saveError })
|
|
4277
4335
|
] }) }) })
|
|
4278
4336
|
] });
|
|
4279
4337
|
}
|