@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.
@@ -307,6 +307,15 @@ function getElementLookupKey(element, state) {
307
307
  function pathJoin(base, key) {
308
308
  return base ? `${base}.${key}` : key;
309
309
  }
310
+ function findDirectContainerRows(scopeRoot, containerPath) {
311
+ if (!containerPath) return [];
312
+ const all = scopeRoot.querySelectorAll("[data-container-item]");
313
+ return Array.from(all).filter((el) => {
314
+ const attr = el.getAttribute("data-container-item") || "";
315
+ if (!attr.startsWith(`${containerPath}[`)) return false;
316
+ return /^\[\d+\]$/.test(attr.slice(containerPath.length));
317
+ });
318
+ }
310
319
  function clear(node) {
311
320
  while (node.firstChild) node.removeChild(node.firstChild);
312
321
  }
@@ -457,6 +466,19 @@ function ensureThemingHooks(doc) {
457
466
  color: var(--fb-error-color);
458
467
  background-color: var(--fb-background-hover-color);
459
468
  }
469
+ .fb-item-remove:disabled {
470
+ opacity: 0.35;
471
+ cursor: not-allowed;
472
+ }
473
+ /* Cards carrying a row-level remove button reserve a right-hand lane for it
474
+ (24px button + inset + gap) so it never sits on top of the first field's
475
+ control. A class rather than an inline style, so host CSS can override the
476
+ gutter without !important; the compound selector keeps it ahead of the
477
+ card's own p-2 utility regardless of stylesheet order. Its own token, not
478
+ --fb-slide-card-padding, which is slides-only and always resolves. */
479
+ .containerItem.fb-row-removable {
480
+ padding-right: var(--fb-row-remove-gutter, 40px);
481
+ }
460
482
  /* Prefill-suggestion pills rendered by createPrefillHints. Outline pill at rest,
461
483
  soft-fill on hover, solid-fill when selected. All colors flow from the active
462
484
  theme \u2014 consumers don't need to ship their own CSS. */
@@ -4934,12 +4956,21 @@ function buildClearAllRow(state, ridCount, onClearAll) {
4934
4956
  return row;
4935
4957
  }
4936
4958
  var gridResizeObservers = /* @__PURE__ */ new WeakMap();
4959
+ var gridMeasureFrames = /* @__PURE__ */ new WeakMap();
4960
+ function cancelPendingMeasure(container) {
4961
+ const frame = gridMeasureFrames.get(container);
4962
+ if (frame !== void 0) {
4963
+ cancelAnimationFrame(frame);
4964
+ gridMeasureFrames.delete(container);
4965
+ }
4966
+ }
4937
4967
  function disposePlaceholdersForUpload(container) {
4938
4968
  const observer = gridResizeObservers.get(container);
4939
4969
  if (observer) {
4940
4970
  observer.disconnect();
4941
4971
  gridResizeObservers.delete(container);
4942
4972
  }
4973
+ cancelPendingMeasure(container);
4943
4974
  container.querySelectorAll(".fb-multi-placeholder").forEach((p) => p.remove());
4944
4975
  }
4945
4976
  function renderResourcePills(opts) {
@@ -4965,6 +4996,7 @@ function renderResourcePills(opts) {
4965
4996
  previousObserver.disconnect();
4966
4997
  gridResizeObservers.delete(container);
4967
4998
  }
4999
+ cancelPendingMeasure(container);
4968
5000
  while (container.firstChild) container.removeChild(container.firstChild);
4969
5001
  const ridList = rids != null ? rids : [];
4970
5002
  const effectiveMax = maxCount != null ? maxCount : Infinity;
@@ -5066,7 +5098,7 @@ function renderResourcePills(opts) {
5066
5098
  if (effectiveMax === Infinity || effectiveMax > occupied) {
5067
5099
  grid.appendChild(buildPlaceholderTile());
5068
5100
  }
5069
- requestAnimationFrame(adjustPlaceholders);
5101
+ gridMeasureFrames.set(container, requestAnimationFrame(adjustPlaceholders));
5070
5102
  if (typeof ResizeObserver !== "undefined") {
5071
5103
  const ro = new ResizeObserver(() => adjustPlaceholders());
5072
5104
  ro.observe(grid);
@@ -6865,17 +6897,13 @@ function mountRemoveButton(item, onRemove, state) {
6865
6897
  `;
6866
6898
  rem.innerHTML = BIN_ICON_SVG;
6867
6899
  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
6900
  rem.style.position = "absolute";
6875
- rem.style.top = "8px";
6876
- rem.style.right = "8px";
6901
+ rem.style.top = "var(--fb-row-remove-inset, 8px)";
6902
+ rem.style.right = "var(--fb-row-remove-inset, 8px)";
6903
+ rem.style.zIndex = "1";
6877
6904
  item.style.position = "relative";
6878
- item.appendChild(rem);
6905
+ item.classList.add("fb-row-removable");
6906
+ item.insertBefore(rem, item.firstChild);
6879
6907
  }
6880
6908
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6881
6909
  var _a, _b, _c, _d;
@@ -6901,24 +6929,29 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6901
6929
  const max = (_b = element.maxCount) != null ? _b : Infinity;
6902
6930
  const pre = Array.isArray((_c = ctx.prefill) == null ? void 0 : _c[element.key]) ? ctx.prefill[element.key] : null;
6903
6931
  const childDefaults = extractChildDefaults(element.elements);
6904
- const countItems = () => itemsWrap.querySelectorAll(":scope > .containerItem").length;
6905
- const handleAddItem = () => {
6906
- if (countItems() >= max) return;
6907
- const idx = countItems();
6908
- const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
6932
+ let nextRowIndex = 0;
6933
+ const takeRowIndex = () => nextRowIndex++;
6934
+ const directRows = () => Array.from(itemsWrap.children).filter(
6935
+ (el) => el.classList.contains("containerItem")
6936
+ );
6937
+ const countItems = () => directRows().length;
6938
+ const handleRemoveItem = (item) => {
6939
+ if (countItems() <= min) return;
6940
+ item.remove();
6941
+ updateAddButton();
6942
+ };
6943
+ const createContainerItem = (idx, rowPrefill, formData) => {
6909
6944
  const subCtx = {
6910
- state: ctx.state,
6945
+ state,
6911
6946
  instance: ctx.instance,
6912
6947
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
6913
- prefill: childDefaults,
6914
- // Defaults for enableIf evaluation
6915
- formData: currentFormData,
6916
- // Current root data from DOM for enableIf
6948
+ prefill: mergeWithDefaults(rowPrefill != null ? rowPrefill : {}, childDefaults),
6949
+ formData,
6917
6950
  inheritedReadonly: childInheritedReadonly
6918
6951
  };
6919
6952
  const item = document.createElement("div");
6920
6953
  item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
6921
- item.setAttribute("data-container-item", `${element.key}[${idx}]`);
6954
+ item.setAttribute("data-container-item", subCtx.path);
6922
6955
  if (isSlides) {
6923
6956
  item.setAttribute("data-fb-slide-card", "");
6924
6957
  }
@@ -6927,13 +6960,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6927
6960
  isSlides ? void 0 : element.columns
6928
6961
  );
6929
6962
  element.elements.forEach((child) => {
6930
- var _a2;
6963
+ var _a2, _b2;
6931
6964
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
6965
+ const hiddenValue = (_b2 = (_a2 = rowPrefill == null ? void 0 : rowPrefill[child.key]) != null ? _a2 : "default" in child ? child.default : null) != null ? _b2 : null;
6932
6966
  childWrapper.appendChild(
6933
- createHiddenInput(
6934
- pathJoin(subCtx.path, child.key),
6935
- (_a2 = "default" in child ? child.default : null) != null ? _a2 : null
6936
- )
6967
+ createHiddenInput(pathJoin(subCtx.path, child.key), hiddenValue)
6937
6968
  );
6938
6969
  } else {
6939
6970
  childWrapper.appendChild(renderElement(child, subCtx));
@@ -6943,6 +6974,17 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6943
6974
  if (!containerIsReadonly) {
6944
6975
  mountRemoveButton(item, () => handleRemoveItem(item), state);
6945
6976
  }
6977
+ return item;
6978
+ };
6979
+ const handleAddItem = () => {
6980
+ if (countItems() >= max) return;
6981
+ const item = createContainerItem(
6982
+ takeRowIndex(),
6983
+ null,
6984
+ // Current root data read back from the DOM, so enableIf sees what the
6985
+ // user has actually typed rather than the original prefill.
6986
+ state.formRoot ? extractRootFormData(state.formRoot) : {}
6987
+ );
6946
6988
  if (slideAddTile && slideAddTile.parentElement === itemsWrap) {
6947
6989
  itemsWrap.insertBefore(item, slideAddTile);
6948
6990
  } else {
@@ -6955,112 +6997,44 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6955
6997
  let pillAddUpdate = null;
6956
6998
  const syncSlideTileSize = () => {
6957
6999
  if (!slideAddTile) return;
6958
- const firstSlide = itemsWrap.querySelector(
6959
- ":scope > .containerItem"
6960
- );
7000
+ const firstSlide = directRows()[0];
6961
7001
  if (firstSlide && firstSlide.offsetHeight > 0) {
6962
7002
  slideAddTile.style.minHeight = `${firstSlide.offsetHeight}px`;
6963
7003
  }
6964
7004
  };
7005
+ const syncRemoveButtons = () => {
7006
+ const atFloor = countItems() <= min;
7007
+ directRows().forEach((row) => {
7008
+ const btn = Array.from(row.children).find(
7009
+ (el) => el.classList.contains("fb-item-remove")
7010
+ );
7011
+ if (btn) btn.disabled = atFloor;
7012
+ });
7013
+ };
6965
7014
  const updateAddButton = () => {
6966
7015
  const currentCount = countItems();
6967
7016
  if (slideAddUpdate) slideAddUpdate(currentCount, max);
6968
7017
  if (pillAddUpdate) pillAddUpdate(currentCount, max);
6969
7018
  if (slideAddTile) syncSlideTileSize();
7019
+ syncRemoveButtons();
6970
7020
  };
6971
- const handleRemoveItem = (item) => {
6972
- item.remove();
6973
- updateAddButton();
6974
- };
6975
- if (pre && Array.isArray(pre)) {
6976
- pre.forEach((prefillObj, idx) => {
7021
+ if (pre) {
7022
+ pre.forEach((prefillObj) => {
6977
7023
  var _a2;
6978
- const mergedPrefill = mergeWithDefaults(prefillObj || {}, childDefaults);
6979
- const subCtx = {
6980
- state: ctx.state,
6981
- instance: ctx.instance,
6982
- path: pathJoin(ctx.path, `${element.key}[${idx}]`),
6983
- prefill: mergedPrefill,
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
7024
+ itemsWrap.appendChild(
7025
+ createContainerItem(
7026
+ takeRowIndex(),
7027
+ prefillObj != null ? prefillObj : {},
7028
+ (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
7029
+ )
6998
7030
  );
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
7031
  });
7016
7032
  }
7017
7033
  if (!containerIsReadonly) {
7018
7034
  while (countItems() < min) {
7019
- const idx = countItems();
7020
- const subCtx = {
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
7035
+ itemsWrap.appendChild(
7036
+ createContainerItem(takeRowIndex(), null, (_d = ctx.formData) != null ? _d : ctx.prefill)
7062
7037
  );
7063
- itemsWrap.appendChild(item);
7064
7038
  }
7065
7039
  }
7066
7040
  containerWrap.appendChild(itemsWrap);
@@ -7128,15 +7102,7 @@ function validateContainerElement(element, key, context) {
7128
7102
  };
7129
7103
  if ("multiple" in element && element.multiple) {
7130
7104
  const items = [];
7131
- const allContainerWrappers = scopeRoot.querySelectorAll(
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
- });
7105
+ const containerWrappers = findDirectContainerRows(scopeRoot, key);
7140
7106
  containerWrappers.forEach((itemContainer) => {
7141
7107
  const itemData = {};
7142
7108
  const containerAttr = itemContainer.getAttribute("data-container-item") || "";
@@ -7262,9 +7228,7 @@ function updateContainerField(element, fieldPath, value, context) {
7262
7228
  });
7263
7229
  }
7264
7230
  });
7265
- const existingContainers = scopeRoot.querySelectorAll(
7266
- `[data-container-item^="${fieldPath}["]`
7267
- );
7231
+ const existingContainers = findDirectContainerRows(scopeRoot, fieldPath);
7268
7232
  if (value.length !== existingContainers.length) {
7269
7233
  console.warn(
7270
7234
  `updateContainerField: Multiple container field "${fieldPath}" item count mismatch. Consider re-rendering for add/remove.`
@@ -12106,17 +12070,12 @@ var FormBuilderInstance = class {
12106
12070
  }
12107
12071
  }
12108
12072
  if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
12109
- const containerData = element.key ? formData == null ? void 0 : formData[element.key] : void 0;
12073
+ const containerData = element.key ? getValueByPath(formData, fullPath) : void 0;
12110
12074
  if (Array.isArray(containerData)) {
12111
- const containerItems = this.state.formRoot.querySelectorAll(
12112
- `[data-container-item]`
12075
+ const directItems = findDirectContainerRows(
12076
+ this.state.formRoot,
12077
+ fullPath
12113
12078
  );
12114
- const directItems = fullPath ? Array.from(containerItems).filter((el) => {
12115
- const attr = el.getAttribute("data-container-item") || "";
12116
- if (!attr.startsWith(`${fullPath}[`)) return false;
12117
- const suffix = attr.slice(fullPath.length);
12118
- return /^\[\d+\]$/.test(suffix);
12119
- }) : [];
12120
12079
  directItems.forEach((el) => {
12121
12080
  const attr = el.getAttribute("data-container-item") || "";
12122
12081
  checkElements(element.elements, attr);