@dmitryvim/form-builder 0.3.0 → 0.3.1

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/esm/index.js CHANGED
@@ -435,6 +435,36 @@ function ensureThemingHooks(doc) {
435
435
  color: var(--fb-error-color);
436
436
  background-color: var(--fb-background-hover-color);
437
437
  }
438
+ /* Prefill-suggestion pills rendered by createPrefillHints. Outline pill at rest,
439
+ soft-fill on hover, solid-fill when selected. All colors flow from the active
440
+ theme \u2014 consumers don't need to ship their own CSS. */
441
+ .fb-prefill-hint {
442
+ padding: 0.25rem 0.625rem;
443
+ border: var(--fb-border-width) solid var(--fb-primary-color);
444
+ border-radius: 9999px;
445
+ background: var(--fb-background-color);
446
+ color: var(--fb-primary-color);
447
+ font-size: var(--fb-font-size-small);
448
+ font-weight: var(--fb-font-weight-medium);
449
+ font-family: var(--fb-font-family);
450
+ cursor: pointer;
451
+ transition: background-color var(--fb-transition-duration), border-color var(--fb-transition-duration), color var(--fb-transition-duration);
452
+ }
453
+ .fb-prefill-hint:hover {
454
+ background: var(--fb-primary-soft-color);
455
+ border-color: var(--fb-primary-hover-color);
456
+ color: var(--fb-primary-hover-color);
457
+ }
458
+ .fb-prefill-hint:focus-visible {
459
+ outline: var(--fb-focus-ring-width) solid var(--fb-focus-ring-color);
460
+ outline-offset: 2px;
461
+ }
462
+ .fb-prefill-hint[aria-pressed="true"],
463
+ .fb-prefill-hint.active {
464
+ background: var(--fb-primary-color);
465
+ color: #ffffff;
466
+ border-color: var(--fb-primary-color);
467
+ }
438
468
  `;
439
469
  doc.head.appendChild(style);
440
470
  }
@@ -446,7 +476,9 @@ function applyAutoExpand(textarea) {
446
476
  const resize = () => {
447
477
  if (!textarea.isConnected) return;
448
478
  textarea.style.height = "0";
449
- textarea.style.height = `${textarea.scrollHeight}px`;
479
+ const cs = getComputedStyle(textarea);
480
+ const borderY = parseFloat(cs.borderTopWidth || "0") + parseFloat(cs.borderBottomWidth || "0");
481
+ textarea.style.height = `${textarea.scrollHeight + borderY}px`;
450
482
  };
451
483
  textarea.addEventListener("input", resize);
452
484
  setTimeout(() => {
@@ -4215,15 +4247,25 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
4215
4247
  }
4216
4248
  return { accepted, errorMessage: errorParts.join(" \u2022 ") };
4217
4249
  }
4218
- async function uploadBatch(accepted, resourceIds, listEl, state) {
4219
- if (listEl) {
4250
+ async function uploadBatch(opts) {
4251
+ const {
4252
+ accepted,
4253
+ listEl,
4254
+ state,
4255
+ shouldHideAddTile,
4256
+ buildSuccessTile,
4257
+ prepareForUpload
4258
+ } = opts;
4259
+ if (listEl && shouldHideAddTile) {
4220
4260
  const tilesWrap = ensureTilesWrap(listEl);
4221
4261
  const addTile = tilesWrap.querySelector(".fb-multi-add-tile-js") ?? tilesWrap.querySelector(".fb-tile-add");
4222
4262
  if (addTile) addTile.style.display = "none";
4223
4263
  }
4264
+ prepareForUpload?.();
4265
+ const orderedIds = new Array(accepted.length).fill(null);
4224
4266
  const failures = [];
4225
4267
  await Promise.allSettled(
4226
- accepted.map(async (file) => {
4268
+ accepted.map(async (file, index) => {
4227
4269
  const placeholder = createUploadingTile(file.name, state);
4228
4270
  if (listEl) {
4229
4271
  const tilesWrap = ensureTilesWrap(listEl);
@@ -4236,20 +4278,24 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
4236
4278
  type: file.type,
4237
4279
  size: file.size,
4238
4280
  uploadedAt: /* @__PURE__ */ new Date(),
4239
- file: void 0
4281
+ file
4240
4282
  });
4241
- resourceIds.push(rid);
4283
+ orderedIds[index] = rid;
4284
+ if (buildSuccessTile && placeholder.parentNode) {
4285
+ placeholder.replaceWith(buildSuccessTile(rid));
4286
+ } else {
4287
+ placeholder.remove();
4288
+ }
4242
4289
  } catch (err) {
4243
4290
  const wrapped = err instanceof Error ? err : new Error(String(err));
4244
4291
  const cause = wrapped.cause;
4245
4292
  const root = cause instanceof Error ? cause : cause !== void 0 ? new Error(String(cause)) : wrapped;
4246
4293
  failures.push({ file, error: root });
4247
- } finally {
4248
4294
  placeholder.remove();
4249
4295
  }
4250
4296
  })
4251
4297
  );
4252
- return { failures };
4298
+ return { failures, orderedIds };
4253
4299
  }
4254
4300
  function buildBatchErrorMessage(filterError, failures, state) {
4255
4301
  if (failures.length === 0) return filterError;
@@ -4261,68 +4307,70 @@ function buildBatchErrorMessage(filterError, failures, state) {
4261
4307
  ).join(" \u2022 ");
4262
4308
  return filterError ? `${filterError} \u2022 ${uploadMsg}` : uploadMsg;
4263
4309
  }
4264
- function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
4310
+ async function runMultiFileBatch(opts, files, listEl, errorTarget) {
4311
+ const {
4312
+ resourceIds,
4313
+ state,
4314
+ updateCallback,
4315
+ constraints,
4316
+ pathKey,
4317
+ instance,
4318
+ buildSuccessTile,
4319
+ prepareForUpload,
4320
+ coordinator
4321
+ } = opts;
4322
+ const { accepted, errorMessage } = filterAndSlice(
4323
+ files,
4324
+ coordinator.getOccupiedCount(),
4325
+ constraints,
4326
+ state
4327
+ );
4328
+ if (errorTarget) {
4329
+ if (errorMessage) showFileError(errorTarget, errorMessage);
4330
+ else clearFileError(errorTarget);
4331
+ }
4332
+ const handle = coordinator.beginBatch(accepted.length);
4333
+ const shouldHideAddTile = coordinator.getOccupiedCount() >= constraints.maxCount;
4334
+ const { failures, orderedIds } = await uploadBatch({
4335
+ accepted,
4336
+ listEl,
4337
+ state,
4338
+ shouldHideAddTile,
4339
+ buildSuccessTile,
4340
+ prepareForUpload
4341
+ });
4342
+ handle.setResults(orderedIds);
4343
+ if (instance && pathKey && !state.config.readonly) {
4344
+ instance.triggerOnChange(pathKey, resourceIds);
4345
+ }
4346
+ const { wasLast } = handle.end();
4347
+ if (wasLast) updateCallback();
4348
+ if (errorTarget) {
4349
+ const combined = buildBatchErrorMessage(errorMessage, failures, state);
4350
+ if (combined) showFileError(errorTarget, combined);
4351
+ else clearFileError(errorTarget);
4352
+ }
4353
+ }
4354
+ function setupFilesDropHandler(opts) {
4355
+ const { filesContainer } = opts;
4265
4356
  setupDragAndDrop(filesContainer, async (files) => {
4266
- const { accepted, errorMessage } = filterAndSlice(
4267
- Array.from(files),
4268
- resourceIds.length,
4269
- constraints,
4270
- state
4271
- );
4272
- if (errorMessage) {
4273
- showFileError(filesContainer, errorMessage);
4274
- } else {
4275
- clearFileError(filesContainer);
4276
- }
4277
4357
  const list = filesContainer.querySelector(".files-list") ?? filesContainer;
4278
- const { failures } = await uploadBatch(accepted, resourceIds, list, state);
4279
- const combined = buildBatchErrorMessage(errorMessage, failures, state);
4280
- if (combined) {
4281
- showFileError(filesContainer, combined);
4282
- } else {
4283
- clearFileError(filesContainer);
4284
- }
4285
- updateCallback();
4286
- if (instance && pathKey && !state.config.readonly) {
4287
- instance.triggerOnChange(pathKey, resourceIds);
4288
- }
4358
+ await runMultiFileBatch(opts, Array.from(files), list, filesContainer);
4289
4359
  });
4290
4360
  }
4291
- function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
4361
+ function setupFilesPickerHandler(opts) {
4362
+ const { filesPicker } = opts;
4292
4363
  filesPicker.onchange = async () => {
4293
4364
  if (!filesPicker.files) return;
4294
- const wrapperEl = filesPicker.closest("[data-files-wrapper]") || filesPicker.parentElement;
4295
- const { accepted, errorMessage } = filterAndSlice(
4365
+ const wrapperEl = filesPicker.closest("[data-files-wrapper]") ?? filesPicker.parentElement;
4366
+ const listEl = wrapperEl?.querySelector(".files-list") ?? null;
4367
+ await runMultiFileBatch(
4368
+ opts,
4296
4369
  Array.from(filesPicker.files),
4297
- resourceIds.length,
4298
- constraints,
4299
- state
4300
- );
4301
- if (errorMessage && wrapperEl) {
4302
- showFileError(wrapperEl, errorMessage);
4303
- } else if (wrapperEl) {
4304
- clearFileError(wrapperEl);
4305
- }
4306
- const listEl = wrapperEl?.querySelector(".files-list");
4307
- const { failures } = await uploadBatch(
4308
- accepted,
4309
- resourceIds,
4310
- listEl ?? null,
4311
- state
4370
+ listEl,
4371
+ wrapperEl
4312
4372
  );
4313
- if (wrapperEl) {
4314
- const combined = buildBatchErrorMessage(errorMessage, failures, state);
4315
- if (combined) {
4316
- showFileError(wrapperEl, combined);
4317
- } else {
4318
- clearFileError(wrapperEl);
4319
- }
4320
- }
4321
- updateCallback();
4322
4373
  filesPicker.value = "";
4323
- if (instance && pathKey && !state.config.readonly) {
4324
- instance.triggerOnChange(pathKey, resourceIds);
4325
- }
4326
4374
  };
4327
4375
  }
4328
4376
 
@@ -4363,16 +4411,6 @@ function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSi
4363
4411
  }
4364
4412
  return null;
4365
4413
  }
4366
- function readCurrentResourceIds(wrapper) {
4367
- const raw = wrapper.dataset.resourceIds;
4368
- if (!raw) return [];
4369
- try {
4370
- const parsed = JSON.parse(raw);
4371
- return Array.isArray(parsed) ? parsed : [];
4372
- } catch {
4373
- return [];
4374
- }
4375
- }
4376
4414
  function registerPickedResource(resource, state) {
4377
4415
  const existing = state.resourceIndex.get(resource.resourceId);
4378
4416
  state.resourceIndex.set(resource.resourceId, {
@@ -4387,13 +4425,27 @@ function extractPickerError(error, state) {
4387
4425
  if (error instanceof Error && error.message) return error.message;
4388
4426
  return t("pickerError", state);
4389
4427
  }
4390
- async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resourceIds, maxCount, updateCallback, instance) {
4428
+ async function handleLibraryPickMulti(opts) {
4429
+ const {
4430
+ state,
4431
+ element,
4432
+ wrapper,
4433
+ fieldPath,
4434
+ resourceIds,
4435
+ maxCount,
4436
+ updateCallback,
4437
+ instance,
4438
+ coordinator,
4439
+ list,
4440
+ buildSuccessTile
4441
+ } = opts;
4391
4442
  if (!state.config.pickExistingFiles) return;
4392
4443
  const allowedExtensions = getAllowedExtensions(element.accept);
4393
4444
  const allowedMimes = getAllowedMimes(element.accept);
4394
4445
  const maxSizeMB = element.maxSize ?? Infinity;
4395
- const currentIds = readCurrentResourceIds(wrapper);
4396
- const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - currentIds.length);
4446
+ const knownRids = coordinator.getAllKnownRids();
4447
+ const existingSet = new Set(knownRids);
4448
+ const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - coordinator.getOccupiedCount());
4397
4449
  let picked;
4398
4450
  try {
4399
4451
  picked = await state.config.pickExistingFiles({
@@ -4402,14 +4454,15 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
4402
4454
  accept: buildAcceptContext(element),
4403
4455
  maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
4404
4456
  remainingSlots: remaining === Infinity ? void 0 : remaining,
4405
- selectedResourceIds: [...currentIds]
4457
+ // Hand the host every rid that's already selected (committed or
4458
+ // staged) so it can grey them out or filter them.
4459
+ selectedResourceIds: knownRids
4406
4460
  });
4407
4461
  } catch (error) {
4408
4462
  showFileError(wrapper, extractPickerError(error, state));
4409
4463
  return;
4410
4464
  }
4411
4465
  if (picked.length === 0) return;
4412
- const existingSet = new Set(currentIds);
4413
4466
  const seen = /* @__PURE__ */ new Set();
4414
4467
  const deduped = picked.filter((r) => {
4415
4468
  if (existingSet.has(r.resourceId)) return false;
@@ -4427,10 +4480,18 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
4427
4480
  );
4428
4481
  return err === null;
4429
4482
  });
4430
- const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
4483
+ const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - coordinator.getOccupiedCount());
4431
4484
  const accepted = validItems.slice(0, freshRemaining);
4432
4485
  const skipped = validItems.length - accepted.length;
4433
- if (accepted.length === 0) return;
4486
+ if (accepted.length === 0) {
4487
+ if (skipped > 0) {
4488
+ showFileError(
4489
+ wrapper,
4490
+ t("filesLimitExceeded", state, { skipped, max: maxCount })
4491
+ );
4492
+ }
4493
+ return;
4494
+ }
4434
4495
  clearFileError(wrapper);
4435
4496
  if (skipped > 0) {
4436
4497
  showFileError(
@@ -4440,13 +4501,22 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
4440
4501
  }
4441
4502
  for (const resource of accepted) {
4442
4503
  registerPickedResource(resource, state);
4443
- resourceIds.push(resource.resourceId);
4444
4504
  }
4445
- wrapper.dataset.resourceIds = JSON.stringify(resourceIds);
4446
- updateCallback();
4505
+ const acceptedIds = accepted.map((r) => r.resourceId);
4506
+ const handle = coordinator.beginBatch(accepted.length);
4507
+ handle.setResults(acceptedIds);
4447
4508
  if (!state.config.readonly) {
4448
4509
  instance.triggerOnChange(fieldPath, resourceIds);
4449
4510
  }
4511
+ const { wasLast } = handle.end();
4512
+ if (wasLast) {
4513
+ updateCallback();
4514
+ } else {
4515
+ const tilesWrap = ensureTilesWrap(list);
4516
+ for (const rid of acceptedIds) {
4517
+ tilesWrap.appendChild(buildSuccessTile(rid));
4518
+ }
4519
+ }
4450
4520
  }
4451
4521
  async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
4452
4522
  if (!state.config.pickExistingFiles) return;
@@ -4767,6 +4837,14 @@ function buildMetaDot() {
4767
4837
  return dot;
4768
4838
  }
4769
4839
  var gridResizeObservers = /* @__PURE__ */ new WeakMap();
4840
+ function disposePlaceholdersForUpload(container) {
4841
+ const observer = gridResizeObservers.get(container);
4842
+ if (observer) {
4843
+ observer.disconnect();
4844
+ gridResizeObservers.delete(container);
4845
+ }
4846
+ container.querySelectorAll(".fb-multi-placeholder").forEach((p) => p.remove());
4847
+ }
4770
4848
  function renderResourcePills(opts) {
4771
4849
  const {
4772
4850
  container,
@@ -5114,16 +5192,19 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
5114
5192
  filesPicker.click();
5115
5193
  };
5116
5194
  const onLibraryPick = state.config.pickExistingFiles && !element.disableLibrary ? () => {
5117
- handleLibraryPickMulti(
5195
+ handleLibraryPickMulti({
5118
5196
  state,
5119
5197
  element,
5120
- filesWrapper,
5121
- pathKey,
5122
- initialFiles,
5123
- maxFiles,
5124
- updateFilesDisplay,
5125
- ctx.instance
5126
- ).catch((err) => {
5198
+ wrapper: filesWrapper,
5199
+ fieldPath: pathKey,
5200
+ resourceIds: initialFiles,
5201
+ maxCount: maxFiles,
5202
+ updateCallback: updateFilesDisplay,
5203
+ instance: ctx.instance,
5204
+ coordinator,
5205
+ list,
5206
+ buildSuccessTile
5207
+ }).catch((err) => {
5127
5208
  console.error("Library pick failed:", err);
5128
5209
  });
5129
5210
  } : null;
@@ -5137,37 +5218,153 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
5137
5218
  releaseLocalFileUrl(state.resourceIndex.get(ridToRemove)?.file);
5138
5219
  const index = initialFiles.indexOf(ridToRemove);
5139
5220
  if (index > -1) initialFiles.splice(index, 1);
5140
- updateFilesDisplay();
5221
+ if (coordinator.hasInFlightBatches()) {
5222
+ pendingRemovals.add(ridToRemove);
5223
+ list.querySelector(`[data-resource-id="${ridToRemove}"]`)?.remove();
5224
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5225
+ } else {
5226
+ updateFilesDisplay();
5227
+ }
5228
+ if (ctx.instance && pathKey && !state.config.readonly) {
5229
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5230
+ }
5141
5231
  },
5142
5232
  maxCount: maxFiles < Infinity ? maxFiles : void 0,
5143
5233
  isReadonly: currentlyReadonly,
5144
5234
  onLibraryPick: currentlyReadonly ? null : onLibraryPick,
5145
5235
  element,
5146
5236
  onClearAll: currentlyReadonly ? void 0 : () => {
5237
+ for (const rid of initialFiles) {
5238
+ releaseLocalFileUrl(state.resourceIndex.get(rid)?.file);
5239
+ }
5147
5240
  initialFiles.splice(0);
5148
- updateFilesDisplay();
5241
+ if (coordinator.hasInFlightBatches()) {
5242
+ const visibleTiles = list.querySelectorAll("[data-resource-id]");
5243
+ for (const tile of visibleTiles) {
5244
+ const rid = tile.dataset.resourceId;
5245
+ if (rid) {
5246
+ releaseLocalFileUrl(state.resourceIndex.get(rid)?.file);
5247
+ pendingRemovals.add(rid);
5248
+ }
5249
+ tile.remove();
5250
+ }
5251
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5252
+ } else {
5253
+ updateFilesDisplay();
5254
+ }
5255
+ if (ctx.instance && pathKey && !state.config.readonly) {
5256
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5257
+ }
5149
5258
  },
5150
5259
  openPicker
5151
5260
  });
5152
5261
  }
5153
- setupFilesDropHandler(
5154
- filesContainer,
5155
- initialFiles,
5156
- state,
5157
- updateFilesDisplay,
5158
- constraints,
5159
- pathKey,
5160
- ctx.instance
5161
- );
5162
- setupFilesPickerHandler(
5163
- filesPicker,
5164
- initialFiles,
5262
+ let inFlightFiles = 0;
5263
+ let activeBatches = 0;
5264
+ let nextBatchOrdinal = 0;
5265
+ let nextCommitOrdinal = 0;
5266
+ const stagedResults = /* @__PURE__ */ new Map();
5267
+ const batchReservations = /* @__PURE__ */ new Map();
5268
+ const pendingRemovals = /* @__PURE__ */ new Set();
5269
+ const drainContiguousStagedResults = () => {
5270
+ while (stagedResults.has(nextCommitOrdinal)) {
5271
+ const ordinal = nextCommitOrdinal;
5272
+ const ids = stagedResults.get(ordinal);
5273
+ stagedResults.delete(ordinal);
5274
+ nextCommitOrdinal += 1;
5275
+ const reservation = batchReservations.get(ordinal) ?? 0;
5276
+ batchReservations.delete(ordinal);
5277
+ inFlightFiles -= reservation;
5278
+ for (const rid of ids) {
5279
+ if (rid === null) continue;
5280
+ if (pendingRemovals.has(rid)) {
5281
+ pendingRemovals.delete(rid);
5282
+ continue;
5283
+ }
5284
+ initialFiles.push(rid);
5285
+ }
5286
+ }
5287
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5288
+ };
5289
+ const coordinator = {
5290
+ getOccupiedCount: () => initialFiles.length + inFlightFiles,
5291
+ getAllKnownRids: () => {
5292
+ const out = [...initialFiles];
5293
+ for (const ids of stagedResults.values()) {
5294
+ for (const rid of ids) {
5295
+ if (rid !== null) out.push(rid);
5296
+ }
5297
+ }
5298
+ return out;
5299
+ },
5300
+ hasInFlightBatches: () => activeBatches > 0 || batchReservations.size > 0,
5301
+ wasRemovedDuringBatch: (rid) => pendingRemovals.has(rid),
5302
+ beginBatch: (count) => {
5303
+ inFlightFiles += count;
5304
+ activeBatches += 1;
5305
+ const ordinal = nextBatchOrdinal++;
5306
+ batchReservations.set(ordinal, count);
5307
+ return {
5308
+ setResults: (orderedIds) => {
5309
+ stagedResults.set(ordinal, orderedIds);
5310
+ drainContiguousStagedResults();
5311
+ },
5312
+ end: () => {
5313
+ activeBatches -= 1;
5314
+ const wasLast = activeBatches === 0 && batchReservations.size === 0;
5315
+ if (wasLast) {
5316
+ pendingRemovals.clear();
5317
+ }
5318
+ return { wasLast };
5319
+ }
5320
+ };
5321
+ }
5322
+ };
5323
+ const buildSuccessTile = (rid) => {
5324
+ const currentlyReadonly = isElementReadonly(element, state);
5325
+ return buildPreviewTile(
5326
+ rid,
5327
+ state,
5328
+ !currentlyReadonly,
5329
+ currentlyReadonly ? null : () => {
5330
+ releaseLocalFileUrl(state.resourceIndex.get(rid)?.file);
5331
+ const idx = initialFiles.indexOf(rid);
5332
+ if (idx > -1) {
5333
+ initialFiles.splice(idx, 1);
5334
+ if (coordinator.hasInFlightBatches()) {
5335
+ pendingRemovals.add(rid);
5336
+ list.querySelector(`[data-resource-id="${rid}"]`)?.remove();
5337
+ filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
5338
+ } else {
5339
+ updateFilesDisplay();
5340
+ }
5341
+ if (ctx.instance && pathKey && !state.config.readonly) {
5342
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5343
+ }
5344
+ return;
5345
+ }
5346
+ pendingRemovals.add(rid);
5347
+ list.querySelector(`[data-resource-id="${rid}"]`)?.remove();
5348
+ if (ctx.instance && pathKey && !state.config.readonly) {
5349
+ ctx.instance.triggerOnChange(pathKey, initialFiles);
5350
+ }
5351
+ }
5352
+ );
5353
+ };
5354
+ const prepareForUpload = () => disposePlaceholdersForUpload(list);
5355
+ const sharedHandlerOpts = {
5356
+ resourceIds: initialFiles,
5165
5357
  state,
5166
- updateFilesDisplay,
5358
+ updateCallback: updateFilesDisplay,
5167
5359
  constraints,
5168
5360
  pathKey,
5169
- ctx.instance
5170
- );
5361
+ instance: ctx.instance,
5362
+ buildSuccessTile,
5363
+ prepareForUpload,
5364
+ coordinator
5365
+ };
5366
+ setupFilesDropHandler({ ...sharedHandlerOpts, filesContainer });
5367
+ setupFilesPickerHandler({ ...sharedHandlerOpts, filesPicker });
5171
5368
  updateFilesDisplay();
5172
5369
  wrapper.appendChild(filesWrapper);
5173
5370
  }
@@ -10850,6 +11047,7 @@ var exampleThemes = {
10850
11047
  },
10851
11048
  // Picaz wizard design tokens — derived from the Picaz Wizard mockups.
10852
11049
  // Pairs with the host-side .card / .section-num / .lede chrome that wraps the form.
11050
+ // Assumes Inter is loaded by the host (e.g. via Google Fonts in index.html).
10853
11051
  picaz: {
10854
11052
  ...defaultTheme,
10855
11053
  primaryColor: "#2f5bea",
@@ -10881,6 +11079,9 @@ var exampleThemes = {
10881
11079
  fileUploadBgColor: "#fafcff",
10882
11080
  fileUploadBorderColor: "#cdd6e3",
10883
11081
  fileUploadHoverBorderColor: "#2f5bea",
11082
+ // Picaz uses roomier inputs (11/14px) than the defaultTheme compact density.
11083
+ inputPaddingX: "14px",
11084
+ inputPaddingY: "11px",
10884
11085
  borderRadius: "12px",
10885
11086
  borderRadiusSmall: "8px",
10886
11087
  borderRadiusLarge: "16px",
@@ -10888,7 +11089,17 @@ var exampleThemes = {
10888
11089
  fontFamily: '"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
10889
11090
  shadowCard: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
10890
11091
  shadowPopover: "0 12px 32px -12px rgba(15,23,42,.18), 0 4px 12px -6px rgba(15,23,42,.08)",
10891
- focusRingColor: "#2f5bea"
11092
+ focusRingColor: "#2f5bea",
11093
+ // Slide cards: subtle gradient lift, no rest-state shadow (mockup adds it
11094
+ // only on hover, which form-builder doesn't yet differentiate).
11095
+ slideCardBg: "linear-gradient(180deg, #f7f9fc 0%, #dde3ee 100%)",
11096
+ slideCardShadow: "none",
11097
+ slideCardRadius: "16px",
11098
+ // Tiny uppercase captions above grouped lists ("ПРЕИМУЩЕСТВА" in the mockup).
11099
+ labelSectionFontSize: "0.625rem",
11100
+ // 10px
11101
+ labelSectionLetterSpacing: "0.07em",
11102
+ labelSectionTextTransform: "uppercase"
10892
11103
  }
10893
11104
  };
10894
11105
 
@@ -11254,6 +11465,13 @@ var FormBuilderInstance = class {
11254
11465
  const value = hintValues[fieldKey];
11255
11466
  this.updateField(fullPath, value);
11256
11467
  }
11468
+ const group = target.closest(".fb-prefill-hints");
11469
+ if (group) {
11470
+ group.querySelectorAll(
11471
+ '.fb-prefill-hint[aria-pressed="true"]'
11472
+ ).forEach((el) => el.removeAttribute("aria-pressed"));
11473
+ }
11474
+ target.setAttribute("aria-pressed", "true");
11257
11475
  } catch (error) {
11258
11476
  console.error("Error parsing prefill hint values:", error);
11259
11477
  }