@dmitryvim/form-builder 0.3.0 → 0.3.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.
@@ -443,6 +443,36 @@ function ensureThemingHooks(doc) {
443
443
  color: var(--fb-error-color);
444
444
  background-color: var(--fb-background-hover-color);
445
445
  }
446
+ /* Prefill-suggestion pills rendered by createPrefillHints. Outline pill at rest,
447
+ soft-fill on hover, solid-fill when selected. All colors flow from the active
448
+ theme \u2014 consumers don't need to ship their own CSS. */
449
+ .fb-prefill-hint {
450
+ padding: 0.25rem 0.625rem;
451
+ border: var(--fb-border-width) solid var(--fb-primary-color);
452
+ border-radius: 9999px;
453
+ background: var(--fb-background-color);
454
+ color: var(--fb-primary-color);
455
+ font-size: var(--fb-font-size-small);
456
+ font-weight: var(--fb-font-weight-medium);
457
+ font-family: var(--fb-font-family);
458
+ cursor: pointer;
459
+ transition: background-color var(--fb-transition-duration), border-color var(--fb-transition-duration), color var(--fb-transition-duration);
460
+ }
461
+ .fb-prefill-hint:hover {
462
+ background: var(--fb-primary-soft-color);
463
+ border-color: var(--fb-primary-hover-color);
464
+ color: var(--fb-primary-hover-color);
465
+ }
466
+ .fb-prefill-hint:focus-visible {
467
+ outline: var(--fb-focus-ring-width) solid var(--fb-focus-ring-color);
468
+ outline-offset: 2px;
469
+ }
470
+ .fb-prefill-hint[aria-pressed="true"],
471
+ .fb-prefill-hint.active {
472
+ background: var(--fb-primary-color);
473
+ color: #ffffff;
474
+ border-color: var(--fb-primary-color);
475
+ }
446
476
  `;
447
477
  doc.head.appendChild(style);
448
478
  }
@@ -454,12 +484,29 @@ function applyAutoExpand(textarea) {
454
484
  const resize = () => {
455
485
  if (!textarea.isConnected) return;
456
486
  textarea.style.height = "0";
457
- textarea.style.height = `${textarea.scrollHeight}px`;
487
+ const cs = getComputedStyle(textarea);
488
+ const borderY = parseFloat(cs.borderTopWidth || "0") + parseFloat(cs.borderBottomWidth || "0");
489
+ textarea.style.height = `${textarea.scrollHeight + borderY}px`;
458
490
  };
459
491
  textarea.addEventListener("input", resize);
460
492
  setTimeout(() => {
461
493
  if (textarea.isConnected) resize();
462
494
  }, 0);
495
+ if (typeof ResizeObserver === "undefined") return;
496
+ let lastWidth = -1;
497
+ const ro = new ResizeObserver((entries) => {
498
+ var _a, _b, _c, _d, _e;
499
+ if (!textarea.isConnected) {
500
+ ro.disconnect();
501
+ return;
502
+ }
503
+ const entry = entries[0];
504
+ const w = (_e = (_d = (_b = (_a = entry == null ? void 0 : entry.contentBoxSize) == null ? void 0 : _a[0]) == null ? void 0 : _b.inlineSize) != null ? _d : (_c = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _c.width) != null ? _e : 0;
505
+ if (w === lastWidth) return;
506
+ lastWidth = w;
507
+ resize();
508
+ });
509
+ ro.observe(textarea);
463
510
  }
464
511
  function applySingleLineMode(textarea) {
465
512
  textarea.addEventListener("keydown", (e) => {
@@ -4275,16 +4322,26 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
4275
4322
  }
4276
4323
  return { accepted, errorMessage: errorParts.join(" \u2022 ") };
4277
4324
  }
4278
- async function uploadBatch(accepted, resourceIds, listEl, state) {
4325
+ async function uploadBatch(opts) {
4279
4326
  var _a;
4280
- if (listEl) {
4327
+ const {
4328
+ accepted,
4329
+ listEl,
4330
+ state,
4331
+ shouldHideAddTile,
4332
+ buildSuccessTile,
4333
+ prepareForUpload
4334
+ } = opts;
4335
+ if (listEl && shouldHideAddTile) {
4281
4336
  const tilesWrap = ensureTilesWrap(listEl);
4282
4337
  const addTile = (_a = tilesWrap.querySelector(".fb-multi-add-tile-js")) != null ? _a : tilesWrap.querySelector(".fb-tile-add");
4283
4338
  if (addTile) addTile.style.display = "none";
4284
4339
  }
4340
+ prepareForUpload == null ? void 0 : prepareForUpload();
4341
+ const orderedIds = new Array(accepted.length).fill(null);
4285
4342
  const failures = [];
4286
4343
  await Promise.allSettled(
4287
- accepted.map(async (file) => {
4344
+ accepted.map(async (file, index) => {
4288
4345
  const placeholder = createUploadingTile(file.name, state);
4289
4346
  if (listEl) {
4290
4347
  const tilesWrap = ensureTilesWrap(listEl);
@@ -4297,20 +4354,24 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
4297
4354
  type: file.type,
4298
4355
  size: file.size,
4299
4356
  uploadedAt: /* @__PURE__ */ new Date(),
4300
- file: void 0
4357
+ file
4301
4358
  });
4302
- resourceIds.push(rid);
4359
+ orderedIds[index] = rid;
4360
+ if (buildSuccessTile && placeholder.parentNode) {
4361
+ placeholder.replaceWith(buildSuccessTile(rid));
4362
+ } else {
4363
+ placeholder.remove();
4364
+ }
4303
4365
  } catch (err) {
4304
4366
  const wrapped = err instanceof Error ? err : new Error(String(err));
4305
4367
  const cause = wrapped.cause;
4306
4368
  const root = cause instanceof Error ? cause : cause !== void 0 ? new Error(String(cause)) : wrapped;
4307
4369
  failures.push({ file, error: root });
4308
- } finally {
4309
4370
  placeholder.remove();
4310
4371
  }
4311
4372
  })
4312
4373
  );
4313
- return { failures };
4374
+ return { failures, orderedIds };
4314
4375
  }
4315
4376
  function buildBatchErrorMessage(filterError, failures, state) {
4316
4377
  if (failures.length === 0) return filterError;
@@ -4322,69 +4383,72 @@ function buildBatchErrorMessage(filterError, failures, state) {
4322
4383
  ).join(" \u2022 ");
4323
4384
  return filterError ? `${filterError} \u2022 ${uploadMsg}` : uploadMsg;
4324
4385
  }
4325
- function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
4386
+ async function runMultiFileBatch(opts, files, listEl, errorTarget) {
4387
+ const {
4388
+ resourceIds,
4389
+ state,
4390
+ updateCallback,
4391
+ constraints,
4392
+ pathKey,
4393
+ instance,
4394
+ buildSuccessTile,
4395
+ prepareForUpload,
4396
+ coordinator
4397
+ } = opts;
4398
+ const { accepted, errorMessage } = filterAndSlice(
4399
+ files,
4400
+ coordinator.getOccupiedCount(),
4401
+ constraints,
4402
+ state
4403
+ );
4404
+ if (errorTarget) {
4405
+ if (errorMessage) showFileError(errorTarget, errorMessage);
4406
+ else clearFileError(errorTarget);
4407
+ }
4408
+ const handle = coordinator.beginBatch(accepted.length);
4409
+ const shouldHideAddTile = coordinator.getOccupiedCount() >= constraints.maxCount;
4410
+ const { failures, orderedIds } = await uploadBatch({
4411
+ accepted,
4412
+ listEl,
4413
+ state,
4414
+ shouldHideAddTile,
4415
+ buildSuccessTile,
4416
+ prepareForUpload
4417
+ });
4418
+ handle.setResults(orderedIds);
4419
+ if (instance && pathKey && !state.config.readonly) {
4420
+ instance.triggerOnChange(pathKey, resourceIds);
4421
+ }
4422
+ const { wasLast } = handle.end();
4423
+ if (wasLast) updateCallback();
4424
+ if (errorTarget) {
4425
+ const combined = buildBatchErrorMessage(errorMessage, failures, state);
4426
+ if (combined) showFileError(errorTarget, combined);
4427
+ else clearFileError(errorTarget);
4428
+ }
4429
+ }
4430
+ function setupFilesDropHandler(opts) {
4431
+ const { filesContainer } = opts;
4326
4432
  setupDragAndDrop(filesContainer, async (files) => {
4327
4433
  var _a;
4328
- const { accepted, errorMessage } = filterAndSlice(
4329
- Array.from(files),
4330
- resourceIds.length,
4331
- constraints,
4332
- state
4333
- );
4334
- if (errorMessage) {
4335
- showFileError(filesContainer, errorMessage);
4336
- } else {
4337
- clearFileError(filesContainer);
4338
- }
4339
4434
  const list = (_a = filesContainer.querySelector(".files-list")) != null ? _a : filesContainer;
4340
- const { failures } = await uploadBatch(accepted, resourceIds, list, state);
4341
- const combined = buildBatchErrorMessage(errorMessage, failures, state);
4342
- if (combined) {
4343
- showFileError(filesContainer, combined);
4344
- } else {
4345
- clearFileError(filesContainer);
4346
- }
4347
- updateCallback();
4348
- if (instance && pathKey && !state.config.readonly) {
4349
- instance.triggerOnChange(pathKey, resourceIds);
4350
- }
4435
+ await runMultiFileBatch(opts, Array.from(files), list, filesContainer);
4351
4436
  });
4352
4437
  }
4353
- function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
4438
+ function setupFilesPickerHandler(opts) {
4439
+ const { filesPicker } = opts;
4354
4440
  filesPicker.onchange = async () => {
4441
+ var _a, _b;
4355
4442
  if (!filesPicker.files) return;
4356
- const wrapperEl = filesPicker.closest("[data-files-wrapper]") || filesPicker.parentElement;
4357
- const { accepted, errorMessage } = filterAndSlice(
4443
+ const wrapperEl = (_a = filesPicker.closest("[data-files-wrapper]")) != null ? _a : filesPicker.parentElement;
4444
+ const listEl = (_b = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list")) != null ? _b : null;
4445
+ await runMultiFileBatch(
4446
+ opts,
4358
4447
  Array.from(filesPicker.files),
4359
- resourceIds.length,
4360
- constraints,
4361
- state
4448
+ listEl,
4449
+ wrapperEl
4362
4450
  );
4363
- if (errorMessage && wrapperEl) {
4364
- showFileError(wrapperEl, errorMessage);
4365
- } else if (wrapperEl) {
4366
- clearFileError(wrapperEl);
4367
- }
4368
- const listEl = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list");
4369
- const { failures } = await uploadBatch(
4370
- accepted,
4371
- resourceIds,
4372
- listEl != null ? listEl : null,
4373
- state
4374
- );
4375
- if (wrapperEl) {
4376
- const combined = buildBatchErrorMessage(errorMessage, failures, state);
4377
- if (combined) {
4378
- showFileError(wrapperEl, combined);
4379
- } else {
4380
- clearFileError(wrapperEl);
4381
- }
4382
- }
4383
- updateCallback();
4384
4451
  filesPicker.value = "";
4385
- if (instance && pathKey && !state.config.readonly) {
4386
- instance.triggerOnChange(pathKey, resourceIds);
4387
- }
4388
4452
  };
4389
4453
  }
4390
4454
 
@@ -4426,16 +4490,6 @@ function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSi
4426
4490
  }
4427
4491
  return null;
4428
4492
  }
4429
- function readCurrentResourceIds(wrapper) {
4430
- const raw = wrapper.dataset.resourceIds;
4431
- if (!raw) return [];
4432
- try {
4433
- const parsed = JSON.parse(raw);
4434
- return Array.isArray(parsed) ? parsed : [];
4435
- } catch {
4436
- return [];
4437
- }
4438
- }
4439
4493
  function registerPickedResource(resource, state) {
4440
4494
  var _a;
4441
4495
  const existing = state.resourceIndex.get(resource.resourceId);
@@ -4451,14 +4505,28 @@ function extractPickerError(error, state) {
4451
4505
  if (error instanceof Error && error.message) return error.message;
4452
4506
  return t("pickerError", state);
4453
4507
  }
4454
- async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resourceIds, maxCount, updateCallback, instance) {
4508
+ async function handleLibraryPickMulti(opts) {
4455
4509
  var _a;
4510
+ const {
4511
+ state,
4512
+ element,
4513
+ wrapper,
4514
+ fieldPath,
4515
+ resourceIds,
4516
+ maxCount,
4517
+ updateCallback,
4518
+ instance,
4519
+ coordinator,
4520
+ list,
4521
+ buildSuccessTile
4522
+ } = opts;
4456
4523
  if (!state.config.pickExistingFiles) return;
4457
4524
  const allowedExtensions = getAllowedExtensions(element.accept);
4458
4525
  const allowedMimes = getAllowedMimes(element.accept);
4459
4526
  const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
4460
- const currentIds = readCurrentResourceIds(wrapper);
4461
- const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - currentIds.length);
4527
+ const knownRids = coordinator.getAllKnownRids();
4528
+ const existingSet = new Set(knownRids);
4529
+ const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - coordinator.getOccupiedCount());
4462
4530
  let picked;
4463
4531
  try {
4464
4532
  picked = await state.config.pickExistingFiles({
@@ -4467,14 +4535,15 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
4467
4535
  accept: buildAcceptContext(element),
4468
4536
  maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
4469
4537
  remainingSlots: remaining === Infinity ? void 0 : remaining,
4470
- selectedResourceIds: [...currentIds]
4538
+ // Hand the host every rid that's already selected (committed or
4539
+ // staged) so it can grey them out or filter them.
4540
+ selectedResourceIds: knownRids
4471
4541
  });
4472
4542
  } catch (error) {
4473
4543
  showFileError(wrapper, extractPickerError(error, state));
4474
4544
  return;
4475
4545
  }
4476
4546
  if (picked.length === 0) return;
4477
- const existingSet = new Set(currentIds);
4478
4547
  const seen = /* @__PURE__ */ new Set();
4479
4548
  const deduped = picked.filter((r) => {
4480
4549
  if (existingSet.has(r.resourceId)) return false;
@@ -4492,10 +4561,18 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
4492
4561
  );
4493
4562
  return err === null;
4494
4563
  });
4495
- const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
4564
+ const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - coordinator.getOccupiedCount());
4496
4565
  const accepted = validItems.slice(0, freshRemaining);
4497
4566
  const skipped = validItems.length - accepted.length;
4498
- if (accepted.length === 0) return;
4567
+ if (accepted.length === 0) {
4568
+ if (skipped > 0) {
4569
+ showFileError(
4570
+ wrapper,
4571
+ t("filesLimitExceeded", state, { skipped, max: maxCount })
4572
+ );
4573
+ }
4574
+ return;
4575
+ }
4499
4576
  clearFileError(wrapper);
4500
4577
  if (skipped > 0) {
4501
4578
  showFileError(
@@ -4505,13 +4582,22 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
4505
4582
  }
4506
4583
  for (const resource of accepted) {
4507
4584
  registerPickedResource(resource, state);
4508
- resourceIds.push(resource.resourceId);
4509
4585
  }
4510
- wrapper.dataset.resourceIds = JSON.stringify(resourceIds);
4511
- updateCallback();
4586
+ const acceptedIds = accepted.map((r) => r.resourceId);
4587
+ const handle = coordinator.beginBatch(accepted.length);
4588
+ handle.setResults(acceptedIds);
4512
4589
  if (!state.config.readonly) {
4513
4590
  instance.triggerOnChange(fieldPath, resourceIds);
4514
4591
  }
4592
+ const { wasLast } = handle.end();
4593
+ if (wasLast) {
4594
+ updateCallback();
4595
+ } else {
4596
+ const tilesWrap = ensureTilesWrap(list);
4597
+ for (const rid of acceptedIds) {
4598
+ tilesWrap.appendChild(buildSuccessTile(rid));
4599
+ }
4600
+ }
4515
4601
  }
4516
4602
  async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
4517
4603
  var _a, _b;
@@ -4839,6 +4925,14 @@ function buildMetaDot() {
4839
4925
  return dot;
4840
4926
  }
4841
4927
  var gridResizeObservers = /* @__PURE__ */ new WeakMap();
4928
+ function disposePlaceholdersForUpload(container) {
4929
+ const observer = gridResizeObservers.get(container);
4930
+ if (observer) {
4931
+ observer.disconnect();
4932
+ gridResizeObservers.delete(container);
4933
+ }
4934
+ container.querySelectorAll(".fb-multi-placeholder").forEach((p) => p.remove());
4935
+ }
4842
4936
  function renderResourcePills(opts) {
4843
4937
  var _a;
4844
4938
  const {
@@ -5193,16 +5287,19 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
5193
5287
  filesPicker.click();
5194
5288
  };
5195
5289
  const onLibraryPick = state.config.pickExistingFiles && !element.disableLibrary ? () => {
5196
- handleLibraryPickMulti(
5290
+ handleLibraryPickMulti({
5197
5291
  state,
5198
5292
  element,
5199
- filesWrapper,
5200
- pathKey,
5201
- initialFiles,
5202
- maxFiles,
5203
- updateFilesDisplay,
5204
- ctx.instance
5205
- ).catch((err) => {
5293
+ wrapper: filesWrapper,
5294
+ fieldPath: pathKey,
5295
+ resourceIds: initialFiles,
5296
+ maxCount: maxFiles,
5297
+ updateCallback: updateFilesDisplay,
5298
+ instance: ctx.instance,
5299
+ coordinator,
5300
+ list,
5301
+ buildSuccessTile
5302
+ }).catch((err) => {
5206
5303
  console.error("Library pick failed:", err);
5207
5304
  });
5208
5305
  } : null;
@@ -5213,41 +5310,160 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
5213
5310
  rids: initialFiles,
5214
5311
  state,
5215
5312
  onRemove: currentlyReadonly ? null : (ridToRemove) => {
5216
- var _a2;
5313
+ var _a2, _b2;
5217
5314
  releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
5218
5315
  const index = initialFiles.indexOf(ridToRemove);
5219
5316
  if (index > -1) initialFiles.splice(index, 1);
5220
- updateFilesDisplay();
5317
+ if (coordinator.hasInFlightBatches()) {
5318
+ pendingRemovals.add(ridToRemove);
5319
+ (_b2 = list.querySelector(`[data-resource-id="${ridToRemove}"]`)) == null ? void 0 : _b2.remove();
5320
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5321
+ } else {
5322
+ updateFilesDisplay();
5323
+ }
5324
+ if (ctx.instance && pathKey && !state.config.readonly) {
5325
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5326
+ }
5221
5327
  },
5222
5328
  maxCount: maxFiles < Infinity ? maxFiles : void 0,
5223
5329
  isReadonly: currentlyReadonly,
5224
5330
  onLibraryPick: currentlyReadonly ? null : onLibraryPick,
5225
5331
  element,
5226
5332
  onClearAll: currentlyReadonly ? void 0 : () => {
5333
+ var _a2, _b2;
5334
+ for (const rid of initialFiles) {
5335
+ releaseLocalFileUrl((_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.file);
5336
+ }
5227
5337
  initialFiles.splice(0);
5228
- updateFilesDisplay();
5338
+ if (coordinator.hasInFlightBatches()) {
5339
+ const visibleTiles = list.querySelectorAll("[data-resource-id]");
5340
+ for (const tile of visibleTiles) {
5341
+ const rid = tile.dataset.resourceId;
5342
+ if (rid) {
5343
+ releaseLocalFileUrl((_b2 = state.resourceIndex.get(rid)) == null ? void 0 : _b2.file);
5344
+ pendingRemovals.add(rid);
5345
+ }
5346
+ tile.remove();
5347
+ }
5348
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5349
+ } else {
5350
+ updateFilesDisplay();
5351
+ }
5352
+ if (ctx.instance && pathKey && !state.config.readonly) {
5353
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5354
+ }
5229
5355
  },
5230
5356
  openPicker
5231
5357
  });
5232
5358
  }
5233
- setupFilesDropHandler(
5234
- filesContainer,
5235
- initialFiles,
5236
- state,
5237
- updateFilesDisplay,
5238
- constraints,
5239
- pathKey,
5240
- ctx.instance
5241
- );
5242
- setupFilesPickerHandler(
5243
- filesPicker,
5244
- initialFiles,
5359
+ let inFlightFiles = 0;
5360
+ let activeBatches = 0;
5361
+ let nextBatchOrdinal = 0;
5362
+ let nextCommitOrdinal = 0;
5363
+ const stagedResults = /* @__PURE__ */ new Map();
5364
+ const batchReservations = /* @__PURE__ */ new Map();
5365
+ const pendingRemovals = /* @__PURE__ */ new Set();
5366
+ const drainContiguousStagedResults = () => {
5367
+ var _a2;
5368
+ while (stagedResults.has(nextCommitOrdinal)) {
5369
+ const ordinal = nextCommitOrdinal;
5370
+ const ids = stagedResults.get(ordinal);
5371
+ stagedResults.delete(ordinal);
5372
+ nextCommitOrdinal += 1;
5373
+ const reservation = (_a2 = batchReservations.get(ordinal)) != null ? _a2 : 0;
5374
+ batchReservations.delete(ordinal);
5375
+ inFlightFiles -= reservation;
5376
+ for (const rid of ids) {
5377
+ if (rid === null) continue;
5378
+ if (pendingRemovals.has(rid)) {
5379
+ pendingRemovals.delete(rid);
5380
+ continue;
5381
+ }
5382
+ initialFiles.push(rid);
5383
+ }
5384
+ }
5385
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5386
+ };
5387
+ const coordinator = {
5388
+ getOccupiedCount: () => initialFiles.length + inFlightFiles,
5389
+ getAllKnownRids: () => {
5390
+ const out = [...initialFiles];
5391
+ for (const ids of stagedResults.values()) {
5392
+ for (const rid of ids) {
5393
+ if (rid !== null) out.push(rid);
5394
+ }
5395
+ }
5396
+ return out;
5397
+ },
5398
+ hasInFlightBatches: () => activeBatches > 0 || batchReservations.size > 0,
5399
+ wasRemovedDuringBatch: (rid) => pendingRemovals.has(rid),
5400
+ beginBatch: (count) => {
5401
+ inFlightFiles += count;
5402
+ activeBatches += 1;
5403
+ const ordinal = nextBatchOrdinal++;
5404
+ batchReservations.set(ordinal, count);
5405
+ return {
5406
+ setResults: (orderedIds) => {
5407
+ stagedResults.set(ordinal, orderedIds);
5408
+ drainContiguousStagedResults();
5409
+ },
5410
+ end: () => {
5411
+ activeBatches -= 1;
5412
+ const wasLast = activeBatches === 0 && batchReservations.size === 0;
5413
+ if (wasLast) {
5414
+ pendingRemovals.clear();
5415
+ }
5416
+ return { wasLast };
5417
+ }
5418
+ };
5419
+ }
5420
+ };
5421
+ const buildSuccessTile = (rid) => {
5422
+ const currentlyReadonly = isElementReadonly(element, state);
5423
+ return buildPreviewTile(
5424
+ rid,
5425
+ state,
5426
+ !currentlyReadonly,
5427
+ currentlyReadonly ? null : () => {
5428
+ var _a2, _b2, _c;
5429
+ releaseLocalFileUrl((_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.file);
5430
+ const idx = initialFiles.indexOf(rid);
5431
+ if (idx > -1) {
5432
+ initialFiles.splice(idx, 1);
5433
+ if (coordinator.hasInFlightBatches()) {
5434
+ pendingRemovals.add(rid);
5435
+ (_b2 = list.querySelector(`[data-resource-id="${rid}"]`)) == null ? void 0 : _b2.remove();
5436
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5437
+ } else {
5438
+ updateFilesDisplay();
5439
+ }
5440
+ if (ctx.instance && pathKey && !state.config.readonly) {
5441
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5442
+ }
5443
+ return;
5444
+ }
5445
+ pendingRemovals.add(rid);
5446
+ (_c = list.querySelector(`[data-resource-id="${rid}"]`)) == null ? void 0 : _c.remove();
5447
+ if (ctx.instance && pathKey && !state.config.readonly) {
5448
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5449
+ }
5450
+ }
5451
+ );
5452
+ };
5453
+ const prepareForUpload = () => disposePlaceholdersForUpload(list);
5454
+ const sharedHandlerOpts = {
5455
+ resourceIds: initialFiles,
5245
5456
  state,
5246
- updateFilesDisplay,
5457
+ updateCallback: updateFilesDisplay,
5247
5458
  constraints,
5248
5459
  pathKey,
5249
- ctx.instance
5250
- );
5460
+ instance: ctx.instance,
5461
+ buildSuccessTile,
5462
+ prepareForUpload,
5463
+ coordinator
5464
+ };
5465
+ setupFilesDropHandler({ ...sharedHandlerOpts, filesContainer });
5466
+ setupFilesPickerHandler({ ...sharedHandlerOpts, filesPicker });
5251
5467
  updateFilesDisplay();
5252
5468
  wrapper.appendChild(filesWrapper);
5253
5469
  }
@@ -6610,6 +6826,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
6610
6826
  formData: (_b = ctx.formData) != null ? _b : ctx.prefill,
6611
6827
  // Complete root data for enableIf evaluation
6612
6828
  state: ctx.state,
6829
+ instance: ctx.instance,
6613
6830
  inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
6614
6831
  };
6615
6832
  element.elements.forEach((child) => {
@@ -6698,6 +6915,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6698
6915
  const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
6699
6916
  const subCtx = {
6700
6917
  state: ctx.state,
6918
+ instance: ctx.instance,
6701
6919
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
6702
6920
  prefill: childDefaults,
6703
6921
  // Defaults for enableIf evaluation
@@ -6765,6 +6983,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6765
6983
  const mergedPrefill = mergeWithDefaults(prefillObj || {}, childDefaults);
6766
6984
  const subCtx = {
6767
6985
  state: ctx.state,
6986
+ instance: ctx.instance,
6768
6987
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
6769
6988
  prefill: mergedPrefill,
6770
6989
  // Merged prefill with defaults for enableIf
@@ -6812,6 +7031,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6812
7031
  const idx = countItems();
6813
7032
  const subCtx = {
6814
7033
  state: ctx.state,
7034
+ instance: ctx.instance,
6815
7035
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
6816
7036
  prefill: childDefaults,
6817
7037
  // Defaults for enableIf evaluation
@@ -8487,6 +8707,21 @@ function applyAutoExpand2(textarea, backdrop) {
8487
8707
  setTimeout(() => {
8488
8708
  if (textarea.isConnected) resize();
8489
8709
  }, 0);
8710
+ if (typeof ResizeObserver === "undefined") return;
8711
+ let lastWidth = -1;
8712
+ const ro = new ResizeObserver((entries) => {
8713
+ var _a, _b, _c, _d, _e;
8714
+ if (!textarea.isConnected) {
8715
+ ro.disconnect();
8716
+ return;
8717
+ }
8718
+ const entry = entries[0];
8719
+ const w = (_e = (_d = (_b = (_a = entry == null ? void 0 : entry.contentBoxSize) == null ? void 0 : _a[0]) == null ? void 0 : _b.inlineSize) != null ? _d : (_c = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _c.width) != null ? _e : 0;
8720
+ if (w === lastWidth) return;
8721
+ lastWidth = w;
8722
+ resize();
8723
+ });
8724
+ ro.observe(textarea);
8490
8725
  }
8491
8726
  function buildFileLabels(files, state) {
8492
8727
  var _a, _b, _c, _d;
@@ -10297,8 +10532,35 @@ function extractDOMValue(fieldPath, formRoot) {
10297
10532
  }
10298
10533
  return void 0;
10299
10534
  }
10535
+ function buildScopedDataAtPath(path, value) {
10536
+ const segments = path.match(/[^.[\]]+|\[\d+\]/g);
10537
+ if (!segments || segments.length === 0) {
10538
+ return {};
10539
+ }
10540
+ const root = {};
10541
+ let current = root;
10542
+ for (let i = 0; i < segments.length - 1; i++) {
10543
+ const seg = segments[i];
10544
+ const next = segments[i + 1];
10545
+ const placeholder = next.startsWith("[") && next.endsWith("]") ? [] : {};
10546
+ if (seg.startsWith("[") && seg.endsWith("]")) {
10547
+ const idx = parseInt(seg.slice(1, -1), 10);
10548
+ current[idx] = placeholder;
10549
+ } else {
10550
+ current[seg] = placeholder;
10551
+ }
10552
+ current = placeholder;
10553
+ }
10554
+ const last = segments[segments.length - 1];
10555
+ if (last.startsWith("[") && last.endsWith("]")) {
10556
+ current[parseInt(last.slice(1, -1), 10)] = value;
10557
+ } else {
10558
+ current[last] = value;
10559
+ }
10560
+ return root;
10561
+ }
10300
10562
  function reevaluateEnableIf(wrapper, element, ctx) {
10301
- var _a, _b;
10563
+ var _a;
10302
10564
  if (!element.enableIf) {
10303
10565
  return;
10304
10566
  }
@@ -10309,54 +10571,13 @@ function reevaluateEnableIf(wrapper, element, ctx) {
10309
10571
  }
10310
10572
  const condition = element.enableIf;
10311
10573
  const scope = (_a = condition.scope) != null ? _a : "relative";
10312
- let rootFormData = {};
10313
- const containerData = {};
10314
10574
  const effectiveScope = !ctx.path || ctx.path === "" ? "absolute" : scope;
10315
- if (effectiveScope === "relative" && ctx.path) {
10316
- const containerMatch = ctx.path.match(/^(.+)\[(\d+)\]$/);
10317
- if (containerMatch) {
10318
- const containerKey = containerMatch[1];
10319
- const containerIndex = parseInt(containerMatch[2], 10);
10320
- const containerItemElement = formRoot.querySelector(
10321
- `[data-container-item="${containerKey}[${containerIndex}]"]`
10322
- );
10323
- if (containerItemElement) {
10324
- const inputs = containerItemElement.querySelectorAll("input, select, textarea");
10325
- inputs.forEach((input) => {
10326
- const fieldName = input.getAttribute("name");
10327
- if (fieldName) {
10328
- const fieldKeyMatch = fieldName.match(/\.([^.[\]]+)$/);
10329
- if (fieldKeyMatch) {
10330
- const fieldKey = fieldKeyMatch[1];
10331
- if (input instanceof HTMLSelectElement) {
10332
- containerData[fieldKey] = input.value;
10333
- } else if (input instanceof HTMLInputElement) {
10334
- if (input.type === "checkbox") {
10335
- containerData[fieldKey] = input.checked;
10336
- } else if (input.type === "radio") {
10337
- if (input.checked) {
10338
- containerData[fieldKey] = input.value;
10339
- }
10340
- } else {
10341
- containerData[fieldKey] = input.value;
10342
- }
10343
- } else if (input instanceof HTMLTextAreaElement) {
10344
- containerData[fieldKey] = input.value;
10345
- }
10346
- }
10347
- }
10348
- });
10349
- }
10350
- }
10351
- } else {
10352
- const dependencyKey = condition.key;
10353
- const dependencyValue = extractDOMValue(dependencyKey, formRoot);
10354
- if (dependencyValue !== void 0) {
10355
- rootFormData[dependencyKey] = dependencyValue;
10356
- } else {
10357
- rootFormData = (_b = ctx.formData) != null ? _b : ctx.prefill;
10358
- }
10359
- }
10575
+ const dependencyKey = condition.key;
10576
+ const dependencyFieldPath = effectiveScope === "relative" && ctx.path ? `${ctx.path}.${dependencyKey}` : dependencyKey;
10577
+ const dependencyValue = extractDOMValue(dependencyFieldPath, formRoot);
10578
+ const scopedData = buildScopedDataAtPath(dependencyKey, dependencyValue);
10579
+ const rootFormData = effectiveScope === "relative" ? {} : scopedData;
10580
+ const containerData = effectiveScope === "relative" ? scopedData : void 0;
10360
10581
  try {
10361
10582
  const shouldEnable = evaluateEnableCondition(
10362
10583
  condition,
@@ -11032,6 +11253,7 @@ var exampleThemes = {
11032
11253
  },
11033
11254
  // Picaz wizard design tokens — derived from the Picaz Wizard mockups.
11034
11255
  // Pairs with the host-side .card / .section-num / .lede chrome that wraps the form.
11256
+ // Assumes Inter is loaded by the host (e.g. via Google Fonts in index.html).
11035
11257
  picaz: {
11036
11258
  ...defaultTheme,
11037
11259
  primaryColor: "#2f5bea",
@@ -11063,6 +11285,9 @@ var exampleThemes = {
11063
11285
  fileUploadBgColor: "#fafcff",
11064
11286
  fileUploadBorderColor: "#cdd6e3",
11065
11287
  fileUploadHoverBorderColor: "#2f5bea",
11288
+ // Picaz uses roomier inputs (11/14px) than the defaultTheme compact density.
11289
+ inputPaddingX: "14px",
11290
+ inputPaddingY: "11px",
11066
11291
  borderRadius: "12px",
11067
11292
  borderRadiusSmall: "8px",
11068
11293
  borderRadiusLarge: "16px",
@@ -11070,7 +11295,17 @@ var exampleThemes = {
11070
11295
  fontFamily: '"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
11071
11296
  shadowCard: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
11072
11297
  shadowPopover: "0 12px 32px -12px rgba(15,23,42,.18), 0 4px 12px -6px rgba(15,23,42,.08)",
11073
- focusRingColor: "#2f5bea"
11298
+ focusRingColor: "#2f5bea",
11299
+ // Slide cards: subtle gradient lift, no rest-state shadow (mockup adds it
11300
+ // only on hover, which form-builder doesn't yet differentiate).
11301
+ slideCardBg: "linear-gradient(180deg, #f7f9fc 0%, #dde3ee 100%)",
11302
+ slideCardShadow: "none",
11303
+ slideCardRadius: "16px",
11304
+ // Tiny uppercase captions above grouped lists ("ПРЕИМУЩЕСТВА" in the mockup).
11305
+ labelSectionFontSize: "0.625rem",
11306
+ // 10px
11307
+ labelSectionLetterSpacing: "0.07em",
11308
+ labelSectionTextTransform: "uppercase"
11074
11309
  }
11075
11310
  };
11076
11311
 
@@ -11436,6 +11671,13 @@ var FormBuilderInstance = class {
11436
11671
  const value = hintValues[fieldKey];
11437
11672
  this.updateField(fullPath, value);
11438
11673
  }
11674
+ const group = target.closest(".fb-prefill-hints");
11675
+ if (group) {
11676
+ group.querySelectorAll(
11677
+ '.fb-prefill-hint[aria-pressed="true"]'
11678
+ ).forEach((el) => el.removeAttribute("aria-pressed"));
11679
+ }
11680
+ target.setAttribute("aria-pressed", "true");
11439
11681
  } catch (error) {
11440
11682
  console.error("Error parsing prefill hint values:", error);
11441
11683
  }