@dmitryvim/form-builder 0.2.34 → 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/browser/formbuilder.min.js +453 -174
- package/dist/browser/formbuilder.v0.3.1.min.js +1441 -0
- package/dist/cjs/index.cjs +1331 -505
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1317 -502
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +453 -174
- package/dist/types/components/boolean.d.ts +4 -0
- package/dist/types/components/file/library.d.ts +40 -16
- package/dist/types/components/file/upload.d.ts +111 -2
- package/dist/types/components/text.d.ts +12 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/instance/FormBuilderInstance.d.ts +14 -1
- package/dist/types/styles/theme.d.ts +47 -0
- package/dist/types/types/component-operations.d.ts +7 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +40 -1
- package/dist/types/utils/styles.d.ts +47 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.34.min.js +0 -1162
package/dist/cjs/index.cjs
CHANGED
|
@@ -407,6 +407,114 @@ function deepEqual(a, b) {
|
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
// src/utils/styles.ts
|
|
410
|
+
function clearFieldError(input) {
|
|
411
|
+
const name = input.getAttribute("name");
|
|
412
|
+
if (!name) return;
|
|
413
|
+
const doc = input.ownerDocument || document;
|
|
414
|
+
const errorNode = doc.getElementById(`error-${name}`);
|
|
415
|
+
if (errorNode) errorNode.remove();
|
|
416
|
+
}
|
|
417
|
+
var BIN_ICON_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
|
418
|
+
function ensureThemingHooks(doc) {
|
|
419
|
+
if (doc.head.querySelector("[data-fb-theming-hooks]")) return;
|
|
420
|
+
const style = doc.createElement("style");
|
|
421
|
+
style.setAttribute("data-fb-theming-hooks", "");
|
|
422
|
+
style.textContent = `
|
|
423
|
+
[data-fb-slide-card] {
|
|
424
|
+
background: var(--fb-slide-card-bg);
|
|
425
|
+
box-shadow: var(--fb-slide-card-shadow);
|
|
426
|
+
border-radius: var(--fb-slide-card-radius);
|
|
427
|
+
min-height: var(--fb-slide-card-min-height);
|
|
428
|
+
padding: var(--fb-slide-card-padding);
|
|
429
|
+
}
|
|
430
|
+
[data-fb-label-row] > label {
|
|
431
|
+
font-size: var(--fb-label-section-font-size);
|
|
432
|
+
letter-spacing: var(--fb-label-section-letter-spacing);
|
|
433
|
+
text-transform: var(--fb-label-section-text-transform);
|
|
434
|
+
}
|
|
435
|
+
/* Per-item remove (trash) button used by multi-container items. Shares the
|
|
436
|
+
same faint-on-rest, error-on-hover palette as .fb-chip-remove. */
|
|
437
|
+
.fb-item-remove {
|
|
438
|
+
color: var(--fb-text-faint-color, #94a3b8);
|
|
439
|
+
background-color: transparent;
|
|
440
|
+
transition: color var(--fb-transition-duration), background-color var(--fb-transition-duration);
|
|
441
|
+
}
|
|
442
|
+
.fb-item-remove:hover {
|
|
443
|
+
color: var(--fb-error-color);
|
|
444
|
+
background-color: var(--fb-background-hover-color);
|
|
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
|
+
}
|
|
476
|
+
`;
|
|
477
|
+
doc.head.appendChild(style);
|
|
478
|
+
}
|
|
479
|
+
function applyAutoExpand(textarea) {
|
|
480
|
+
textarea.style.overflow = "hidden";
|
|
481
|
+
textarea.style.resize = "none";
|
|
482
|
+
const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
|
|
483
|
+
textarea.rows = Math.max(1, lineCount);
|
|
484
|
+
const resize = () => {
|
|
485
|
+
if (!textarea.isConnected) return;
|
|
486
|
+
textarea.style.height = "0";
|
|
487
|
+
const cs = getComputedStyle(textarea);
|
|
488
|
+
const borderY = parseFloat(cs.borderTopWidth || "0") + parseFloat(cs.borderBottomWidth || "0");
|
|
489
|
+
textarea.style.height = `${textarea.scrollHeight + borderY}px`;
|
|
490
|
+
};
|
|
491
|
+
textarea.addEventListener("input", resize);
|
|
492
|
+
setTimeout(() => {
|
|
493
|
+
if (textarea.isConnected) resize();
|
|
494
|
+
}, 0);
|
|
495
|
+
}
|
|
496
|
+
function applySingleLineMode(textarea) {
|
|
497
|
+
textarea.addEventListener("keydown", (e) => {
|
|
498
|
+
if (e.key === "Enter") {
|
|
499
|
+
e.preventDefault();
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
textarea.addEventListener("paste", (e) => {
|
|
503
|
+
var _a, _b, _c, _d;
|
|
504
|
+
const pasted = (_b = (_a = e.clipboardData) == null ? void 0 : _a.getData("text")) != null ? _b : "";
|
|
505
|
+
if (!/[\r\n]/.test(pasted)) return;
|
|
506
|
+
e.preventDefault();
|
|
507
|
+
const cleaned = pasted.replace(/[\r\n]+/g, " ");
|
|
508
|
+
const start = (_c = textarea.selectionStart) != null ? _c : textarea.value.length;
|
|
509
|
+
const end = (_d = textarea.selectionEnd) != null ? _d : textarea.value.length;
|
|
510
|
+
const before = textarea.value.slice(0, start);
|
|
511
|
+
const after = textarea.value.slice(end);
|
|
512
|
+
textarea.value = before + cleaned + after;
|
|
513
|
+
const pos = start + cleaned.length;
|
|
514
|
+
textarea.setSelectionRange(pos, pos);
|
|
515
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
516
|
+
});
|
|
517
|
+
}
|
|
410
518
|
function mountCounterInLabel(wrapper, counter) {
|
|
411
519
|
const labelRow = wrapper.querySelector(
|
|
412
520
|
":scope > [data-fb-label-row]"
|
|
@@ -570,46 +678,94 @@ function applyActionButtonStyles(button, isFormLevel = false) {
|
|
|
570
678
|
}
|
|
571
679
|
|
|
572
680
|
// src/components/text.ts
|
|
573
|
-
function
|
|
681
|
+
function ensureChipStyles(doc) {
|
|
682
|
+
if (doc.head.querySelector("[data-fb-chip-styles]")) return;
|
|
683
|
+
const style = doc.createElement("style");
|
|
684
|
+
style.setAttribute("data-fb-chip-styles", "");
|
|
685
|
+
style.textContent = `
|
|
686
|
+
.fb-chip-list { display: flex; flex-direction: column; gap: 4px; }
|
|
687
|
+
.fb-chip {
|
|
688
|
+
display: flex;
|
|
689
|
+
align-items: center;
|
|
690
|
+
gap: 8px;
|
|
691
|
+
padding: 5px 6px 5px 10px;
|
|
692
|
+
background: var(--fb-chip-bg, var(--fb-background-color, #fff));
|
|
693
|
+
border: 1px solid var(--fb-chip-border, var(--fb-border-color, #e2e8f0));
|
|
694
|
+
border-radius: 6px;
|
|
695
|
+
position: relative;
|
|
696
|
+
transition: border-color var(--fb-transition-duration, 0.15s);
|
|
697
|
+
}
|
|
698
|
+
.fb-chip:hover { border-color: var(--fb-border-hover-color, var(--fb-border-color, #cbd5e1)); }
|
|
699
|
+
.fb-chip:focus-within { border-color: var(--fb-border-focus-color, var(--fb-primary-color, #2f5bea)); }
|
|
700
|
+
.fb-chip-dot {
|
|
701
|
+
flex: 0 0 6px;
|
|
702
|
+
width: 6px;
|
|
703
|
+
height: 6px;
|
|
704
|
+
border-radius: 50%;
|
|
705
|
+
background: var(--fb-chip-dot, var(--fb-primary-color, #2f5bea));
|
|
706
|
+
}
|
|
707
|
+
.fb-chip-input {
|
|
708
|
+
flex: 1;
|
|
709
|
+
min-width: 0;
|
|
710
|
+
padding: 2px 0;
|
|
711
|
+
border: 0;
|
|
712
|
+
outline: none;
|
|
713
|
+
background: transparent;
|
|
714
|
+
color: var(--fb-chip-text, var(--fb-text-color, inherit));
|
|
715
|
+
font-size: var(--fb-font-size, 14px);
|
|
716
|
+
font-family: var(--fb-font-family, inherit);
|
|
717
|
+
line-height: 1.4;
|
|
718
|
+
}
|
|
719
|
+
.fb-chip-input::placeholder { color: var(--fb-text-placeholder-color, #94a3b8); }
|
|
720
|
+
.fb-chip-input:read-only { color: var(--fb-text-secondary-color, #475569); }
|
|
721
|
+
.fb-chip-remove {
|
|
722
|
+
flex: 0 0 auto;
|
|
723
|
+
width: 22px;
|
|
724
|
+
height: 22px;
|
|
725
|
+
display: inline-flex;
|
|
726
|
+
align-items: center;
|
|
727
|
+
justify-content: center;
|
|
728
|
+
padding: 0;
|
|
729
|
+
border: 0;
|
|
730
|
+
border-radius: 4px;
|
|
731
|
+
background: transparent;
|
|
732
|
+
color: var(--fb-text-faint-color, #94a3b8);
|
|
733
|
+
cursor: pointer;
|
|
734
|
+
opacity: 0;
|
|
735
|
+
transition: opacity 0.12s, color 0.12s, background-color 0.12s;
|
|
736
|
+
}
|
|
737
|
+
.fb-chip:hover .fb-chip-remove,
|
|
738
|
+
.fb-chip-remove:focus-visible { opacity: 1; }
|
|
739
|
+
.fb-chip-remove:hover {
|
|
740
|
+
color: var(--fb-error-color, #dc2626);
|
|
741
|
+
background: var(--fb-background-hover-color, #f1f5f9);
|
|
742
|
+
}
|
|
743
|
+
.fb-chip-remove:disabled { opacity: 0 !important; pointer-events: none; }
|
|
744
|
+
`;
|
|
745
|
+
doc.head.appendChild(style);
|
|
746
|
+
}
|
|
747
|
+
function createCharCounter(element, input) {
|
|
574
748
|
const counter = document.createElement("span");
|
|
575
749
|
counter.className = "char-counter";
|
|
576
750
|
counter.style.cssText = `
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
751
|
+
margin-top: 4px;
|
|
752
|
+
padding-right: 12px;
|
|
753
|
+
text-align: right;
|
|
580
754
|
font-size: var(--fb-font-size-small);
|
|
581
|
-
|
|
755
|
+
line-height: 1;
|
|
756
|
+
color: var(--fb-error-color);
|
|
582
757
|
pointer-events: none;
|
|
583
|
-
|
|
584
|
-
padding: 0 4px;
|
|
758
|
+
display: none;
|
|
585
759
|
`;
|
|
586
760
|
const updateCounter = () => {
|
|
587
761
|
const len = input.value.length;
|
|
588
|
-
const min = element.minLength;
|
|
589
762
|
const max = element.maxLength;
|
|
590
|
-
if (
|
|
591
|
-
counter.textContent = "";
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
if (len === 0 || min != null && len < min) {
|
|
595
|
-
if (min != null && max != null) {
|
|
596
|
-
counter.textContent = `${min}-${max}`;
|
|
597
|
-
} else if (max != null) {
|
|
598
|
-
counter.textContent = `\u2264${max}`;
|
|
599
|
-
} else if (min != null) {
|
|
600
|
-
counter.textContent = `\u2265${min}`;
|
|
601
|
-
}
|
|
602
|
-
counter.style.color = "var(--fb-text-secondary-color)";
|
|
603
|
-
} else if (max != null && len > max) {
|
|
763
|
+
if (max != null && len > max) {
|
|
604
764
|
counter.textContent = `${len}/${max}`;
|
|
605
|
-
counter.style.
|
|
765
|
+
counter.style.display = "block";
|
|
606
766
|
} else {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
} else {
|
|
610
|
-
counter.textContent = `${len}`;
|
|
611
|
-
}
|
|
612
|
-
counter.style.color = "var(--fb-text-secondary-color)";
|
|
767
|
+
counter.textContent = "";
|
|
768
|
+
counter.style.display = "none";
|
|
613
769
|
}
|
|
614
770
|
};
|
|
615
771
|
input.addEventListener("input", updateCounter);
|
|
@@ -622,26 +778,32 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
622
778
|
const inputWrapper = document.createElement("div");
|
|
623
779
|
inputWrapper.style.cssText = "position: relative;";
|
|
624
780
|
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
625
|
-
const textInput = document.createElement("
|
|
626
|
-
textInput.
|
|
781
|
+
const textInput = document.createElement("textarea");
|
|
782
|
+
textInput.rows = 1;
|
|
627
783
|
textInput.className = "w-full rounded-lg";
|
|
628
784
|
textInput.style.cssText = `
|
|
629
785
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
630
|
-
${hasCharCounter ? "padding-right: 60px;" : ""}
|
|
631
786
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
632
787
|
border-radius: var(--fb-border-radius);
|
|
633
788
|
background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
634
789
|
color: var(--fb-text-color);
|
|
635
790
|
font-size: var(--fb-font-size);
|
|
636
791
|
font-family: var(--fb-font-family);
|
|
792
|
+
line-height: var(--fb-line-height, 1.5);
|
|
637
793
|
transition: all var(--fb-transition-duration) ease-in-out;
|
|
638
794
|
width: 100%;
|
|
639
795
|
box-sizing: border-box;
|
|
796
|
+
resize: none;
|
|
797
|
+
overflow: hidden;
|
|
798
|
+
word-break: break-word;
|
|
799
|
+
overflow-wrap: anywhere;
|
|
640
800
|
`;
|
|
641
801
|
textInput.name = pathKey;
|
|
642
802
|
textInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
643
803
|
textInput.value = ctx.prefill[element.key] || element.default || "";
|
|
644
804
|
textInput.readOnly = readonly;
|
|
805
|
+
applySingleLineMode(textInput);
|
|
806
|
+
applyAutoExpand(textInput);
|
|
645
807
|
if (!readonly) {
|
|
646
808
|
textInput.addEventListener("focus", () => {
|
|
647
809
|
textInput.style.borderColor = "var(--fb-border-focus-color)";
|
|
@@ -673,7 +835,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
673
835
|
}
|
|
674
836
|
inputWrapper.appendChild(textInput);
|
|
675
837
|
if (hasCharCounter) {
|
|
676
|
-
const counter = createCharCounter(element, textInput
|
|
838
|
+
const counter = createCharCounter(element, textInput);
|
|
677
839
|
inputWrapper.appendChild(counter);
|
|
678
840
|
}
|
|
679
841
|
wrapper.appendChild(inputWrapper);
|
|
@@ -684,132 +846,83 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
684
846
|
const readonly = isElementReadonly(element, state, ctx);
|
|
685
847
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
686
848
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
687
|
-
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
688
849
|
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
689
850
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
690
851
|
while (values.length < minCount) {
|
|
691
852
|
values.push(element.default || "");
|
|
692
853
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
854
|
+
ensureChipStyles(document);
|
|
855
|
+
const list = document.createElement("div");
|
|
856
|
+
list.className = "fb-chip-list";
|
|
857
|
+
wrapper.appendChild(list);
|
|
696
858
|
function updateIndices() {
|
|
697
|
-
const items =
|
|
698
|
-
items.forEach((
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
859
|
+
const items = list.querySelectorAll(".fb-chip-input");
|
|
860
|
+
items.forEach((input, index) => {
|
|
861
|
+
input.name = `${pathKey}[${index}]`;
|
|
862
|
+
const chip = input.closest(".fb-chip");
|
|
863
|
+
const sib = chip == null ? void 0 : chip.nextElementSibling;
|
|
864
|
+
if (sib && sib.classList.contains("error-message")) {
|
|
865
|
+
sib.id = `error-${input.name}`;
|
|
702
866
|
}
|
|
703
867
|
});
|
|
704
868
|
}
|
|
705
|
-
function
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
font-size: var(--fb-font-size);
|
|
720
|
-
font-family: var(--fb-font-family);
|
|
721
|
-
transition: all var(--fb-transition-duration) ease-in-out;
|
|
722
|
-
width: 100%;
|
|
723
|
-
box-sizing: border-box;
|
|
724
|
-
`;
|
|
725
|
-
textInput.placeholder = element.placeholder || t("placeholderText", state);
|
|
726
|
-
textInput.value = value;
|
|
727
|
-
textInput.readOnly = readonly;
|
|
728
|
-
if (!readonly) {
|
|
729
|
-
textInput.addEventListener("focus", () => {
|
|
730
|
-
textInput.style.borderColor = "var(--fb-border-focus-color)";
|
|
731
|
-
textInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
|
|
732
|
-
textInput.style.outlineOffset = "0";
|
|
733
|
-
});
|
|
734
|
-
textInput.addEventListener("blur", () => {
|
|
735
|
-
textInput.style.borderColor = "var(--fb-border-color)";
|
|
736
|
-
textInput.style.outline = "none";
|
|
737
|
-
});
|
|
738
|
-
textInput.addEventListener("mouseenter", () => {
|
|
739
|
-
if (document.activeElement !== textInput) {
|
|
740
|
-
textInput.style.borderColor = "var(--fb-border-hover-color)";
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
textInput.addEventListener("mouseleave", () => {
|
|
744
|
-
if (document.activeElement !== textInput) {
|
|
745
|
-
textInput.style.borderColor = "var(--fb-border-color)";
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
}
|
|
869
|
+
function addChip(value = "") {
|
|
870
|
+
const chip = document.createElement("div");
|
|
871
|
+
chip.className = "fb-chip";
|
|
872
|
+
const dot = document.createElement("span");
|
|
873
|
+
dot.className = "fb-chip-dot";
|
|
874
|
+
dot.setAttribute("aria-hidden", "true");
|
|
875
|
+
chip.appendChild(dot);
|
|
876
|
+
const input = document.createElement("input");
|
|
877
|
+
input.type = "text";
|
|
878
|
+
input.className = "fb-chip-input";
|
|
879
|
+
input.value = value;
|
|
880
|
+
input.placeholder = element.placeholder || t("placeholderText", state);
|
|
881
|
+
input.readOnly = readonly;
|
|
882
|
+
chip.appendChild(input);
|
|
749
883
|
if (!readonly && ctx.instance) {
|
|
750
884
|
const handleChange = () => {
|
|
751
|
-
|
|
752
|
-
|
|
885
|
+
ctx.instance.triggerOnChange(
|
|
886
|
+
input.name,
|
|
887
|
+
input.value === "" ? null : input.value
|
|
888
|
+
);
|
|
753
889
|
};
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
inputContainer.appendChild(textInput);
|
|
758
|
-
if (hasCharCounter) {
|
|
759
|
-
const counter = createCharCounter(element, textInput, false);
|
|
760
|
-
inputContainer.appendChild(counter);
|
|
890
|
+
input.addEventListener("blur", handleChange);
|
|
891
|
+
input.addEventListener("input", handleChange);
|
|
761
892
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
893
|
+
if (!readonly) {
|
|
894
|
+
const rem = document.createElement("button");
|
|
895
|
+
rem.type = "button";
|
|
896
|
+
rem.className = "fb-chip-remove";
|
|
897
|
+
rem.setAttribute("aria-label", t("removeElement", state));
|
|
898
|
+
rem.innerHTML = BIN_ICON_SVG;
|
|
899
|
+
rem.onclick = () => {
|
|
900
|
+
const chips = list.querySelectorAll(".fb-chip");
|
|
901
|
+
const idx = Array.prototype.indexOf.call(chips, chip);
|
|
902
|
+
if (idx < 0) return;
|
|
903
|
+
if (chips.length <= minCount) return;
|
|
904
|
+
values.splice(idx, 1);
|
|
905
|
+
const trailingError = chip.nextElementSibling;
|
|
906
|
+
if (trailingError && trailingError.classList.contains("error-message")) {
|
|
907
|
+
trailingError.remove();
|
|
908
|
+
}
|
|
909
|
+
chip.remove();
|
|
910
|
+
updateIndices();
|
|
911
|
+
updateAddButton();
|
|
912
|
+
updateRemoveButtons();
|
|
913
|
+
};
|
|
914
|
+
chip.appendChild(rem);
|
|
767
915
|
}
|
|
916
|
+
list.appendChild(chip);
|
|
768
917
|
updateIndices();
|
|
769
|
-
return
|
|
918
|
+
return chip;
|
|
770
919
|
}
|
|
771
920
|
function updateRemoveButtons() {
|
|
772
921
|
if (readonly) return;
|
|
773
|
-
const
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
".remove-item-btn"
|
|
778
|
-
);
|
|
779
|
-
if (!removeBtn) {
|
|
780
|
-
removeBtn = document.createElement("button");
|
|
781
|
-
removeBtn.type = "button";
|
|
782
|
-
removeBtn.className = "remove-item-btn px-2 py-1 rounded";
|
|
783
|
-
removeBtn.style.cssText = `
|
|
784
|
-
color: var(--fb-error-color);
|
|
785
|
-
background-color: transparent;
|
|
786
|
-
transition: background-color var(--fb-transition-duration);
|
|
787
|
-
`;
|
|
788
|
-
removeBtn.innerHTML = "\u2715";
|
|
789
|
-
removeBtn.addEventListener("mouseenter", () => {
|
|
790
|
-
removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
791
|
-
});
|
|
792
|
-
removeBtn.addEventListener("mouseleave", () => {
|
|
793
|
-
removeBtn.style.backgroundColor = "transparent";
|
|
794
|
-
});
|
|
795
|
-
removeBtn.onclick = () => {
|
|
796
|
-
const currentIndex = Array.from(container.children).indexOf(
|
|
797
|
-
item
|
|
798
|
-
);
|
|
799
|
-
if (container.children.length > minCount) {
|
|
800
|
-
values.splice(currentIndex, 1);
|
|
801
|
-
item.remove();
|
|
802
|
-
updateIndices();
|
|
803
|
-
updateAddButton();
|
|
804
|
-
updateRemoveButtons();
|
|
805
|
-
}
|
|
806
|
-
};
|
|
807
|
-
item.appendChild(removeBtn);
|
|
808
|
-
}
|
|
809
|
-
const disabled = currentCount <= minCount;
|
|
810
|
-
removeBtn.disabled = disabled;
|
|
811
|
-
removeBtn.style.opacity = disabled ? "0.5" : "1";
|
|
812
|
-
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
922
|
+
const chipCount = list.querySelectorAll(".fb-chip").length;
|
|
923
|
+
const disabled = chipCount <= minCount;
|
|
924
|
+
list.querySelectorAll(".fb-chip-remove").forEach((btn) => {
|
|
925
|
+
btn.disabled = disabled;
|
|
813
926
|
});
|
|
814
927
|
}
|
|
815
928
|
let addUpdate = null;
|
|
@@ -818,7 +931,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
818
931
|
"text",
|
|
819
932
|
() => {
|
|
820
933
|
values.push(element.default || "");
|
|
821
|
-
|
|
934
|
+
addChip(element.default || "");
|
|
822
935
|
updateAddButton();
|
|
823
936
|
updateRemoveButtons();
|
|
824
937
|
},
|
|
@@ -831,7 +944,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
831
944
|
function updateAddButton() {
|
|
832
945
|
if (addUpdate) addUpdate(values.length, maxCount);
|
|
833
946
|
}
|
|
834
|
-
values.forEach((value) =>
|
|
947
|
+
values.forEach((value) => addChip(value));
|
|
835
948
|
updateAddButton();
|
|
836
949
|
updateRemoveButtons();
|
|
837
950
|
}
|
|
@@ -840,7 +953,7 @@ function validateTextElement(element, key, context) {
|
|
|
840
953
|
const errors = [];
|
|
841
954
|
const { scopeRoot, skipValidation } = context;
|
|
842
955
|
const markValidity = (input, errorMessage) => {
|
|
843
|
-
var _a2, _b2;
|
|
956
|
+
var _a2, _b2, _c2;
|
|
844
957
|
if (!input) return;
|
|
845
958
|
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
846
959
|
let errorElement = document.getElementById(errorId);
|
|
@@ -856,10 +969,12 @@ function validateTextElement(element, key, context) {
|
|
|
856
969
|
font-size: var(--fb-font-size-small);
|
|
857
970
|
margin-top: 0.25rem;
|
|
858
971
|
`;
|
|
859
|
-
|
|
860
|
-
|
|
972
|
+
const chipAncestor = (_a2 = input.closest) == null ? void 0 : _a2.call(input, ".fb-chip");
|
|
973
|
+
const anchor = chipAncestor || input;
|
|
974
|
+
if (anchor.nextSibling) {
|
|
975
|
+
(_b2 = anchor.parentNode) == null ? void 0 : _b2.insertBefore(errorElement, anchor.nextSibling);
|
|
861
976
|
} else {
|
|
862
|
-
(
|
|
977
|
+
(_c2 = anchor.parentNode) == null ? void 0 : _c2.appendChild(errorElement);
|
|
863
978
|
}
|
|
864
979
|
}
|
|
865
980
|
errorElement.textContent = errorMessage;
|
|
@@ -908,7 +1023,7 @@ function validateTextElement(element, key, context) {
|
|
|
908
1023
|
}
|
|
909
1024
|
};
|
|
910
1025
|
if (element.multiple) {
|
|
911
|
-
const inputs = scopeRoot.querySelectorAll(`[name^="${key}["]`);
|
|
1026
|
+
const inputs = scopeRoot.querySelectorAll(`[name^="${key}\\["]`);
|
|
912
1027
|
const values = [];
|
|
913
1028
|
const rawValues = [];
|
|
914
1029
|
inputs.forEach((input, index) => {
|
|
@@ -958,12 +1073,14 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
958
1073
|
);
|
|
959
1074
|
return;
|
|
960
1075
|
}
|
|
961
|
-
const inputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["]`);
|
|
1076
|
+
const inputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}\\["]`);
|
|
962
1077
|
inputs.forEach((input, index) => {
|
|
963
1078
|
if (index < value.length) {
|
|
964
1079
|
input.value = value[index] != null ? String(value[index]) : "";
|
|
965
1080
|
input.classList.remove("invalid");
|
|
966
1081
|
input.title = "";
|
|
1082
|
+
clearFieldError(input);
|
|
1083
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
967
1084
|
}
|
|
968
1085
|
});
|
|
969
1086
|
if (value.length !== inputs.length) {
|
|
@@ -977,26 +1094,15 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
977
1094
|
input.value = value != null ? String(value) : "";
|
|
978
1095
|
input.classList.remove("invalid");
|
|
979
1096
|
input.title = "";
|
|
1097
|
+
clearFieldError(input);
|
|
1098
|
+
if (input instanceof HTMLTextAreaElement) {
|
|
1099
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1100
|
+
}
|
|
980
1101
|
}
|
|
981
1102
|
}
|
|
982
1103
|
}
|
|
983
1104
|
|
|
984
1105
|
// src/components/textarea.ts
|
|
985
|
-
function applyAutoExpand(textarea) {
|
|
986
|
-
textarea.style.overflow = "hidden";
|
|
987
|
-
textarea.style.resize = "none";
|
|
988
|
-
const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
|
|
989
|
-
textarea.rows = Math.max(1, lineCount);
|
|
990
|
-
const resize = () => {
|
|
991
|
-
if (!textarea.isConnected) return;
|
|
992
|
-
textarea.style.height = "0";
|
|
993
|
-
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
994
|
-
};
|
|
995
|
-
textarea.addEventListener("input", resize);
|
|
996
|
-
setTimeout(() => {
|
|
997
|
-
if (textarea.isConnected) resize();
|
|
998
|
-
}, 0);
|
|
999
|
-
}
|
|
1000
1106
|
function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
1001
1107
|
const state = ctx.state;
|
|
1002
1108
|
const readonly = isElementReadonly(element, state, ctx);
|
|
@@ -1027,7 +1133,7 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1027
1133
|
}
|
|
1028
1134
|
textareaWrapper.appendChild(textareaInput);
|
|
1029
1135
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1030
|
-
const counter = createCharCounter(element, textareaInput
|
|
1136
|
+
const counter = createCharCounter(element, textareaInput);
|
|
1031
1137
|
textareaWrapper.appendChild(counter);
|
|
1032
1138
|
}
|
|
1033
1139
|
wrapper.appendChild(textareaWrapper);
|
|
@@ -1084,7 +1190,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1084
1190
|
}
|
|
1085
1191
|
textareaContainer.appendChild(textareaInput);
|
|
1086
1192
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1087
|
-
const counter = createCharCounter(element, textareaInput
|
|
1193
|
+
const counter = createCharCounter(element, textareaInput);
|
|
1088
1194
|
textareaContainer.appendChild(counter);
|
|
1089
1195
|
}
|
|
1090
1196
|
itemWrapper.appendChild(textareaContainer);
|
|
@@ -1178,6 +1284,91 @@ function updateTextareaField(element, fieldPath, value, context) {
|
|
|
1178
1284
|
}
|
|
1179
1285
|
|
|
1180
1286
|
// src/components/number.ts
|
|
1287
|
+
function ensureStepperStyles(doc) {
|
|
1288
|
+
const ID = "fb-number-stepper-styles";
|
|
1289
|
+
if (doc.getElementById(ID)) return;
|
|
1290
|
+
const style = doc.createElement("style");
|
|
1291
|
+
style.id = ID;
|
|
1292
|
+
style.textContent = `
|
|
1293
|
+
.fb-stepper-input::-webkit-outer-spin-button,
|
|
1294
|
+
.fb-stepper-input::-webkit-inner-spin-button {
|
|
1295
|
+
-webkit-appearance: none;
|
|
1296
|
+
margin: 0;
|
|
1297
|
+
}
|
|
1298
|
+
.fb-stepper-input { -moz-appearance: textfield; }
|
|
1299
|
+
`;
|
|
1300
|
+
doc.head.appendChild(style);
|
|
1301
|
+
}
|
|
1302
|
+
function buildStepper(input, element, readonly) {
|
|
1303
|
+
var _a;
|
|
1304
|
+
ensureStepperStyles(input.ownerDocument);
|
|
1305
|
+
const step = (_a = element.step) != null ? _a : 1;
|
|
1306
|
+
const min = element.min;
|
|
1307
|
+
const max = element.max;
|
|
1308
|
+
const wrap = document.createElement("div");
|
|
1309
|
+
wrap.className = "fb-stepper";
|
|
1310
|
+
wrap.style.cssText = `
|
|
1311
|
+
display: inline-flex;
|
|
1312
|
+
align-items: stretch;
|
|
1313
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
1314
|
+
border-radius: var(--fb-border-radius);
|
|
1315
|
+
overflow: hidden;
|
|
1316
|
+
background: var(--fb-background-color);
|
|
1317
|
+
`;
|
|
1318
|
+
const makeBtn = (label, delta) => {
|
|
1319
|
+
const b = document.createElement("button");
|
|
1320
|
+
b.type = "button";
|
|
1321
|
+
b.textContent = label;
|
|
1322
|
+
b.tabIndex = -1;
|
|
1323
|
+
b.style.cssText = `
|
|
1324
|
+
width: 32px;
|
|
1325
|
+
border: none;
|
|
1326
|
+
background: transparent;
|
|
1327
|
+
color: var(--fb-text-color);
|
|
1328
|
+
font-size: var(--fb-font-size);
|
|
1329
|
+
font-family: var(--fb-font-family);
|
|
1330
|
+
cursor: ${readonly ? "default" : "pointer"};
|
|
1331
|
+
user-select: none;
|
|
1332
|
+
`;
|
|
1333
|
+
if (readonly) {
|
|
1334
|
+
b.disabled = true;
|
|
1335
|
+
b.style.opacity = "0.5";
|
|
1336
|
+
} else {
|
|
1337
|
+
b.addEventListener("click", (e) => {
|
|
1338
|
+
var _a2;
|
|
1339
|
+
e.preventDefault();
|
|
1340
|
+
const current = parseFloat(input.value);
|
|
1341
|
+
const base = Number.isFinite(current) ? current : (_a2 = min != null ? min : element.default) != null ? _a2 : 0;
|
|
1342
|
+
let next = parseFloat((base + delta * step).toPrecision(12));
|
|
1343
|
+
if (min != null) next = Math.max(min, next);
|
|
1344
|
+
if (max != null) next = Math.min(max, next);
|
|
1345
|
+
input.value = String(next);
|
|
1346
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1347
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
return b;
|
|
1351
|
+
};
|
|
1352
|
+
input.classList.add("fb-stepper-input");
|
|
1353
|
+
input.style.cssText = `
|
|
1354
|
+
width: 56px;
|
|
1355
|
+
border: none;
|
|
1356
|
+
border-left: var(--fb-border-width) solid var(--fb-border-color);
|
|
1357
|
+
border-right: var(--fb-border-width) solid var(--fb-border-color);
|
|
1358
|
+
padding: var(--fb-input-padding-y) 0;
|
|
1359
|
+
font-size: var(--fb-font-size);
|
|
1360
|
+
font-family: var(--fb-font-family);
|
|
1361
|
+
text-align: center;
|
|
1362
|
+
background: transparent;
|
|
1363
|
+
color: var(--fb-text-color);
|
|
1364
|
+
-moz-appearance: textfield;
|
|
1365
|
+
box-sizing: border-box;
|
|
1366
|
+
`;
|
|
1367
|
+
wrap.appendChild(makeBtn("\u2212", -1));
|
|
1368
|
+
wrap.appendChild(input);
|
|
1369
|
+
wrap.appendChild(makeBtn("+", 1));
|
|
1370
|
+
return wrap;
|
|
1371
|
+
}
|
|
1181
1372
|
function createNumberRangeHint(element, input) {
|
|
1182
1373
|
const hint = document.createElement("span");
|
|
1183
1374
|
hint.className = "number-range-hint";
|
|
@@ -1224,14 +1415,6 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1224
1415
|
inputWrapper.style.cssText = "position: relative;";
|
|
1225
1416
|
const numberInput = document.createElement("input");
|
|
1226
1417
|
numberInput.type = "number";
|
|
1227
|
-
numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1228
|
-
numberInput.style.cssText = `
|
|
1229
|
-
padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1230
|
-
font-size: var(--fb-font-size);
|
|
1231
|
-
font-family: var(--fb-font-family);
|
|
1232
|
-
width: 100%;
|
|
1233
|
-
box-sizing: border-box;
|
|
1234
|
-
`;
|
|
1235
1418
|
numberInput.name = pathKey;
|
|
1236
1419
|
numberInput.placeholder = element.placeholder || "0";
|
|
1237
1420
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
@@ -1239,6 +1422,16 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1239
1422
|
if (element.step !== void 0) numberInput.step = element.step.toString();
|
|
1240
1423
|
numberInput.value = ctx.prefill[element.key] || element.default || "";
|
|
1241
1424
|
numberInput.readOnly = readonly;
|
|
1425
|
+
if (!element.stepper) {
|
|
1426
|
+
numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1427
|
+
numberInput.style.cssText = `
|
|
1428
|
+
padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1429
|
+
font-size: var(--fb-font-size);
|
|
1430
|
+
font-family: var(--fb-font-family);
|
|
1431
|
+
width: 100%;
|
|
1432
|
+
box-sizing: border-box;
|
|
1433
|
+
`;
|
|
1434
|
+
}
|
|
1242
1435
|
if (!readonly && ctx.instance) {
|
|
1243
1436
|
const handleChange = () => {
|
|
1244
1437
|
const value = numberInput.value ? parseFloat(numberInput.value) : null;
|
|
@@ -1247,10 +1440,14 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1247
1440
|
numberInput.addEventListener("blur", handleChange);
|
|
1248
1441
|
numberInput.addEventListener("input", handleChange);
|
|
1249
1442
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
inputWrapper.appendChild(
|
|
1443
|
+
if (element.stepper) {
|
|
1444
|
+
inputWrapper.appendChild(buildStepper(numberInput, element, readonly));
|
|
1445
|
+
} else {
|
|
1446
|
+
inputWrapper.appendChild(numberInput);
|
|
1447
|
+
if (!readonly && (element.min != null || element.max != null)) {
|
|
1448
|
+
const counter = createNumberRangeHint(element, numberInput);
|
|
1449
|
+
inputWrapper.appendChild(counter);
|
|
1450
|
+
}
|
|
1254
1451
|
}
|
|
1255
1452
|
wrapper.appendChild(inputWrapper);
|
|
1256
1453
|
}
|
|
@@ -1508,13 +1705,14 @@ function updateNumberField(element, fieldPath, value, context) {
|
|
|
1508
1705
|
return;
|
|
1509
1706
|
}
|
|
1510
1707
|
const inputs = scopeRoot.querySelectorAll(
|
|
1511
|
-
`[name^="${fieldPath}["]`
|
|
1708
|
+
`[name^="${fieldPath}\\["]`
|
|
1512
1709
|
);
|
|
1513
1710
|
inputs.forEach((input, index) => {
|
|
1514
1711
|
if (index < value.length) {
|
|
1515
1712
|
input.value = value[index] != null ? String(value[index]) : "";
|
|
1516
1713
|
input.classList.remove("invalid");
|
|
1517
1714
|
input.title = "";
|
|
1715
|
+
clearFieldError(input);
|
|
1518
1716
|
}
|
|
1519
1717
|
});
|
|
1520
1718
|
if (value.length !== inputs.length) {
|
|
@@ -1530,6 +1728,7 @@ function updateNumberField(element, fieldPath, value, context) {
|
|
|
1530
1728
|
input.value = value != null ? String(value) : "";
|
|
1531
1729
|
input.classList.remove("invalid");
|
|
1532
1730
|
input.title = "";
|
|
1731
|
+
clearFieldError(input);
|
|
1533
1732
|
}
|
|
1534
1733
|
}
|
|
1535
1734
|
}
|
|
@@ -1746,7 +1945,7 @@ function validateSelectElement(element, key, context) {
|
|
|
1746
1945
|
};
|
|
1747
1946
|
if ("multiple" in element && element.multiple) {
|
|
1748
1947
|
const inputs = scopeRoot.querySelectorAll(
|
|
1749
|
-
`[name^="${key}["]`
|
|
1948
|
+
`[name^="${key}\\["]`
|
|
1750
1949
|
);
|
|
1751
1950
|
const values = [];
|
|
1752
1951
|
inputs.forEach((input) => {
|
|
@@ -1783,7 +1982,7 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1783
1982
|
return;
|
|
1784
1983
|
}
|
|
1785
1984
|
const selects = scopeRoot.querySelectorAll(
|
|
1786
|
-
`[name^="${fieldPath}["]`
|
|
1985
|
+
`[name^="${fieldPath}\\["]`
|
|
1787
1986
|
);
|
|
1788
1987
|
selects.forEach((select, index) => {
|
|
1789
1988
|
if (index < value.length) {
|
|
@@ -1794,6 +1993,7 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1794
1993
|
});
|
|
1795
1994
|
select.classList.remove("invalid");
|
|
1796
1995
|
select.title = "";
|
|
1996
|
+
clearFieldError(select);
|
|
1797
1997
|
}
|
|
1798
1998
|
});
|
|
1799
1999
|
if (value.length !== selects.length) {
|
|
@@ -1813,72 +2013,147 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1813
2013
|
});
|
|
1814
2014
|
select.classList.remove("invalid");
|
|
1815
2015
|
select.title = "";
|
|
2016
|
+
clearFieldError(select);
|
|
1816
2017
|
}
|
|
1817
2018
|
}
|
|
1818
2019
|
}
|
|
1819
2020
|
|
|
1820
2021
|
// src/components/switcher.ts
|
|
1821
|
-
function applySelectedStyle(btn) {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2022
|
+
function applySelectedStyle(btn, isPreset) {
|
|
2023
|
+
if (isPreset) {
|
|
2024
|
+
btn.style.backgroundColor = "var(--fb-primary-soft-color)";
|
|
2025
|
+
btn.style.color = "var(--fb-primary-color)";
|
|
2026
|
+
btn.style.borderColor = "var(--fb-primary-color)";
|
|
2027
|
+
} else {
|
|
2028
|
+
btn.style.backgroundColor = "var(--fb-primary-color)";
|
|
2029
|
+
btn.style.color = "#ffffff";
|
|
2030
|
+
btn.style.borderColor = "var(--fb-primary-color)";
|
|
2031
|
+
}
|
|
1825
2032
|
}
|
|
1826
|
-
function applyUnselectedStyle(btn) {
|
|
1827
|
-
btn.style.backgroundColor = "transparent";
|
|
2033
|
+
function applyUnselectedStyle(btn, isPreset) {
|
|
2034
|
+
btn.style.backgroundColor = isPreset ? "var(--fb-background-color)" : "transparent";
|
|
1828
2035
|
btn.style.color = "var(--fb-text-color)";
|
|
1829
2036
|
btn.style.borderColor = "var(--fb-border-color)";
|
|
1830
2037
|
}
|
|
2038
|
+
function isPresetButton(btn) {
|
|
2039
|
+
return btn.classList.contains("fb-switcher-preset");
|
|
2040
|
+
}
|
|
2041
|
+
function buildPresetCard(option, readonly) {
|
|
2042
|
+
const btn = document.createElement("button");
|
|
2043
|
+
btn.type = "button";
|
|
2044
|
+
btn.className = "fb-switcher-btn fb-switcher-preset";
|
|
2045
|
+
btn.dataset.value = option.value;
|
|
2046
|
+
btn.style.cssText = `
|
|
2047
|
+
display: inline-flex;
|
|
2048
|
+
align-items: center;
|
|
2049
|
+
gap: 8px;
|
|
2050
|
+
padding: 7px 12px 7px 10px;
|
|
2051
|
+
border-width: var(--fb-border-width);
|
|
2052
|
+
border-style: solid;
|
|
2053
|
+
border-radius: 999px;
|
|
2054
|
+
background: var(--fb-background-color);
|
|
2055
|
+
font-size: var(--fb-font-size);
|
|
2056
|
+
font-family: var(--fb-font-family);
|
|
2057
|
+
line-height: 1.25;
|
|
2058
|
+
cursor: ${readonly ? "default" : "pointer"};
|
|
2059
|
+
transition: background-color var(--fb-transition-duration), color var(--fb-transition-duration), border-color var(--fb-transition-duration);
|
|
2060
|
+
outline: none;
|
|
2061
|
+
`;
|
|
2062
|
+
if (option.iconUrl) {
|
|
2063
|
+
const icon = document.createElement("img");
|
|
2064
|
+
icon.className = "fb-switcher-icon";
|
|
2065
|
+
icon.src = option.iconUrl;
|
|
2066
|
+
icon.alt = "";
|
|
2067
|
+
icon.setAttribute("aria-hidden", "true");
|
|
2068
|
+
icon.style.cssText = `
|
|
2069
|
+
display: block;
|
|
2070
|
+
flex: 0 0 auto;
|
|
2071
|
+
width: 20px;
|
|
2072
|
+
height: 20px;
|
|
2073
|
+
object-fit: contain;
|
|
2074
|
+
`;
|
|
2075
|
+
btn.appendChild(icon);
|
|
2076
|
+
}
|
|
2077
|
+
const name = document.createElement("span");
|
|
2078
|
+
name.className = "fb-switcher-name";
|
|
2079
|
+
name.textContent = option.label;
|
|
2080
|
+
name.style.cssText = "font-weight: 600;";
|
|
2081
|
+
btn.appendChild(name);
|
|
2082
|
+
if (option.subtitle) {
|
|
2083
|
+
const sub = document.createElement("span");
|
|
2084
|
+
sub.className = "fb-switcher-subtitle";
|
|
2085
|
+
sub.textContent = option.subtitle;
|
|
2086
|
+
sub.style.cssText = `
|
|
2087
|
+
font-size: var(--fb-font-size-small);
|
|
2088
|
+
opacity: 0.7;
|
|
2089
|
+
font-variant-numeric: tabular-nums;
|
|
2090
|
+
`;
|
|
2091
|
+
btn.appendChild(sub);
|
|
2092
|
+
}
|
|
2093
|
+
return btn;
|
|
2094
|
+
}
|
|
1831
2095
|
function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onChange) {
|
|
1832
2096
|
const options = element.options || [];
|
|
2097
|
+
const isPresetMode = options.some((o) => o.subtitle || o.iconUrl);
|
|
1833
2098
|
const group = document.createElement("div");
|
|
1834
2099
|
group.className = "fb-switcher-group";
|
|
1835
|
-
group.style.cssText = `
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2100
|
+
group.style.cssText = isPresetMode ? `
|
|
2101
|
+
display: flex;
|
|
2102
|
+
flex-direction: row;
|
|
2103
|
+
flex-wrap: wrap;
|
|
2104
|
+
gap: 6px;
|
|
2105
|
+
` : `
|
|
2106
|
+
display: inline-flex;
|
|
2107
|
+
flex-direction: row;
|
|
2108
|
+
flex-wrap: nowrap;
|
|
2109
|
+
`;
|
|
1840
2110
|
const buttons = [];
|
|
1841
2111
|
options.forEach((option, index) => {
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
btn.dataset.value = option.value;
|
|
1846
|
-
btn.textContent = option.label;
|
|
1847
|
-
btn.style.cssText = `
|
|
1848
|
-
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1849
|
-
font-size: var(--fb-font-size);
|
|
1850
|
-
border-width: var(--fb-border-width);
|
|
1851
|
-
border-style: solid;
|
|
1852
|
-
cursor: ${readonly ? "default" : "pointer"};
|
|
1853
|
-
transition: background-color var(--fb-transition-duration), color var(--fb-transition-duration), border-color var(--fb-transition-duration);
|
|
1854
|
-
white-space: nowrap;
|
|
1855
|
-
line-height: 1.25;
|
|
1856
|
-
outline: none;
|
|
1857
|
-
`;
|
|
1858
|
-
if (options.length === 1) {
|
|
1859
|
-
btn.style.borderRadius = "var(--fb-border-radius)";
|
|
1860
|
-
} else if (index === 0) {
|
|
1861
|
-
btn.style.borderRadius = "var(--fb-border-radius) 0 0 var(--fb-border-radius)";
|
|
1862
|
-
btn.style.borderRightWidth = "0";
|
|
1863
|
-
} else if (index === options.length - 1) {
|
|
1864
|
-
btn.style.borderRadius = "0 var(--fb-border-radius) var(--fb-border-radius) 0";
|
|
2112
|
+
let btn;
|
|
2113
|
+
if (isPresetMode) {
|
|
2114
|
+
btn = buildPresetCard(option, readonly);
|
|
1865
2115
|
} else {
|
|
1866
|
-
btn
|
|
1867
|
-
btn.
|
|
2116
|
+
btn = document.createElement("button");
|
|
2117
|
+
btn.type = "button";
|
|
2118
|
+
btn.className = "fb-switcher-btn";
|
|
2119
|
+
btn.dataset.value = option.value;
|
|
2120
|
+
btn.textContent = option.label;
|
|
2121
|
+
btn.style.cssText = `
|
|
2122
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
2123
|
+
font-size: var(--fb-font-size);
|
|
2124
|
+
border-width: var(--fb-border-width);
|
|
2125
|
+
border-style: solid;
|
|
2126
|
+
cursor: ${readonly ? "default" : "pointer"};
|
|
2127
|
+
transition: background-color var(--fb-transition-duration), color var(--fb-transition-duration), border-color var(--fb-transition-duration);
|
|
2128
|
+
white-space: nowrap;
|
|
2129
|
+
line-height: 1.25;
|
|
2130
|
+
outline: none;
|
|
2131
|
+
`;
|
|
2132
|
+
if (options.length === 1) {
|
|
2133
|
+
btn.style.borderRadius = "var(--fb-border-radius)";
|
|
2134
|
+
} else if (index === 0) {
|
|
2135
|
+
btn.style.borderRadius = "var(--fb-border-radius) 0 0 var(--fb-border-radius)";
|
|
2136
|
+
btn.style.borderRightWidth = "0";
|
|
2137
|
+
} else if (index === options.length - 1) {
|
|
2138
|
+
btn.style.borderRadius = "0 var(--fb-border-radius) var(--fb-border-radius) 0";
|
|
2139
|
+
} else {
|
|
2140
|
+
btn.style.borderRadius = "0";
|
|
2141
|
+
btn.style.borderRightWidth = "0";
|
|
2142
|
+
}
|
|
1868
2143
|
}
|
|
1869
2144
|
if (option.value === currentValue) {
|
|
1870
|
-
applySelectedStyle(btn);
|
|
2145
|
+
applySelectedStyle(btn, isPresetMode);
|
|
1871
2146
|
} else {
|
|
1872
|
-
applyUnselectedStyle(btn);
|
|
2147
|
+
applyUnselectedStyle(btn, isPresetMode);
|
|
1873
2148
|
}
|
|
1874
2149
|
if (!readonly) {
|
|
1875
2150
|
btn.addEventListener("click", () => {
|
|
1876
2151
|
hiddenInput.value = option.value;
|
|
1877
2152
|
buttons.forEach((b) => {
|
|
1878
2153
|
if (b.dataset.value === option.value) {
|
|
1879
|
-
applySelectedStyle(b);
|
|
2154
|
+
applySelectedStyle(b, isPresetMode);
|
|
1880
2155
|
} else {
|
|
1881
|
-
applyUnselectedStyle(b);
|
|
2156
|
+
applyUnselectedStyle(b, isPresetMode);
|
|
1882
2157
|
}
|
|
1883
2158
|
});
|
|
1884
2159
|
if (onChange) {
|
|
@@ -1892,7 +2167,7 @@ function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onCha
|
|
|
1892
2167
|
});
|
|
1893
2168
|
btn.addEventListener("mouseleave", () => {
|
|
1894
2169
|
if (hiddenInput.value !== option.value) {
|
|
1895
|
-
btn.style.backgroundColor = "transparent";
|
|
2170
|
+
btn.style.backgroundColor = isPresetMode ? "var(--fb-background-color)" : "transparent";
|
|
1896
2171
|
}
|
|
1897
2172
|
});
|
|
1898
2173
|
}
|
|
@@ -2117,7 +2392,7 @@ function validateSwitcherElement(element, key, context) {
|
|
|
2117
2392
|
);
|
|
2118
2393
|
if ("multiple" in element && element.multiple) {
|
|
2119
2394
|
const inputs = scopeRoot.querySelectorAll(
|
|
2120
|
-
`input[type="hidden"][name^="${key}["]`
|
|
2395
|
+
`input[type="hidden"][name^="${key}\\["]`
|
|
2121
2396
|
);
|
|
2122
2397
|
const values = [];
|
|
2123
2398
|
inputs.forEach((input) => {
|
|
@@ -2166,7 +2441,7 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2166
2441
|
return;
|
|
2167
2442
|
}
|
|
2168
2443
|
const inputs = scopeRoot.querySelectorAll(
|
|
2169
|
-
`input[type="hidden"][name^="${fieldPath}["]`
|
|
2444
|
+
`input[type="hidden"][name^="${fieldPath}\\["]`
|
|
2170
2445
|
);
|
|
2171
2446
|
inputs.forEach((input, index) => {
|
|
2172
2447
|
var _a2;
|
|
@@ -2176,15 +2451,17 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2176
2451
|
const group = (_a2 = input.parentElement) == null ? void 0 : _a2.querySelector(".fb-switcher-group");
|
|
2177
2452
|
if (group) {
|
|
2178
2453
|
group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
|
|
2454
|
+
const isPreset = isPresetButton(btn);
|
|
2179
2455
|
if (btn.dataset.value === newVal) {
|
|
2180
|
-
applySelectedStyle(btn);
|
|
2456
|
+
applySelectedStyle(btn, isPreset);
|
|
2181
2457
|
} else {
|
|
2182
|
-
applyUnselectedStyle(btn);
|
|
2458
|
+
applyUnselectedStyle(btn, isPreset);
|
|
2183
2459
|
}
|
|
2184
2460
|
});
|
|
2185
2461
|
}
|
|
2186
2462
|
input.classList.remove("invalid");
|
|
2187
2463
|
input.title = "";
|
|
2464
|
+
clearFieldError(input);
|
|
2188
2465
|
}
|
|
2189
2466
|
});
|
|
2190
2467
|
if (value.length !== inputs.length) {
|
|
@@ -2202,16 +2479,212 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2202
2479
|
const group = (_a = input.parentElement) == null ? void 0 : _a.querySelector(".fb-switcher-group");
|
|
2203
2480
|
if (group) {
|
|
2204
2481
|
group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
|
|
2482
|
+
const isPreset = isPresetButton(btn);
|
|
2205
2483
|
if (btn.dataset.value === newVal) {
|
|
2206
|
-
applySelectedStyle(btn);
|
|
2484
|
+
applySelectedStyle(btn, isPreset);
|
|
2207
2485
|
} else {
|
|
2208
|
-
applyUnselectedStyle(btn);
|
|
2486
|
+
applyUnselectedStyle(btn, isPreset);
|
|
2209
2487
|
}
|
|
2210
2488
|
});
|
|
2211
2489
|
}
|
|
2212
2490
|
input.classList.remove("invalid");
|
|
2213
2491
|
input.title = "";
|
|
2492
|
+
clearFieldError(input);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// src/components/boolean.ts
|
|
2498
|
+
var TOGGLE_W = 36;
|
|
2499
|
+
var TOGGLE_H = 20;
|
|
2500
|
+
var KNOB = 16;
|
|
2501
|
+
function ensureStyles(doc) {
|
|
2502
|
+
const ID = "fb-boolean-styles";
|
|
2503
|
+
if (doc.getElementById(ID)) return;
|
|
2504
|
+
const style = doc.createElement("style");
|
|
2505
|
+
style.id = ID;
|
|
2506
|
+
style.textContent = `
|
|
2507
|
+
.fb-toggle {
|
|
2508
|
+
position: relative;
|
|
2509
|
+
display: inline-block;
|
|
2510
|
+
width: ${TOGGLE_W}px;
|
|
2511
|
+
height: ${TOGGLE_H}px;
|
|
2512
|
+
border-radius: ${TOGGLE_H}px;
|
|
2513
|
+
background: var(--fb-border-color);
|
|
2514
|
+
transition: background-color var(--fb-transition-duration);
|
|
2515
|
+
flex-shrink: 0;
|
|
2516
|
+
}
|
|
2517
|
+
.fb-toggle::after {
|
|
2518
|
+
content: "";
|
|
2519
|
+
position: absolute;
|
|
2520
|
+
top: ${(TOGGLE_H - KNOB) / 2}px;
|
|
2521
|
+
left: ${(TOGGLE_H - KNOB) / 2}px;
|
|
2522
|
+
width: ${KNOB}px;
|
|
2523
|
+
height: ${KNOB}px;
|
|
2524
|
+
border-radius: 50%;
|
|
2525
|
+
background: #ffffff;
|
|
2526
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|
2527
|
+
transition: transform var(--fb-transition-duration);
|
|
2528
|
+
}
|
|
2529
|
+
.fb-toggle.fb-on {
|
|
2530
|
+
background: var(--fb-primary-color);
|
|
2531
|
+
}
|
|
2532
|
+
.fb-toggle.fb-on::after {
|
|
2533
|
+
transform: translateX(${TOGGLE_W - KNOB - (TOGGLE_H - KNOB)}px);
|
|
2534
|
+
}
|
|
2535
|
+
.fb-toggle-row {
|
|
2536
|
+
display: flex;
|
|
2537
|
+
align-items: center;
|
|
2538
|
+
gap: 12px;
|
|
2539
|
+
padding: 12px 14px;
|
|
2540
|
+
background: var(--fb-surface-soft-color);
|
|
2541
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
2542
|
+
border-radius: var(--fb-border-radius);
|
|
2543
|
+
cursor: pointer;
|
|
2544
|
+
user-select: none;
|
|
2545
|
+
}
|
|
2546
|
+
.fb-toggle-row[aria-disabled="true"] {
|
|
2547
|
+
cursor: default;
|
|
2548
|
+
opacity: 0.7;
|
|
2549
|
+
}
|
|
2550
|
+
.fb-toggle-row:focus-visible {
|
|
2551
|
+
outline: var(--fb-focus-ring-width) solid var(--fb-focus-ring-color);
|
|
2552
|
+
outline-offset: var(--fb-focus-ring-offset);
|
|
2553
|
+
}
|
|
2554
|
+
.fb-toggle-text { flex: 1; min-width: 0; }
|
|
2555
|
+
.fb-toggle-title {
|
|
2556
|
+
display: flex;
|
|
2557
|
+
align-items: center;
|
|
2558
|
+
gap: 4px;
|
|
2559
|
+
font-size: var(--fb-font-size);
|
|
2560
|
+
font-weight: 500;
|
|
2561
|
+
color: var(--fb-text-color);
|
|
2562
|
+
line-height: 1.3;
|
|
2214
2563
|
}
|
|
2564
|
+
.fb-toggle-subtitle {
|
|
2565
|
+
font-size: var(--fb-font-size-small);
|
|
2566
|
+
color: var(--fb-text-secondary-color);
|
|
2567
|
+
margin-top: 2px;
|
|
2568
|
+
line-height: 1.35;
|
|
2569
|
+
}
|
|
2570
|
+
.fb-toggle-info {
|
|
2571
|
+
flex: 0 0 14px;
|
|
2572
|
+
display: inline-flex;
|
|
2573
|
+
align-items: center;
|
|
2574
|
+
justify-content: center;
|
|
2575
|
+
width: 14px;
|
|
2576
|
+
height: 14px;
|
|
2577
|
+
border-radius: 50%;
|
|
2578
|
+
background: var(--fb-border-color);
|
|
2579
|
+
color: #fff;
|
|
2580
|
+
font-size: 10px;
|
|
2581
|
+
font-weight: 700;
|
|
2582
|
+
font-style: italic;
|
|
2583
|
+
font-family: serif;
|
|
2584
|
+
cursor: help;
|
|
2585
|
+
}
|
|
2586
|
+
`;
|
|
2587
|
+
doc.head.appendChild(style);
|
|
2588
|
+
}
|
|
2589
|
+
function parseBool(v) {
|
|
2590
|
+
if (typeof v === "boolean") return v;
|
|
2591
|
+
if (typeof v === "string") return v === "true" || v === "on" || v === "1";
|
|
2592
|
+
return false;
|
|
2593
|
+
}
|
|
2594
|
+
function renderBooleanElement(element, ctx, wrapper, pathKey) {
|
|
2595
|
+
var _a;
|
|
2596
|
+
ensureStyles(document);
|
|
2597
|
+
const state = ctx.state;
|
|
2598
|
+
const readonly = isElementReadonly(element, state, ctx);
|
|
2599
|
+
const prefillRaw = ctx.prefill[element.key];
|
|
2600
|
+
const initial = prefillRaw !== void 0 ? parseBool(prefillRaw) : parseBool(element.default);
|
|
2601
|
+
const hiddenInput = document.createElement("input");
|
|
2602
|
+
hiddenInput.type = "hidden";
|
|
2603
|
+
hiddenInput.name = pathKey;
|
|
2604
|
+
hiddenInput.value = initial ? "true" : "false";
|
|
2605
|
+
const row = document.createElement("div");
|
|
2606
|
+
row.className = "fb-toggle-row";
|
|
2607
|
+
row.setAttribute("role", "switch");
|
|
2608
|
+
row.setAttribute("aria-checked", initial ? "true" : "false");
|
|
2609
|
+
if (readonly) {
|
|
2610
|
+
row.setAttribute("aria-disabled", "true");
|
|
2611
|
+
} else {
|
|
2612
|
+
row.tabIndex = 0;
|
|
2613
|
+
}
|
|
2614
|
+
const pill = document.createElement("span");
|
|
2615
|
+
pill.className = "fb-toggle" + (initial ? " fb-on" : "");
|
|
2616
|
+
pill.setAttribute("aria-hidden", "true");
|
|
2617
|
+
row.appendChild(pill);
|
|
2618
|
+
const textBlock = document.createElement("div");
|
|
2619
|
+
textBlock.className = "fb-toggle-text";
|
|
2620
|
+
const titleEl = document.createElement("div");
|
|
2621
|
+
titleEl.className = "fb-toggle-title";
|
|
2622
|
+
titleEl.appendChild(document.createTextNode((_a = element.label) != null ? _a : ""));
|
|
2623
|
+
if (element.description) {
|
|
2624
|
+
const info = document.createElement("span");
|
|
2625
|
+
info.className = "fb-toggle-info";
|
|
2626
|
+
info.textContent = "i";
|
|
2627
|
+
info.title = element.description;
|
|
2628
|
+
titleEl.appendChild(info);
|
|
2629
|
+
}
|
|
2630
|
+
textBlock.appendChild(titleEl);
|
|
2631
|
+
if (element.hint) {
|
|
2632
|
+
const subtitle = document.createElement("div");
|
|
2633
|
+
subtitle.className = "fb-toggle-subtitle";
|
|
2634
|
+
subtitle.textContent = element.hint;
|
|
2635
|
+
textBlock.appendChild(subtitle);
|
|
2636
|
+
}
|
|
2637
|
+
row.appendChild(textBlock);
|
|
2638
|
+
if (!readonly) {
|
|
2639
|
+
const toggle = () => {
|
|
2640
|
+
const next = hiddenInput.value !== "true";
|
|
2641
|
+
hiddenInput.value = next ? "true" : "false";
|
|
2642
|
+
pill.classList.toggle("fb-on", next);
|
|
2643
|
+
row.setAttribute("aria-checked", next ? "true" : "false");
|
|
2644
|
+
if (ctx.instance) ctx.instance.triggerOnChange(pathKey, next);
|
|
2645
|
+
};
|
|
2646
|
+
row.addEventListener("click", (e) => {
|
|
2647
|
+
var _a2;
|
|
2648
|
+
if ((_a2 = e.target) == null ? void 0 : _a2.classList.contains("fb-toggle-info")) {
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
toggle();
|
|
2652
|
+
});
|
|
2653
|
+
row.addEventListener("keydown", (e) => {
|
|
2654
|
+
if (e.key === " " || e.key === "Enter") {
|
|
2655
|
+
e.preventDefault();
|
|
2656
|
+
toggle();
|
|
2657
|
+
}
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
wrapper.appendChild(hiddenInput);
|
|
2661
|
+
wrapper.appendChild(row);
|
|
2662
|
+
}
|
|
2663
|
+
function validateBooleanElement(element, key, context) {
|
|
2664
|
+
var _a;
|
|
2665
|
+
const { scopeRoot } = context;
|
|
2666
|
+
const input = scopeRoot.querySelector(
|
|
2667
|
+
`input[type="hidden"][name="${key}"]`
|
|
2668
|
+
);
|
|
2669
|
+
const raw = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
2670
|
+
const value = parseBool(raw);
|
|
2671
|
+
const errors = [];
|
|
2672
|
+
return { value, errors };
|
|
2673
|
+
}
|
|
2674
|
+
function updateBooleanField(_element, fieldPath, value, context) {
|
|
2675
|
+
var _a;
|
|
2676
|
+
const { scopeRoot } = context;
|
|
2677
|
+
const input = scopeRoot.querySelector(
|
|
2678
|
+
`input[type="hidden"][name="${fieldPath}"]`
|
|
2679
|
+
);
|
|
2680
|
+
if (!input) return;
|
|
2681
|
+
const bool = parseBool(value);
|
|
2682
|
+
input.value = bool ? "true" : "false";
|
|
2683
|
+
const row = (_a = input.parentElement) == null ? void 0 : _a.querySelector(".fb-toggle-row");
|
|
2684
|
+
if (row) {
|
|
2685
|
+
row.setAttribute("aria-checked", bool ? "true" : "false");
|
|
2686
|
+
const pill = row.querySelector(".fb-toggle");
|
|
2687
|
+
if (pill) pill.classList.toggle("fb-on", bool);
|
|
2215
2688
|
}
|
|
2216
2689
|
}
|
|
2217
2690
|
|
|
@@ -2317,14 +2790,20 @@ function ensureFileStyles() {
|
|
|
2317
2790
|
}
|
|
2318
2791
|
|
|
2319
2792
|
/* \u2500\u2500\u2500 Wide single-file add tile (empty state) \u2500\u2500\u2500 */
|
|
2793
|
+
/* Flex-wraps: side-by-side when wide enough, stacks upload/library
|
|
2794
|
+
vertically when narrow (e.g. inside a 50/50 container column). */
|
|
2320
2795
|
.fb-wide-tile {
|
|
2321
2796
|
width: 100%;
|
|
2797
|
+
box-sizing: border-box;
|
|
2322
2798
|
border-radius: 0.75rem;
|
|
2323
2799
|
border: 1px dashed #60a5fa;
|
|
2324
2800
|
background: rgba(239,246,255,0.5);
|
|
2325
2801
|
display: flex;
|
|
2802
|
+
flex-wrap: wrap;
|
|
2803
|
+
align-items: stretch;
|
|
2804
|
+
gap: 0;
|
|
2326
2805
|
overflow: hidden;
|
|
2327
|
-
height: 180px;
|
|
2806
|
+
min-height: 180px;
|
|
2328
2807
|
transition: border-color 150ms, background 150ms, box-shadow 150ms;
|
|
2329
2808
|
cursor: pointer;
|
|
2330
2809
|
}
|
|
@@ -2338,9 +2817,12 @@ function ensureFileStyles() {
|
|
|
2338
2817
|
box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
|
|
2339
2818
|
}
|
|
2340
2819
|
|
|
2341
|
-
/* Upload zone inside wide tile
|
|
2820
|
+
/* Upload zone inside wide tile.
|
|
2821
|
+
flex: 1 1 220px \u2014 wants at least 220px; if the container can't fit
|
|
2822
|
+
upload + library on one row (~220 + 176), library wraps below. */
|
|
2342
2823
|
.fb-wide-tile-upload {
|
|
2343
|
-
flex: 1;
|
|
2824
|
+
flex: 1 1 220px;
|
|
2825
|
+
min-height: 140px;
|
|
2344
2826
|
display: flex;
|
|
2345
2827
|
flex-direction: column;
|
|
2346
2828
|
align-items: center;
|
|
@@ -2353,24 +2835,21 @@ function ensureFileStyles() {
|
|
|
2353
2835
|
background: transparent;
|
|
2354
2836
|
border: none;
|
|
2355
2837
|
font-family: inherit;
|
|
2838
|
+
/* Dashed separator from library: right side when in a row, bottom when
|
|
2839
|
+
wrapped (the line then sits between the two stacked cards). */
|
|
2840
|
+
border-right: 1px dashed rgba(96,165,250,0.5);
|
|
2356
2841
|
}
|
|
2357
2842
|
.fb-wide-tile-upload:hover {
|
|
2358
2843
|
background: rgba(191,219,254,0.25);
|
|
2359
2844
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
.
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
border-left: 1px dashed rgba(96,165,250,0.5);
|
|
2366
|
-
background: transparent;
|
|
2367
|
-
flex-shrink: 0;
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2370
|
-
/* Library zone inside wide tile */
|
|
2845
|
+
/* Library zone inside wide tile.
|
|
2846
|
+
flex: 0 0 176px \u2014 fixed 176px, never grows. Upload fills the rest in
|
|
2847
|
+
row layout. When the tile wraps to two rows on narrow containers,
|
|
2848
|
+
library stays 176px wide on its own row (left-aligned), preserving the
|
|
2849
|
+
visual hierarchy "upload > library" in both layouts. */
|
|
2371
2850
|
.fb-wide-tile-library {
|
|
2372
|
-
|
|
2373
|
-
|
|
2851
|
+
flex: 0 0 176px;
|
|
2852
|
+
min-height: 120px;
|
|
2374
2853
|
display: flex;
|
|
2375
2854
|
flex-direction: column;
|
|
2376
2855
|
align-items: center;
|
|
@@ -2387,6 +2866,10 @@ function ensureFileStyles() {
|
|
|
2387
2866
|
.fb-wide-tile-library:hover {
|
|
2388
2867
|
background: rgba(191,219,254,0.25);
|
|
2389
2868
|
}
|
|
2869
|
+
/* Narrow-tile mode lives in a separate <style> tag (see below) \u2014 the
|
|
2870
|
+
@container rule is appended only when the runtime actually supports
|
|
2871
|
+
container queries, so jsdom (which doesn't) never sees it and stays
|
|
2872
|
+
quiet in test logs. */
|
|
2390
2873
|
|
|
2391
2874
|
/* \u2500\u2500\u2500 Multi-file outer grid container \u2500\u2500\u2500 */
|
|
2392
2875
|
.fb-multi-outer {
|
|
@@ -2765,6 +3248,39 @@ function ensureFileStyles() {
|
|
|
2765
3248
|
}
|
|
2766
3249
|
`;
|
|
2767
3250
|
document.head.appendChild(style);
|
|
3251
|
+
if (typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("container-type", "inline-size")) {
|
|
3252
|
+
const cq = document.createElement("style");
|
|
3253
|
+
cq.setAttribute("data-fb-file-styles-cq", "true");
|
|
3254
|
+
cq.textContent = `
|
|
3255
|
+
.fb-wide-tile { container-type: inline-size; }
|
|
3256
|
+
@container (max-width: 408px) {
|
|
3257
|
+
.fb-wide-tile-upload {
|
|
3258
|
+
border-right: none;
|
|
3259
|
+
border-bottom: 1px dashed rgba(96,165,250,0.5);
|
|
3260
|
+
}
|
|
3261
|
+
.fb-wide-tile-library {
|
|
3262
|
+
flex: 1 0 100%;
|
|
3263
|
+
min-height: 0;
|
|
3264
|
+
flex-direction: row;
|
|
3265
|
+
gap: 6px;
|
|
3266
|
+
padding: 8px 12px;
|
|
3267
|
+
font-size: 12px;
|
|
3268
|
+
}
|
|
3269
|
+
.fb-wide-tile-library .fb-wide-tile-library-icon {
|
|
3270
|
+
width: 16px;
|
|
3271
|
+
height: 16px;
|
|
3272
|
+
}
|
|
3273
|
+
.fb-wide-tile-library .fb-wide-tile-library-label {
|
|
3274
|
+
font-size: 12px;
|
|
3275
|
+
font-weight: 500;
|
|
3276
|
+
}
|
|
3277
|
+
.fb-wide-tile-library .fb-wide-tile-library-hint {
|
|
3278
|
+
display: none;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
`;
|
|
3282
|
+
document.head.appendChild(cq);
|
|
3283
|
+
}
|
|
2768
3284
|
}
|
|
2769
3285
|
|
|
2770
3286
|
// src/components/file/dom.ts
|
|
@@ -3791,16 +4307,26 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3791
4307
|
}
|
|
3792
4308
|
return { accepted, errorMessage: errorParts.join(" \u2022 ") };
|
|
3793
4309
|
}
|
|
3794
|
-
async function uploadBatch(
|
|
4310
|
+
async function uploadBatch(opts) {
|
|
3795
4311
|
var _a;
|
|
3796
|
-
|
|
4312
|
+
const {
|
|
4313
|
+
accepted,
|
|
4314
|
+
listEl,
|
|
4315
|
+
state,
|
|
4316
|
+
shouldHideAddTile,
|
|
4317
|
+
buildSuccessTile,
|
|
4318
|
+
prepareForUpload
|
|
4319
|
+
} = opts;
|
|
4320
|
+
if (listEl && shouldHideAddTile) {
|
|
3797
4321
|
const tilesWrap = ensureTilesWrap(listEl);
|
|
3798
4322
|
const addTile = (_a = tilesWrap.querySelector(".fb-multi-add-tile-js")) != null ? _a : tilesWrap.querySelector(".fb-tile-add");
|
|
3799
4323
|
if (addTile) addTile.style.display = "none";
|
|
3800
4324
|
}
|
|
4325
|
+
prepareForUpload == null ? void 0 : prepareForUpload();
|
|
4326
|
+
const orderedIds = new Array(accepted.length).fill(null);
|
|
3801
4327
|
const failures = [];
|
|
3802
4328
|
await Promise.allSettled(
|
|
3803
|
-
accepted.map(async (file) => {
|
|
4329
|
+
accepted.map(async (file, index) => {
|
|
3804
4330
|
const placeholder = createUploadingTile(file.name, state);
|
|
3805
4331
|
if (listEl) {
|
|
3806
4332
|
const tilesWrap = ensureTilesWrap(listEl);
|
|
@@ -3813,20 +4339,24 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
|
3813
4339
|
type: file.type,
|
|
3814
4340
|
size: file.size,
|
|
3815
4341
|
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3816
|
-
file
|
|
4342
|
+
file
|
|
3817
4343
|
});
|
|
3818
|
-
|
|
4344
|
+
orderedIds[index] = rid;
|
|
4345
|
+
if (buildSuccessTile && placeholder.parentNode) {
|
|
4346
|
+
placeholder.replaceWith(buildSuccessTile(rid));
|
|
4347
|
+
} else {
|
|
4348
|
+
placeholder.remove();
|
|
4349
|
+
}
|
|
3819
4350
|
} catch (err) {
|
|
3820
4351
|
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
3821
4352
|
const cause = wrapped.cause;
|
|
3822
4353
|
const root = cause instanceof Error ? cause : cause !== void 0 ? new Error(String(cause)) : wrapped;
|
|
3823
4354
|
failures.push({ file, error: root });
|
|
3824
|
-
} finally {
|
|
3825
4355
|
placeholder.remove();
|
|
3826
4356
|
}
|
|
3827
4357
|
})
|
|
3828
4358
|
);
|
|
3829
|
-
return { failures };
|
|
4359
|
+
return { failures, orderedIds };
|
|
3830
4360
|
}
|
|
3831
4361
|
function buildBatchErrorMessage(filterError, failures, state) {
|
|
3832
4362
|
if (failures.length === 0) return filterError;
|
|
@@ -3838,69 +4368,72 @@ function buildBatchErrorMessage(filterError, failures, state) {
|
|
|
3838
4368
|
).join(" \u2022 ");
|
|
3839
4369
|
return filterError ? `${filterError} \u2022 ${uploadMsg}` : uploadMsg;
|
|
3840
4370
|
}
|
|
3841
|
-
function
|
|
4371
|
+
async function runMultiFileBatch(opts, files, listEl, errorTarget) {
|
|
4372
|
+
const {
|
|
4373
|
+
resourceIds,
|
|
4374
|
+
state,
|
|
4375
|
+
updateCallback,
|
|
4376
|
+
constraints,
|
|
4377
|
+
pathKey,
|
|
4378
|
+
instance,
|
|
4379
|
+
buildSuccessTile,
|
|
4380
|
+
prepareForUpload,
|
|
4381
|
+
coordinator
|
|
4382
|
+
} = opts;
|
|
4383
|
+
const { accepted, errorMessage } = filterAndSlice(
|
|
4384
|
+
files,
|
|
4385
|
+
coordinator.getOccupiedCount(),
|
|
4386
|
+
constraints,
|
|
4387
|
+
state
|
|
4388
|
+
);
|
|
4389
|
+
if (errorTarget) {
|
|
4390
|
+
if (errorMessage) showFileError(errorTarget, errorMessage);
|
|
4391
|
+
else clearFileError(errorTarget);
|
|
4392
|
+
}
|
|
4393
|
+
const handle = coordinator.beginBatch(accepted.length);
|
|
4394
|
+
const shouldHideAddTile = coordinator.getOccupiedCount() >= constraints.maxCount;
|
|
4395
|
+
const { failures, orderedIds } = await uploadBatch({
|
|
4396
|
+
accepted,
|
|
4397
|
+
listEl,
|
|
4398
|
+
state,
|
|
4399
|
+
shouldHideAddTile,
|
|
4400
|
+
buildSuccessTile,
|
|
4401
|
+
prepareForUpload
|
|
4402
|
+
});
|
|
4403
|
+
handle.setResults(orderedIds);
|
|
4404
|
+
if (instance && pathKey && !state.config.readonly) {
|
|
4405
|
+
instance.triggerOnChange(pathKey, resourceIds);
|
|
4406
|
+
}
|
|
4407
|
+
const { wasLast } = handle.end();
|
|
4408
|
+
if (wasLast) updateCallback();
|
|
4409
|
+
if (errorTarget) {
|
|
4410
|
+
const combined = buildBatchErrorMessage(errorMessage, failures, state);
|
|
4411
|
+
if (combined) showFileError(errorTarget, combined);
|
|
4412
|
+
else clearFileError(errorTarget);
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
function setupFilesDropHandler(opts) {
|
|
4416
|
+
const { filesContainer } = opts;
|
|
3842
4417
|
setupDragAndDrop(filesContainer, async (files) => {
|
|
3843
4418
|
var _a;
|
|
3844
|
-
const { accepted, errorMessage } = filterAndSlice(
|
|
3845
|
-
Array.from(files),
|
|
3846
|
-
resourceIds.length,
|
|
3847
|
-
constraints,
|
|
3848
|
-
state
|
|
3849
|
-
);
|
|
3850
|
-
if (errorMessage) {
|
|
3851
|
-
showFileError(filesContainer, errorMessage);
|
|
3852
|
-
} else {
|
|
3853
|
-
clearFileError(filesContainer);
|
|
3854
|
-
}
|
|
3855
4419
|
const list = (_a = filesContainer.querySelector(".files-list")) != null ? _a : filesContainer;
|
|
3856
|
-
|
|
3857
|
-
const combined = buildBatchErrorMessage(errorMessage, failures, state);
|
|
3858
|
-
if (combined) {
|
|
3859
|
-
showFileError(filesContainer, combined);
|
|
3860
|
-
} else {
|
|
3861
|
-
clearFileError(filesContainer);
|
|
3862
|
-
}
|
|
3863
|
-
updateCallback();
|
|
3864
|
-
if (instance && pathKey && !state.config.readonly) {
|
|
3865
|
-
instance.triggerOnChange(pathKey, resourceIds);
|
|
3866
|
-
}
|
|
4420
|
+
await runMultiFileBatch(opts, Array.from(files), list, filesContainer);
|
|
3867
4421
|
});
|
|
3868
4422
|
}
|
|
3869
|
-
function setupFilesPickerHandler(
|
|
4423
|
+
function setupFilesPickerHandler(opts) {
|
|
4424
|
+
const { filesPicker } = opts;
|
|
3870
4425
|
filesPicker.onchange = async () => {
|
|
4426
|
+
var _a, _b;
|
|
3871
4427
|
if (!filesPicker.files) return;
|
|
3872
|
-
const wrapperEl = filesPicker.closest("[data-files-wrapper]")
|
|
3873
|
-
const
|
|
4428
|
+
const wrapperEl = (_a = filesPicker.closest("[data-files-wrapper]")) != null ? _a : filesPicker.parentElement;
|
|
4429
|
+
const listEl = (_b = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list")) != null ? _b : null;
|
|
4430
|
+
await runMultiFileBatch(
|
|
4431
|
+
opts,
|
|
3874
4432
|
Array.from(filesPicker.files),
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
state
|
|
3878
|
-
);
|
|
3879
|
-
if (errorMessage && wrapperEl) {
|
|
3880
|
-
showFileError(wrapperEl, errorMessage);
|
|
3881
|
-
} else if (wrapperEl) {
|
|
3882
|
-
clearFileError(wrapperEl);
|
|
3883
|
-
}
|
|
3884
|
-
const listEl = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list");
|
|
3885
|
-
const { failures } = await uploadBatch(
|
|
3886
|
-
accepted,
|
|
3887
|
-
resourceIds,
|
|
3888
|
-
listEl != null ? listEl : null,
|
|
3889
|
-
state
|
|
4433
|
+
listEl,
|
|
4434
|
+
wrapperEl
|
|
3890
4435
|
);
|
|
3891
|
-
if (wrapperEl) {
|
|
3892
|
-
const combined = buildBatchErrorMessage(errorMessage, failures, state);
|
|
3893
|
-
if (combined) {
|
|
3894
|
-
showFileError(wrapperEl, combined);
|
|
3895
|
-
} else {
|
|
3896
|
-
clearFileError(wrapperEl);
|
|
3897
|
-
}
|
|
3898
|
-
}
|
|
3899
|
-
updateCallback();
|
|
3900
4436
|
filesPicker.value = "";
|
|
3901
|
-
if (instance && pathKey && !state.config.readonly) {
|
|
3902
|
-
instance.triggerOnChange(pathKey, resourceIds);
|
|
3903
|
-
}
|
|
3904
4437
|
};
|
|
3905
4438
|
}
|
|
3906
4439
|
|
|
@@ -3942,16 +4475,6 @@ function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSi
|
|
|
3942
4475
|
}
|
|
3943
4476
|
return null;
|
|
3944
4477
|
}
|
|
3945
|
-
function readCurrentResourceIds(wrapper) {
|
|
3946
|
-
const raw = wrapper.dataset.resourceIds;
|
|
3947
|
-
if (!raw) return [];
|
|
3948
|
-
try {
|
|
3949
|
-
const parsed = JSON.parse(raw);
|
|
3950
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
3951
|
-
} catch {
|
|
3952
|
-
return [];
|
|
3953
|
-
}
|
|
3954
|
-
}
|
|
3955
4478
|
function registerPickedResource(resource, state) {
|
|
3956
4479
|
var _a;
|
|
3957
4480
|
const existing = state.resourceIndex.get(resource.resourceId);
|
|
@@ -3967,14 +4490,28 @@ function extractPickerError(error, state) {
|
|
|
3967
4490
|
if (error instanceof Error && error.message) return error.message;
|
|
3968
4491
|
return t("pickerError", state);
|
|
3969
4492
|
}
|
|
3970
|
-
async function handleLibraryPickMulti(
|
|
4493
|
+
async function handleLibraryPickMulti(opts) {
|
|
3971
4494
|
var _a;
|
|
4495
|
+
const {
|
|
4496
|
+
state,
|
|
4497
|
+
element,
|
|
4498
|
+
wrapper,
|
|
4499
|
+
fieldPath,
|
|
4500
|
+
resourceIds,
|
|
4501
|
+
maxCount,
|
|
4502
|
+
updateCallback,
|
|
4503
|
+
instance,
|
|
4504
|
+
coordinator,
|
|
4505
|
+
list,
|
|
4506
|
+
buildSuccessTile
|
|
4507
|
+
} = opts;
|
|
3972
4508
|
if (!state.config.pickExistingFiles) return;
|
|
3973
4509
|
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
3974
4510
|
const allowedMimes = getAllowedMimes(element.accept);
|
|
3975
4511
|
const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
|
|
3976
|
-
const
|
|
3977
|
-
const
|
|
4512
|
+
const knownRids = coordinator.getAllKnownRids();
|
|
4513
|
+
const existingSet = new Set(knownRids);
|
|
4514
|
+
const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - coordinator.getOccupiedCount());
|
|
3978
4515
|
let picked;
|
|
3979
4516
|
try {
|
|
3980
4517
|
picked = await state.config.pickExistingFiles({
|
|
@@ -3983,14 +4520,15 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
3983
4520
|
accept: buildAcceptContext(element),
|
|
3984
4521
|
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
3985
4522
|
remainingSlots: remaining === Infinity ? void 0 : remaining,
|
|
3986
|
-
|
|
4523
|
+
// Hand the host every rid that's already selected (committed or
|
|
4524
|
+
// staged) so it can grey them out or filter them.
|
|
4525
|
+
selectedResourceIds: knownRids
|
|
3987
4526
|
});
|
|
3988
4527
|
} catch (error) {
|
|
3989
4528
|
showFileError(wrapper, extractPickerError(error, state));
|
|
3990
4529
|
return;
|
|
3991
4530
|
}
|
|
3992
4531
|
if (picked.length === 0) return;
|
|
3993
|
-
const existingSet = new Set(currentIds);
|
|
3994
4532
|
const seen = /* @__PURE__ */ new Set();
|
|
3995
4533
|
const deduped = picked.filter((r) => {
|
|
3996
4534
|
if (existingSet.has(r.resourceId)) return false;
|
|
@@ -4008,10 +4546,18 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
4008
4546
|
);
|
|
4009
4547
|
return err === null;
|
|
4010
4548
|
});
|
|
4011
|
-
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount -
|
|
4549
|
+
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - coordinator.getOccupiedCount());
|
|
4012
4550
|
const accepted = validItems.slice(0, freshRemaining);
|
|
4013
4551
|
const skipped = validItems.length - accepted.length;
|
|
4014
|
-
if (accepted.length === 0)
|
|
4552
|
+
if (accepted.length === 0) {
|
|
4553
|
+
if (skipped > 0) {
|
|
4554
|
+
showFileError(
|
|
4555
|
+
wrapper,
|
|
4556
|
+
t("filesLimitExceeded", state, { skipped, max: maxCount })
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4559
|
+
return;
|
|
4560
|
+
}
|
|
4015
4561
|
clearFileError(wrapper);
|
|
4016
4562
|
if (skipped > 0) {
|
|
4017
4563
|
showFileError(
|
|
@@ -4021,13 +4567,22 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
|
|
|
4021
4567
|
}
|
|
4022
4568
|
for (const resource of accepted) {
|
|
4023
4569
|
registerPickedResource(resource, state);
|
|
4024
|
-
resourceIds.push(resource.resourceId);
|
|
4025
4570
|
}
|
|
4026
|
-
|
|
4027
|
-
|
|
4571
|
+
const acceptedIds = accepted.map((r) => r.resourceId);
|
|
4572
|
+
const handle = coordinator.beginBatch(accepted.length);
|
|
4573
|
+
handle.setResults(acceptedIds);
|
|
4028
4574
|
if (!state.config.readonly) {
|
|
4029
4575
|
instance.triggerOnChange(fieldPath, resourceIds);
|
|
4030
4576
|
}
|
|
4577
|
+
const { wasLast } = handle.end();
|
|
4578
|
+
if (wasLast) {
|
|
4579
|
+
updateCallback();
|
|
4580
|
+
} else {
|
|
4581
|
+
const tilesWrap = ensureTilesWrap(list);
|
|
4582
|
+
for (const rid of acceptedIds) {
|
|
4583
|
+
tilesWrap.appendChild(buildSuccessTile(rid));
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4031
4586
|
}
|
|
4032
4587
|
async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
|
|
4033
4588
|
var _a, _b;
|
|
@@ -4134,21 +4689,21 @@ function buildWideTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragO
|
|
|
4134
4689
|
};
|
|
4135
4690
|
outer.appendChild(uploadBtn);
|
|
4136
4691
|
if (hasLibrary && onLibraryClick) {
|
|
4137
|
-
const divider = document.createElement("div");
|
|
4138
|
-
divider.className = "fb-wide-tile-divider";
|
|
4139
|
-
outer.appendChild(divider);
|
|
4140
4692
|
const libBtn = document.createElement("button");
|
|
4141
4693
|
libBtn.type = "button";
|
|
4142
4694
|
libBtn.className = "fb-wide-tile-library fb-file-library-card";
|
|
4143
4695
|
const libIcon = document.createElement("span");
|
|
4696
|
+
libIcon.className = "fb-wide-tile-library-icon";
|
|
4144
4697
|
libIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
|
|
4145
4698
|
libIcon.innerHTML = ICON_LIBRARY2;
|
|
4146
4699
|
libBtn.appendChild(libIcon);
|
|
4147
4700
|
const libLabel = document.createElement("div");
|
|
4701
|
+
libLabel.className = "fb-wide-tile-library-label";
|
|
4148
4702
|
libLabel.style.cssText = "font-size:13px;font-weight:600;text-align:center;";
|
|
4149
4703
|
libLabel.textContent = t("fromLibrary", state);
|
|
4150
4704
|
libBtn.appendChild(libLabel);
|
|
4151
4705
|
const libHint = document.createElement("div");
|
|
4706
|
+
libHint.className = "fb-wide-tile-library-hint";
|
|
4152
4707
|
libHint.style.cssText = "font-size:11px;opacity:0.75;text-align:center;";
|
|
4153
4708
|
libHint.textContent = t("libraryHint", state);
|
|
4154
4709
|
libBtn.appendChild(libHint);
|
|
@@ -4355,6 +4910,14 @@ function buildMetaDot() {
|
|
|
4355
4910
|
return dot;
|
|
4356
4911
|
}
|
|
4357
4912
|
var gridResizeObservers = /* @__PURE__ */ new WeakMap();
|
|
4913
|
+
function disposePlaceholdersForUpload(container) {
|
|
4914
|
+
const observer = gridResizeObservers.get(container);
|
|
4915
|
+
if (observer) {
|
|
4916
|
+
observer.disconnect();
|
|
4917
|
+
gridResizeObservers.delete(container);
|
|
4918
|
+
}
|
|
4919
|
+
container.querySelectorAll(".fb-multi-placeholder").forEach((p) => p.remove());
|
|
4920
|
+
}
|
|
4358
4921
|
function renderResourcePills(opts) {
|
|
4359
4922
|
var _a;
|
|
4360
4923
|
const {
|
|
@@ -4709,16 +5272,19 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
4709
5272
|
filesPicker.click();
|
|
4710
5273
|
};
|
|
4711
5274
|
const onLibraryPick = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4712
|
-
handleLibraryPickMulti(
|
|
5275
|
+
handleLibraryPickMulti({
|
|
4713
5276
|
state,
|
|
4714
5277
|
element,
|
|
4715
|
-
filesWrapper,
|
|
4716
|
-
pathKey,
|
|
4717
|
-
initialFiles,
|
|
4718
|
-
maxFiles,
|
|
4719
|
-
updateFilesDisplay,
|
|
4720
|
-
ctx.instance
|
|
4721
|
-
|
|
5278
|
+
wrapper: filesWrapper,
|
|
5279
|
+
fieldPath: pathKey,
|
|
5280
|
+
resourceIds: initialFiles,
|
|
5281
|
+
maxCount: maxFiles,
|
|
5282
|
+
updateCallback: updateFilesDisplay,
|
|
5283
|
+
instance: ctx.instance,
|
|
5284
|
+
coordinator,
|
|
5285
|
+
list,
|
|
5286
|
+
buildSuccessTile
|
|
5287
|
+
}).catch((err) => {
|
|
4722
5288
|
console.error("Library pick failed:", err);
|
|
4723
5289
|
});
|
|
4724
5290
|
} : null;
|
|
@@ -4729,41 +5295,160 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
4729
5295
|
rids: initialFiles,
|
|
4730
5296
|
state,
|
|
4731
5297
|
onRemove: currentlyReadonly ? null : (ridToRemove) => {
|
|
4732
|
-
var _a2;
|
|
5298
|
+
var _a2, _b2;
|
|
4733
5299
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
|
|
4734
5300
|
const index = initialFiles.indexOf(ridToRemove);
|
|
4735
5301
|
if (index > -1) initialFiles.splice(index, 1);
|
|
4736
|
-
|
|
5302
|
+
if (coordinator.hasInFlightBatches()) {
|
|
5303
|
+
pendingRemovals.add(ridToRemove);
|
|
5304
|
+
(_b2 = list.querySelector(`[data-resource-id="${ridToRemove}"]`)) == null ? void 0 : _b2.remove();
|
|
5305
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5306
|
+
} else {
|
|
5307
|
+
updateFilesDisplay();
|
|
5308
|
+
}
|
|
5309
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5310
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5311
|
+
}
|
|
4737
5312
|
},
|
|
4738
5313
|
maxCount: maxFiles < Infinity ? maxFiles : void 0,
|
|
4739
5314
|
isReadonly: currentlyReadonly,
|
|
4740
5315
|
onLibraryPick: currentlyReadonly ? null : onLibraryPick,
|
|
4741
5316
|
element,
|
|
4742
5317
|
onClearAll: currentlyReadonly ? void 0 : () => {
|
|
5318
|
+
var _a2, _b2;
|
|
5319
|
+
for (const rid of initialFiles) {
|
|
5320
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.file);
|
|
5321
|
+
}
|
|
4743
5322
|
initialFiles.splice(0);
|
|
4744
|
-
|
|
5323
|
+
if (coordinator.hasInFlightBatches()) {
|
|
5324
|
+
const visibleTiles = list.querySelectorAll("[data-resource-id]");
|
|
5325
|
+
for (const tile of visibleTiles) {
|
|
5326
|
+
const rid = tile.dataset.resourceId;
|
|
5327
|
+
if (rid) {
|
|
5328
|
+
releaseLocalFileUrl((_b2 = state.resourceIndex.get(rid)) == null ? void 0 : _b2.file);
|
|
5329
|
+
pendingRemovals.add(rid);
|
|
5330
|
+
}
|
|
5331
|
+
tile.remove();
|
|
5332
|
+
}
|
|
5333
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5334
|
+
} else {
|
|
5335
|
+
updateFilesDisplay();
|
|
5336
|
+
}
|
|
5337
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5338
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5339
|
+
}
|
|
4745
5340
|
},
|
|
4746
5341
|
openPicker
|
|
4747
5342
|
});
|
|
4748
5343
|
}
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
5344
|
+
let inFlightFiles = 0;
|
|
5345
|
+
let activeBatches = 0;
|
|
5346
|
+
let nextBatchOrdinal = 0;
|
|
5347
|
+
let nextCommitOrdinal = 0;
|
|
5348
|
+
const stagedResults = /* @__PURE__ */ new Map();
|
|
5349
|
+
const batchReservations = /* @__PURE__ */ new Map();
|
|
5350
|
+
const pendingRemovals = /* @__PURE__ */ new Set();
|
|
5351
|
+
const drainContiguousStagedResults = () => {
|
|
5352
|
+
var _a2;
|
|
5353
|
+
while (stagedResults.has(nextCommitOrdinal)) {
|
|
5354
|
+
const ordinal = nextCommitOrdinal;
|
|
5355
|
+
const ids = stagedResults.get(ordinal);
|
|
5356
|
+
stagedResults.delete(ordinal);
|
|
5357
|
+
nextCommitOrdinal += 1;
|
|
5358
|
+
const reservation = (_a2 = batchReservations.get(ordinal)) != null ? _a2 : 0;
|
|
5359
|
+
batchReservations.delete(ordinal);
|
|
5360
|
+
inFlightFiles -= reservation;
|
|
5361
|
+
for (const rid of ids) {
|
|
5362
|
+
if (rid === null) continue;
|
|
5363
|
+
if (pendingRemovals.has(rid)) {
|
|
5364
|
+
pendingRemovals.delete(rid);
|
|
5365
|
+
continue;
|
|
5366
|
+
}
|
|
5367
|
+
initialFiles.push(rid);
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5371
|
+
};
|
|
5372
|
+
const coordinator = {
|
|
5373
|
+
getOccupiedCount: () => initialFiles.length + inFlightFiles,
|
|
5374
|
+
getAllKnownRids: () => {
|
|
5375
|
+
const out = [...initialFiles];
|
|
5376
|
+
for (const ids of stagedResults.values()) {
|
|
5377
|
+
for (const rid of ids) {
|
|
5378
|
+
if (rid !== null) out.push(rid);
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
return out;
|
|
5382
|
+
},
|
|
5383
|
+
hasInFlightBatches: () => activeBatches > 0 || batchReservations.size > 0,
|
|
5384
|
+
wasRemovedDuringBatch: (rid) => pendingRemovals.has(rid),
|
|
5385
|
+
beginBatch: (count) => {
|
|
5386
|
+
inFlightFiles += count;
|
|
5387
|
+
activeBatches += 1;
|
|
5388
|
+
const ordinal = nextBatchOrdinal++;
|
|
5389
|
+
batchReservations.set(ordinal, count);
|
|
5390
|
+
return {
|
|
5391
|
+
setResults: (orderedIds) => {
|
|
5392
|
+
stagedResults.set(ordinal, orderedIds);
|
|
5393
|
+
drainContiguousStagedResults();
|
|
5394
|
+
},
|
|
5395
|
+
end: () => {
|
|
5396
|
+
activeBatches -= 1;
|
|
5397
|
+
const wasLast = activeBatches === 0 && batchReservations.size === 0;
|
|
5398
|
+
if (wasLast) {
|
|
5399
|
+
pendingRemovals.clear();
|
|
5400
|
+
}
|
|
5401
|
+
return { wasLast };
|
|
5402
|
+
}
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
};
|
|
5406
|
+
const buildSuccessTile = (rid) => {
|
|
5407
|
+
const currentlyReadonly = isElementReadonly(element, state);
|
|
5408
|
+
return buildPreviewTile(
|
|
5409
|
+
rid,
|
|
5410
|
+
state,
|
|
5411
|
+
!currentlyReadonly,
|
|
5412
|
+
currentlyReadonly ? null : () => {
|
|
5413
|
+
var _a2, _b2, _c;
|
|
5414
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.file);
|
|
5415
|
+
const idx = initialFiles.indexOf(rid);
|
|
5416
|
+
if (idx > -1) {
|
|
5417
|
+
initialFiles.splice(idx, 1);
|
|
5418
|
+
if (coordinator.hasInFlightBatches()) {
|
|
5419
|
+
pendingRemovals.add(rid);
|
|
5420
|
+
(_b2 = list.querySelector(`[data-resource-id="${rid}"]`)) == null ? void 0 : _b2.remove();
|
|
5421
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
5422
|
+
} else {
|
|
5423
|
+
updateFilesDisplay();
|
|
5424
|
+
}
|
|
5425
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5426
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5427
|
+
}
|
|
5428
|
+
return;
|
|
5429
|
+
}
|
|
5430
|
+
pendingRemovals.add(rid);
|
|
5431
|
+
(_c = list.querySelector(`[data-resource-id="${rid}"]`)) == null ? void 0 : _c.remove();
|
|
5432
|
+
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5433
|
+
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
);
|
|
5437
|
+
};
|
|
5438
|
+
const prepareForUpload = () => disposePlaceholdersForUpload(list);
|
|
5439
|
+
const sharedHandlerOpts = {
|
|
5440
|
+
resourceIds: initialFiles,
|
|
4761
5441
|
state,
|
|
4762
|
-
updateFilesDisplay,
|
|
5442
|
+
updateCallback: updateFilesDisplay,
|
|
4763
5443
|
constraints,
|
|
4764
5444
|
pathKey,
|
|
4765
|
-
ctx.instance
|
|
4766
|
-
|
|
5445
|
+
instance: ctx.instance,
|
|
5446
|
+
buildSuccessTile,
|
|
5447
|
+
prepareForUpload,
|
|
5448
|
+
coordinator
|
|
5449
|
+
};
|
|
5450
|
+
setupFilesDropHandler({ ...sharedHandlerOpts, filesContainer });
|
|
5451
|
+
setupFilesPickerHandler({ ...sharedHandlerOpts, filesPicker });
|
|
4767
5452
|
updateFilesDisplay();
|
|
4768
5453
|
wrapper.appendChild(filesWrapper);
|
|
4769
5454
|
}
|
|
@@ -5411,7 +6096,7 @@ function validateColourElement(element, key, context) {
|
|
|
5411
6096
|
};
|
|
5412
6097
|
if (element.multiple) {
|
|
5413
6098
|
const hexInputs = scopeRoot.querySelectorAll(
|
|
5414
|
-
`[name^="${key}["].colour-hex-input`
|
|
6099
|
+
`[name^="${key}\\["].colour-hex-input`
|
|
5415
6100
|
);
|
|
5416
6101
|
const values = [];
|
|
5417
6102
|
hexInputs.forEach((input, index) => {
|
|
@@ -5461,7 +6146,7 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
5461
6146
|
return;
|
|
5462
6147
|
}
|
|
5463
6148
|
const hexInputs = scopeRoot.querySelectorAll(
|
|
5464
|
-
`[name^="${fieldPath}["].colour-hex-input`
|
|
6149
|
+
`[name^="${fieldPath}\\["].colour-hex-input`
|
|
5465
6150
|
);
|
|
5466
6151
|
hexInputs.forEach((hexInput, index) => {
|
|
5467
6152
|
if (index < value.length) {
|
|
@@ -5469,6 +6154,7 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
5469
6154
|
hexInput.value = normalized;
|
|
5470
6155
|
hexInput.classList.remove("invalid");
|
|
5471
6156
|
hexInput.title = "";
|
|
6157
|
+
clearFieldError(hexInput);
|
|
5472
6158
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
5473
6159
|
if (wrapper) {
|
|
5474
6160
|
const swatch = wrapper.querySelector(".colour-swatch");
|
|
@@ -5498,6 +6184,7 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
5498
6184
|
hexInput.value = normalized;
|
|
5499
6185
|
hexInput.classList.remove("invalid");
|
|
5500
6186
|
hexInput.title = "";
|
|
6187
|
+
clearFieldError(hexInput);
|
|
5501
6188
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
5502
6189
|
if (wrapper) {
|
|
5503
6190
|
const swatch = wrapper.querySelector(".colour-swatch");
|
|
@@ -5905,7 +6592,7 @@ function validateSliderElement(element, key, context) {
|
|
|
5905
6592
|
};
|
|
5906
6593
|
if (element.multiple) {
|
|
5907
6594
|
const sliders = scopeRoot.querySelectorAll(
|
|
5908
|
-
`input[type="range"][name^="${key}["]`
|
|
6595
|
+
`input[type="range"][name^="${key}\\["]`
|
|
5909
6596
|
);
|
|
5910
6597
|
const values = [];
|
|
5911
6598
|
sliders.forEach((slider, index) => {
|
|
@@ -5957,7 +6644,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
5957
6644
|
return;
|
|
5958
6645
|
}
|
|
5959
6646
|
const sliders = scopeRoot.querySelectorAll(
|
|
5960
|
-
`input[type="range"][name^="${fieldPath}["]`
|
|
6647
|
+
`input[type="range"][name^="${fieldPath}\\["]`
|
|
5961
6648
|
);
|
|
5962
6649
|
sliders.forEach((slider, index) => {
|
|
5963
6650
|
if (index < value.length && value[index] !== null) {
|
|
@@ -5985,6 +6672,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
5985
6672
|
}
|
|
5986
6673
|
slider.classList.remove("invalid");
|
|
5987
6674
|
slider.title = "";
|
|
6675
|
+
clearFieldError(slider);
|
|
5988
6676
|
}
|
|
5989
6677
|
});
|
|
5990
6678
|
if (value.length !== sliders.length) {
|
|
@@ -6021,6 +6709,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
6021
6709
|
}
|
|
6022
6710
|
slider.classList.remove("invalid");
|
|
6023
6711
|
slider.title = "";
|
|
6712
|
+
clearFieldError(slider);
|
|
6024
6713
|
}
|
|
6025
6714
|
}
|
|
6026
6715
|
}
|
|
@@ -6145,34 +6834,24 @@ function getChildWrapperClass(isSlides, columns) {
|
|
|
6145
6834
|
const cols = columns || 1;
|
|
6146
6835
|
return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
|
|
6147
6836
|
}
|
|
6148
|
-
function mountRemoveButton(item, onRemove) {
|
|
6837
|
+
function mountRemoveButton(item, onRemove, state) {
|
|
6149
6838
|
const rem = document.createElement("button");
|
|
6150
6839
|
rem.type = "button";
|
|
6151
6840
|
rem.className = "fb-item-remove";
|
|
6841
|
+
rem.setAttribute("aria-label", t("removeElement", state));
|
|
6152
6842
|
rem.style.cssText = `
|
|
6153
|
-
width:
|
|
6154
|
-
height:
|
|
6843
|
+
width: 24px;
|
|
6844
|
+
height: 24px;
|
|
6155
6845
|
display: inline-flex;
|
|
6156
6846
|
align-items: center;
|
|
6157
6847
|
justify-content: center;
|
|
6158
6848
|
padding: 0;
|
|
6159
|
-
line-height: 1;
|
|
6160
|
-
font-size: 14px;
|
|
6161
|
-
color: var(--fb-error-color);
|
|
6162
|
-
background-color: transparent;
|
|
6163
6849
|
border: 0;
|
|
6164
6850
|
border-radius: 4px;
|
|
6165
6851
|
cursor: pointer;
|
|
6166
6852
|
flex-shrink: 0;
|
|
6167
|
-
transition: background-color var(--fb-transition-duration);
|
|
6168
6853
|
`;
|
|
6169
|
-
rem.
|
|
6170
|
-
rem.addEventListener("mouseenter", () => {
|
|
6171
|
-
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
6172
|
-
});
|
|
6173
|
-
rem.addEventListener("mouseleave", () => {
|
|
6174
|
-
rem.style.backgroundColor = "transparent";
|
|
6175
|
-
});
|
|
6854
|
+
rem.innerHTML = BIN_ICON_SVG;
|
|
6176
6855
|
rem.onclick = onRemove;
|
|
6177
6856
|
const labelRow = item.querySelector("[data-fb-label-row]");
|
|
6178
6857
|
if (labelRow) {
|
|
@@ -6199,7 +6878,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6199
6878
|
itemsWrap.className = "fb-container-slides";
|
|
6200
6879
|
const slideCols = element.columns;
|
|
6201
6880
|
const gridTemplateColumns = typeof slideCols === "number" && slideCols > 0 ? `repeat(${slideCols}, 1fr)` : "repeat(auto-fit, minmax(280px, 1fr))";
|
|
6202
|
-
itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:
|
|
6881
|
+
itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:var(--fb-slides-gap, 14px);align-items:start;`;
|
|
6203
6882
|
} else {
|
|
6204
6883
|
itemsWrap.className = "space-y-2";
|
|
6205
6884
|
}
|
|
@@ -6230,6 +6909,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6230
6909
|
const item = document.createElement("div");
|
|
6231
6910
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6232
6911
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6912
|
+
if (isSlides) {
|
|
6913
|
+
item.setAttribute("data-fb-slide-card", "");
|
|
6914
|
+
}
|
|
6233
6915
|
const childWrapper = document.createElement("div");
|
|
6234
6916
|
childWrapper.className = getChildWrapperClass(isSlides, element.columns);
|
|
6235
6917
|
element.elements.forEach((child) => {
|
|
@@ -6247,7 +6929,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6247
6929
|
});
|
|
6248
6930
|
item.appendChild(childWrapper);
|
|
6249
6931
|
if (!containerIsReadonly) {
|
|
6250
|
-
mountRemoveButton(item, () => handleRemoveItem(item));
|
|
6932
|
+
mountRemoveButton(item, () => handleRemoveItem(item), state);
|
|
6251
6933
|
}
|
|
6252
6934
|
if (slideAddTile && slideAddTile.parentElement === itemsWrap) {
|
|
6253
6935
|
itemsWrap.insertBefore(item, slideAddTile);
|
|
@@ -6294,6 +6976,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6294
6976
|
const item = document.createElement("div");
|
|
6295
6977
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6296
6978
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6979
|
+
if (isSlides) {
|
|
6980
|
+
item.setAttribute("data-fb-slide-card", "");
|
|
6981
|
+
}
|
|
6297
6982
|
const childWrapper = document.createElement("div");
|
|
6298
6983
|
if (isSlides) {
|
|
6299
6984
|
childWrapper.className = "space-y-2";
|
|
@@ -6318,7 +7003,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6318
7003
|
});
|
|
6319
7004
|
item.appendChild(childWrapper);
|
|
6320
7005
|
if (!containerIsReadonly) {
|
|
6321
|
-
mountRemoveButton(item, () => handleRemoveItem(item));
|
|
7006
|
+
mountRemoveButton(item, () => handleRemoveItem(item), ctx.state);
|
|
6322
7007
|
}
|
|
6323
7008
|
itemsWrap.appendChild(item);
|
|
6324
7009
|
});
|
|
@@ -6338,6 +7023,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6338
7023
|
const item = document.createElement("div");
|
|
6339
7024
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6340
7025
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
7026
|
+
if (isSlides) {
|
|
7027
|
+
item.setAttribute("data-fb-slide-card", "");
|
|
7028
|
+
}
|
|
6341
7029
|
const childWrapper = document.createElement("div");
|
|
6342
7030
|
if (isSlides) {
|
|
6343
7031
|
childWrapper.className = "space-y-2";
|
|
@@ -6363,11 +7051,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6363
7051
|
}
|
|
6364
7052
|
});
|
|
6365
7053
|
item.appendChild(childWrapper);
|
|
6366
|
-
mountRemoveButton(
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
7054
|
+
mountRemoveButton(
|
|
7055
|
+
item,
|
|
7056
|
+
() => {
|
|
7057
|
+
if (countItems() > min) {
|
|
7058
|
+
handleRemoveItem(item);
|
|
7059
|
+
}
|
|
7060
|
+
},
|
|
7061
|
+
ctx.state
|
|
7062
|
+
);
|
|
6371
7063
|
itemsWrap.appendChild(item);
|
|
6372
7064
|
}
|
|
6373
7065
|
}
|
|
@@ -9060,7 +9752,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9060
9752
|
if (element.minLength != null || element.maxLength != null) {
|
|
9061
9753
|
const counterRow = document.createElement("div");
|
|
9062
9754
|
counterRow.style.cssText = "position: relative; padding: 2px 10px 4px; text-align: right;";
|
|
9063
|
-
const counter = createCharCounter(element, textarea
|
|
9755
|
+
const counter = createCharCounter(element, textarea);
|
|
9064
9756
|
counter.style.cssText = `
|
|
9065
9757
|
position: static;
|
|
9066
9758
|
display: inline-block;
|
|
@@ -9592,6 +10284,118 @@ function validateMarkdown(_element, _key, _context) {
|
|
|
9592
10284
|
function updateMarkdown(_element, _fieldPath, _value, _context) {
|
|
9593
10285
|
}
|
|
9594
10286
|
|
|
10287
|
+
// src/components/registry.ts
|
|
10288
|
+
function validateHiddenElement(element, key, context) {
|
|
10289
|
+
var _a;
|
|
10290
|
+
const { scopeRoot } = context;
|
|
10291
|
+
const input = scopeRoot.querySelector(
|
|
10292
|
+
`input[type="hidden"][data-hidden-field="true"][name="${key}"]`
|
|
10293
|
+
);
|
|
10294
|
+
const raw = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
10295
|
+
if (raw === "") {
|
|
10296
|
+
const defaultVal = "default" in element ? element.default : null;
|
|
10297
|
+
return { value: defaultVal !== void 0 ? defaultVal : null, errors: [] };
|
|
10298
|
+
}
|
|
10299
|
+
return { value: deserializeHiddenValue(raw), errors: [] };
|
|
10300
|
+
}
|
|
10301
|
+
function updateHiddenField(_element, fieldPath, value, context) {
|
|
10302
|
+
const { scopeRoot } = context;
|
|
10303
|
+
const input = scopeRoot.querySelector(
|
|
10304
|
+
`input[type="hidden"][data-hidden-field="true"][name="${fieldPath}"]`
|
|
10305
|
+
);
|
|
10306
|
+
if (!input) return;
|
|
10307
|
+
input.value = serializeHiddenValue(value);
|
|
10308
|
+
}
|
|
10309
|
+
var componentRegistry = {
|
|
10310
|
+
text: {
|
|
10311
|
+
validate: validateTextElement,
|
|
10312
|
+
update: updateTextField
|
|
10313
|
+
},
|
|
10314
|
+
textarea: {
|
|
10315
|
+
validate: validateTextareaElement,
|
|
10316
|
+
update: updateTextareaField
|
|
10317
|
+
},
|
|
10318
|
+
number: {
|
|
10319
|
+
validate: validateNumberElement,
|
|
10320
|
+
update: updateNumberField
|
|
10321
|
+
},
|
|
10322
|
+
select: {
|
|
10323
|
+
validate: validateSelectElement,
|
|
10324
|
+
update: updateSelectField
|
|
10325
|
+
},
|
|
10326
|
+
switcher: {
|
|
10327
|
+
validate: validateSwitcherElement,
|
|
10328
|
+
update: updateSwitcherField
|
|
10329
|
+
},
|
|
10330
|
+
boolean: {
|
|
10331
|
+
validate: validateBooleanElement,
|
|
10332
|
+
update: updateBooleanField,
|
|
10333
|
+
ownsLabel: true
|
|
10334
|
+
},
|
|
10335
|
+
file: {
|
|
10336
|
+
validate: validateFileElement,
|
|
10337
|
+
update: updateFileField
|
|
10338
|
+
},
|
|
10339
|
+
files: {
|
|
10340
|
+
// Legacy type - delegates to file
|
|
10341
|
+
validate: validateFileElement,
|
|
10342
|
+
update: updateFileField
|
|
10343
|
+
},
|
|
10344
|
+
colour: {
|
|
10345
|
+
validate: validateColourElement,
|
|
10346
|
+
update: updateColourField
|
|
10347
|
+
},
|
|
10348
|
+
slider: {
|
|
10349
|
+
validate: validateSliderElement,
|
|
10350
|
+
update: updateSliderField
|
|
10351
|
+
},
|
|
10352
|
+
container: {
|
|
10353
|
+
validate: validateContainerElement,
|
|
10354
|
+
update: updateContainerField
|
|
10355
|
+
},
|
|
10356
|
+
group: {
|
|
10357
|
+
// Deprecated type - delegates to container
|
|
10358
|
+
validate: validateGroupElement,
|
|
10359
|
+
update: updateGroupField
|
|
10360
|
+
},
|
|
10361
|
+
table: {
|
|
10362
|
+
validate: validateTableElement,
|
|
10363
|
+
update: updateTableField
|
|
10364
|
+
},
|
|
10365
|
+
richinput: {
|
|
10366
|
+
validate: validateRichInputElement,
|
|
10367
|
+
update: updateRichInputField
|
|
10368
|
+
},
|
|
10369
|
+
hidden: {
|
|
10370
|
+
// Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
|
|
10371
|
+
validate: validateHiddenElement,
|
|
10372
|
+
update: updateHiddenField
|
|
10373
|
+
},
|
|
10374
|
+
markdown: {
|
|
10375
|
+
// Display-only element — no value, no errors, skip from form data
|
|
10376
|
+
validate: validateMarkdown,
|
|
10377
|
+
update: updateMarkdown
|
|
10378
|
+
}
|
|
10379
|
+
};
|
|
10380
|
+
function getComponentOperations(elementType) {
|
|
10381
|
+
return componentRegistry[elementType] || null;
|
|
10382
|
+
}
|
|
10383
|
+
function validateElementWithComponent(element, key, context) {
|
|
10384
|
+
const ops = getComponentOperations(element.type);
|
|
10385
|
+
if (ops && ops.validate) {
|
|
10386
|
+
return ops.validate(element, key, context);
|
|
10387
|
+
}
|
|
10388
|
+
return null;
|
|
10389
|
+
}
|
|
10390
|
+
function updateElementWithComponent(element, fieldPath, value, context) {
|
|
10391
|
+
const ops = getComponentOperations(element.type);
|
|
10392
|
+
if (ops && ops.update) {
|
|
10393
|
+
ops.update(element, fieldPath, value, context);
|
|
10394
|
+
return true;
|
|
10395
|
+
}
|
|
10396
|
+
return false;
|
|
10397
|
+
}
|
|
10398
|
+
|
|
9595
10399
|
// src/components/index.ts
|
|
9596
10400
|
function showTooltip(tooltipId, button) {
|
|
9597
10401
|
const tooltip = document.getElementById(tooltipId);
|
|
@@ -9904,6 +10708,9 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
9904
10708
|
renderSwitcherElement(element, ctx, wrapper, pathKey);
|
|
9905
10709
|
}
|
|
9906
10710
|
break;
|
|
10711
|
+
case "boolean":
|
|
10712
|
+
renderBooleanElement(element, ctx, wrapper, pathKey);
|
|
10713
|
+
break;
|
|
9907
10714
|
case "file":
|
|
9908
10715
|
if (isMultiple) {
|
|
9909
10716
|
renderMultipleFileElement(element, ctx, wrapper, pathKey);
|
|
@@ -9982,8 +10789,11 @@ function renderElement2(element, ctx) {
|
|
|
9982
10789
|
const wrapper = document.createElement("div");
|
|
9983
10790
|
wrapper.className = "mb-2 fb-field-wrapper";
|
|
9984
10791
|
wrapper.setAttribute("data-field-key", element.key);
|
|
9985
|
-
const
|
|
9986
|
-
|
|
10792
|
+
const ops = getComponentOperations(element.type);
|
|
10793
|
+
if (!(ops == null ? void 0 : ops.ownsLabel)) {
|
|
10794
|
+
const label = createLabelContainer(element);
|
|
10795
|
+
wrapper.appendChild(label);
|
|
10796
|
+
}
|
|
9987
10797
|
const pathKey = pathJoin(ctx.path, element.key);
|
|
9988
10798
|
dispatchToRenderer(element, ctx, wrapper, pathKey);
|
|
9989
10799
|
if (initiallyDisabled) {
|
|
@@ -10216,28 +11026,52 @@ var defaultTheme = {
|
|
|
10216
11026
|
// blue-500
|
|
10217
11027
|
primaryHoverColor: "#2563eb",
|
|
10218
11028
|
// blue-600
|
|
11029
|
+
primarySoftColor: "#dbeafe",
|
|
11030
|
+
// blue-100
|
|
11031
|
+
primarySoftHoverColor: "#bfdbfe",
|
|
11032
|
+
// blue-200
|
|
10219
11033
|
errorColor: "#ef4444",
|
|
10220
11034
|
// red-500
|
|
10221
11035
|
errorHoverColor: "#dc2626",
|
|
10222
11036
|
// red-600
|
|
10223
11037
|
successColor: "#10b981",
|
|
10224
11038
|
// green-500
|
|
11039
|
+
accentColor: "#f59e0b",
|
|
11040
|
+
// amber-500
|
|
11041
|
+
accentSoftColor: "#fef3c7",
|
|
11042
|
+
// amber-100
|
|
11043
|
+
accentBorderColor: "#fde68a",
|
|
11044
|
+
// amber-200
|
|
11045
|
+
accentTextColor: "#92400e",
|
|
11046
|
+
// amber-800
|
|
10225
11047
|
borderColor: "#d1d5db",
|
|
10226
11048
|
// gray-300
|
|
10227
11049
|
borderHoverColor: "#9ca3af",
|
|
10228
11050
|
// gray-400
|
|
10229
11051
|
borderFocusColor: "#3b82f6",
|
|
10230
11052
|
// blue-500
|
|
11053
|
+
borderStrongColor: "#9ca3af",
|
|
11054
|
+
// gray-400
|
|
10231
11055
|
backgroundColor: "#ffffff",
|
|
10232
11056
|
// white
|
|
10233
11057
|
backgroundHoverColor: "#f9fafb",
|
|
10234
11058
|
// gray-50
|
|
10235
11059
|
backgroundReadonlyColor: "#f3f4f6",
|
|
10236
11060
|
// gray-100
|
|
11061
|
+
pageBackgroundColor: "#f9fafb",
|
|
11062
|
+
// gray-50
|
|
11063
|
+
surfaceSoftColor: "#eff6ff",
|
|
11064
|
+
// blue-50
|
|
11065
|
+
surfaceTintColor: "#f8fafc",
|
|
11066
|
+
// slate-50
|
|
10237
11067
|
textColor: "#1f2937",
|
|
10238
11068
|
// gray-800
|
|
10239
11069
|
textSecondaryColor: "#6b7280",
|
|
10240
11070
|
// gray-500
|
|
11071
|
+
textMutedColor: "#9ca3af",
|
|
11072
|
+
// gray-400
|
|
11073
|
+
textFaintColor: "#cbd5e1",
|
|
11074
|
+
// slate-300
|
|
10241
11075
|
textPlaceholderColor: "#9ca3af",
|
|
10242
11076
|
// gray-400
|
|
10243
11077
|
textDisabledColor: "#d1d5db",
|
|
@@ -10280,6 +11114,12 @@ var defaultTheme = {
|
|
|
10280
11114
|
// 4px (compact density v2)
|
|
10281
11115
|
borderRadius: "0.5rem",
|
|
10282
11116
|
// rounded-lg (8px)
|
|
11117
|
+
borderRadiusSmall: "0.375rem",
|
|
11118
|
+
// 6px
|
|
11119
|
+
borderRadiusLarge: "0.75rem",
|
|
11120
|
+
// 12px
|
|
11121
|
+
borderRadiusXLarge: "1rem",
|
|
11122
|
+
// 16px
|
|
10283
11123
|
borderWidth: "1px",
|
|
10284
11124
|
// Typography
|
|
10285
11125
|
fontSize: "0.875rem",
|
|
@@ -10291,13 +11131,31 @@ var defaultTheme = {
|
|
|
10291
11131
|
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
10292
11132
|
fontWeightNormal: "400",
|
|
10293
11133
|
fontWeightMedium: "500",
|
|
11134
|
+
lineHeight: "1.5",
|
|
11135
|
+
// Shadows
|
|
11136
|
+
shadowCard: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
|
|
11137
|
+
shadowPopover: "0 12px 32px -12px rgba(15,23,42,.18), 0 4px 12px -6px rgba(15,23,42,.08)",
|
|
10294
11138
|
// Focus ring
|
|
10295
11139
|
focusRingWidth: "2px",
|
|
10296
11140
|
focusRingColor: "#3b82f6",
|
|
10297
11141
|
// blue-500
|
|
10298
11142
|
focusRingOpacity: "0.5",
|
|
10299
11143
|
// Transitions
|
|
10300
|
-
transitionDuration: "200ms"
|
|
11144
|
+
transitionDuration: "200ms",
|
|
11145
|
+
// Slide-card defaults — flat-white to match every other item card. The
|
|
11146
|
+
// Picaz theme overrides this with a gradient + shadow to lift the slides.
|
|
11147
|
+
slideCardBg: "#ffffff",
|
|
11148
|
+
slideCardShadow: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
|
|
11149
|
+
slideCardRadius: "0.5rem",
|
|
11150
|
+
// matches borderRadius
|
|
11151
|
+
slideCardMinHeight: "0",
|
|
11152
|
+
slideCardPadding: "12px",
|
|
11153
|
+
// Section-label defaults — same look as the regular field label. Themes
|
|
11154
|
+
// that want the "ПРЕИМУЩЕСТВА" caps style override these three vars.
|
|
11155
|
+
labelSectionFontSize: "0.875rem",
|
|
11156
|
+
// matches fontSize
|
|
11157
|
+
labelSectionLetterSpacing: "normal",
|
|
11158
|
+
labelSectionTextTransform: "none"
|
|
10301
11159
|
};
|
|
10302
11160
|
function generateCSSVariables(theme) {
|
|
10303
11161
|
const mergedTheme = { ...defaultTheme, ...theme };
|
|
@@ -10309,6 +11167,7 @@ function generateCSSVariables(theme) {
|
|
|
10309
11167
|
return cssVars.join("\n");
|
|
10310
11168
|
}
|
|
10311
11169
|
function injectThemeVariables(container, theme) {
|
|
11170
|
+
ensureThemingHooks(container.ownerDocument || document);
|
|
10312
11171
|
const cssVariables = generateCSSVariables(theme);
|
|
10313
11172
|
let styleTag = container.querySelector(
|
|
10314
11173
|
"style[data-fb-theme]"
|
|
@@ -10371,115 +11230,64 @@ var exampleThemes = {
|
|
|
10371
11230
|
fontSize: "16px",
|
|
10372
11231
|
fontSizeSmall: "14px",
|
|
10373
11232
|
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif'
|
|
10374
|
-
}
|
|
10375
|
-
};
|
|
10376
|
-
|
|
10377
|
-
// src/components/registry.ts
|
|
10378
|
-
function validateHiddenElement(element, key, context) {
|
|
10379
|
-
var _a;
|
|
10380
|
-
const { scopeRoot } = context;
|
|
10381
|
-
const input = scopeRoot.querySelector(
|
|
10382
|
-
`input[type="hidden"][data-hidden-field="true"][name="${key}"]`
|
|
10383
|
-
);
|
|
10384
|
-
const raw = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
10385
|
-
if (raw === "") {
|
|
10386
|
-
const defaultVal = "default" in element ? element.default : null;
|
|
10387
|
-
return { value: defaultVal !== void 0 ? defaultVal : null, errors: [] };
|
|
10388
|
-
}
|
|
10389
|
-
return { value: deserializeHiddenValue(raw), errors: [] };
|
|
10390
|
-
}
|
|
10391
|
-
function updateHiddenField(_element, fieldPath, value, context) {
|
|
10392
|
-
const { scopeRoot } = context;
|
|
10393
|
-
const input = scopeRoot.querySelector(
|
|
10394
|
-
`input[type="hidden"][data-hidden-field="true"][name="${fieldPath}"]`
|
|
10395
|
-
);
|
|
10396
|
-
if (!input) return;
|
|
10397
|
-
input.value = serializeHiddenValue(value);
|
|
10398
|
-
}
|
|
10399
|
-
var componentRegistry = {
|
|
10400
|
-
text: {
|
|
10401
|
-
validate: validateTextElement,
|
|
10402
|
-
update: updateTextField
|
|
10403
|
-
},
|
|
10404
|
-
textarea: {
|
|
10405
|
-
validate: validateTextareaElement,
|
|
10406
|
-
update: updateTextareaField
|
|
10407
|
-
},
|
|
10408
|
-
number: {
|
|
10409
|
-
validate: validateNumberElement,
|
|
10410
|
-
update: updateNumberField
|
|
10411
|
-
},
|
|
10412
|
-
select: {
|
|
10413
|
-
validate: validateSelectElement,
|
|
10414
|
-
update: updateSelectField
|
|
10415
|
-
},
|
|
10416
|
-
switcher: {
|
|
10417
|
-
validate: validateSwitcherElement,
|
|
10418
|
-
update: updateSwitcherField
|
|
10419
|
-
},
|
|
10420
|
-
file: {
|
|
10421
|
-
validate: validateFileElement,
|
|
10422
|
-
update: updateFileField
|
|
10423
|
-
},
|
|
10424
|
-
files: {
|
|
10425
|
-
// Legacy type - delegates to file
|
|
10426
|
-
validate: validateFileElement,
|
|
10427
|
-
update: updateFileField
|
|
10428
|
-
},
|
|
10429
|
-
colour: {
|
|
10430
|
-
validate: validateColourElement,
|
|
10431
|
-
update: updateColourField
|
|
10432
|
-
},
|
|
10433
|
-
slider: {
|
|
10434
|
-
validate: validateSliderElement,
|
|
10435
|
-
update: updateSliderField
|
|
10436
|
-
},
|
|
10437
|
-
container: {
|
|
10438
|
-
validate: validateContainerElement,
|
|
10439
|
-
update: updateContainerField
|
|
10440
|
-
},
|
|
10441
|
-
group: {
|
|
10442
|
-
// Deprecated type - delegates to container
|
|
10443
|
-
validate: validateGroupElement,
|
|
10444
|
-
update: updateGroupField
|
|
10445
|
-
},
|
|
10446
|
-
table: {
|
|
10447
|
-
validate: validateTableElement,
|
|
10448
|
-
update: updateTableField
|
|
10449
|
-
},
|
|
10450
|
-
richinput: {
|
|
10451
|
-
validate: validateRichInputElement,
|
|
10452
|
-
update: updateRichInputField
|
|
10453
|
-
},
|
|
10454
|
-
hidden: {
|
|
10455
|
-
// Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
|
|
10456
|
-
validate: validateHiddenElement,
|
|
10457
|
-
update: updateHiddenField
|
|
10458
11233
|
},
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
11234
|
+
// Picaz wizard design tokens — derived from the Picaz Wizard mockups.
|
|
11235
|
+
// Pairs with the host-side .card / .section-num / .lede chrome that wraps the form.
|
|
11236
|
+
// Assumes Inter is loaded by the host (e.g. via Google Fonts in index.html).
|
|
11237
|
+
picaz: {
|
|
11238
|
+
...defaultTheme,
|
|
11239
|
+
primaryColor: "#2f5bea",
|
|
11240
|
+
primaryHoverColor: "#2349c8",
|
|
11241
|
+
primarySoftColor: "#eaf0ff",
|
|
11242
|
+
primarySoftHoverColor: "#d6e0ff",
|
|
11243
|
+
errorColor: "#ef4444",
|
|
11244
|
+
successColor: "#16a34a",
|
|
11245
|
+
accentColor: "#ffb020",
|
|
11246
|
+
accentSoftColor: "#fff7e6",
|
|
11247
|
+
accentBorderColor: "#fde7b5",
|
|
11248
|
+
accentTextColor: "#92400e",
|
|
11249
|
+
borderColor: "#e3e8f0",
|
|
11250
|
+
borderHoverColor: "#cdd6e3",
|
|
11251
|
+
borderFocusColor: "#2f5bea",
|
|
11252
|
+
borderStrongColor: "#cdd6e3",
|
|
11253
|
+
backgroundColor: "#ffffff",
|
|
11254
|
+
backgroundHoverColor: "#f3f7ff",
|
|
11255
|
+
pageBackgroundColor: "#f6f8fb",
|
|
11256
|
+
surfaceSoftColor: "#eef4ff",
|
|
11257
|
+
surfaceTintColor: "#f3f7ff",
|
|
11258
|
+
textColor: "#0f172a",
|
|
11259
|
+
textSecondaryColor: "#334155",
|
|
11260
|
+
textMutedColor: "#64748b",
|
|
11261
|
+
textFaintColor: "#94a3b8",
|
|
11262
|
+
textPlaceholderColor: "#94a3b8",
|
|
11263
|
+
buttonBgColor: "#2f5bea",
|
|
11264
|
+
buttonHoverBgColor: "#2349c8",
|
|
11265
|
+
fileUploadBgColor: "#fafcff",
|
|
11266
|
+
fileUploadBorderColor: "#cdd6e3",
|
|
11267
|
+
fileUploadHoverBorderColor: "#2f5bea",
|
|
11268
|
+
// Picaz uses roomier inputs (11/14px) than the defaultTheme compact density.
|
|
11269
|
+
inputPaddingX: "14px",
|
|
11270
|
+
inputPaddingY: "11px",
|
|
11271
|
+
borderRadius: "12px",
|
|
11272
|
+
borderRadiusSmall: "8px",
|
|
11273
|
+
borderRadiusLarge: "16px",
|
|
11274
|
+
borderRadiusXLarge: "22px",
|
|
11275
|
+
fontFamily: '"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
11276
|
+
shadowCard: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
|
|
11277
|
+
shadowPopover: "0 12px 32px -12px rgba(15,23,42,.18), 0 4px 12px -6px rgba(15,23,42,.08)",
|
|
11278
|
+
focusRingColor: "#2f5bea",
|
|
11279
|
+
// Slide cards: subtle gradient lift, no rest-state shadow (mockup adds it
|
|
11280
|
+
// only on hover, which form-builder doesn't yet differentiate).
|
|
11281
|
+
slideCardBg: "linear-gradient(180deg, #f7f9fc 0%, #dde3ee 100%)",
|
|
11282
|
+
slideCardShadow: "none",
|
|
11283
|
+
slideCardRadius: "16px",
|
|
11284
|
+
// Tiny uppercase captions above grouped lists ("ПРЕИМУЩЕСТВА" in the mockup).
|
|
11285
|
+
labelSectionFontSize: "0.625rem",
|
|
11286
|
+
// 10px
|
|
11287
|
+
labelSectionLetterSpacing: "0.07em",
|
|
11288
|
+
labelSectionTextTransform: "uppercase"
|
|
10463
11289
|
}
|
|
10464
11290
|
};
|
|
10465
|
-
function getComponentOperations(elementType) {
|
|
10466
|
-
return componentRegistry[elementType] || null;
|
|
10467
|
-
}
|
|
10468
|
-
function validateElementWithComponent(element, key, context) {
|
|
10469
|
-
const ops = getComponentOperations(element.type);
|
|
10470
|
-
if (ops && ops.validate) {
|
|
10471
|
-
return ops.validate(element, key, context);
|
|
10472
|
-
}
|
|
10473
|
-
return null;
|
|
10474
|
-
}
|
|
10475
|
-
function updateElementWithComponent(element, fieldPath, value, context) {
|
|
10476
|
-
const ops = getComponentOperations(element.type);
|
|
10477
|
-
if (ops && ops.update) {
|
|
10478
|
-
ops.update(element, fieldPath, value, context);
|
|
10479
|
-
return true;
|
|
10480
|
-
}
|
|
10481
|
-
return false;
|
|
10482
|
-
}
|
|
10483
11291
|
|
|
10484
11292
|
// src/instance/FormBuilderInstance.ts
|
|
10485
11293
|
var FormBuilderInstance = class {
|
|
@@ -10604,26 +11412,37 @@ var FormBuilderInstance = class {
|
|
|
10604
11412
|
}
|
|
10605
11413
|
}
|
|
10606
11414
|
/**
|
|
10607
|
-
* Find the DOM element corresponding to a field path (instance-scoped)
|
|
11415
|
+
* Find the DOM element corresponding to a field path (instance-scoped).
|
|
11416
|
+
*
|
|
11417
|
+
* Strategy:
|
|
11418
|
+
* 1. Try a `[name="…"]` lookup first — works for any field that renders
|
|
11419
|
+
* an input/hidden with the path as its name, in either mode. Some
|
|
11420
|
+
* readonly renderers still emit a hidden input (boolean, switcher),
|
|
11421
|
+
* so this path must run regardless of `state.config.readonly`. A
|
|
11422
|
+
* prior version gated this on edit mode only, which made
|
|
11423
|
+
* `updateField` / `setFormData` silently miss readonly boolean
|
|
11424
|
+
* fields whose component also opts out of the standard label row
|
|
11425
|
+
* (`ownsLabel: true`).
|
|
11426
|
+
* 2. If no input matched, fall back to locating the field wrapper by
|
|
11427
|
+
* its visible label text — needed for readonly previews that don't
|
|
11428
|
+
* emit any `name=` attribute (e.g. file/markdown previews).
|
|
10608
11429
|
*/
|
|
10609
11430
|
findFormElementByFieldPath(fieldPath) {
|
|
10610
11431
|
if (!this.state.formRoot) return null;
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
11432
|
+
let element = this.state.formRoot.querySelector(
|
|
11433
|
+
`[name="${fieldPath}"]`
|
|
11434
|
+
);
|
|
11435
|
+
if (element) return element;
|
|
11436
|
+
const variations = [
|
|
11437
|
+
fieldPath,
|
|
11438
|
+
fieldPath.replace(/\[(\d+)\]/g, "[$1]"),
|
|
11439
|
+
fieldPath.replace(/\./g, "[") + "]".repeat((fieldPath.match(/\./g) || []).length)
|
|
11440
|
+
];
|
|
11441
|
+
for (const variation of variations) {
|
|
11442
|
+
element = this.state.formRoot.querySelector(
|
|
11443
|
+
`[name="${variation}"]`
|
|
10614
11444
|
);
|
|
10615
11445
|
if (element) return element;
|
|
10616
|
-
const variations = [
|
|
10617
|
-
fieldPath,
|
|
10618
|
-
fieldPath.replace(/\[(\d+)\]/g, "[$1]"),
|
|
10619
|
-
fieldPath.replace(/\./g, "[") + "]".repeat((fieldPath.match(/\./g) || []).length)
|
|
10620
|
-
];
|
|
10621
|
-
for (const variation of variations) {
|
|
10622
|
-
element = this.state.formRoot.querySelector(
|
|
10623
|
-
`[name="${variation}"]`
|
|
10624
|
-
);
|
|
10625
|
-
if (element) return element;
|
|
10626
|
-
}
|
|
10627
11446
|
}
|
|
10628
11447
|
const schemaElement = this.findSchemaElement(fieldPath);
|
|
10629
11448
|
if (!schemaElement) return null;
|
|
@@ -10832,6 +11651,13 @@ var FormBuilderInstance = class {
|
|
|
10832
11651
|
const value = hintValues[fieldKey];
|
|
10833
11652
|
this.updateField(fullPath, value);
|
|
10834
11653
|
}
|
|
11654
|
+
const group = target.closest(".fb-prefill-hints");
|
|
11655
|
+
if (group) {
|
|
11656
|
+
group.querySelectorAll(
|
|
11657
|
+
'.fb-prefill-hint[aria-pressed="true"]'
|
|
11658
|
+
).forEach((el) => el.removeAttribute("aria-pressed"));
|
|
11659
|
+
}
|
|
11660
|
+
target.setAttribute("aria-pressed", "true");
|
|
10835
11661
|
} catch (error) {
|
|
10836
11662
|
console.error("Error parsing prefill hint values:", error);
|
|
10837
11663
|
}
|