@dmitryvim/form-builder 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/formbuilder.min.js +114 -101
- package/dist/browser/{formbuilder.v0.5.1.min.js → formbuilder.v0.5.2.min.js} +114 -101
- package/dist/cjs/index.cjs +95 -136
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +94 -133
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +114 -101
- package/dist/types/utils/helpers.d.ts +8 -0
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -300,6 +300,15 @@ function getElementLookupKey(element, state) {
|
|
|
300
300
|
function pathJoin(base, key) {
|
|
301
301
|
return base ? `${base}.${key}` : key;
|
|
302
302
|
}
|
|
303
|
+
function findDirectContainerRows(scopeRoot, containerPath) {
|
|
304
|
+
if (!containerPath) return [];
|
|
305
|
+
const all = scopeRoot.querySelectorAll("[data-container-item]");
|
|
306
|
+
return Array.from(all).filter((el) => {
|
|
307
|
+
const attr = el.getAttribute("data-container-item") || "";
|
|
308
|
+
if (!attr.startsWith(`${containerPath}[`)) return false;
|
|
309
|
+
return /^\[\d+\]$/.test(attr.slice(containerPath.length));
|
|
310
|
+
});
|
|
311
|
+
}
|
|
303
312
|
function clear(node) {
|
|
304
313
|
while (node.firstChild) node.removeChild(node.firstChild);
|
|
305
314
|
}
|
|
@@ -449,6 +458,19 @@ function ensureThemingHooks(doc) {
|
|
|
449
458
|
color: var(--fb-error-color);
|
|
450
459
|
background-color: var(--fb-background-hover-color);
|
|
451
460
|
}
|
|
461
|
+
.fb-item-remove:disabled {
|
|
462
|
+
opacity: 0.35;
|
|
463
|
+
cursor: not-allowed;
|
|
464
|
+
}
|
|
465
|
+
/* Cards carrying a row-level remove button reserve a right-hand lane for it
|
|
466
|
+
(24px button + inset + gap) so it never sits on top of the first field's
|
|
467
|
+
control. A class rather than an inline style, so host CSS can override the
|
|
468
|
+
gutter without !important; the compound selector keeps it ahead of the
|
|
469
|
+
card's own p-2 utility regardless of stylesheet order. Its own token, not
|
|
470
|
+
--fb-slide-card-padding, which is slides-only and always resolves. */
|
|
471
|
+
.containerItem.fb-row-removable {
|
|
472
|
+
padding-right: var(--fb-row-remove-gutter, 40px);
|
|
473
|
+
}
|
|
452
474
|
/* Prefill-suggestion pills rendered by createPrefillHints. Outline pill at rest,
|
|
453
475
|
soft-fill on hover, solid-fill when selected. All colors flow from the active
|
|
454
476
|
theme \u2014 consumers don't need to ship their own CSS. */
|
|
@@ -4856,12 +4878,21 @@ function buildClearAllRow(state, ridCount, onClearAll) {
|
|
|
4856
4878
|
return row;
|
|
4857
4879
|
}
|
|
4858
4880
|
var gridResizeObservers = /* @__PURE__ */ new WeakMap();
|
|
4881
|
+
var gridMeasureFrames = /* @__PURE__ */ new WeakMap();
|
|
4882
|
+
function cancelPendingMeasure(container) {
|
|
4883
|
+
const frame = gridMeasureFrames.get(container);
|
|
4884
|
+
if (frame !== void 0) {
|
|
4885
|
+
cancelAnimationFrame(frame);
|
|
4886
|
+
gridMeasureFrames.delete(container);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4859
4889
|
function disposePlaceholdersForUpload(container) {
|
|
4860
4890
|
const observer = gridResizeObservers.get(container);
|
|
4861
4891
|
if (observer) {
|
|
4862
4892
|
observer.disconnect();
|
|
4863
4893
|
gridResizeObservers.delete(container);
|
|
4864
4894
|
}
|
|
4895
|
+
cancelPendingMeasure(container);
|
|
4865
4896
|
container.querySelectorAll(".fb-multi-placeholder").forEach((p) => p.remove());
|
|
4866
4897
|
}
|
|
4867
4898
|
function renderResourcePills(opts) {
|
|
@@ -4886,6 +4917,7 @@ function renderResourcePills(opts) {
|
|
|
4886
4917
|
previousObserver.disconnect();
|
|
4887
4918
|
gridResizeObservers.delete(container);
|
|
4888
4919
|
}
|
|
4920
|
+
cancelPendingMeasure(container);
|
|
4889
4921
|
while (container.firstChild) container.removeChild(container.firstChild);
|
|
4890
4922
|
const ridList = rids ?? [];
|
|
4891
4923
|
const effectiveMax = maxCount ?? Infinity;
|
|
@@ -4985,7 +5017,7 @@ function renderResourcePills(opts) {
|
|
|
4985
5017
|
if (effectiveMax === Infinity || effectiveMax > occupied) {
|
|
4986
5018
|
grid.appendChild(buildPlaceholderTile());
|
|
4987
5019
|
}
|
|
4988
|
-
requestAnimationFrame(adjustPlaceholders);
|
|
5020
|
+
gridMeasureFrames.set(container, requestAnimationFrame(adjustPlaceholders));
|
|
4989
5021
|
if (typeof ResizeObserver !== "undefined") {
|
|
4990
5022
|
const ro = new ResizeObserver(() => adjustPlaceholders());
|
|
4991
5023
|
ro.observe(grid);
|
|
@@ -6759,17 +6791,13 @@ function mountRemoveButton(item, onRemove, state) {
|
|
|
6759
6791
|
`;
|
|
6760
6792
|
rem.innerHTML = BIN_ICON_SVG;
|
|
6761
6793
|
rem.onclick = onRemove;
|
|
6762
|
-
const labelRow = item.querySelector("[data-fb-label-row]");
|
|
6763
|
-
if (labelRow) {
|
|
6764
|
-
rem.style.marginLeft = "auto";
|
|
6765
|
-
labelRow.appendChild(rem);
|
|
6766
|
-
return;
|
|
6767
|
-
}
|
|
6768
6794
|
rem.style.position = "absolute";
|
|
6769
|
-
rem.style.top = "8px";
|
|
6770
|
-
rem.style.right = "8px";
|
|
6795
|
+
rem.style.top = "var(--fb-row-remove-inset, 8px)";
|
|
6796
|
+
rem.style.right = "var(--fb-row-remove-inset, 8px)";
|
|
6797
|
+
rem.style.zIndex = "1";
|
|
6771
6798
|
item.style.position = "relative";
|
|
6772
|
-
item.
|
|
6799
|
+
item.classList.add("fb-row-removable");
|
|
6800
|
+
item.insertBefore(rem, item.firstChild);
|
|
6773
6801
|
}
|
|
6774
6802
|
function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
6775
6803
|
const state = ctx.state;
|
|
@@ -6794,24 +6822,29 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6794
6822
|
const max = element.maxCount ?? Infinity;
|
|
6795
6823
|
const pre = Array.isArray(ctx.prefill?.[element.key]) ? ctx.prefill[element.key] : null;
|
|
6796
6824
|
const childDefaults = extractChildDefaults(element.elements);
|
|
6797
|
-
|
|
6798
|
-
const
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6825
|
+
let nextRowIndex = 0;
|
|
6826
|
+
const takeRowIndex = () => nextRowIndex++;
|
|
6827
|
+
const directRows = () => Array.from(itemsWrap.children).filter(
|
|
6828
|
+
(el) => el.classList.contains("containerItem")
|
|
6829
|
+
);
|
|
6830
|
+
const countItems = () => directRows().length;
|
|
6831
|
+
const handleRemoveItem = (item) => {
|
|
6832
|
+
if (countItems() <= min) return;
|
|
6833
|
+
item.remove();
|
|
6834
|
+
updateAddButton();
|
|
6835
|
+
};
|
|
6836
|
+
const createContainerItem = (idx, rowPrefill, formData) => {
|
|
6802
6837
|
const subCtx = {
|
|
6803
|
-
state
|
|
6838
|
+
state,
|
|
6804
6839
|
instance: ctx.instance,
|
|
6805
6840
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6806
|
-
prefill: childDefaults,
|
|
6807
|
-
|
|
6808
|
-
formData: currentFormData,
|
|
6809
|
-
// Current root data from DOM for enableIf
|
|
6841
|
+
prefill: mergeWithDefaults(rowPrefill ?? {}, childDefaults),
|
|
6842
|
+
formData,
|
|
6810
6843
|
inheritedReadonly: childInheritedReadonly
|
|
6811
6844
|
};
|
|
6812
6845
|
const item = document.createElement("div");
|
|
6813
6846
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6814
|
-
item.setAttribute("data-container-item",
|
|
6847
|
+
item.setAttribute("data-container-item", subCtx.path);
|
|
6815
6848
|
if (isSlides) {
|
|
6816
6849
|
item.setAttribute("data-fb-slide-card", "");
|
|
6817
6850
|
}
|
|
@@ -6821,11 +6854,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6821
6854
|
);
|
|
6822
6855
|
element.elements.forEach((child) => {
|
|
6823
6856
|
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
6857
|
+
const hiddenValue = rowPrefill?.[child.key] ?? ("default" in child ? child.default : null) ?? null;
|
|
6824
6858
|
childWrapper.appendChild(
|
|
6825
|
-
createHiddenInput(
|
|
6826
|
-
pathJoin(subCtx.path, child.key),
|
|
6827
|
-
("default" in child ? child.default : null) ?? null
|
|
6828
|
-
)
|
|
6859
|
+
createHiddenInput(pathJoin(subCtx.path, child.key), hiddenValue)
|
|
6829
6860
|
);
|
|
6830
6861
|
} else {
|
|
6831
6862
|
childWrapper.appendChild(renderElement(child, subCtx));
|
|
@@ -6835,6 +6866,17 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6835
6866
|
if (!containerIsReadonly) {
|
|
6836
6867
|
mountRemoveButton(item, () => handleRemoveItem(item), state);
|
|
6837
6868
|
}
|
|
6869
|
+
return item;
|
|
6870
|
+
};
|
|
6871
|
+
const handleAddItem = () => {
|
|
6872
|
+
if (countItems() >= max) return;
|
|
6873
|
+
const item = createContainerItem(
|
|
6874
|
+
takeRowIndex(),
|
|
6875
|
+
null,
|
|
6876
|
+
// Current root data read back from the DOM, so enableIf sees what the
|
|
6877
|
+
// user has actually typed rather than the original prefill.
|
|
6878
|
+
state.formRoot ? extractRootFormData(state.formRoot) : {}
|
|
6879
|
+
);
|
|
6838
6880
|
if (slideAddTile && slideAddTile.parentElement === itemsWrap) {
|
|
6839
6881
|
itemsWrap.insertBefore(item, slideAddTile);
|
|
6840
6882
|
} else {
|
|
@@ -6847,109 +6889,43 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6847
6889
|
let pillAddUpdate = null;
|
|
6848
6890
|
const syncSlideTileSize = () => {
|
|
6849
6891
|
if (!slideAddTile) return;
|
|
6850
|
-
const firstSlide =
|
|
6851
|
-
":scope > .containerItem"
|
|
6852
|
-
);
|
|
6892
|
+
const firstSlide = directRows()[0];
|
|
6853
6893
|
if (firstSlide && firstSlide.offsetHeight > 0) {
|
|
6854
6894
|
slideAddTile.style.minHeight = `${firstSlide.offsetHeight}px`;
|
|
6855
6895
|
}
|
|
6856
6896
|
};
|
|
6897
|
+
const syncRemoveButtons = () => {
|
|
6898
|
+
const atFloor = countItems() <= min;
|
|
6899
|
+
directRows().forEach((row) => {
|
|
6900
|
+
const btn = Array.from(row.children).find(
|
|
6901
|
+
(el) => el.classList.contains("fb-item-remove")
|
|
6902
|
+
);
|
|
6903
|
+
if (btn) btn.disabled = atFloor;
|
|
6904
|
+
});
|
|
6905
|
+
};
|
|
6857
6906
|
const updateAddButton = () => {
|
|
6858
6907
|
const currentCount = countItems();
|
|
6859
6908
|
if (slideAddUpdate) slideAddUpdate(currentCount, max);
|
|
6860
6909
|
if (pillAddUpdate) pillAddUpdate(currentCount, max);
|
|
6861
6910
|
if (slideAddTile) syncSlideTileSize();
|
|
6911
|
+
syncRemoveButtons();
|
|
6862
6912
|
};
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
state: ctx.state,
|
|
6872
|
-
instance: ctx.instance,
|
|
6873
|
-
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6874
|
-
prefill: mergedPrefill,
|
|
6875
|
-
// Merged prefill with defaults for enableIf
|
|
6876
|
-
formData: ctx.formData ?? ctx.prefill,
|
|
6877
|
-
// Complete root data for enableIf
|
|
6878
|
-
inheritedReadonly: childInheritedReadonly
|
|
6879
|
-
};
|
|
6880
|
-
const item = document.createElement("div");
|
|
6881
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6882
|
-
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6883
|
-
if (isSlides) {
|
|
6884
|
-
item.setAttribute("data-fb-slide-card", "");
|
|
6885
|
-
}
|
|
6886
|
-
const childWrapper = document.createElement("div");
|
|
6887
|
-
childWrapper.className = getChildWrapperClass(
|
|
6888
|
-
isSlides ? void 0 : element.columns
|
|
6913
|
+
if (pre) {
|
|
6914
|
+
pre.forEach((prefillObj) => {
|
|
6915
|
+
itemsWrap.appendChild(
|
|
6916
|
+
createContainerItem(
|
|
6917
|
+
takeRowIndex(),
|
|
6918
|
+
prefillObj ?? {},
|
|
6919
|
+
ctx.formData ?? ctx.prefill
|
|
6920
|
+
)
|
|
6889
6921
|
);
|
|
6890
|
-
element.elements.forEach((child) => {
|
|
6891
|
-
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
6892
|
-
const prefillVal = prefillObj?.[child.key] ?? ("default" in child ? child.default : null) ?? null;
|
|
6893
|
-
childWrapper.appendChild(
|
|
6894
|
-
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
6895
|
-
);
|
|
6896
|
-
} else {
|
|
6897
|
-
childWrapper.appendChild(renderElement(child, subCtx));
|
|
6898
|
-
}
|
|
6899
|
-
});
|
|
6900
|
-
item.appendChild(childWrapper);
|
|
6901
|
-
if (!containerIsReadonly) {
|
|
6902
|
-
mountRemoveButton(item, () => handleRemoveItem(item), ctx.state);
|
|
6903
|
-
}
|
|
6904
|
-
itemsWrap.appendChild(item);
|
|
6905
6922
|
});
|
|
6906
6923
|
}
|
|
6907
6924
|
if (!containerIsReadonly) {
|
|
6908
6925
|
while (countItems() < min) {
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
state: ctx.state,
|
|
6912
|
-
instance: ctx.instance,
|
|
6913
|
-
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6914
|
-
prefill: childDefaults,
|
|
6915
|
-
// Defaults for enableIf evaluation
|
|
6916
|
-
formData: ctx.formData ?? ctx.prefill,
|
|
6917
|
-
// Complete root data for enableIf
|
|
6918
|
-
inheritedReadonly: childInheritedReadonly
|
|
6919
|
-
};
|
|
6920
|
-
const item = document.createElement("div");
|
|
6921
|
-
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6922
|
-
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6923
|
-
if (isSlides) {
|
|
6924
|
-
item.setAttribute("data-fb-slide-card", "");
|
|
6925
|
-
}
|
|
6926
|
-
const childWrapper = document.createElement("div");
|
|
6927
|
-
childWrapper.className = getChildWrapperClass(
|
|
6928
|
-
isSlides ? void 0 : element.columns
|
|
6929
|
-
);
|
|
6930
|
-
element.elements.forEach((child) => {
|
|
6931
|
-
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
6932
|
-
childWrapper.appendChild(
|
|
6933
|
-
createHiddenInput(
|
|
6934
|
-
pathJoin(subCtx.path, child.key),
|
|
6935
|
-
("default" in child ? child.default : null) ?? null
|
|
6936
|
-
)
|
|
6937
|
-
);
|
|
6938
|
-
} else {
|
|
6939
|
-
childWrapper.appendChild(renderElement(child, subCtx));
|
|
6940
|
-
}
|
|
6941
|
-
});
|
|
6942
|
-
item.appendChild(childWrapper);
|
|
6943
|
-
mountRemoveButton(
|
|
6944
|
-
item,
|
|
6945
|
-
() => {
|
|
6946
|
-
if (countItems() > min) {
|
|
6947
|
-
handleRemoveItem(item);
|
|
6948
|
-
}
|
|
6949
|
-
},
|
|
6950
|
-
ctx.state
|
|
6926
|
+
itemsWrap.appendChild(
|
|
6927
|
+
createContainerItem(takeRowIndex(), null, ctx.formData ?? ctx.prefill)
|
|
6951
6928
|
);
|
|
6952
|
-
itemsWrap.appendChild(item);
|
|
6953
6929
|
}
|
|
6954
6930
|
}
|
|
6955
6931
|
containerWrap.appendChild(itemsWrap);
|
|
@@ -7016,15 +6992,7 @@ function validateContainerElement(element, key, context) {
|
|
|
7016
6992
|
};
|
|
7017
6993
|
if ("multiple" in element && element.multiple) {
|
|
7018
6994
|
const items = [];
|
|
7019
|
-
const
|
|
7020
|
-
"[data-container-item]"
|
|
7021
|
-
);
|
|
7022
|
-
const containerWrappers = Array.from(allContainerWrappers).filter((el) => {
|
|
7023
|
-
const attr = el.getAttribute("data-container-item") || "";
|
|
7024
|
-
if (!attr.startsWith(`${key}[`)) return false;
|
|
7025
|
-
const suffix = attr.slice(key.length);
|
|
7026
|
-
return /^\[\d+\]$/.test(suffix);
|
|
7027
|
-
});
|
|
6995
|
+
const containerWrappers = findDirectContainerRows(scopeRoot, key);
|
|
7028
6996
|
containerWrappers.forEach((itemContainer) => {
|
|
7029
6997
|
const itemData = {};
|
|
7030
6998
|
const containerAttr = itemContainer.getAttribute("data-container-item") || "";
|
|
@@ -7147,9 +7115,7 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
7147
7115
|
});
|
|
7148
7116
|
}
|
|
7149
7117
|
});
|
|
7150
|
-
const existingContainers = scopeRoot
|
|
7151
|
-
`[data-container-item^="${fieldPath}["]`
|
|
7152
|
-
);
|
|
7118
|
+
const existingContainers = findDirectContainerRows(scopeRoot, fieldPath);
|
|
7153
7119
|
if (value.length !== existingContainers.length) {
|
|
7154
7120
|
console.warn(
|
|
7155
7121
|
`updateContainerField: Multiple container field "${fieldPath}" item count mismatch. Consider re-rendering for add/remove.`
|
|
@@ -11908,17 +11874,12 @@ var FormBuilderInstance = class {
|
|
|
11908
11874
|
}
|
|
11909
11875
|
}
|
|
11910
11876
|
if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
|
|
11911
|
-
const containerData = element.key ? formData
|
|
11877
|
+
const containerData = element.key ? getValueByPath(formData, fullPath) : void 0;
|
|
11912
11878
|
if (Array.isArray(containerData)) {
|
|
11913
|
-
const
|
|
11914
|
-
|
|
11879
|
+
const directItems = findDirectContainerRows(
|
|
11880
|
+
this.state.formRoot,
|
|
11881
|
+
fullPath
|
|
11915
11882
|
);
|
|
11916
|
-
const directItems = fullPath ? Array.from(containerItems).filter((el) => {
|
|
11917
|
-
const attr = el.getAttribute("data-container-item") || "";
|
|
11918
|
-
if (!attr.startsWith(`${fullPath}[`)) return false;
|
|
11919
|
-
const suffix = attr.slice(fullPath.length);
|
|
11920
|
-
return /^\[\d+\]$/.test(suffix);
|
|
11921
|
-
}) : [];
|
|
11922
11883
|
directItems.forEach((el) => {
|
|
11923
11884
|
const attr = el.getAttribute("data-container-item") || "";
|
|
11924
11885
|
checkElements(element.elements, attr);
|