@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.
- package/README.md +32 -6
- package/dist/browser/formbuilder.min.js +146 -116
- package/dist/browser/formbuilder.v0.3.2.min.js +1441 -0
- package/dist/cjs/index.cjs +398 -156
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +390 -154
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +146 -116
- package/dist/types/components/file/library.d.ts +40 -16
- package/dist/types/components/file/upload.d.ts +111 -2
- package/dist/types/utils/styles.d.ts +24 -1
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.3.0.min.js +0 -1411
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,12 +476,28 @@ function applyAutoExpand(textarea) {
|
|
|
446
476
|
const resize = () => {
|
|
447
477
|
if (!textarea.isConnected) return;
|
|
448
478
|
textarea.style.height = "0";
|
|
449
|
-
|
|
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(() => {
|
|
453
485
|
if (textarea.isConnected) resize();
|
|
454
486
|
}, 0);
|
|
487
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
488
|
+
let lastWidth = -1;
|
|
489
|
+
const ro = new ResizeObserver((entries) => {
|
|
490
|
+
if (!textarea.isConnected) {
|
|
491
|
+
ro.disconnect();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const entry = entries[0];
|
|
495
|
+
const w = entry?.contentBoxSize?.[0]?.inlineSize ?? entry?.contentRect?.width ?? 0;
|
|
496
|
+
if (w === lastWidth) return;
|
|
497
|
+
lastWidth = w;
|
|
498
|
+
resize();
|
|
499
|
+
});
|
|
500
|
+
ro.observe(textarea);
|
|
455
501
|
}
|
|
456
502
|
function applySingleLineMode(textarea) {
|
|
457
503
|
textarea.addEventListener("keydown", (e) => {
|
|
@@ -4215,15 +4261,25 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
4215
4261
|
}
|
|
4216
4262
|
return { accepted, errorMessage: errorParts.join(" \u2022 ") };
|
|
4217
4263
|
}
|
|
4218
|
-
async function uploadBatch(
|
|
4219
|
-
|
|
4264
|
+
async function uploadBatch(opts) {
|
|
4265
|
+
const {
|
|
4266
|
+
accepted,
|
|
4267
|
+
listEl,
|
|
4268
|
+
state,
|
|
4269
|
+
shouldHideAddTile,
|
|
4270
|
+
buildSuccessTile,
|
|
4271
|
+
prepareForUpload
|
|
4272
|
+
} = opts;
|
|
4273
|
+
if (listEl && shouldHideAddTile) {
|
|
4220
4274
|
const tilesWrap = ensureTilesWrap(listEl);
|
|
4221
4275
|
const addTile = tilesWrap.querySelector(".fb-multi-add-tile-js") ?? tilesWrap.querySelector(".fb-tile-add");
|
|
4222
4276
|
if (addTile) addTile.style.display = "none";
|
|
4223
4277
|
}
|
|
4278
|
+
prepareForUpload?.();
|
|
4279
|
+
const orderedIds = new Array(accepted.length).fill(null);
|
|
4224
4280
|
const failures = [];
|
|
4225
4281
|
await Promise.allSettled(
|
|
4226
|
-
accepted.map(async (file) => {
|
|
4282
|
+
accepted.map(async (file, index) => {
|
|
4227
4283
|
const placeholder = createUploadingTile(file.name, state);
|
|
4228
4284
|
if (listEl) {
|
|
4229
4285
|
const tilesWrap = ensureTilesWrap(listEl);
|
|
@@ -4236,20 +4292,24 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
|
4236
4292
|
type: file.type,
|
|
4237
4293
|
size: file.size,
|
|
4238
4294
|
uploadedAt: /* @__PURE__ */ new Date(),
|
|
4239
|
-
file
|
|
4295
|
+
file
|
|
4240
4296
|
});
|
|
4241
|
-
|
|
4297
|
+
orderedIds[index] = rid;
|
|
4298
|
+
if (buildSuccessTile && placeholder.parentNode) {
|
|
4299
|
+
placeholder.replaceWith(buildSuccessTile(rid));
|
|
4300
|
+
} else {
|
|
4301
|
+
placeholder.remove();
|
|
4302
|
+
}
|
|
4242
4303
|
} catch (err) {
|
|
4243
4304
|
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
4244
4305
|
const cause = wrapped.cause;
|
|
4245
4306
|
const root = cause instanceof Error ? cause : cause !== void 0 ? new Error(String(cause)) : wrapped;
|
|
4246
4307
|
failures.push({ file, error: root });
|
|
4247
|
-
} finally {
|
|
4248
4308
|
placeholder.remove();
|
|
4249
4309
|
}
|
|
4250
4310
|
})
|
|
4251
4311
|
);
|
|
4252
|
-
return { failures };
|
|
4312
|
+
return { failures, orderedIds };
|
|
4253
4313
|
}
|
|
4254
4314
|
function buildBatchErrorMessage(filterError, failures, state) {
|
|
4255
4315
|
if (failures.length === 0) return filterError;
|
|
@@ -4261,68 +4321,70 @@ function buildBatchErrorMessage(filterError, failures, state) {
|
|
|
4261
4321
|
).join(" \u2022 ");
|
|
4262
4322
|
return filterError ? `${filterError} \u2022 ${uploadMsg}` : uploadMsg;
|
|
4263
4323
|
}
|
|
4264
|
-
function
|
|
4324
|
+
async function runMultiFileBatch(opts, files, listEl, errorTarget) {
|
|
4325
|
+
const {
|
|
4326
|
+
resourceIds,
|
|
4327
|
+
state,
|
|
4328
|
+
updateCallback,
|
|
4329
|
+
constraints,
|
|
4330
|
+
pathKey,
|
|
4331
|
+
instance,
|
|
4332
|
+
buildSuccessTile,
|
|
4333
|
+
prepareForUpload,
|
|
4334
|
+
coordinator
|
|
4335
|
+
} = opts;
|
|
4336
|
+
const { accepted, errorMessage } = filterAndSlice(
|
|
4337
|
+
files,
|
|
4338
|
+
coordinator.getOccupiedCount(),
|
|
4339
|
+
constraints,
|
|
4340
|
+
state
|
|
4341
|
+
);
|
|
4342
|
+
if (errorTarget) {
|
|
4343
|
+
if (errorMessage) showFileError(errorTarget, errorMessage);
|
|
4344
|
+
else clearFileError(errorTarget);
|
|
4345
|
+
}
|
|
4346
|
+
const handle = coordinator.beginBatch(accepted.length);
|
|
4347
|
+
const shouldHideAddTile = coordinator.getOccupiedCount() >= constraints.maxCount;
|
|
4348
|
+
const { failures, orderedIds } = await uploadBatch({
|
|
4349
|
+
accepted,
|
|
4350
|
+
listEl,
|
|
4351
|
+
state,
|
|
4352
|
+
shouldHideAddTile,
|
|
4353
|
+
buildSuccessTile,
|
|
4354
|
+
prepareForUpload
|
|
4355
|
+
});
|
|
4356
|
+
handle.setResults(orderedIds);
|
|
4357
|
+
if (instance && pathKey && !state.config.readonly) {
|
|
4358
|
+
instance.triggerOnChange(pathKey, resourceIds);
|
|
4359
|
+
}
|
|
4360
|
+
const { wasLast } = handle.end();
|
|
4361
|
+
if (wasLast) updateCallback();
|
|
4362
|
+
if (errorTarget) {
|
|
4363
|
+
const combined = buildBatchErrorMessage(errorMessage, failures, state);
|
|
4364
|
+
if (combined) showFileError(errorTarget, combined);
|
|
4365
|
+
else clearFileError(errorTarget);
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
function setupFilesDropHandler(opts) {
|
|
4369
|
+
const { filesContainer } = opts;
|
|
4265
4370
|
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
4371
|
const list = filesContainer.querySelector(".files-list") ?? filesContainer;
|
|
4278
|
-
|
|
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
|
-
}
|
|
4372
|
+
await runMultiFileBatch(opts, Array.from(files), list, filesContainer);
|
|
4289
4373
|
});
|
|
4290
4374
|
}
|
|
4291
|
-
function setupFilesPickerHandler(
|
|
4375
|
+
function setupFilesPickerHandler(opts) {
|
|
4376
|
+
const { filesPicker } = opts;
|
|
4292
4377
|
filesPicker.onchange = async () => {
|
|
4293
4378
|
if (!filesPicker.files) return;
|
|
4294
|
-
const wrapperEl = filesPicker.closest("[data-files-wrapper]")
|
|
4295
|
-
const
|
|
4379
|
+
const wrapperEl = filesPicker.closest("[data-files-wrapper]") ?? filesPicker.parentElement;
|
|
4380
|
+
const listEl = wrapperEl?.querySelector(".files-list") ?? null;
|
|
4381
|
+
await runMultiFileBatch(
|
|
4382
|
+
opts,
|
|
4296
4383
|
Array.from(filesPicker.files),
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
state
|
|
4384
|
+
listEl,
|
|
4385
|
+
wrapperEl
|
|
4300
4386
|
);
|
|
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
|
|
4312
|
-
);
|
|
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
4387
|
filesPicker.value = "";
|
|
4323
|
-
if (instance && pathKey && !state.config.readonly) {
|
|
4324
|
-
instance.triggerOnChange(pathKey, resourceIds);
|
|
4325
|
-
}
|
|
4326
4388
|
};
|
|
4327
4389
|
}
|
|
4328
4390
|
|
|
@@ -4363,16 +4425,6 @@ function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSi
|
|
|
4363
4425
|
}
|
|
4364
4426
|
return null;
|
|
4365
4427
|
}
|
|
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
4428
|
function registerPickedResource(resource, state) {
|
|
4377
4429
|
const existing = state.resourceIndex.get(resource.resourceId);
|
|
4378
4430
|
state.resourceIndex.set(resource.resourceId, {
|
|
@@ -4387,13 +4439,27 @@ function extractPickerError(error, state) {
|
|
|
4387
4439
|
if (error instanceof Error && error.message) return error.message;
|
|
4388
4440
|
return t("pickerError", state);
|
|
4389
4441
|
}
|
|
4390
|
-
async function handleLibraryPickMulti(
|
|
4442
|
+
async function handleLibraryPickMulti(opts) {
|
|
4443
|
+
const {
|
|
4444
|
+
state,
|
|
4445
|
+
element,
|
|
4446
|
+
wrapper,
|
|
4447
|
+
fieldPath,
|
|
4448
|
+
resourceIds,
|
|
4449
|
+
maxCount,
|
|
4450
|
+
updateCallback,
|
|
4451
|
+
instance,
|
|
4452
|
+
coordinator,
|
|
4453
|
+
list,
|
|
4454
|
+
buildSuccessTile
|
|
4455
|
+
} = opts;
|
|
4391
4456
|
if (!state.config.pickExistingFiles) return;
|
|
4392
4457
|
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
4393
4458
|
const allowedMimes = getAllowedMimes(element.accept);
|
|
4394
4459
|
const maxSizeMB = element.maxSize ?? Infinity;
|
|
4395
|
-
const
|
|
4396
|
-
const
|
|
4460
|
+
const knownRids = coordinator.getAllKnownRids();
|
|
4461
|
+
const existingSet = new Set(knownRids);
|
|
4462
|
+
const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - coordinator.getOccupiedCount());
|
|
4397
4463
|
let picked;
|
|
4398
4464
|
try {
|
|
4399
4465
|
picked = await state.config.pickExistingFiles({
|
|
@@ -4402,14 +4468,15 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
4402
4468
|
accept: buildAcceptContext(element),
|
|
4403
4469
|
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
4404
4470
|
remainingSlots: remaining === Infinity ? void 0 : remaining,
|
|
4405
|
-
|
|
4471
|
+
// Hand the host every rid that's already selected (committed or
|
|
4472
|
+
// staged) so it can grey them out or filter them.
|
|
4473
|
+
selectedResourceIds: knownRids
|
|
4406
4474
|
});
|
|
4407
4475
|
} catch (error) {
|
|
4408
4476
|
showFileError(wrapper, extractPickerError(error, state));
|
|
4409
4477
|
return;
|
|
4410
4478
|
}
|
|
4411
4479
|
if (picked.length === 0) return;
|
|
4412
|
-
const existingSet = new Set(currentIds);
|
|
4413
4480
|
const seen = /* @__PURE__ */ new Set();
|
|
4414
4481
|
const deduped = picked.filter((r) => {
|
|
4415
4482
|
if (existingSet.has(r.resourceId)) return false;
|
|
@@ -4427,10 +4494,18 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
4427
4494
|
);
|
|
4428
4495
|
return err === null;
|
|
4429
4496
|
});
|
|
4430
|
-
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount -
|
|
4497
|
+
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - coordinator.getOccupiedCount());
|
|
4431
4498
|
const accepted = validItems.slice(0, freshRemaining);
|
|
4432
4499
|
const skipped = validItems.length - accepted.length;
|
|
4433
|
-
if (accepted.length === 0)
|
|
4500
|
+
if (accepted.length === 0) {
|
|
4501
|
+
if (skipped > 0) {
|
|
4502
|
+
showFileError(
|
|
4503
|
+
wrapper,
|
|
4504
|
+
t("filesLimitExceeded", state, { skipped, max: maxCount })
|
|
4505
|
+
);
|
|
4506
|
+
}
|
|
4507
|
+
return;
|
|
4508
|
+
}
|
|
4434
4509
|
clearFileError(wrapper);
|
|
4435
4510
|
if (skipped > 0) {
|
|
4436
4511
|
showFileError(
|
|
@@ -4440,13 +4515,22 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
4440
4515
|
}
|
|
4441
4516
|
for (const resource of accepted) {
|
|
4442
4517
|
registerPickedResource(resource, state);
|
|
4443
|
-
resourceIds.push(resource.resourceId);
|
|
4444
4518
|
}
|
|
4445
|
-
|
|
4446
|
-
|
|
4519
|
+
const acceptedIds = accepted.map((r) => r.resourceId);
|
|
4520
|
+
const handle = coordinator.beginBatch(accepted.length);
|
|
4521
|
+
handle.setResults(acceptedIds);
|
|
4447
4522
|
if (!state.config.readonly) {
|
|
4448
4523
|
instance.triggerOnChange(fieldPath, resourceIds);
|
|
4449
4524
|
}
|
|
4525
|
+
const { wasLast } = handle.end();
|
|
4526
|
+
if (wasLast) {
|
|
4527
|
+
updateCallback();
|
|
4528
|
+
} else {
|
|
4529
|
+
const tilesWrap = ensureTilesWrap(list);
|
|
4530
|
+
for (const rid of acceptedIds) {
|
|
4531
|
+
tilesWrap.appendChild(buildSuccessTile(rid));
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4450
4534
|
}
|
|
4451
4535
|
async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
|
|
4452
4536
|
if (!state.config.pickExistingFiles) return;
|
|
@@ -4767,6 +4851,14 @@ function buildMetaDot() {
|
|
|
4767
4851
|
return dot;
|
|
4768
4852
|
}
|
|
4769
4853
|
var gridResizeObservers = /* @__PURE__ */ new WeakMap();
|
|
4854
|
+
function disposePlaceholdersForUpload(container) {
|
|
4855
|
+
const observer = gridResizeObservers.get(container);
|
|
4856
|
+
if (observer) {
|
|
4857
|
+
observer.disconnect();
|
|
4858
|
+
gridResizeObservers.delete(container);
|
|
4859
|
+
}
|
|
4860
|
+
container.querySelectorAll(".fb-multi-placeholder").forEach((p) => p.remove());
|
|
4861
|
+
}
|
|
4770
4862
|
function renderResourcePills(opts) {
|
|
4771
4863
|
const {
|
|
4772
4864
|
container,
|
|
@@ -5114,16 +5206,19 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
5114
5206
|
filesPicker.click();
|
|
5115
5207
|
};
|
|
5116
5208
|
const onLibraryPick = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
5117
|
-
handleLibraryPickMulti(
|
|
5209
|
+
handleLibraryPickMulti({
|
|
5118
5210
|
state,
|
|
5119
5211
|
element,
|
|
5120
|
-
filesWrapper,
|
|
5121
|
-
pathKey,
|
|
5122
|
-
initialFiles,
|
|
5123
|
-
maxFiles,
|
|
5124
|
-
updateFilesDisplay,
|
|
5125
|
-
ctx.instance
|
|
5126
|
-
|
|
5212
|
+
wrapper: filesWrapper,
|
|
5213
|
+
fieldPath: pathKey,
|
|
5214
|
+
resourceIds: initialFiles,
|
|
5215
|
+
maxCount: maxFiles,
|
|
5216
|
+
updateCallback: updateFilesDisplay,
|
|
5217
|
+
instance: ctx.instance,
|
|
5218
|
+
coordinator,
|
|
5219
|
+
list,
|
|
5220
|
+
buildSuccessTile
|
|
5221
|
+
}).catch((err) => {
|
|
5127
5222
|
console.error("Library pick failed:", err);
|
|
5128
5223
|
});
|
|
5129
5224
|
} : null;
|
|
@@ -5137,37 +5232,153 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
5137
5232
|
releaseLocalFileUrl(state.resourceIndex.get(ridToRemove)?.file);
|
|
5138
5233
|
const index = initialFiles.indexOf(ridToRemove);
|
|
5139
5234
|
if (index > -1) initialFiles.splice(index, 1);
|
|
5140
|
-
|
|
5235
|
+
if (coordinator.hasInFlightBatches()) {
|
|
5236
|
+
pendingRemovals.add(ridToRemove);
|
|
5237
|
+
list.querySelector(`[data-resource-id="${ridToRemove}"]`)?.remove();
|
|
5238
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5239
|
+
} else {
|
|
5240
|
+
updateFilesDisplay();
|
|
5241
|
+
}
|
|
5242
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5243
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5244
|
+
}
|
|
5141
5245
|
},
|
|
5142
5246
|
maxCount: maxFiles < Infinity ? maxFiles : void 0,
|
|
5143
5247
|
isReadonly: currentlyReadonly,
|
|
5144
5248
|
onLibraryPick: currentlyReadonly ? null : onLibraryPick,
|
|
5145
5249
|
element,
|
|
5146
5250
|
onClearAll: currentlyReadonly ? void 0 : () => {
|
|
5251
|
+
for (const rid of initialFiles) {
|
|
5252
|
+
releaseLocalFileUrl(state.resourceIndex.get(rid)?.file);
|
|
5253
|
+
}
|
|
5147
5254
|
initialFiles.splice(0);
|
|
5148
|
-
|
|
5255
|
+
if (coordinator.hasInFlightBatches()) {
|
|
5256
|
+
const visibleTiles = list.querySelectorAll("[data-resource-id]");
|
|
5257
|
+
for (const tile of visibleTiles) {
|
|
5258
|
+
const rid = tile.dataset.resourceId;
|
|
5259
|
+
if (rid) {
|
|
5260
|
+
releaseLocalFileUrl(state.resourceIndex.get(rid)?.file);
|
|
5261
|
+
pendingRemovals.add(rid);
|
|
5262
|
+
}
|
|
5263
|
+
tile.remove();
|
|
5264
|
+
}
|
|
5265
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5266
|
+
} else {
|
|
5267
|
+
updateFilesDisplay();
|
|
5268
|
+
}
|
|
5269
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5270
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5271
|
+
}
|
|
5149
5272
|
},
|
|
5150
5273
|
openPicker
|
|
5151
5274
|
});
|
|
5152
5275
|
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5276
|
+
let inFlightFiles = 0;
|
|
5277
|
+
let activeBatches = 0;
|
|
5278
|
+
let nextBatchOrdinal = 0;
|
|
5279
|
+
let nextCommitOrdinal = 0;
|
|
5280
|
+
const stagedResults = /* @__PURE__ */ new Map();
|
|
5281
|
+
const batchReservations = /* @__PURE__ */ new Map();
|
|
5282
|
+
const pendingRemovals = /* @__PURE__ */ new Set();
|
|
5283
|
+
const drainContiguousStagedResults = () => {
|
|
5284
|
+
while (stagedResults.has(nextCommitOrdinal)) {
|
|
5285
|
+
const ordinal = nextCommitOrdinal;
|
|
5286
|
+
const ids = stagedResults.get(ordinal);
|
|
5287
|
+
stagedResults.delete(ordinal);
|
|
5288
|
+
nextCommitOrdinal += 1;
|
|
5289
|
+
const reservation = batchReservations.get(ordinal) ?? 0;
|
|
5290
|
+
batchReservations.delete(ordinal);
|
|
5291
|
+
inFlightFiles -= reservation;
|
|
5292
|
+
for (const rid of ids) {
|
|
5293
|
+
if (rid === null) continue;
|
|
5294
|
+
if (pendingRemovals.has(rid)) {
|
|
5295
|
+
pendingRemovals.delete(rid);
|
|
5296
|
+
continue;
|
|
5297
|
+
}
|
|
5298
|
+
initialFiles.push(rid);
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5302
|
+
};
|
|
5303
|
+
const coordinator = {
|
|
5304
|
+
getOccupiedCount: () => initialFiles.length + inFlightFiles,
|
|
5305
|
+
getAllKnownRids: () => {
|
|
5306
|
+
const out = [...initialFiles];
|
|
5307
|
+
for (const ids of stagedResults.values()) {
|
|
5308
|
+
for (const rid of ids) {
|
|
5309
|
+
if (rid !== null) out.push(rid);
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
return out;
|
|
5313
|
+
},
|
|
5314
|
+
hasInFlightBatches: () => activeBatches > 0 || batchReservations.size > 0,
|
|
5315
|
+
wasRemovedDuringBatch: (rid) => pendingRemovals.has(rid),
|
|
5316
|
+
beginBatch: (count) => {
|
|
5317
|
+
inFlightFiles += count;
|
|
5318
|
+
activeBatches += 1;
|
|
5319
|
+
const ordinal = nextBatchOrdinal++;
|
|
5320
|
+
batchReservations.set(ordinal, count);
|
|
5321
|
+
return {
|
|
5322
|
+
setResults: (orderedIds) => {
|
|
5323
|
+
stagedResults.set(ordinal, orderedIds);
|
|
5324
|
+
drainContiguousStagedResults();
|
|
5325
|
+
},
|
|
5326
|
+
end: () => {
|
|
5327
|
+
activeBatches -= 1;
|
|
5328
|
+
const wasLast = activeBatches === 0 && batchReservations.size === 0;
|
|
5329
|
+
if (wasLast) {
|
|
5330
|
+
pendingRemovals.clear();
|
|
5331
|
+
}
|
|
5332
|
+
return { wasLast };
|
|
5333
|
+
}
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
5336
|
+
};
|
|
5337
|
+
const buildSuccessTile = (rid) => {
|
|
5338
|
+
const currentlyReadonly = isElementReadonly(element, state);
|
|
5339
|
+
return buildPreviewTile(
|
|
5340
|
+
rid,
|
|
5341
|
+
state,
|
|
5342
|
+
!currentlyReadonly,
|
|
5343
|
+
currentlyReadonly ? null : () => {
|
|
5344
|
+
releaseLocalFileUrl(state.resourceIndex.get(rid)?.file);
|
|
5345
|
+
const idx = initialFiles.indexOf(rid);
|
|
5346
|
+
if (idx > -1) {
|
|
5347
|
+
initialFiles.splice(idx, 1);
|
|
5348
|
+
if (coordinator.hasInFlightBatches()) {
|
|
5349
|
+
pendingRemovals.add(rid);
|
|
5350
|
+
list.querySelector(`[data-resource-id="${rid}"]`)?.remove();
|
|
5351
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5352
|
+
} else {
|
|
5353
|
+
updateFilesDisplay();
|
|
5354
|
+
}
|
|
5355
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5356
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5357
|
+
}
|
|
5358
|
+
return;
|
|
5359
|
+
}
|
|
5360
|
+
pendingRemovals.add(rid);
|
|
5361
|
+
list.querySelector(`[data-resource-id="${rid}"]`)?.remove();
|
|
5362
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5363
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5366
|
+
);
|
|
5367
|
+
};
|
|
5368
|
+
const prepareForUpload = () => disposePlaceholdersForUpload(list);
|
|
5369
|
+
const sharedHandlerOpts = {
|
|
5370
|
+
resourceIds: initialFiles,
|
|
5165
5371
|
state,
|
|
5166
|
-
updateFilesDisplay,
|
|
5372
|
+
updateCallback: updateFilesDisplay,
|
|
5167
5373
|
constraints,
|
|
5168
5374
|
pathKey,
|
|
5169
|
-
ctx.instance
|
|
5170
|
-
|
|
5375
|
+
instance: ctx.instance,
|
|
5376
|
+
buildSuccessTile,
|
|
5377
|
+
prepareForUpload,
|
|
5378
|
+
coordinator
|
|
5379
|
+
};
|
|
5380
|
+
setupFilesDropHandler({ ...sharedHandlerOpts, filesContainer });
|
|
5381
|
+
setupFilesPickerHandler({ ...sharedHandlerOpts, filesPicker });
|
|
5171
5382
|
updateFilesDisplay();
|
|
5172
5383
|
wrapper.appendChild(filesWrapper);
|
|
5173
5384
|
}
|
|
@@ -6514,6 +6725,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
6514
6725
|
formData: ctx.formData ?? ctx.prefill,
|
|
6515
6726
|
// Complete root data for enableIf evaluation
|
|
6516
6727
|
state: ctx.state,
|
|
6728
|
+
instance: ctx.instance,
|
|
6517
6729
|
inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
|
|
6518
6730
|
};
|
|
6519
6731
|
element.elements.forEach((child) => {
|
|
@@ -6600,6 +6812,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6600
6812
|
const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
|
|
6601
6813
|
const subCtx = {
|
|
6602
6814
|
state: ctx.state,
|
|
6815
|
+
instance: ctx.instance,
|
|
6603
6816
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6604
6817
|
prefill: childDefaults,
|
|
6605
6818
|
// Defaults for enableIf evaluation
|
|
@@ -6665,6 +6878,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6665
6878
|
const mergedPrefill = mergeWithDefaults(prefillObj || {}, childDefaults);
|
|
6666
6879
|
const subCtx = {
|
|
6667
6880
|
state: ctx.state,
|
|
6881
|
+
instance: ctx.instance,
|
|
6668
6882
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6669
6883
|
prefill: mergedPrefill,
|
|
6670
6884
|
// Merged prefill with defaults for enableIf
|
|
@@ -6711,6 +6925,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6711
6925
|
const idx = countItems();
|
|
6712
6926
|
const subCtx = {
|
|
6713
6927
|
state: ctx.state,
|
|
6928
|
+
instance: ctx.instance,
|
|
6714
6929
|
path: pathJoin(ctx.path, `${element.key}[${idx}]`),
|
|
6715
6930
|
prefill: childDefaults,
|
|
6716
6931
|
// Defaults for enableIf evaluation
|
|
@@ -8353,6 +8568,20 @@ function applyAutoExpand2(textarea, backdrop) {
|
|
|
8353
8568
|
setTimeout(() => {
|
|
8354
8569
|
if (textarea.isConnected) resize();
|
|
8355
8570
|
}, 0);
|
|
8571
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
8572
|
+
let lastWidth = -1;
|
|
8573
|
+
const ro = new ResizeObserver((entries) => {
|
|
8574
|
+
if (!textarea.isConnected) {
|
|
8575
|
+
ro.disconnect();
|
|
8576
|
+
return;
|
|
8577
|
+
}
|
|
8578
|
+
const entry = entries[0];
|
|
8579
|
+
const w = entry?.contentBoxSize?.[0]?.inlineSize ?? entry?.contentRect?.width ?? 0;
|
|
8580
|
+
if (w === lastWidth) return;
|
|
8581
|
+
lastWidth = w;
|
|
8582
|
+
resize();
|
|
8583
|
+
});
|
|
8584
|
+
ro.observe(textarea);
|
|
8356
8585
|
}
|
|
8357
8586
|
function buildFileLabels(files, state) {
|
|
8358
8587
|
const labels = /* @__PURE__ */ new Map();
|
|
@@ -10118,6 +10347,33 @@ function extractDOMValue(fieldPath, formRoot) {
|
|
|
10118
10347
|
}
|
|
10119
10348
|
return void 0;
|
|
10120
10349
|
}
|
|
10350
|
+
function buildScopedDataAtPath(path, value) {
|
|
10351
|
+
const segments = path.match(/[^.[\]]+|\[\d+\]/g);
|
|
10352
|
+
if (!segments || segments.length === 0) {
|
|
10353
|
+
return {};
|
|
10354
|
+
}
|
|
10355
|
+
const root = {};
|
|
10356
|
+
let current = root;
|
|
10357
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
10358
|
+
const seg = segments[i];
|
|
10359
|
+
const next = segments[i + 1];
|
|
10360
|
+
const placeholder = next.startsWith("[") && next.endsWith("]") ? [] : {};
|
|
10361
|
+
if (seg.startsWith("[") && seg.endsWith("]")) {
|
|
10362
|
+
const idx = parseInt(seg.slice(1, -1), 10);
|
|
10363
|
+
current[idx] = placeholder;
|
|
10364
|
+
} else {
|
|
10365
|
+
current[seg] = placeholder;
|
|
10366
|
+
}
|
|
10367
|
+
current = placeholder;
|
|
10368
|
+
}
|
|
10369
|
+
const last = segments[segments.length - 1];
|
|
10370
|
+
if (last.startsWith("[") && last.endsWith("]")) {
|
|
10371
|
+
current[parseInt(last.slice(1, -1), 10)] = value;
|
|
10372
|
+
} else {
|
|
10373
|
+
current[last] = value;
|
|
10374
|
+
}
|
|
10375
|
+
return root;
|
|
10376
|
+
}
|
|
10121
10377
|
function reevaluateEnableIf(wrapper, element, ctx) {
|
|
10122
10378
|
if (!element.enableIf) {
|
|
10123
10379
|
return;
|
|
@@ -10129,54 +10385,13 @@ function reevaluateEnableIf(wrapper, element, ctx) {
|
|
|
10129
10385
|
}
|
|
10130
10386
|
const condition = element.enableIf;
|
|
10131
10387
|
const scope = condition.scope ?? "relative";
|
|
10132
|
-
let rootFormData = {};
|
|
10133
|
-
const containerData = {};
|
|
10134
10388
|
const effectiveScope = !ctx.path || ctx.path === "" ? "absolute" : scope;
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
`[data-container-item="${containerKey}[${containerIndex}]"]`
|
|
10142
|
-
);
|
|
10143
|
-
if (containerItemElement) {
|
|
10144
|
-
const inputs = containerItemElement.querySelectorAll("input, select, textarea");
|
|
10145
|
-
inputs.forEach((input) => {
|
|
10146
|
-
const fieldName = input.getAttribute("name");
|
|
10147
|
-
if (fieldName) {
|
|
10148
|
-
const fieldKeyMatch = fieldName.match(/\.([^.[\]]+)$/);
|
|
10149
|
-
if (fieldKeyMatch) {
|
|
10150
|
-
const fieldKey = fieldKeyMatch[1];
|
|
10151
|
-
if (input instanceof HTMLSelectElement) {
|
|
10152
|
-
containerData[fieldKey] = input.value;
|
|
10153
|
-
} else if (input instanceof HTMLInputElement) {
|
|
10154
|
-
if (input.type === "checkbox") {
|
|
10155
|
-
containerData[fieldKey] = input.checked;
|
|
10156
|
-
} else if (input.type === "radio") {
|
|
10157
|
-
if (input.checked) {
|
|
10158
|
-
containerData[fieldKey] = input.value;
|
|
10159
|
-
}
|
|
10160
|
-
} else {
|
|
10161
|
-
containerData[fieldKey] = input.value;
|
|
10162
|
-
}
|
|
10163
|
-
} else if (input instanceof HTMLTextAreaElement) {
|
|
10164
|
-
containerData[fieldKey] = input.value;
|
|
10165
|
-
}
|
|
10166
|
-
}
|
|
10167
|
-
}
|
|
10168
|
-
});
|
|
10169
|
-
}
|
|
10170
|
-
}
|
|
10171
|
-
} else {
|
|
10172
|
-
const dependencyKey = condition.key;
|
|
10173
|
-
const dependencyValue = extractDOMValue(dependencyKey, formRoot);
|
|
10174
|
-
if (dependencyValue !== void 0) {
|
|
10175
|
-
rootFormData[dependencyKey] = dependencyValue;
|
|
10176
|
-
} else {
|
|
10177
|
-
rootFormData = ctx.formData ?? ctx.prefill;
|
|
10178
|
-
}
|
|
10179
|
-
}
|
|
10389
|
+
const dependencyKey = condition.key;
|
|
10390
|
+
const dependencyFieldPath = effectiveScope === "relative" && ctx.path ? `${ctx.path}.${dependencyKey}` : dependencyKey;
|
|
10391
|
+
const dependencyValue = extractDOMValue(dependencyFieldPath, formRoot);
|
|
10392
|
+
const scopedData = buildScopedDataAtPath(dependencyKey, dependencyValue);
|
|
10393
|
+
const rootFormData = effectiveScope === "relative" ? {} : scopedData;
|
|
10394
|
+
const containerData = effectiveScope === "relative" ? scopedData : void 0;
|
|
10180
10395
|
try {
|
|
10181
10396
|
const shouldEnable = evaluateEnableCondition(
|
|
10182
10397
|
condition,
|
|
@@ -10850,6 +11065,7 @@ var exampleThemes = {
|
|
|
10850
11065
|
},
|
|
10851
11066
|
// Picaz wizard design tokens — derived from the Picaz Wizard mockups.
|
|
10852
11067
|
// Pairs with the host-side .card / .section-num / .lede chrome that wraps the form.
|
|
11068
|
+
// Assumes Inter is loaded by the host (e.g. via Google Fonts in index.html).
|
|
10853
11069
|
picaz: {
|
|
10854
11070
|
...defaultTheme,
|
|
10855
11071
|
primaryColor: "#2f5bea",
|
|
@@ -10881,6 +11097,9 @@ var exampleThemes = {
|
|
|
10881
11097
|
fileUploadBgColor: "#fafcff",
|
|
10882
11098
|
fileUploadBorderColor: "#cdd6e3",
|
|
10883
11099
|
fileUploadHoverBorderColor: "#2f5bea",
|
|
11100
|
+
// Picaz uses roomier inputs (11/14px) than the defaultTheme compact density.
|
|
11101
|
+
inputPaddingX: "14px",
|
|
11102
|
+
inputPaddingY: "11px",
|
|
10884
11103
|
borderRadius: "12px",
|
|
10885
11104
|
borderRadiusSmall: "8px",
|
|
10886
11105
|
borderRadiusLarge: "16px",
|
|
@@ -10888,7 +11107,17 @@ var exampleThemes = {
|
|
|
10888
11107
|
fontFamily: '"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
10889
11108
|
shadowCard: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
|
|
10890
11109
|
shadowPopover: "0 12px 32px -12px rgba(15,23,42,.18), 0 4px 12px -6px rgba(15,23,42,.08)",
|
|
10891
|
-
focusRingColor: "#2f5bea"
|
|
11110
|
+
focusRingColor: "#2f5bea",
|
|
11111
|
+
// Slide cards: subtle gradient lift, no rest-state shadow (mockup adds it
|
|
11112
|
+
// only on hover, which form-builder doesn't yet differentiate).
|
|
11113
|
+
slideCardBg: "linear-gradient(180deg, #f7f9fc 0%, #dde3ee 100%)",
|
|
11114
|
+
slideCardShadow: "none",
|
|
11115
|
+
slideCardRadius: "16px",
|
|
11116
|
+
// Tiny uppercase captions above grouped lists ("ПРЕИМУЩЕСТВА" in the mockup).
|
|
11117
|
+
labelSectionFontSize: "0.625rem",
|
|
11118
|
+
// 10px
|
|
11119
|
+
labelSectionLetterSpacing: "0.07em",
|
|
11120
|
+
labelSectionTextTransform: "uppercase"
|
|
10892
11121
|
}
|
|
10893
11122
|
};
|
|
10894
11123
|
|
|
@@ -11254,6 +11483,13 @@ var FormBuilderInstance = class {
|
|
|
11254
11483
|
const value = hintValues[fieldKey];
|
|
11255
11484
|
this.updateField(fullPath, value);
|
|
11256
11485
|
}
|
|
11486
|
+
const group = target.closest(".fb-prefill-hints");
|
|
11487
|
+
if (group) {
|
|
11488
|
+
group.querySelectorAll(
|
|
11489
|
+
'.fb-prefill-hint[aria-pressed="true"]'
|
|
11490
|
+
).forEach((el) => el.removeAttribute("aria-pressed"));
|
|
11491
|
+
}
|
|
11492
|
+
target.setAttribute("aria-pressed", "true");
|
|
11257
11493
|
} catch (error) {
|
|
11258
11494
|
console.error("Error parsing prefill hint values:", error);
|
|
11259
11495
|
}
|