@dmitryvim/form-builder 0.5.1 → 0.5.3
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 +125 -112
- package/dist/browser/formbuilder.v0.5.3.min.js +1490 -0
- package/dist/cjs/index.cjs +276 -222
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +270 -216
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +125 -112
- package/dist/types/components/container.d.ts +1 -0
- package/dist/types/types/config.d.ts +2 -0
- package/dist/types/utils/helpers.d.ts +8 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.5.1.min.js +0 -1477
package/dist/cjs/index.cjs
CHANGED
|
@@ -19,6 +19,71 @@ function t(key, state, params) {
|
|
|
19
19
|
return text;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// src/utils/helpers.ts
|
|
23
|
+
function isElementReadonly(element, state, ctx) {
|
|
24
|
+
return element.readonly === true || state.config.readonly === true || (ctx == null ? void 0 : ctx.inheritedReadonly) === true;
|
|
25
|
+
}
|
|
26
|
+
function isPlainObject(obj) {
|
|
27
|
+
return obj && typeof obj === "object" && obj.constructor === Object;
|
|
28
|
+
}
|
|
29
|
+
function escapeHtml(text) {
|
|
30
|
+
const div = document.createElement("div");
|
|
31
|
+
div.textContent = text;
|
|
32
|
+
return div.innerHTML;
|
|
33
|
+
}
|
|
34
|
+
function getElementLookupKey(element, state) {
|
|
35
|
+
if (element.key) {
|
|
36
|
+
return element.key;
|
|
37
|
+
}
|
|
38
|
+
const cached = state.syntheticElementIds.get(element);
|
|
39
|
+
if (cached !== void 0) {
|
|
40
|
+
return cached;
|
|
41
|
+
}
|
|
42
|
+
const id = `fb-synthetic-${state.syntheticElementIdCounter++}`;
|
|
43
|
+
state.syntheticElementIds.set(element, id);
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
function pathJoin(base, key) {
|
|
47
|
+
return base ? `${base}.${key}` : key;
|
|
48
|
+
}
|
|
49
|
+
function findDirectContainerRows(scopeRoot, containerPath) {
|
|
50
|
+
if (!containerPath) return [];
|
|
51
|
+
const all = scopeRoot.querySelectorAll("[data-container-item]");
|
|
52
|
+
return Array.from(all).filter((el) => {
|
|
53
|
+
const attr = el.getAttribute("data-container-item") || "";
|
|
54
|
+
if (!attr.startsWith(`${containerPath}[`)) return false;
|
|
55
|
+
return /^\[\d+\]$/.test(attr.slice(containerPath.length));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function clear(node) {
|
|
59
|
+
while (node.firstChild) node.removeChild(node.firstChild);
|
|
60
|
+
}
|
|
61
|
+
function formatFileSize(bytes) {
|
|
62
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
63
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
64
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
65
|
+
}
|
|
66
|
+
function serializeHiddenValue(value) {
|
|
67
|
+
if (value === null || value === void 0) return "";
|
|
68
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
69
|
+
}
|
|
70
|
+
function deserializeHiddenValue(raw) {
|
|
71
|
+
if (raw === "") return null;
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(raw);
|
|
74
|
+
} catch {
|
|
75
|
+
return raw;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function createHiddenInput(name, value) {
|
|
79
|
+
const input = document.createElement("input");
|
|
80
|
+
input.type = "hidden";
|
|
81
|
+
input.name = name;
|
|
82
|
+
input.setAttribute("data-hidden-field", "true");
|
|
83
|
+
input.value = serializeHiddenValue(value);
|
|
84
|
+
return input;
|
|
85
|
+
}
|
|
86
|
+
|
|
22
87
|
// src/utils/validation.ts
|
|
23
88
|
function addLengthHint(element, parts, state) {
|
|
24
89
|
if (element.minLength != null || element.maxLength != null) {
|
|
@@ -191,6 +256,65 @@ function validateSchema(schema) {
|
|
|
191
256
|
}
|
|
192
257
|
}
|
|
193
258
|
}
|
|
259
|
+
function validateCountBounds(element, elementPath, errors2) {
|
|
260
|
+
var _a, _b;
|
|
261
|
+
const el = element;
|
|
262
|
+
if (el.type === "group") {
|
|
263
|
+
if (!isPlainObject(el.repeat)) return;
|
|
264
|
+
checkBounds(
|
|
265
|
+
elementPath,
|
|
266
|
+
(_a = el.repeat) == null ? void 0 : _a.min,
|
|
267
|
+
(_b = el.repeat) == null ? void 0 : _b.max,
|
|
268
|
+
"repeat.min",
|
|
269
|
+
"repeat.max",
|
|
270
|
+
el.required === true,
|
|
271
|
+
errors2
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const isMultiple = el.multiple === true || el.type === "files";
|
|
276
|
+
if (!isMultiple) return;
|
|
277
|
+
checkBounds(
|
|
278
|
+
elementPath,
|
|
279
|
+
el.minCount,
|
|
280
|
+
el.maxCount,
|
|
281
|
+
"minCount",
|
|
282
|
+
"maxCount",
|
|
283
|
+
el.required === true,
|
|
284
|
+
errors2
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
function checkBounds(elementPath, minCount, maxCount, minName, maxName, requiredImpliesFloor, errors2) {
|
|
288
|
+
for (const [name, bound] of [
|
|
289
|
+
[minName, minCount],
|
|
290
|
+
[maxName, maxCount]
|
|
291
|
+
]) {
|
|
292
|
+
if (bound !== void 0 && typeof bound !== "number") {
|
|
293
|
+
errors2.push(
|
|
294
|
+
`${elementPath}: ${name} must be a number (got ${typeof bound})`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const min = typeof minCount === "number" ? minCount : void 0;
|
|
299
|
+
const max = typeof maxCount === "number" ? maxCount : void 0;
|
|
300
|
+
if (max !== void 0 && (max < 0 || Number.isNaN(max))) {
|
|
301
|
+
errors2.push(
|
|
302
|
+
`${elementPath}: ${maxName} must be a non-negative number or Infinity (got ${max})`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (min !== void 0 && (min < 0 || !Number.isFinite(min))) {
|
|
306
|
+
errors2.push(
|
|
307
|
+
`${elementPath}: ${minName} must be a finite non-negative number (got ${min})`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const effectiveMin = min != null ? min : requiredImpliesFloor ? 1 : void 0;
|
|
311
|
+
if (effectiveMin !== void 0 && max !== void 0 && effectiveMin > max) {
|
|
312
|
+
const shown = min !== void 0 ? `${minName} (${min})` : `required: true (implies ${minName} 1)`;
|
|
313
|
+
errors2.push(
|
|
314
|
+
`${elementPath}: ${shown} cannot be greater than ${maxName} (${max})`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
194
318
|
function validateElements(elements, path) {
|
|
195
319
|
elements.forEach((element, index) => {
|
|
196
320
|
const elementPath = `${path}[${index}]`;
|
|
@@ -200,6 +324,7 @@ function validateSchema(schema) {
|
|
|
200
324
|
if (!element.key && element.type !== "markdown") {
|
|
201
325
|
errors.push(`${elementPath}: missing key`);
|
|
202
326
|
}
|
|
327
|
+
validateCountBounds(element, elementPath, errors);
|
|
203
328
|
if (element.type === "markdown") {
|
|
204
329
|
const content = element.content;
|
|
205
330
|
if (typeof content !== "string") {
|
|
@@ -280,62 +405,6 @@ function validateSchema(schema) {
|
|
|
280
405
|
return errors;
|
|
281
406
|
}
|
|
282
407
|
|
|
283
|
-
// src/utils/helpers.ts
|
|
284
|
-
function isElementReadonly(element, state, ctx) {
|
|
285
|
-
return element.readonly === true || state.config.readonly === true || (ctx == null ? void 0 : ctx.inheritedReadonly) === true;
|
|
286
|
-
}
|
|
287
|
-
function isPlainObject(obj) {
|
|
288
|
-
return obj && typeof obj === "object" && obj.constructor === Object;
|
|
289
|
-
}
|
|
290
|
-
function escapeHtml(text) {
|
|
291
|
-
const div = document.createElement("div");
|
|
292
|
-
div.textContent = text;
|
|
293
|
-
return div.innerHTML;
|
|
294
|
-
}
|
|
295
|
-
function getElementLookupKey(element, state) {
|
|
296
|
-
if (element.key) {
|
|
297
|
-
return element.key;
|
|
298
|
-
}
|
|
299
|
-
const cached = state.syntheticElementIds.get(element);
|
|
300
|
-
if (cached !== void 0) {
|
|
301
|
-
return cached;
|
|
302
|
-
}
|
|
303
|
-
const id = `fb-synthetic-${state.syntheticElementIdCounter++}`;
|
|
304
|
-
state.syntheticElementIds.set(element, id);
|
|
305
|
-
return id;
|
|
306
|
-
}
|
|
307
|
-
function pathJoin(base, key) {
|
|
308
|
-
return base ? `${base}.${key}` : key;
|
|
309
|
-
}
|
|
310
|
-
function clear(node) {
|
|
311
|
-
while (node.firstChild) node.removeChild(node.firstChild);
|
|
312
|
-
}
|
|
313
|
-
function formatFileSize(bytes) {
|
|
314
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
315
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
316
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
317
|
-
}
|
|
318
|
-
function serializeHiddenValue(value) {
|
|
319
|
-
if (value === null || value === void 0) return "";
|
|
320
|
-
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
321
|
-
}
|
|
322
|
-
function deserializeHiddenValue(raw) {
|
|
323
|
-
if (raw === "") return null;
|
|
324
|
-
try {
|
|
325
|
-
return JSON.parse(raw);
|
|
326
|
-
} catch {
|
|
327
|
-
return raw;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
function createHiddenInput(name, value) {
|
|
331
|
-
const input = document.createElement("input");
|
|
332
|
-
input.type = "hidden";
|
|
333
|
-
input.name = name;
|
|
334
|
-
input.setAttribute("data-hidden-field", "true");
|
|
335
|
-
input.value = serializeHiddenValue(value);
|
|
336
|
-
return input;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
408
|
// src/utils/enable-conditions.ts
|
|
340
409
|
function getValueByPath(data, path) {
|
|
341
410
|
if (!data || typeof data !== "object") {
|
|
@@ -457,6 +526,19 @@ function ensureThemingHooks(doc) {
|
|
|
457
526
|
color: var(--fb-error-color);
|
|
458
527
|
background-color: var(--fb-background-hover-color);
|
|
459
528
|
}
|
|
529
|
+
.fb-item-remove:disabled {
|
|
530
|
+
opacity: 0.35;
|
|
531
|
+
cursor: not-allowed;
|
|
532
|
+
}
|
|
533
|
+
/* Cards carrying a row-level remove button reserve a right-hand lane for it
|
|
534
|
+
(24px button + inset + gap) so it never sits on top of the first field's
|
|
535
|
+
control. A class rather than an inline style, so host CSS can override the
|
|
536
|
+
gutter without !important; the compound selector keeps it ahead of the
|
|
537
|
+
card's own p-2 utility regardless of stylesheet order. Its own token, not
|
|
538
|
+
--fb-slide-card-padding, which is slides-only and always resolves. */
|
|
539
|
+
.containerItem.fb-row-removable {
|
|
540
|
+
padding-right: var(--fb-row-remove-gutter, 40px);
|
|
541
|
+
}
|
|
460
542
|
/* Prefill-suggestion pills rendered by createPrefillHints. Outline pill at rest,
|
|
461
543
|
soft-fill on hover, solid-fill when selected. All colors flow from the active
|
|
462
544
|
theme \u2014 consumers don't need to ship their own CSS. */
|
|
@@ -4934,12 +5016,21 @@ function buildClearAllRow(state, ridCount, onClearAll) {
|
|
|
4934
5016
|
return row;
|
|
4935
5017
|
}
|
|
4936
5018
|
var gridResizeObservers = /* @__PURE__ */ new WeakMap();
|
|
5019
|
+
var gridMeasureFrames = /* @__PURE__ */ new WeakMap();
|
|
5020
|
+
function cancelPendingMeasure(container) {
|
|
5021
|
+
const frame = gridMeasureFrames.get(container);
|
|
5022
|
+
if (frame !== void 0) {
|
|
5023
|
+
cancelAnimationFrame(frame);
|
|
5024
|
+
gridMeasureFrames.delete(container);
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
4937
5027
|
function disposePlaceholdersForUpload(container) {
|
|
4938
5028
|
const observer = gridResizeObservers.get(container);
|
|
4939
5029
|
if (observer) {
|
|
4940
5030
|
observer.disconnect();
|
|
4941
5031
|
gridResizeObservers.delete(container);
|
|
4942
5032
|
}
|
|
5033
|
+
cancelPendingMeasure(container);
|
|
4943
5034
|
container.querySelectorAll(".fb-multi-placeholder").forEach((p) => p.remove());
|
|
4944
5035
|
}
|
|
4945
5036
|
function renderResourcePills(opts) {
|
|
@@ -4965,6 +5056,7 @@ function renderResourcePills(opts) {
|
|
|
4965
5056
|
previousObserver.disconnect();
|
|
4966
5057
|
gridResizeObservers.delete(container);
|
|
4967
5058
|
}
|
|
5059
|
+
cancelPendingMeasure(container);
|
|
4968
5060
|
while (container.firstChild) container.removeChild(container.firstChild);
|
|
4969
5061
|
const ridList = rids != null ? rids : [];
|
|
4970
5062
|
const effectiveMax = maxCount != null ? maxCount : Infinity;
|
|
@@ -5066,7 +5158,7 @@ function renderResourcePills(opts) {
|
|
|
5066
5158
|
if (effectiveMax === Infinity || effectiveMax > occupied) {
|
|
5067
5159
|
grid.appendChild(buildPlaceholderTile());
|
|
5068
5160
|
}
|
|
5069
|
-
requestAnimationFrame(adjustPlaceholders);
|
|
5161
|
+
gridMeasureFrames.set(container, requestAnimationFrame(adjustPlaceholders));
|
|
5070
5162
|
if (typeof ResizeObserver !== "undefined") {
|
|
5071
5163
|
const ro = new ResizeObserver(() => adjustPlaceholders());
|
|
5072
5164
|
ro.observe(grid);
|
|
@@ -6846,11 +6938,14 @@ function getChildWrapperClass(columns) {
|
|
|
6846
6938
|
const cols = columns || 1;
|
|
6847
6939
|
return cols === 1 ? "fb-row" : `grid grid-cols-${cols} gap-2`;
|
|
6848
6940
|
}
|
|
6849
|
-
function mountRemoveButton(item, onRemove, state) {
|
|
6941
|
+
function mountRemoveButton(item, onRemove, state, containerLabel) {
|
|
6850
6942
|
const rem = document.createElement("button");
|
|
6851
6943
|
rem.type = "button";
|
|
6852
6944
|
rem.className = "fb-item-remove";
|
|
6853
|
-
rem.setAttribute(
|
|
6945
|
+
rem.setAttribute(
|
|
6946
|
+
"aria-label",
|
|
6947
|
+
containerLabel ? t("removeRowFrom", state, { label: containerLabel }) : t("removeRow", state)
|
|
6948
|
+
);
|
|
6854
6949
|
rem.style.cssText = `
|
|
6855
6950
|
width: 24px;
|
|
6856
6951
|
height: 24px;
|
|
@@ -6865,17 +6960,13 @@ function mountRemoveButton(item, onRemove, state) {
|
|
|
6865
6960
|
`;
|
|
6866
6961
|
rem.innerHTML = BIN_ICON_SVG;
|
|
6867
6962
|
rem.onclick = onRemove;
|
|
6868
|
-
const labelRow = item.querySelector("[data-fb-label-row]");
|
|
6869
|
-
if (labelRow) {
|
|
6870
|
-
rem.style.marginLeft = "auto";
|
|
6871
|
-
labelRow.appendChild(rem);
|
|
6872
|
-
return;
|
|
6873
|
-
}
|
|
6874
6963
|
rem.style.position = "absolute";
|
|
6875
|
-
rem.style.top = "8px";
|
|
6876
|
-
rem.style.right = "8px";
|
|
6964
|
+
rem.style.top = "var(--fb-row-remove-inset, 8px)";
|
|
6965
|
+
rem.style.right = "var(--fb-row-remove-inset, 8px)";
|
|
6966
|
+
rem.style.zIndex = "1";
|
|
6877
6967
|
item.style.position = "relative";
|
|
6878
|
-
item.
|
|
6968
|
+
item.classList.add("fb-row-removable");
|
|
6969
|
+
item.insertBefore(rem, item.firstChild);
|
|
6879
6970
|
}
|
|
6880
6971
|
function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
6881
6972
|
var _a, _b, _c, _d;
|
|
@@ -6901,24 +6992,29 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6901
6992
|
const max = (_b = element.maxCount) != null ? _b : Infinity;
|
|
6902
6993
|
const pre = Array.isArray((_c = ctx.prefill) == null ? void 0 : _c[element.key]) ? ctx.prefill[element.key] : null;
|
|
6903
6994
|
const childDefaults = extractChildDefaults(element.elements);
|
|
6904
|
-
|
|
6905
|
-
const
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6995
|
+
let nextRowIndex = 0;
|
|
6996
|
+
const takeRowIndex = () => nextRowIndex++;
|
|
6997
|
+
const directRows = () => Array.from(itemsWrap.children).filter(
|
|
6998
|
+
(el) => el.classList.contains("containerItem")
|
|
6999
|
+
);
|
|
7000
|
+
const countItems = () => directRows().length;
|
|
7001
|
+
const handleRemoveItem = (item) => {
|
|
7002
|
+
if (countItems() <= min) return;
|
|
7003
|
+
item.remove();
|
|
7004
|
+
updateAddButton();
|
|
7005
|
+
};
|
|
7006
|
+
const createContainerItem = (idx, rowPrefill, formData) => {
|
|
6909
7007
|
const subCtx = {
|
|
6910
|
-
state
|
|
7008
|
+
state,
|
|
6911
7009
|
instance: ctx.instance,
|
|
6912
7010
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6913
|
-
prefill: childDefaults,
|
|
6914
|
-
|
|
6915
|
-
formData: currentFormData,
|
|
6916
|
-
// Current root data from DOM for enableIf
|
|
7011
|
+
prefill: mergeWithDefaults(rowPrefill != null ? rowPrefill : {}, childDefaults),
|
|
7012
|
+
formData,
|
|
6917
7013
|
inheritedReadonly: childInheritedReadonly
|
|
6918
7014
|
};
|
|
6919
7015
|
const item = document.createElement("div");
|
|
6920
7016
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6921
|
-
item.setAttribute("data-container-item",
|
|
7017
|
+
item.setAttribute("data-container-item", subCtx.path);
|
|
6922
7018
|
if (isSlides) {
|
|
6923
7019
|
item.setAttribute("data-fb-slide-card", "");
|
|
6924
7020
|
}
|
|
@@ -6927,13 +7023,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6927
7023
|
isSlides ? void 0 : element.columns
|
|
6928
7024
|
);
|
|
6929
7025
|
element.elements.forEach((child) => {
|
|
6930
|
-
var _a2;
|
|
7026
|
+
var _a2, _b2;
|
|
6931
7027
|
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
7028
|
+
const hiddenValue = (_b2 = (_a2 = rowPrefill == null ? void 0 : rowPrefill[child.key]) != null ? _a2 : "default" in child ? child.default : null) != null ? _b2 : null;
|
|
6932
7029
|
childWrapper.appendChild(
|
|
6933
|
-
createHiddenInput(
|
|
6934
|
-
pathJoin(subCtx.path, child.key),
|
|
6935
|
-
(_a2 = "default" in child ? child.default : null) != null ? _a2 : null
|
|
6936
|
-
)
|
|
7030
|
+
createHiddenInput(pathJoin(subCtx.path, child.key), hiddenValue)
|
|
6937
7031
|
);
|
|
6938
7032
|
} else {
|
|
6939
7033
|
childWrapper.appendChild(renderElement(child, subCtx));
|
|
@@ -6941,8 +7035,24 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6941
7035
|
});
|
|
6942
7036
|
item.appendChild(childWrapper);
|
|
6943
7037
|
if (!containerIsReadonly) {
|
|
6944
|
-
mountRemoveButton(
|
|
7038
|
+
mountRemoveButton(
|
|
7039
|
+
item,
|
|
7040
|
+
() => handleRemoveItem(item),
|
|
7041
|
+
state,
|
|
7042
|
+
element.label
|
|
7043
|
+
);
|
|
6945
7044
|
}
|
|
7045
|
+
return item;
|
|
7046
|
+
};
|
|
7047
|
+
const handleAddItem = () => {
|
|
7048
|
+
if (countItems() >= max) return;
|
|
7049
|
+
const item = createContainerItem(
|
|
7050
|
+
takeRowIndex(),
|
|
7051
|
+
null,
|
|
7052
|
+
// Current root data read back from the DOM, so enableIf sees what the
|
|
7053
|
+
// user has actually typed rather than the original prefill.
|
|
7054
|
+
state.formRoot ? extractRootFormData(state.formRoot) : {}
|
|
7055
|
+
);
|
|
6946
7056
|
if (slideAddTile && slideAddTile.parentElement === itemsWrap) {
|
|
6947
7057
|
itemsWrap.insertBefore(item, slideAddTile);
|
|
6948
7058
|
} else {
|
|
@@ -6955,112 +7065,44 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6955
7065
|
let pillAddUpdate = null;
|
|
6956
7066
|
const syncSlideTileSize = () => {
|
|
6957
7067
|
if (!slideAddTile) return;
|
|
6958
|
-
const firstSlide =
|
|
6959
|
-
":scope > .containerItem"
|
|
6960
|
-
);
|
|
7068
|
+
const firstSlide = directRows()[0];
|
|
6961
7069
|
if (firstSlide && firstSlide.offsetHeight > 0) {
|
|
6962
7070
|
slideAddTile.style.minHeight = `${firstSlide.offsetHeight}px`;
|
|
6963
7071
|
}
|
|
6964
7072
|
};
|
|
7073
|
+
const syncRemoveButtons = () => {
|
|
7074
|
+
const atFloor = countItems() <= min;
|
|
7075
|
+
directRows().forEach((row) => {
|
|
7076
|
+
const btn = Array.from(row.children).find(
|
|
7077
|
+
(el) => el.classList.contains("fb-item-remove")
|
|
7078
|
+
);
|
|
7079
|
+
if (btn) btn.disabled = atFloor;
|
|
7080
|
+
});
|
|
7081
|
+
};
|
|
6965
7082
|
const updateAddButton = () => {
|
|
6966
7083
|
const currentCount = countItems();
|
|
6967
7084
|
if (slideAddUpdate) slideAddUpdate(currentCount, max);
|
|
6968
7085
|
if (pillAddUpdate) pillAddUpdate(currentCount, max);
|
|
6969
7086
|
if (slideAddTile) syncSlideTileSize();
|
|
7087
|
+
syncRemoveButtons();
|
|
6970
7088
|
};
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
updateAddButton();
|
|
6974
|
-
};
|
|
6975
|
-
if (pre && Array.isArray(pre)) {
|
|
6976
|
-
pre.forEach((prefillObj, idx) => {
|
|
7089
|
+
if (pre) {
|
|
7090
|
+
pre.forEach((prefillObj) => {
|
|
6977
7091
|
var _a2;
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
// Merged prefill with defaults for enableIf
|
|
6985
|
-
formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill,
|
|
6986
|
-
// Complete root data for enableIf
|
|
6987
|
-
inheritedReadonly: childInheritedReadonly
|
|
6988
|
-
};
|
|
6989
|
-
const item = document.createElement("div");
|
|
6990
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6991
|
-
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6992
|
-
if (isSlides) {
|
|
6993
|
-
item.setAttribute("data-fb-slide-card", "");
|
|
6994
|
-
}
|
|
6995
|
-
const childWrapper = document.createElement("div");
|
|
6996
|
-
childWrapper.className = getChildWrapperClass(
|
|
6997
|
-
isSlides ? void 0 : element.columns
|
|
7092
|
+
itemsWrap.appendChild(
|
|
7093
|
+
createContainerItem(
|
|
7094
|
+
takeRowIndex(),
|
|
7095
|
+
prefillObj != null ? prefillObj : {},
|
|
7096
|
+
(_a2 = ctx.formData) != null ? _a2 : ctx.prefill
|
|
7097
|
+
)
|
|
6998
7098
|
);
|
|
6999
|
-
element.elements.forEach((child) => {
|
|
7000
|
-
var _a3, _b2;
|
|
7001
|
-
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
7002
|
-
const prefillVal = (_b2 = (_a3 = prefillObj == null ? void 0 : prefillObj[child.key]) != null ? _a3 : "default" in child ? child.default : null) != null ? _b2 : null;
|
|
7003
|
-
childWrapper.appendChild(
|
|
7004
|
-
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
7005
|
-
);
|
|
7006
|
-
} else {
|
|
7007
|
-
childWrapper.appendChild(renderElement(child, subCtx));
|
|
7008
|
-
}
|
|
7009
|
-
});
|
|
7010
|
-
item.appendChild(childWrapper);
|
|
7011
|
-
if (!containerIsReadonly) {
|
|
7012
|
-
mountRemoveButton(item, () => handleRemoveItem(item), ctx.state);
|
|
7013
|
-
}
|
|
7014
|
-
itemsWrap.appendChild(item);
|
|
7015
7099
|
});
|
|
7016
7100
|
}
|
|
7017
7101
|
if (!containerIsReadonly) {
|
|
7018
7102
|
while (countItems() < min) {
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
state: ctx.state,
|
|
7022
|
-
instance: ctx.instance,
|
|
7023
|
-
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
7024
|
-
prefill: childDefaults,
|
|
7025
|
-
// Defaults for enableIf evaluation
|
|
7026
|
-
formData: (_d = ctx.formData) != null ? _d : ctx.prefill,
|
|
7027
|
-
// Complete root data for enableIf
|
|
7028
|
-
inheritedReadonly: childInheritedReadonly
|
|
7029
|
-
};
|
|
7030
|
-
const item = document.createElement("div");
|
|
7031
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
7032
|
-
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
7033
|
-
if (isSlides) {
|
|
7034
|
-
item.setAttribute("data-fb-slide-card", "");
|
|
7035
|
-
}
|
|
7036
|
-
const childWrapper = document.createElement("div");
|
|
7037
|
-
childWrapper.className = getChildWrapperClass(
|
|
7038
|
-
isSlides ? void 0 : element.columns
|
|
7039
|
-
);
|
|
7040
|
-
element.elements.forEach((child) => {
|
|
7041
|
-
var _a2;
|
|
7042
|
-
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
7043
|
-
childWrapper.appendChild(
|
|
7044
|
-
createHiddenInput(
|
|
7045
|
-
pathJoin(subCtx.path, child.key),
|
|
7046
|
-
(_a2 = "default" in child ? child.default : null) != null ? _a2 : null
|
|
7047
|
-
)
|
|
7048
|
-
);
|
|
7049
|
-
} else {
|
|
7050
|
-
childWrapper.appendChild(renderElement(child, subCtx));
|
|
7051
|
-
}
|
|
7052
|
-
});
|
|
7053
|
-
item.appendChild(childWrapper);
|
|
7054
|
-
mountRemoveButton(
|
|
7055
|
-
item,
|
|
7056
|
-
() => {
|
|
7057
|
-
if (countItems() > min) {
|
|
7058
|
-
handleRemoveItem(item);
|
|
7059
|
-
}
|
|
7060
|
-
},
|
|
7061
|
-
ctx.state
|
|
7103
|
+
itemsWrap.appendChild(
|
|
7104
|
+
createContainerItem(takeRowIndex(), null, (_d = ctx.formData) != null ? _d : ctx.prefill)
|
|
7062
7105
|
);
|
|
7063
|
-
itemsWrap.appendChild(item);
|
|
7064
7106
|
}
|
|
7065
7107
|
}
|
|
7066
7108
|
containerWrap.appendChild(itemsWrap);
|
|
@@ -7128,15 +7170,7 @@ function validateContainerElement(element, key, context) {
|
|
|
7128
7170
|
};
|
|
7129
7171
|
if ("multiple" in element && element.multiple) {
|
|
7130
7172
|
const items = [];
|
|
7131
|
-
const
|
|
7132
|
-
"[data-container-item]"
|
|
7133
|
-
);
|
|
7134
|
-
const containerWrappers = Array.from(allContainerWrappers).filter((el) => {
|
|
7135
|
-
const attr = el.getAttribute("data-container-item") || "";
|
|
7136
|
-
if (!attr.startsWith(`${key}[`)) return false;
|
|
7137
|
-
const suffix = attr.slice(key.length);
|
|
7138
|
-
return /^\[\d+\]$/.test(suffix);
|
|
7139
|
-
});
|
|
7173
|
+
const containerWrappers = findDirectContainerRows(scopeRoot, key);
|
|
7140
7174
|
containerWrappers.forEach((itemContainer) => {
|
|
7141
7175
|
const itemData = {};
|
|
7142
7176
|
const containerAttr = itemContainer.getAttribute("data-container-item") || "";
|
|
@@ -7172,7 +7206,7 @@ function validateContainerElement(element, key, context) {
|
|
|
7172
7206
|
);
|
|
7173
7207
|
if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
|
|
7174
7208
|
Object.assign(itemData, childResult.value);
|
|
7175
|
-
} else {
|
|
7209
|
+
} else if (!childResult.skip && child.key) {
|
|
7176
7210
|
itemData[child.key] = childResult.value;
|
|
7177
7211
|
}
|
|
7178
7212
|
});
|
|
@@ -7214,7 +7248,7 @@ function validateContainerElement(element, key, context) {
|
|
|
7214
7248
|
);
|
|
7215
7249
|
if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
|
|
7216
7250
|
Object.assign(containerData, childResult.value);
|
|
7217
|
-
} else {
|
|
7251
|
+
} else if (!childResult.skip && child.key) {
|
|
7218
7252
|
containerData[child.key] = childResult.value;
|
|
7219
7253
|
}
|
|
7220
7254
|
}
|
|
@@ -7234,15 +7268,18 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
7234
7268
|
);
|
|
7235
7269
|
return;
|
|
7236
7270
|
}
|
|
7271
|
+
const rows = findDirectContainerRows(scopeRoot, fieldPath);
|
|
7237
7272
|
value.forEach((itemValue, index) => {
|
|
7238
|
-
|
|
7273
|
+
var _a;
|
|
7274
|
+
const rowPath = (_a = rows[index]) == null ? void 0 : _a.getAttribute("data-container-item");
|
|
7275
|
+
if (isPlainObject(itemValue) && rowPath) {
|
|
7239
7276
|
element.elements.forEach((childElement) => {
|
|
7240
|
-
var
|
|
7277
|
+
var _a2, _b;
|
|
7241
7278
|
if (childElement.type === "markdown" || !childElement.key) return;
|
|
7242
|
-
const childPath = `${
|
|
7279
|
+
const childPath = `${rowPath}.${childElement.key}`;
|
|
7243
7280
|
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
7244
7281
|
const richChild = childElement;
|
|
7245
|
-
const textKey = (
|
|
7282
|
+
const textKey = (_a2 = richChild.textKey) != null ? _a2 : "text";
|
|
7246
7283
|
const filesKey = (_b = richChild.filesKey) != null ? _b : "files";
|
|
7247
7284
|
const containerValue = itemValue;
|
|
7248
7285
|
const compositeValue = {};
|
|
@@ -7262,12 +7299,9 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
7262
7299
|
});
|
|
7263
7300
|
}
|
|
7264
7301
|
});
|
|
7265
|
-
|
|
7266
|
-
`[data-container-item^="${fieldPath}["]`
|
|
7267
|
-
);
|
|
7268
|
-
if (value.length !== existingContainers.length) {
|
|
7302
|
+
if (value.length !== rows.length) {
|
|
7269
7303
|
console.warn(
|
|
7270
|
-
`updateContainerField: Multiple container field "${fieldPath}"
|
|
7304
|
+
`updateContainerField: Multiple container field "${fieldPath}" received ${value.length} items for ${rows.length} rendered rows. Rows are not added or removed here \u2014 re-render for add/remove.`
|
|
7271
7305
|
);
|
|
7272
7306
|
}
|
|
7273
7307
|
} else {
|
|
@@ -10836,6 +10870,8 @@ var defaultConfig = {
|
|
|
10836
10870
|
en: {
|
|
10837
10871
|
// UI texts
|
|
10838
10872
|
removeElement: "Remove",
|
|
10873
|
+
removeRow: "Remove row",
|
|
10874
|
+
removeRowFrom: "Remove row from {label}",
|
|
10839
10875
|
clickDragText: "Click or drag file",
|
|
10840
10876
|
clickDragTextMultiple: "Click or drag files",
|
|
10841
10877
|
noFileSelected: "No file selected",
|
|
@@ -10909,6 +10945,8 @@ var defaultConfig = {
|
|
|
10909
10945
|
ru: {
|
|
10910
10946
|
// UI texts
|
|
10911
10947
|
removeElement: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C",
|
|
10948
|
+
removeRow: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443",
|
|
10949
|
+
removeRowFrom: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443 \u0438\u0437 \xAB{label}\xBB",
|
|
10912
10950
|
clickDragText: "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B",
|
|
10913
10951
|
clickDragTextMultiple: "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B",
|
|
10914
10952
|
noFileSelected: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D",
|
|
@@ -11298,6 +11336,18 @@ var exampleThemes = {
|
|
|
11298
11336
|
};
|
|
11299
11337
|
|
|
11300
11338
|
// src/instance/FormBuilderInstance.ts
|
|
11339
|
+
function findOwnField(scope, lookupKey, ownBoundary) {
|
|
11340
|
+
const matches = scope.querySelectorAll(
|
|
11341
|
+
`[data-field-key="${lookupKey}"]`
|
|
11342
|
+
);
|
|
11343
|
+
for (const el of Array.from(matches)) {
|
|
11344
|
+
const boundary = el.closest(
|
|
11345
|
+
"[data-container-item], [data-container]"
|
|
11346
|
+
);
|
|
11347
|
+
if (boundary === ownBoundary) return el;
|
|
11348
|
+
}
|
|
11349
|
+
return null;
|
|
11350
|
+
}
|
|
11301
11351
|
var FormBuilderInstance = class {
|
|
11302
11352
|
constructor(config) {
|
|
11303
11353
|
this.instanceId = generateInstanceId();
|
|
@@ -12024,29 +12074,29 @@ var FormBuilderInstance = class {
|
|
|
12024
12074
|
const formRoot = this.state.formRoot;
|
|
12025
12075
|
const lookupKey = getElementLookupKey(element, this.state);
|
|
12026
12076
|
if (!currentPath) {
|
|
12027
|
-
return formRoot
|
|
12077
|
+
return findOwnField(formRoot, lookupKey, null);
|
|
12028
12078
|
}
|
|
12029
12079
|
const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
|
|
12030
12080
|
if (pathMatch) {
|
|
12031
12081
|
const containerEl2 = formRoot.querySelector(
|
|
12032
12082
|
`[data-container-item="${pathMatch[1]}[${pathMatch[2]}]"]`
|
|
12033
12083
|
);
|
|
12034
|
-
return containerEl2 ? containerEl2
|
|
12084
|
+
return containerEl2 ? findOwnField(containerEl2, lookupKey, containerEl2) : null;
|
|
12035
12085
|
}
|
|
12036
12086
|
const containerEl = formRoot.querySelector(
|
|
12037
12087
|
`[data-container="${currentPath}"]`
|
|
12038
12088
|
);
|
|
12039
|
-
return containerEl ? containerEl
|
|
12089
|
+
return containerEl ? findOwnField(containerEl, lookupKey, containerEl) : null;
|
|
12040
12090
|
}
|
|
12041
12091
|
/**
|
|
12042
12092
|
* Apply enableIf show/hide logic to a single field wrapper.
|
|
12043
12093
|
* Extracted to reduce cyclomatic complexity of checkElements.
|
|
12044
12094
|
*/
|
|
12045
|
-
applyEnableIfVisibility(element, wrapper,
|
|
12095
|
+
applyEnableIfVisibility(element, wrapper, domPath, dataPath, fullDomPath, formData) {
|
|
12046
12096
|
var _a, _b, _c, _d;
|
|
12047
12097
|
try {
|
|
12048
12098
|
const scope = (_a = element.enableIf.scope) != null ? _a : "relative";
|
|
12049
|
-
const containerData = scope === "relative" &&
|
|
12099
|
+
const containerData = scope === "relative" && dataPath ? getValueByPath(formData, dataPath) : void 0;
|
|
12050
12100
|
const shouldEnable = evaluateEnableCondition(
|
|
12051
12101
|
element.enableIf,
|
|
12052
12102
|
formData,
|
|
@@ -12054,10 +12104,12 @@ var FormBuilderInstance = class {
|
|
|
12054
12104
|
);
|
|
12055
12105
|
const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
|
|
12056
12106
|
if (shouldEnable && isCurrentlyDisabled) {
|
|
12057
|
-
const containerPrefill =
|
|
12107
|
+
const containerPrefill = dataPath ? getValueByPath(formData, dataPath) : formData;
|
|
12058
12108
|
const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
|
|
12059
12109
|
const newWrapper = renderElement2(element, {
|
|
12060
|
-
path:
|
|
12110
|
+
// DOM path: the re-rendered control's `name` must match the row it
|
|
12111
|
+
// lives in, not the row's position in the extracted array.
|
|
12112
|
+
path: domPath,
|
|
12061
12113
|
prefill: prefillContext,
|
|
12062
12114
|
formData,
|
|
12063
12115
|
state: this.state,
|
|
@@ -12077,7 +12129,7 @@ var FormBuilderInstance = class {
|
|
|
12077
12129
|
}
|
|
12078
12130
|
} catch (error) {
|
|
12079
12131
|
console.error(
|
|
12080
|
-
`Error re-evaluating enableIf for field "${(_d = element.key) != null ? _d : "<no key>"}" at path "${
|
|
12132
|
+
`Error re-evaluating enableIf for field "${(_d = element.key) != null ? _d : "<no key>"}" at path "${fullDomPath}":`,
|
|
12081
12133
|
error
|
|
12082
12134
|
);
|
|
12083
12135
|
}
|
|
@@ -12089,45 +12141,47 @@ var FormBuilderInstance = class {
|
|
|
12089
12141
|
reevaluateConditionalFields() {
|
|
12090
12142
|
if (!this.state.schema || !this.state.formRoot) return;
|
|
12091
12143
|
const formData = this.validateForm(true).data;
|
|
12092
|
-
const checkElements = (elements,
|
|
12144
|
+
const checkElements = (elements, domPath, dataPath) => {
|
|
12093
12145
|
elements.forEach((element) => {
|
|
12094
|
-
var _a
|
|
12095
|
-
const
|
|
12146
|
+
var _a;
|
|
12147
|
+
const key = (_a = element.key) != null ? _a : "";
|
|
12148
|
+
const fullDomPath = domPath ? `${domPath}.${key}` : key;
|
|
12149
|
+
const fullDataPath = dataPath ? `${dataPath}.${key}` : key;
|
|
12096
12150
|
if (element.enableIf) {
|
|
12097
|
-
const fieldWrapper = this.findFieldWrapper(element,
|
|
12151
|
+
const fieldWrapper = this.findFieldWrapper(element, domPath);
|
|
12098
12152
|
if (fieldWrapper) {
|
|
12099
12153
|
this.applyEnableIfVisibility(
|
|
12100
12154
|
element,
|
|
12101
12155
|
fieldWrapper,
|
|
12102
|
-
|
|
12103
|
-
|
|
12156
|
+
domPath,
|
|
12157
|
+
dataPath,
|
|
12158
|
+
fullDomPath,
|
|
12104
12159
|
formData
|
|
12105
12160
|
);
|
|
12106
12161
|
}
|
|
12107
12162
|
}
|
|
12108
12163
|
if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
|
|
12109
|
-
const containerData = element.key ? formData
|
|
12164
|
+
const containerData = element.key ? getValueByPath(formData, fullDataPath) : void 0;
|
|
12110
12165
|
if (Array.isArray(containerData)) {
|
|
12111
|
-
const
|
|
12112
|
-
|
|
12166
|
+
const directItems = findDirectContainerRows(
|
|
12167
|
+
this.state.formRoot,
|
|
12168
|
+
fullDomPath
|
|
12113
12169
|
);
|
|
12114
|
-
|
|
12115
|
-
const
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
const attr = el.getAttribute("data-container-item") || "";
|
|
12122
|
-
checkElements(element.elements, attr);
|
|
12170
|
+
directItems.forEach((el, rowIndex) => {
|
|
12171
|
+
const marker = el.getAttribute("data-container-item") || "";
|
|
12172
|
+
checkElements(
|
|
12173
|
+
element.elements,
|
|
12174
|
+
marker,
|
|
12175
|
+
`${fullDataPath}[${rowIndex}]`
|
|
12176
|
+
);
|
|
12123
12177
|
});
|
|
12124
12178
|
} else {
|
|
12125
|
-
checkElements(element.elements,
|
|
12179
|
+
checkElements(element.elements, fullDomPath, fullDataPath);
|
|
12126
12180
|
}
|
|
12127
12181
|
}
|
|
12128
12182
|
});
|
|
12129
12183
|
};
|
|
12130
|
-
checkElements(this.state.schema.elements, "");
|
|
12184
|
+
checkElements(this.state.schema.elements, "", "");
|
|
12131
12185
|
}
|
|
12132
12186
|
/**
|
|
12133
12187
|
* Destroy instance and clean up resources
|