@dmitryvim/form-builder 0.2.22 → 0.2.24
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/browser/formbuilder.min.js +120 -116
- package/dist/browser/formbuilder.v0.2.24.min.js +606 -0
- package/dist/cjs/index.cjs +524 -58
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +504 -55
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +120 -116
- package/dist/types/components/container.d.ts +4 -1
- package/dist/types/components/file.d.ts +5 -0
- package/dist/types/instance/FormBuilderInstance.d.ts +5 -0
- package/dist/types/types/component-operations.d.ts +2 -0
- package/dist/types/types/config.d.ts +3 -0
- package/dist/types/types/schema.d.ts +9 -1
- package/dist/types/types/state.d.ts +1 -0
- package/dist/types/utils/helpers.d.ts +14 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.22.min.js +0 -602
package/dist/esm/index.js
CHANGED
|
@@ -117,6 +117,48 @@ function validateSchema(schema) {
|
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
function checkFlatOutputCollisions(elements, scopePath) {
|
|
121
|
+
const allOutputKeys = /* @__PURE__ */ new Set();
|
|
122
|
+
for (const el of elements) {
|
|
123
|
+
if (el.type === "richinput" && el.flatOutput) {
|
|
124
|
+
const richEl = el;
|
|
125
|
+
const textKey = richEl.textKey ?? "text";
|
|
126
|
+
const filesKey = richEl.filesKey ?? "files";
|
|
127
|
+
for (const otherEl of elements) {
|
|
128
|
+
if (otherEl === el) continue;
|
|
129
|
+
if (otherEl.key === textKey) {
|
|
130
|
+
errors.push(
|
|
131
|
+
`${scopePath}: RichInput "${el.key}" flatOutput textKey "${textKey}" collides with element key "${otherEl.key}"`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (otherEl.key === filesKey) {
|
|
135
|
+
errors.push(
|
|
136
|
+
`${scopePath}: RichInput "${el.key}" flatOutput filesKey "${filesKey}" collides with element key "${otherEl.key}"`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (allOutputKeys.has(textKey)) {
|
|
141
|
+
errors.push(
|
|
142
|
+
`${scopePath}: RichInput "${el.key}" flatOutput textKey "${textKey}" collides with another flatOutput key`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (allOutputKeys.has(filesKey)) {
|
|
146
|
+
errors.push(
|
|
147
|
+
`${scopePath}: RichInput "${el.key}" flatOutput filesKey "${filesKey}" collides with another flatOutput key`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
allOutputKeys.add(textKey);
|
|
151
|
+
allOutputKeys.add(filesKey);
|
|
152
|
+
} else {
|
|
153
|
+
if (allOutputKeys.has(el.key)) {
|
|
154
|
+
errors.push(
|
|
155
|
+
`${scopePath}: Element key "${el.key}" collides with a flatOutput richinput key`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
allOutputKeys.add(el.key);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
120
162
|
function validateElements(elements, path) {
|
|
121
163
|
elements.forEach((element, index) => {
|
|
122
164
|
const elementPath = `${path}[${index}]`;
|
|
@@ -182,6 +224,7 @@ function validateSchema(schema) {
|
|
|
182
224
|
}
|
|
183
225
|
}
|
|
184
226
|
validateElements(element.elements, `${elementPath}.elements`);
|
|
227
|
+
checkFlatOutputCollisions(element.elements, `${elementPath}.elements`);
|
|
185
228
|
}
|
|
186
229
|
if (element.type === "select" && element.options) {
|
|
187
230
|
const defaultValue = element.default;
|
|
@@ -198,8 +241,10 @@ function validateSchema(schema) {
|
|
|
198
241
|
}
|
|
199
242
|
});
|
|
200
243
|
}
|
|
201
|
-
if (Array.isArray(schema.elements))
|
|
244
|
+
if (Array.isArray(schema.elements)) {
|
|
202
245
|
validateElements(schema.elements, "elements");
|
|
246
|
+
checkFlatOutputCollisions(schema.elements, "elements");
|
|
247
|
+
}
|
|
203
248
|
return errors;
|
|
204
249
|
}
|
|
205
250
|
|
|
@@ -223,6 +268,26 @@ function formatFileSize(bytes) {
|
|
|
223
268
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
224
269
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
225
270
|
}
|
|
271
|
+
function serializeHiddenValue(value) {
|
|
272
|
+
if (value === null || value === void 0) return "";
|
|
273
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
274
|
+
}
|
|
275
|
+
function deserializeHiddenValue(raw) {
|
|
276
|
+
if (raw === "") return null;
|
|
277
|
+
try {
|
|
278
|
+
return JSON.parse(raw);
|
|
279
|
+
} catch {
|
|
280
|
+
return raw;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function createHiddenInput(name, value) {
|
|
284
|
+
const input = document.createElement("input");
|
|
285
|
+
input.type = "hidden";
|
|
286
|
+
input.name = name;
|
|
287
|
+
input.setAttribute("data-hidden-field", "true");
|
|
288
|
+
input.value = serializeHiddenValue(value);
|
|
289
|
+
return input;
|
|
290
|
+
}
|
|
226
291
|
|
|
227
292
|
// src/utils/enable-conditions.ts
|
|
228
293
|
function getValueByPath(data, path) {
|
|
@@ -2006,6 +2071,25 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2006
2071
|
}
|
|
2007
2072
|
|
|
2008
2073
|
// src/components/file.ts
|
|
2074
|
+
function getAllowedExtensions(accept) {
|
|
2075
|
+
if (!accept) return [];
|
|
2076
|
+
if (typeof accept === "object" && Array.isArray(accept.extensions)) {
|
|
2077
|
+
return accept.extensions.map((ext) => ext.toLowerCase());
|
|
2078
|
+
}
|
|
2079
|
+
if (typeof accept === "string") {
|
|
2080
|
+
return accept.split(",").map((s) => s.trim()).filter((s) => s.startsWith(".")).map((s) => s.substring(1).toLowerCase());
|
|
2081
|
+
}
|
|
2082
|
+
return [];
|
|
2083
|
+
}
|
|
2084
|
+
function isFileExtensionAllowed(fileName, allowedExtensions) {
|
|
2085
|
+
if (allowedExtensions.length === 0) return true;
|
|
2086
|
+
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
2087
|
+
return allowedExtensions.includes(ext);
|
|
2088
|
+
}
|
|
2089
|
+
function isFileSizeAllowed(file, maxSizeMB) {
|
|
2090
|
+
if (maxSizeMB === Infinity) return true;
|
|
2091
|
+
return file.size <= maxSizeMB * 1024 * 1024;
|
|
2092
|
+
}
|
|
2009
2093
|
function renderLocalImagePreview(container, file, fileName, state) {
|
|
2010
2094
|
const img = document.createElement("img");
|
|
2011
2095
|
img.className = "w-full h-full object-contain";
|
|
@@ -2535,7 +2619,40 @@ function setEmptyFileContainer(fileContainer, state, hint) {
|
|
|
2535
2619
|
</div>
|
|
2536
2620
|
`;
|
|
2537
2621
|
}
|
|
2538
|
-
|
|
2622
|
+
function showFileError(container, message) {
|
|
2623
|
+
const existing = container.closest(".space-y-2")?.querySelector(".file-error-message");
|
|
2624
|
+
if (existing) existing.remove();
|
|
2625
|
+
const errorEl = document.createElement("div");
|
|
2626
|
+
errorEl.className = "file-error-message error-message";
|
|
2627
|
+
errorEl.style.cssText = `
|
|
2628
|
+
color: var(--fb-error-color);
|
|
2629
|
+
font-size: var(--fb-font-size-small);
|
|
2630
|
+
margin-top: 0.25rem;
|
|
2631
|
+
`;
|
|
2632
|
+
errorEl.textContent = message;
|
|
2633
|
+
container.closest(".space-y-2")?.appendChild(errorEl);
|
|
2634
|
+
}
|
|
2635
|
+
function clearFileError(container) {
|
|
2636
|
+
const existing = container.closest(".space-y-2")?.querySelector(".file-error-message");
|
|
2637
|
+
if (existing) existing.remove();
|
|
2638
|
+
}
|
|
2639
|
+
async function handleFileSelect(file, container, fieldName, state, deps = null, instance, allowedExtensions = [], maxSizeMB = Infinity) {
|
|
2640
|
+
if (!isFileExtensionAllowed(file.name, allowedExtensions)) {
|
|
2641
|
+
const formats = allowedExtensions.join(", ");
|
|
2642
|
+
showFileError(
|
|
2643
|
+
container,
|
|
2644
|
+
t("invalidFileExtension", state, { name: file.name, formats })
|
|
2645
|
+
);
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
if (!isFileSizeAllowed(file, maxSizeMB)) {
|
|
2649
|
+
showFileError(
|
|
2650
|
+
container,
|
|
2651
|
+
t("fileTooLarge", state, { name: file.name, maxSize: maxSizeMB })
|
|
2652
|
+
);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
clearFileError(container);
|
|
2539
2656
|
let rid;
|
|
2540
2657
|
if (state.config.uploadFile) {
|
|
2541
2658
|
try {
|
|
@@ -2738,9 +2855,51 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
|
|
|
2738
2855
|
hiddenInput.value = initial;
|
|
2739
2856
|
fileWrapper.appendChild(hiddenInput);
|
|
2740
2857
|
}
|
|
2741
|
-
function setupFilesDropHandler(filesContainer, initialFiles, state, updateCallback, pathKey, instance) {
|
|
2858
|
+
function setupFilesDropHandler(filesContainer, initialFiles, state, updateCallback, constraints, pathKey, instance) {
|
|
2742
2859
|
setupDragAndDrop(filesContainer, async (files) => {
|
|
2743
|
-
const
|
|
2860
|
+
const allFiles = Array.from(files);
|
|
2861
|
+
const rejectedByExtension = allFiles.filter(
|
|
2862
|
+
(f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2863
|
+
);
|
|
2864
|
+
const afterExtension = allFiles.filter(
|
|
2865
|
+
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2866
|
+
);
|
|
2867
|
+
const rejectedBySize = afterExtension.filter(
|
|
2868
|
+
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
2869
|
+
);
|
|
2870
|
+
const validFiles = afterExtension.filter(
|
|
2871
|
+
(f) => isFileSizeAllowed(f, constraints.maxSize)
|
|
2872
|
+
);
|
|
2873
|
+
const remaining = constraints.maxCount === Infinity ? validFiles.length : Math.max(0, constraints.maxCount - initialFiles.length);
|
|
2874
|
+
const arr = validFiles.slice(0, remaining);
|
|
2875
|
+
const skippedByCount = validFiles.length - arr.length;
|
|
2876
|
+
const errorParts = [];
|
|
2877
|
+
if (rejectedByExtension.length > 0) {
|
|
2878
|
+
const formats = constraints.allowedExtensions.join(", ");
|
|
2879
|
+
const names = rejectedByExtension.map((f) => f.name).join(", ");
|
|
2880
|
+
errorParts.push(
|
|
2881
|
+
t("invalidFileExtension", state, { name: names, formats })
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
if (rejectedBySize.length > 0) {
|
|
2885
|
+
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
2886
|
+
errorParts.push(
|
|
2887
|
+
t("fileTooLarge", state, { name: names, maxSize: constraints.maxSize })
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
if (skippedByCount > 0) {
|
|
2891
|
+
errorParts.push(
|
|
2892
|
+
t("filesLimitExceeded", state, {
|
|
2893
|
+
skipped: skippedByCount,
|
|
2894
|
+
max: constraints.maxCount
|
|
2895
|
+
})
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
if (errorParts.length > 0) {
|
|
2899
|
+
showFileError(filesContainer, errorParts.join(" \u2022 "));
|
|
2900
|
+
} else {
|
|
2901
|
+
clearFileError(filesContainer);
|
|
2902
|
+
}
|
|
2744
2903
|
for (const file of arr) {
|
|
2745
2904
|
const rid = await uploadSingleFile(file, state);
|
|
2746
2905
|
state.resourceIndex.set(rid, {
|
|
@@ -2758,10 +2917,57 @@ function setupFilesDropHandler(filesContainer, initialFiles, state, updateCallba
|
|
|
2758
2917
|
}
|
|
2759
2918
|
});
|
|
2760
2919
|
}
|
|
2761
|
-
function setupFilesPickerHandler(filesPicker, initialFiles, state, updateCallback, pathKey, instance) {
|
|
2920
|
+
function setupFilesPickerHandler(filesPicker, initialFiles, state, updateCallback, constraints, pathKey, instance) {
|
|
2762
2921
|
filesPicker.onchange = async () => {
|
|
2763
2922
|
if (filesPicker.files) {
|
|
2764
|
-
|
|
2923
|
+
const allFiles = Array.from(filesPicker.files);
|
|
2924
|
+
const rejectedByExtension = allFiles.filter(
|
|
2925
|
+
(f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2926
|
+
);
|
|
2927
|
+
const afterExtension = allFiles.filter(
|
|
2928
|
+
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2929
|
+
);
|
|
2930
|
+
const rejectedBySize = afterExtension.filter(
|
|
2931
|
+
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
2932
|
+
);
|
|
2933
|
+
const validFiles = afterExtension.filter(
|
|
2934
|
+
(f) => isFileSizeAllowed(f, constraints.maxSize)
|
|
2935
|
+
);
|
|
2936
|
+
const remaining = constraints.maxCount === Infinity ? validFiles.length : Math.max(0, constraints.maxCount - initialFiles.length);
|
|
2937
|
+
const arr = validFiles.slice(0, remaining);
|
|
2938
|
+
const skippedByCount = validFiles.length - arr.length;
|
|
2939
|
+
const errorParts = [];
|
|
2940
|
+
if (rejectedByExtension.length > 0) {
|
|
2941
|
+
const formats = constraints.allowedExtensions.join(", ");
|
|
2942
|
+
const names = rejectedByExtension.map((f) => f.name).join(", ");
|
|
2943
|
+
errorParts.push(
|
|
2944
|
+
t("invalidFileExtension", state, { name: names, formats })
|
|
2945
|
+
);
|
|
2946
|
+
}
|
|
2947
|
+
if (rejectedBySize.length > 0) {
|
|
2948
|
+
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
2949
|
+
errorParts.push(
|
|
2950
|
+
t("fileTooLarge", state, {
|
|
2951
|
+
name: names,
|
|
2952
|
+
maxSize: constraints.maxSize
|
|
2953
|
+
})
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
if (skippedByCount > 0) {
|
|
2957
|
+
errorParts.push(
|
|
2958
|
+
t("filesLimitExceeded", state, {
|
|
2959
|
+
skipped: skippedByCount,
|
|
2960
|
+
max: constraints.maxCount
|
|
2961
|
+
})
|
|
2962
|
+
);
|
|
2963
|
+
}
|
|
2964
|
+
const wrapper = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
|
|
2965
|
+
if (errorParts.length > 0 && wrapper) {
|
|
2966
|
+
showFileError(wrapper, errorParts.join(" \u2022 "));
|
|
2967
|
+
} else if (wrapper) {
|
|
2968
|
+
clearFileError(wrapper);
|
|
2969
|
+
}
|
|
2970
|
+
for (const file of arr) {
|
|
2765
2971
|
const rid = await uploadSingleFile(file, state);
|
|
2766
2972
|
state.resourceIndex.set(rid, {
|
|
2767
2973
|
name: file.name,
|
|
@@ -2813,6 +3019,8 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2813
3019
|
const fileContainer = document.createElement("div");
|
|
2814
3020
|
fileContainer.className = "file-preview-container w-full aspect-square max-w-xs bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
|
|
2815
3021
|
const initial = ctx.prefill[element.key];
|
|
3022
|
+
const allowedExts = getAllowedExtensions(element.accept);
|
|
3023
|
+
const maxSizeMB = element.maxSize ?? Infinity;
|
|
2816
3024
|
const fileUploadHandler = () => picker.click();
|
|
2817
3025
|
const dragHandler = (files) => {
|
|
2818
3026
|
if (files.length > 0) {
|
|
@@ -2823,7 +3031,9 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2823
3031
|
pathKey,
|
|
2824
3032
|
state,
|
|
2825
3033
|
deps,
|
|
2826
|
-
ctx.instance
|
|
3034
|
+
ctx.instance,
|
|
3035
|
+
allowedExts,
|
|
3036
|
+
maxSizeMB
|
|
2827
3037
|
);
|
|
2828
3038
|
}
|
|
2829
3039
|
};
|
|
@@ -2855,7 +3065,9 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2855
3065
|
pathKey,
|
|
2856
3066
|
state,
|
|
2857
3067
|
deps,
|
|
2858
|
-
ctx.instance
|
|
3068
|
+
ctx.instance,
|
|
3069
|
+
allowedExts,
|
|
3070
|
+
maxSizeMB
|
|
2859
3071
|
);
|
|
2860
3072
|
}
|
|
2861
3073
|
};
|
|
@@ -2915,12 +3127,18 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2915
3127
|
const initialFiles = ctx.prefill[element.key] || [];
|
|
2916
3128
|
addPrefillFilesToIndex(initialFiles, state);
|
|
2917
3129
|
const filesFieldHint = makeFieldHint(element, state);
|
|
3130
|
+
const filesConstraints = {
|
|
3131
|
+
maxCount: Infinity,
|
|
3132
|
+
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3133
|
+
maxSize: element.maxSize ?? Infinity
|
|
3134
|
+
};
|
|
2918
3135
|
updateFilesList2();
|
|
2919
3136
|
setupFilesDropHandler(
|
|
2920
3137
|
filesContainer,
|
|
2921
3138
|
initialFiles,
|
|
2922
3139
|
state,
|
|
2923
3140
|
updateFilesList2,
|
|
3141
|
+
filesConstraints,
|
|
2924
3142
|
pathKey,
|
|
2925
3143
|
ctx.instance
|
|
2926
3144
|
);
|
|
@@ -2929,6 +3147,7 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2929
3147
|
initialFiles,
|
|
2930
3148
|
state,
|
|
2931
3149
|
updateFilesList2,
|
|
3150
|
+
filesConstraints,
|
|
2932
3151
|
pathKey,
|
|
2933
3152
|
ctx.instance
|
|
2934
3153
|
);
|
|
@@ -2976,6 +3195,11 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2976
3195
|
const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
|
|
2977
3196
|
addPrefillFilesToIndex(initialFiles, state);
|
|
2978
3197
|
const multipleFilesHint = makeFieldHint(element, state);
|
|
3198
|
+
const multipleConstraints = {
|
|
3199
|
+
maxCount: maxFiles,
|
|
3200
|
+
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3201
|
+
maxSize: element.maxSize ?? Infinity
|
|
3202
|
+
};
|
|
2979
3203
|
const buildCountInfo = () => {
|
|
2980
3204
|
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
2981
3205
|
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
@@ -2999,6 +3223,7 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2999
3223
|
initialFiles,
|
|
3000
3224
|
state,
|
|
3001
3225
|
updateFilesDisplay,
|
|
3226
|
+
multipleConstraints,
|
|
3002
3227
|
pathKey,
|
|
3003
3228
|
ctx.instance
|
|
3004
3229
|
);
|
|
@@ -3007,6 +3232,7 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
3007
3232
|
initialFiles,
|
|
3008
3233
|
state,
|
|
3009
3234
|
updateFilesDisplay,
|
|
3235
|
+
multipleConstraints,
|
|
3010
3236
|
pathKey,
|
|
3011
3237
|
ctx.instance
|
|
3012
3238
|
);
|
|
@@ -3033,6 +3259,38 @@ function validateFileElement(element, key, context) {
|
|
|
3033
3259
|
errors.push(`${key2}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
3034
3260
|
}
|
|
3035
3261
|
};
|
|
3262
|
+
const validateFileExtensions = (key2, resourceIds, element2) => {
|
|
3263
|
+
if (skipValidation) return;
|
|
3264
|
+
const { state } = context;
|
|
3265
|
+
const acceptField = "accept" in element2 ? element2.accept : void 0;
|
|
3266
|
+
const allowedExtensions = getAllowedExtensions(acceptField);
|
|
3267
|
+
if (allowedExtensions.length === 0) return;
|
|
3268
|
+
const formats = allowedExtensions.join(", ");
|
|
3269
|
+
for (const rid of resourceIds) {
|
|
3270
|
+
const meta = state.resourceIndex.get(rid);
|
|
3271
|
+
const fileName = meta?.name ?? rid;
|
|
3272
|
+
if (!isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
3273
|
+
errors.push(
|
|
3274
|
+
`${key2}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
|
|
3275
|
+
);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
};
|
|
3279
|
+
const validateFileSizes = (key2, resourceIds, element2) => {
|
|
3280
|
+
if (skipValidation) return;
|
|
3281
|
+
const { state } = context;
|
|
3282
|
+
const maxSizeMB = "maxSize" in element2 ? element2.maxSize ?? Infinity : Infinity;
|
|
3283
|
+
if (maxSizeMB === Infinity) return;
|
|
3284
|
+
for (const rid of resourceIds) {
|
|
3285
|
+
const meta = state.resourceIndex.get(rid);
|
|
3286
|
+
if (!meta) continue;
|
|
3287
|
+
if (meta.size > maxSizeMB * 1024 * 1024) {
|
|
3288
|
+
errors.push(
|
|
3289
|
+
`${key2}: ${t("fileTooLarge", state, { name: meta.name, maxSize: maxSizeMB })}`
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
3036
3294
|
if (isMultipleField) {
|
|
3037
3295
|
const fullKey = pathJoin(path, key);
|
|
3038
3296
|
const pickerInput = scopeRoot.querySelector(
|
|
@@ -3051,6 +3309,8 @@ function validateFileElement(element, key, context) {
|
|
|
3051
3309
|
});
|
|
3052
3310
|
}
|
|
3053
3311
|
validateFileCount(key, resourceIds, element);
|
|
3312
|
+
validateFileExtensions(key, resourceIds, element);
|
|
3313
|
+
validateFileSizes(key, resourceIds, element);
|
|
3054
3314
|
return { value: resourceIds, errors };
|
|
3055
3315
|
} else {
|
|
3056
3316
|
const input = scopeRoot.querySelector(
|
|
@@ -3061,6 +3321,10 @@ function validateFileElement(element, key, context) {
|
|
|
3061
3321
|
errors.push(`${key}: ${t("required", context.state)}`);
|
|
3062
3322
|
return { value: null, errors };
|
|
3063
3323
|
}
|
|
3324
|
+
if (!skipValidation && rid !== "") {
|
|
3325
|
+
validateFileExtensions(key, [rid], element);
|
|
3326
|
+
validateFileSizes(key, [rid], element);
|
|
3327
|
+
}
|
|
3064
3328
|
return { value: rid || null, errors };
|
|
3065
3329
|
}
|
|
3066
3330
|
}
|
|
@@ -4248,7 +4512,10 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
4248
4512
|
state: ctx.state
|
|
4249
4513
|
};
|
|
4250
4514
|
element.elements.forEach((child) => {
|
|
4251
|
-
if (
|
|
4515
|
+
if (child.hidden || child.type === "hidden") {
|
|
4516
|
+
const prefillVal = containerPrefill[child.key] ?? child.default ?? null;
|
|
4517
|
+
itemsWrap.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
|
|
4518
|
+
} else {
|
|
4252
4519
|
itemsWrap.appendChild(renderElement(child, subCtx));
|
|
4253
4520
|
}
|
|
4254
4521
|
});
|
|
@@ -4315,7 +4582,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
4315
4582
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
4316
4583
|
}
|
|
4317
4584
|
element.elements.forEach((child) => {
|
|
4318
|
-
if (
|
|
4585
|
+
if (child.hidden || child.type === "hidden") {
|
|
4586
|
+
childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), child.default ?? null));
|
|
4587
|
+
} else {
|
|
4319
4588
|
childWrapper.appendChild(renderElement(child, subCtx));
|
|
4320
4589
|
}
|
|
4321
4590
|
});
|
|
@@ -4384,7 +4653,10 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
4384
4653
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
4385
4654
|
}
|
|
4386
4655
|
element.elements.forEach((child) => {
|
|
4387
|
-
if (
|
|
4656
|
+
if (child.hidden || child.type === "hidden") {
|
|
4657
|
+
const prefillVal = prefillObj?.[child.key] ?? child.default ?? null;
|
|
4658
|
+
childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal));
|
|
4659
|
+
} else {
|
|
4388
4660
|
childWrapper.appendChild(renderElement(child, subCtx));
|
|
4389
4661
|
}
|
|
4390
4662
|
});
|
|
@@ -4434,7 +4706,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
4434
4706
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
4435
4707
|
}
|
|
4436
4708
|
element.elements.forEach((child) => {
|
|
4437
|
-
if (
|
|
4709
|
+
if (child.hidden || child.type === "hidden") {
|
|
4710
|
+
childWrapper.appendChild(createHiddenInput(pathJoin(subCtx.path, child.key), child.default ?? null));
|
|
4711
|
+
} else {
|
|
4438
4712
|
childWrapper.appendChild(renderElement(child, subCtx));
|
|
4439
4713
|
}
|
|
4440
4714
|
});
|
|
@@ -4545,15 +4819,16 @@ function validateContainerElement(element, key, context) {
|
|
|
4545
4819
|
);
|
|
4546
4820
|
}
|
|
4547
4821
|
}
|
|
4548
|
-
|
|
4549
|
-
|
|
4822
|
+
const childKey = `${key}[${domIndex}].${child.key}`;
|
|
4823
|
+
const childResult = validateElement(
|
|
4824
|
+
{ ...child, key: childKey },
|
|
4825
|
+
{ path },
|
|
4826
|
+
itemContainer
|
|
4827
|
+
);
|
|
4828
|
+
if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
|
|
4829
|
+
Object.assign(itemData, childResult.value);
|
|
4550
4830
|
} else {
|
|
4551
|
-
|
|
4552
|
-
itemData[child.key] = validateElement(
|
|
4553
|
-
{ ...child, key: childKey },
|
|
4554
|
-
{ path },
|
|
4555
|
-
itemContainer
|
|
4556
|
-
);
|
|
4831
|
+
itemData[child.key] = childResult.value;
|
|
4557
4832
|
}
|
|
4558
4833
|
});
|
|
4559
4834
|
items.push(itemData);
|
|
@@ -4584,15 +4859,18 @@ function validateContainerElement(element, key, context) {
|
|
|
4584
4859
|
);
|
|
4585
4860
|
}
|
|
4586
4861
|
}
|
|
4587
|
-
|
|
4588
|
-
containerData[child.key] = child.default !== void 0 ? child.default : null;
|
|
4589
|
-
} else {
|
|
4862
|
+
{
|
|
4590
4863
|
const childKey = `${key}.${child.key}`;
|
|
4591
|
-
|
|
4864
|
+
const childResult = validateElement(
|
|
4592
4865
|
{ ...child, key: childKey },
|
|
4593
4866
|
{ path },
|
|
4594
4867
|
containerContainer
|
|
4595
4868
|
);
|
|
4869
|
+
if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
|
|
4870
|
+
Object.assign(containerData, childResult.value);
|
|
4871
|
+
} else {
|
|
4872
|
+
containerData[child.key] = childResult.value;
|
|
4873
|
+
}
|
|
4596
4874
|
}
|
|
4597
4875
|
});
|
|
4598
4876
|
return { value: containerData, errors };
|
|
@@ -4613,11 +4891,23 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
4613
4891
|
value.forEach((itemValue, index) => {
|
|
4614
4892
|
if (isPlainObject(itemValue)) {
|
|
4615
4893
|
element.elements.forEach((childElement) => {
|
|
4616
|
-
const
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4894
|
+
const childPath = `${fieldPath}[${index}].${childElement.key}`;
|
|
4895
|
+
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
4896
|
+
const richChild = childElement;
|
|
4897
|
+
const textKey = richChild.textKey ?? "text";
|
|
4898
|
+
const filesKey = richChild.filesKey ?? "files";
|
|
4899
|
+
const containerValue = itemValue;
|
|
4900
|
+
const compositeValue = {};
|
|
4901
|
+
if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
|
|
4902
|
+
if (filesKey in containerValue) compositeValue[filesKey] = containerValue[filesKey];
|
|
4903
|
+
if (Object.keys(compositeValue).length > 0) {
|
|
4904
|
+
instance.updateField(childPath, compositeValue);
|
|
4905
|
+
}
|
|
4906
|
+
} else {
|
|
4907
|
+
const childValue = itemValue[childElement.key];
|
|
4908
|
+
if (childValue !== void 0) {
|
|
4909
|
+
instance.updateField(childPath, childValue);
|
|
4910
|
+
}
|
|
4621
4911
|
}
|
|
4622
4912
|
});
|
|
4623
4913
|
}
|
|
@@ -4638,11 +4928,23 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
4638
4928
|
return;
|
|
4639
4929
|
}
|
|
4640
4930
|
element.elements.forEach((childElement) => {
|
|
4641
|
-
const
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4931
|
+
const childPath = `${fieldPath}.${childElement.key}`;
|
|
4932
|
+
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
4933
|
+
const richChild = childElement;
|
|
4934
|
+
const textKey = richChild.textKey ?? "text";
|
|
4935
|
+
const filesKey = richChild.filesKey ?? "files";
|
|
4936
|
+
const containerValue = value;
|
|
4937
|
+
const compositeValue = {};
|
|
4938
|
+
if (textKey in containerValue) compositeValue[textKey] = containerValue[textKey];
|
|
4939
|
+
if (filesKey in containerValue) compositeValue[filesKey] = containerValue[filesKey];
|
|
4940
|
+
if (Object.keys(compositeValue).length > 0) {
|
|
4941
|
+
instance.updateField(childPath, compositeValue);
|
|
4942
|
+
}
|
|
4943
|
+
} else {
|
|
4944
|
+
const childValue = value[childElement.key];
|
|
4945
|
+
if (childValue !== void 0) {
|
|
4946
|
+
instance.updateField(childPath, childValue);
|
|
4947
|
+
}
|
|
4646
4948
|
}
|
|
4647
4949
|
});
|
|
4648
4950
|
}
|
|
@@ -6436,6 +6738,41 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
6436
6738
|
outerDiv.style.borderColor = "var(--fb-border-color, #d1d5db)";
|
|
6437
6739
|
outerDiv.style.boxShadow = "none";
|
|
6438
6740
|
});
|
|
6741
|
+
const errorEl = document.createElement("div");
|
|
6742
|
+
errorEl.className = "fb-richinput-error";
|
|
6743
|
+
errorEl.style.cssText = "display: none; color: var(--fb-error-color, #ef4444); font-size: var(--fb-font-size-small, 12px); padding: 4px 14px 8px;";
|
|
6744
|
+
let errorTimer = null;
|
|
6745
|
+
function showUploadError(message) {
|
|
6746
|
+
errorEl.textContent = message;
|
|
6747
|
+
errorEl.style.display = "block";
|
|
6748
|
+
if (errorTimer) clearTimeout(errorTimer);
|
|
6749
|
+
errorTimer = setTimeout(() => {
|
|
6750
|
+
errorEl.style.display = "none";
|
|
6751
|
+
errorEl.textContent = "";
|
|
6752
|
+
errorTimer = null;
|
|
6753
|
+
}, 5e3);
|
|
6754
|
+
}
|
|
6755
|
+
function validateFileForUpload(file) {
|
|
6756
|
+
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
6757
|
+
if (!isFileExtensionAllowed(file.name, allowedExtensions)) {
|
|
6758
|
+
const formats = allowedExtensions.join(", ");
|
|
6759
|
+
showUploadError(
|
|
6760
|
+
t("invalidFileExtension", state, { name: file.name, formats })
|
|
6761
|
+
);
|
|
6762
|
+
return false;
|
|
6763
|
+
}
|
|
6764
|
+
const maxSizeMB = element.maxSize ?? Infinity;
|
|
6765
|
+
if (!isFileSizeAllowed(file, maxSizeMB)) {
|
|
6766
|
+
showUploadError(
|
|
6767
|
+
t("fileTooLarge", state, {
|
|
6768
|
+
name: file.name,
|
|
6769
|
+
maxSize: maxSizeMB
|
|
6770
|
+
})
|
|
6771
|
+
);
|
|
6772
|
+
return false;
|
|
6773
|
+
}
|
|
6774
|
+
return true;
|
|
6775
|
+
}
|
|
6439
6776
|
let dragCounter = 0;
|
|
6440
6777
|
outerDiv.addEventListener("dragenter", (e) => {
|
|
6441
6778
|
e.preventDefault();
|
|
@@ -6463,7 +6800,17 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
6463
6800
|
const droppedFiles = e.dataTransfer?.files;
|
|
6464
6801
|
if (!droppedFiles || !state.config.uploadFile) return;
|
|
6465
6802
|
const maxFiles = element.maxFiles ?? Infinity;
|
|
6466
|
-
for (let i = 0; i < droppedFiles.length
|
|
6803
|
+
for (let i = 0; i < droppedFiles.length; i++) {
|
|
6804
|
+
if (files.length >= maxFiles) {
|
|
6805
|
+
showUploadError(
|
|
6806
|
+
t("filesLimitExceeded", state, {
|
|
6807
|
+
skipped: droppedFiles.length - i,
|
|
6808
|
+
max: maxFiles
|
|
6809
|
+
})
|
|
6810
|
+
);
|
|
6811
|
+
break;
|
|
6812
|
+
}
|
|
6813
|
+
if (!validateFileForUpload(droppedFiles[i])) continue;
|
|
6467
6814
|
uploadFile(droppedFiles[i]);
|
|
6468
6815
|
}
|
|
6469
6816
|
});
|
|
@@ -6619,9 +6966,13 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
6619
6966
|
});
|
|
6620
6967
|
paperclipBtn.addEventListener("click", () => {
|
|
6621
6968
|
const maxFiles = element.maxFiles ?? Infinity;
|
|
6622
|
-
if (files.length
|
|
6623
|
-
|
|
6969
|
+
if (files.length >= maxFiles) {
|
|
6970
|
+
showUploadError(
|
|
6971
|
+
t("filesLimitExceeded", state, { skipped: 1, max: maxFiles })
|
|
6972
|
+
);
|
|
6973
|
+
return;
|
|
6624
6974
|
}
|
|
6975
|
+
fileInput.click();
|
|
6625
6976
|
});
|
|
6626
6977
|
const dropdown = document.createElement("div");
|
|
6627
6978
|
dropdown.className = "fb-richinput-dropdown";
|
|
@@ -6929,7 +7280,17 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
6929
7280
|
const selected = fileInput.files;
|
|
6930
7281
|
if (!selected || selected.length === 0) return;
|
|
6931
7282
|
const maxFiles = element.maxFiles ?? Infinity;
|
|
6932
|
-
for (let i = 0; i < selected.length
|
|
7283
|
+
for (let i = 0; i < selected.length; i++) {
|
|
7284
|
+
if (files.length >= maxFiles) {
|
|
7285
|
+
showUploadError(
|
|
7286
|
+
t("filesLimitExceeded", state, {
|
|
7287
|
+
skipped: selected.length - i,
|
|
7288
|
+
max: maxFiles
|
|
7289
|
+
})
|
|
7290
|
+
);
|
|
7291
|
+
break;
|
|
7292
|
+
}
|
|
7293
|
+
if (!validateFileForUpload(selected[i])) continue;
|
|
6933
7294
|
uploadFile(selected[i]);
|
|
6934
7295
|
}
|
|
6935
7296
|
fileInput.value = "";
|
|
@@ -6940,6 +7301,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
6940
7301
|
textareaArea.appendChild(dropdown);
|
|
6941
7302
|
outerDiv.appendChild(filesRow);
|
|
6942
7303
|
outerDiv.appendChild(textareaArea);
|
|
7304
|
+
outerDiv.appendChild(errorEl);
|
|
6943
7305
|
if (element.minLength != null || element.maxLength != null) {
|
|
6944
7306
|
const counterRow = document.createElement("div");
|
|
6945
7307
|
counterRow.style.cssText = "position: relative; padding: 2px 14px 6px; text-align: right;";
|
|
@@ -7099,20 +7461,29 @@ function renderRichInputElement(element, ctx, wrapper, pathKey) {
|
|
|
7099
7461
|
const state = ctx.state;
|
|
7100
7462
|
const textKey = element.textKey ?? "text";
|
|
7101
7463
|
const filesKey = element.filesKey ?? "files";
|
|
7102
|
-
const rawPrefill = ctx.prefill[element.key];
|
|
7103
7464
|
let initialValue;
|
|
7104
|
-
if (
|
|
7105
|
-
const
|
|
7106
|
-
const
|
|
7107
|
-
const filesVal = obj[filesKey] ?? obj["files"];
|
|
7465
|
+
if (element.flatOutput) {
|
|
7466
|
+
const textVal = ctx.prefill[textKey];
|
|
7467
|
+
const filesVal = ctx.prefill[filesKey];
|
|
7108
7468
|
initialValue = {
|
|
7109
7469
|
text: typeof textVal === "string" ? textVal : null,
|
|
7110
7470
|
files: Array.isArray(filesVal) ? filesVal : []
|
|
7111
7471
|
};
|
|
7112
|
-
} else if (typeof rawPrefill === "string") {
|
|
7113
|
-
initialValue = { text: rawPrefill || null, files: [] };
|
|
7114
7472
|
} else {
|
|
7115
|
-
|
|
7473
|
+
const rawPrefill = ctx.prefill[element.key];
|
|
7474
|
+
if (rawPrefill && typeof rawPrefill === "object" && !Array.isArray(rawPrefill)) {
|
|
7475
|
+
const obj = rawPrefill;
|
|
7476
|
+
const textVal = obj[textKey] ?? obj["text"];
|
|
7477
|
+
const filesVal = obj[filesKey] ?? obj["files"];
|
|
7478
|
+
initialValue = {
|
|
7479
|
+
text: typeof textVal === "string" ? textVal : null,
|
|
7480
|
+
files: Array.isArray(filesVal) ? filesVal : []
|
|
7481
|
+
};
|
|
7482
|
+
} else if (typeof rawPrefill === "string") {
|
|
7483
|
+
initialValue = { text: rawPrefill || null, files: [] };
|
|
7484
|
+
} else {
|
|
7485
|
+
initialValue = { text: null, files: [] };
|
|
7486
|
+
}
|
|
7116
7487
|
}
|
|
7117
7488
|
for (const rid of initialValue.files) {
|
|
7118
7489
|
if (!state.resourceIndex.has(rid)) {
|
|
@@ -7192,7 +7563,7 @@ function validateRichInputElement(element, key, context) {
|
|
|
7192
7563
|
);
|
|
7193
7564
|
}
|
|
7194
7565
|
}
|
|
7195
|
-
return { value, errors };
|
|
7566
|
+
return { value, errors, spread: !!element.flatOutput };
|
|
7196
7567
|
}
|
|
7197
7568
|
function updateRichInputField(element, fieldPath, value, context) {
|
|
7198
7569
|
const { scopeRoot } = context;
|
|
@@ -7668,6 +8039,9 @@ var defaultConfig = {
|
|
|
7668
8039
|
invalidHexColour: "Invalid hex color",
|
|
7669
8040
|
minFiles: "Minimum {min} files required",
|
|
7670
8041
|
maxFiles: "Maximum {max} files allowed",
|
|
8042
|
+
invalidFileExtension: 'File "{name}" has unsupported format. Allowed: {formats}',
|
|
8043
|
+
fileTooLarge: 'File "{name}" exceeds maximum size of {maxSize}MB',
|
|
8044
|
+
filesLimitExceeded: "{skipped} file(s) skipped: maximum {max} files allowed",
|
|
7671
8045
|
unsupportedFieldType: "Unsupported field type: {type}",
|
|
7672
8046
|
invalidOption: "Invalid option",
|
|
7673
8047
|
tableAddRow: "Add row",
|
|
@@ -7727,6 +8101,9 @@ var defaultConfig = {
|
|
|
7727
8101
|
invalidHexColour: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0446\u0432\u0435\u0442\u0430",
|
|
7728
8102
|
minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
7729
8103
|
maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8104
|
+
invalidFileExtension: '\u0424\u0430\u0439\u043B "{name}" \u0438\u043C\u0435\u0435\u0442 \u043D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442. \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0435: {formats}',
|
|
8105
|
+
fileTooLarge: '\u0424\u0430\u0439\u043B "{name}" \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0435\u0442 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 {maxSize}\u041C\u0411',
|
|
8106
|
+
filesLimitExceeded: "{skipped} \u0444\u0430\u0439\u043B(\u043E\u0432) \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E: \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
7730
8107
|
unsupportedFieldType: "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0442\u0438\u043F \u043F\u043E\u043B\u044F: {type}",
|
|
7731
8108
|
invalidOption: "\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435",
|
|
7732
8109
|
tableAddRow: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443",
|
|
@@ -7771,7 +8148,8 @@ function createInstanceState(config) {
|
|
|
7771
8148
|
...config,
|
|
7772
8149
|
translations: mergedTranslations
|
|
7773
8150
|
},
|
|
7774
|
-
debounceTimer: null
|
|
8151
|
+
debounceTimer: null,
|
|
8152
|
+
prefill: {}
|
|
7775
8153
|
};
|
|
7776
8154
|
}
|
|
7777
8155
|
function generateInstanceId() {
|
|
@@ -7969,6 +8347,26 @@ function applyActionButtonStyles(button, isFormLevel = false) {
|
|
|
7969
8347
|
}
|
|
7970
8348
|
|
|
7971
8349
|
// src/components/registry.ts
|
|
8350
|
+
function validateHiddenElement(element, key, context) {
|
|
8351
|
+
const { scopeRoot } = context;
|
|
8352
|
+
const input = scopeRoot.querySelector(
|
|
8353
|
+
`input[type="hidden"][data-hidden-field="true"][name="${key}"]`
|
|
8354
|
+
);
|
|
8355
|
+
const raw = input?.value ?? "";
|
|
8356
|
+
if (raw === "") {
|
|
8357
|
+
const defaultVal = "default" in element ? element.default : null;
|
|
8358
|
+
return { value: defaultVal !== void 0 ? defaultVal : null, errors: [] };
|
|
8359
|
+
}
|
|
8360
|
+
return { value: deserializeHiddenValue(raw), errors: [] };
|
|
8361
|
+
}
|
|
8362
|
+
function updateHiddenField(_element, fieldPath, value, context) {
|
|
8363
|
+
const { scopeRoot } = context;
|
|
8364
|
+
const input = scopeRoot.querySelector(
|
|
8365
|
+
`input[type="hidden"][data-hidden-field="true"][name="${fieldPath}"]`
|
|
8366
|
+
);
|
|
8367
|
+
if (!input) return;
|
|
8368
|
+
input.value = serializeHiddenValue(value);
|
|
8369
|
+
}
|
|
7972
8370
|
var componentRegistry = {
|
|
7973
8371
|
text: {
|
|
7974
8372
|
validate: validateTextElement,
|
|
@@ -8023,6 +8421,11 @@ var componentRegistry = {
|
|
|
8023
8421
|
richinput: {
|
|
8024
8422
|
validate: validateRichInputElement,
|
|
8025
8423
|
update: updateRichInputField
|
|
8424
|
+
},
|
|
8425
|
+
hidden: {
|
|
8426
|
+
// Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
|
|
8427
|
+
validate: validateHiddenElement,
|
|
8428
|
+
update: updateHiddenField
|
|
8026
8429
|
}
|
|
8027
8430
|
};
|
|
8028
8431
|
function getComponentOperations(elementType) {
|
|
@@ -8432,6 +8835,7 @@ var FormBuilderInstance = class {
|
|
|
8432
8835
|
this.state.formRoot = root;
|
|
8433
8836
|
this.state.schema = schema;
|
|
8434
8837
|
this.state.externalActions = actions || null;
|
|
8838
|
+
this.state.prefill = prefill || {};
|
|
8435
8839
|
clear(root);
|
|
8436
8840
|
root.setAttribute("data-fb-root", "true");
|
|
8437
8841
|
injectThemeVariables(root, this.state.config.theme);
|
|
@@ -8449,7 +8853,9 @@ var FormBuilderInstance = class {
|
|
|
8449
8853
|
fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
8450
8854
|
}
|
|
8451
8855
|
schema.elements.forEach((element) => {
|
|
8452
|
-
if (element.hidden) {
|
|
8856
|
+
if (element.hidden || element.type === "hidden") {
|
|
8857
|
+
const val = prefill?.[element.key] ?? element.default ?? null;
|
|
8858
|
+
fieldsWrapper.appendChild(createHiddenInput(element.key, val));
|
|
8453
8859
|
return;
|
|
8454
8860
|
}
|
|
8455
8861
|
const block = renderElement2(element, {
|
|
@@ -8498,10 +8904,10 @@ var FormBuilderInstance = class {
|
|
|
8498
8904
|
);
|
|
8499
8905
|
if (componentResult !== null) {
|
|
8500
8906
|
errors.push(...componentResult.errors);
|
|
8501
|
-
return componentResult.value;
|
|
8907
|
+
return { value: componentResult.value, spread: !!componentResult.spread };
|
|
8502
8908
|
}
|
|
8503
8909
|
console.warn(`Unknown field type "${element.type}" for key "${key}"`);
|
|
8504
|
-
return null;
|
|
8910
|
+
return { value: null, spread: false };
|
|
8505
8911
|
};
|
|
8506
8912
|
setValidateElement(validateElement2);
|
|
8507
8913
|
this.state.schema.elements.forEach((element) => {
|
|
@@ -8521,10 +8927,23 @@ var FormBuilderInstance = class {
|
|
|
8521
8927
|
);
|
|
8522
8928
|
}
|
|
8523
8929
|
}
|
|
8524
|
-
if (element.hidden) {
|
|
8525
|
-
|
|
8930
|
+
if (element.hidden || element.type === "hidden") {
|
|
8931
|
+
const hiddenInput = this.state.formRoot.querySelector(
|
|
8932
|
+
`input[type="hidden"][data-hidden-field="true"][name="${element.key}"]`
|
|
8933
|
+
);
|
|
8934
|
+
const raw = hiddenInput?.value ?? "";
|
|
8935
|
+
if (raw !== "") {
|
|
8936
|
+
data[element.key] = deserializeHiddenValue(raw);
|
|
8937
|
+
} else {
|
|
8938
|
+
data[element.key] = element.default !== void 0 ? element.default : null;
|
|
8939
|
+
}
|
|
8526
8940
|
} else {
|
|
8527
|
-
|
|
8941
|
+
const result = validateElement2(element, { path: "" });
|
|
8942
|
+
if (result.spread && result.value !== null && typeof result.value === "object") {
|
|
8943
|
+
Object.assign(data, result.value);
|
|
8944
|
+
} else {
|
|
8945
|
+
data[element.key] = result.value;
|
|
8946
|
+
}
|
|
8528
8947
|
}
|
|
8529
8948
|
});
|
|
8530
8949
|
return {
|
|
@@ -8618,6 +9037,23 @@ var FormBuilderInstance = class {
|
|
|
8618
9037
|
}
|
|
8619
9038
|
return data;
|
|
8620
9039
|
}
|
|
9040
|
+
/**
|
|
9041
|
+
* Build a map from flat output keys (textKey/filesKey) to the richinput schema element info.
|
|
9042
|
+
* Used by setFormData to detect flat richinput keys and remap them to their composite values.
|
|
9043
|
+
*/
|
|
9044
|
+
buildFlatKeyMap(elements) {
|
|
9045
|
+
const map = /* @__PURE__ */ new Map();
|
|
9046
|
+
for (const el of elements) {
|
|
9047
|
+
if (el.type === "richinput" && el.flatOutput) {
|
|
9048
|
+
const richEl = el;
|
|
9049
|
+
const textKey = richEl.textKey ?? "text";
|
|
9050
|
+
const filesKey = richEl.filesKey ?? "files";
|
|
9051
|
+
map.set(textKey, { schemaKey: el.key, role: "text" });
|
|
9052
|
+
map.set(filesKey, { schemaKey: el.key, role: "files" });
|
|
9053
|
+
}
|
|
9054
|
+
}
|
|
9055
|
+
return map;
|
|
9056
|
+
}
|
|
8621
9057
|
/**
|
|
8622
9058
|
* Set form data - update multiple fields without full re-render
|
|
8623
9059
|
* @param data - Object with field paths and their values
|
|
@@ -8629,8 +9065,21 @@ var FormBuilderInstance = class {
|
|
|
8629
9065
|
);
|
|
8630
9066
|
return;
|
|
8631
9067
|
}
|
|
9068
|
+
const flatKeyMap = this.buildFlatKeyMap(this.state.schema.elements);
|
|
9069
|
+
const flatUpdates = /* @__PURE__ */ new Map();
|
|
8632
9070
|
for (const fieldPath in data) {
|
|
8633
|
-
|
|
9071
|
+
const flatInfo = flatKeyMap.get(fieldPath);
|
|
9072
|
+
if (flatInfo) {
|
|
9073
|
+
if (!flatUpdates.has(flatInfo.schemaKey)) {
|
|
9074
|
+
flatUpdates.set(flatInfo.schemaKey, {});
|
|
9075
|
+
}
|
|
9076
|
+
flatUpdates.get(flatInfo.schemaKey)[fieldPath] = data[fieldPath];
|
|
9077
|
+
} else {
|
|
9078
|
+
this.updateField(fieldPath, data[fieldPath]);
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
for (const [schemaKey, compositeValue] of flatUpdates) {
|
|
9082
|
+
this.updateField(schemaKey, compositeValue);
|
|
8634
9083
|
}
|
|
8635
9084
|
}
|
|
8636
9085
|
/**
|