@dmitryvim/form-builder 0.2.34 → 0.3.0
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 +411 -162
- package/dist/browser/formbuilder.v0.3.0.min.js +1411 -0
- package/dist/cjs/index.cjs +1003 -399
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +995 -398
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +411 -162
- package/dist/types/components/boolean.d.ts +4 -0
- 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 +39 -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,82 @@ 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
|
+
`;
|
|
447
|
+
doc.head.appendChild(style);
|
|
448
|
+
}
|
|
449
|
+
function applyAutoExpand(textarea) {
|
|
450
|
+
textarea.style.overflow = "hidden";
|
|
451
|
+
textarea.style.resize = "none";
|
|
452
|
+
const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
|
|
453
|
+
textarea.rows = Math.max(1, lineCount);
|
|
454
|
+
const resize = () => {
|
|
455
|
+
if (!textarea.isConnected) return;
|
|
456
|
+
textarea.style.height = "0";
|
|
457
|
+
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
458
|
+
};
|
|
459
|
+
textarea.addEventListener("input", resize);
|
|
460
|
+
setTimeout(() => {
|
|
461
|
+
if (textarea.isConnected) resize();
|
|
462
|
+
}, 0);
|
|
463
|
+
}
|
|
464
|
+
function applySingleLineMode(textarea) {
|
|
465
|
+
textarea.addEventListener("keydown", (e) => {
|
|
466
|
+
if (e.key === "Enter") {
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
textarea.addEventListener("paste", (e) => {
|
|
471
|
+
var _a, _b, _c, _d;
|
|
472
|
+
const pasted = (_b = (_a = e.clipboardData) == null ? void 0 : _a.getData("text")) != null ? _b : "";
|
|
473
|
+
if (!/[\r\n]/.test(pasted)) return;
|
|
474
|
+
e.preventDefault();
|
|
475
|
+
const cleaned = pasted.replace(/[\r\n]+/g, " ");
|
|
476
|
+
const start = (_c = textarea.selectionStart) != null ? _c : textarea.value.length;
|
|
477
|
+
const end = (_d = textarea.selectionEnd) != null ? _d : textarea.value.length;
|
|
478
|
+
const before = textarea.value.slice(0, start);
|
|
479
|
+
const after = textarea.value.slice(end);
|
|
480
|
+
textarea.value = before + cleaned + after;
|
|
481
|
+
const pos = start + cleaned.length;
|
|
482
|
+
textarea.setSelectionRange(pos, pos);
|
|
483
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
484
|
+
});
|
|
485
|
+
}
|
|
410
486
|
function mountCounterInLabel(wrapper, counter) {
|
|
411
487
|
const labelRow = wrapper.querySelector(
|
|
412
488
|
":scope > [data-fb-label-row]"
|
|
@@ -570,46 +646,94 @@ function applyActionButtonStyles(button, isFormLevel = false) {
|
|
|
570
646
|
}
|
|
571
647
|
|
|
572
648
|
// src/components/text.ts
|
|
573
|
-
function
|
|
649
|
+
function ensureChipStyles(doc) {
|
|
650
|
+
if (doc.head.querySelector("[data-fb-chip-styles]")) return;
|
|
651
|
+
const style = doc.createElement("style");
|
|
652
|
+
style.setAttribute("data-fb-chip-styles", "");
|
|
653
|
+
style.textContent = `
|
|
654
|
+
.fb-chip-list { display: flex; flex-direction: column; gap: 4px; }
|
|
655
|
+
.fb-chip {
|
|
656
|
+
display: flex;
|
|
657
|
+
align-items: center;
|
|
658
|
+
gap: 8px;
|
|
659
|
+
padding: 5px 6px 5px 10px;
|
|
660
|
+
background: var(--fb-chip-bg, var(--fb-background-color, #fff));
|
|
661
|
+
border: 1px solid var(--fb-chip-border, var(--fb-border-color, #e2e8f0));
|
|
662
|
+
border-radius: 6px;
|
|
663
|
+
position: relative;
|
|
664
|
+
transition: border-color var(--fb-transition-duration, 0.15s);
|
|
665
|
+
}
|
|
666
|
+
.fb-chip:hover { border-color: var(--fb-border-hover-color, var(--fb-border-color, #cbd5e1)); }
|
|
667
|
+
.fb-chip:focus-within { border-color: var(--fb-border-focus-color, var(--fb-primary-color, #2f5bea)); }
|
|
668
|
+
.fb-chip-dot {
|
|
669
|
+
flex: 0 0 6px;
|
|
670
|
+
width: 6px;
|
|
671
|
+
height: 6px;
|
|
672
|
+
border-radius: 50%;
|
|
673
|
+
background: var(--fb-chip-dot, var(--fb-primary-color, #2f5bea));
|
|
674
|
+
}
|
|
675
|
+
.fb-chip-input {
|
|
676
|
+
flex: 1;
|
|
677
|
+
min-width: 0;
|
|
678
|
+
padding: 2px 0;
|
|
679
|
+
border: 0;
|
|
680
|
+
outline: none;
|
|
681
|
+
background: transparent;
|
|
682
|
+
color: var(--fb-chip-text, var(--fb-text-color, inherit));
|
|
683
|
+
font-size: var(--fb-font-size, 14px);
|
|
684
|
+
font-family: var(--fb-font-family, inherit);
|
|
685
|
+
line-height: 1.4;
|
|
686
|
+
}
|
|
687
|
+
.fb-chip-input::placeholder { color: var(--fb-text-placeholder-color, #94a3b8); }
|
|
688
|
+
.fb-chip-input:read-only { color: var(--fb-text-secondary-color, #475569); }
|
|
689
|
+
.fb-chip-remove {
|
|
690
|
+
flex: 0 0 auto;
|
|
691
|
+
width: 22px;
|
|
692
|
+
height: 22px;
|
|
693
|
+
display: inline-flex;
|
|
694
|
+
align-items: center;
|
|
695
|
+
justify-content: center;
|
|
696
|
+
padding: 0;
|
|
697
|
+
border: 0;
|
|
698
|
+
border-radius: 4px;
|
|
699
|
+
background: transparent;
|
|
700
|
+
color: var(--fb-text-faint-color, #94a3b8);
|
|
701
|
+
cursor: pointer;
|
|
702
|
+
opacity: 0;
|
|
703
|
+
transition: opacity 0.12s, color 0.12s, background-color 0.12s;
|
|
704
|
+
}
|
|
705
|
+
.fb-chip:hover .fb-chip-remove,
|
|
706
|
+
.fb-chip-remove:focus-visible { opacity: 1; }
|
|
707
|
+
.fb-chip-remove:hover {
|
|
708
|
+
color: var(--fb-error-color, #dc2626);
|
|
709
|
+
background: var(--fb-background-hover-color, #f1f5f9);
|
|
710
|
+
}
|
|
711
|
+
.fb-chip-remove:disabled { opacity: 0 !important; pointer-events: none; }
|
|
712
|
+
`;
|
|
713
|
+
doc.head.appendChild(style);
|
|
714
|
+
}
|
|
715
|
+
function createCharCounter(element, input) {
|
|
574
716
|
const counter = document.createElement("span");
|
|
575
717
|
counter.className = "char-counter";
|
|
576
718
|
counter.style.cssText = `
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
719
|
+
margin-top: 4px;
|
|
720
|
+
padding-right: 12px;
|
|
721
|
+
text-align: right;
|
|
580
722
|
font-size: var(--fb-font-size-small);
|
|
581
|
-
|
|
723
|
+
line-height: 1;
|
|
724
|
+
color: var(--fb-error-color);
|
|
582
725
|
pointer-events: none;
|
|
583
|
-
|
|
584
|
-
padding: 0 4px;
|
|
726
|
+
display: none;
|
|
585
727
|
`;
|
|
586
728
|
const updateCounter = () => {
|
|
587
729
|
const len = input.value.length;
|
|
588
|
-
const min = element.minLength;
|
|
589
730
|
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) {
|
|
731
|
+
if (max != null && len > max) {
|
|
604
732
|
counter.textContent = `${len}/${max}`;
|
|
605
|
-
counter.style.
|
|
733
|
+
counter.style.display = "block";
|
|
606
734
|
} else {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
} else {
|
|
610
|
-
counter.textContent = `${len}`;
|
|
611
|
-
}
|
|
612
|
-
counter.style.color = "var(--fb-text-secondary-color)";
|
|
735
|
+
counter.textContent = "";
|
|
736
|
+
counter.style.display = "none";
|
|
613
737
|
}
|
|
614
738
|
};
|
|
615
739
|
input.addEventListener("input", updateCounter);
|
|
@@ -622,26 +746,32 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
622
746
|
const inputWrapper = document.createElement("div");
|
|
623
747
|
inputWrapper.style.cssText = "position: relative;";
|
|
624
748
|
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
625
|
-
const textInput = document.createElement("
|
|
626
|
-
textInput.
|
|
749
|
+
const textInput = document.createElement("textarea");
|
|
750
|
+
textInput.rows = 1;
|
|
627
751
|
textInput.className = "w-full rounded-lg";
|
|
628
752
|
textInput.style.cssText = `
|
|
629
753
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
630
|
-
${hasCharCounter ? "padding-right: 60px;" : ""}
|
|
631
754
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
632
755
|
border-radius: var(--fb-border-radius);
|
|
633
756
|
background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
634
757
|
color: var(--fb-text-color);
|
|
635
758
|
font-size: var(--fb-font-size);
|
|
636
759
|
font-family: var(--fb-font-family);
|
|
760
|
+
line-height: var(--fb-line-height, 1.5);
|
|
637
761
|
transition: all var(--fb-transition-duration) ease-in-out;
|
|
638
762
|
width: 100%;
|
|
639
763
|
box-sizing: border-box;
|
|
764
|
+
resize: none;
|
|
765
|
+
overflow: hidden;
|
|
766
|
+
word-break: break-word;
|
|
767
|
+
overflow-wrap: anywhere;
|
|
640
768
|
`;
|
|
641
769
|
textInput.name = pathKey;
|
|
642
770
|
textInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
643
771
|
textInput.value = ctx.prefill[element.key] || element.default || "";
|
|
644
772
|
textInput.readOnly = readonly;
|
|
773
|
+
applySingleLineMode(textInput);
|
|
774
|
+
applyAutoExpand(textInput);
|
|
645
775
|
if (!readonly) {
|
|
646
776
|
textInput.addEventListener("focus", () => {
|
|
647
777
|
textInput.style.borderColor = "var(--fb-border-focus-color)";
|
|
@@ -673,7 +803,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
673
803
|
}
|
|
674
804
|
inputWrapper.appendChild(textInput);
|
|
675
805
|
if (hasCharCounter) {
|
|
676
|
-
const counter = createCharCounter(element, textInput
|
|
806
|
+
const counter = createCharCounter(element, textInput);
|
|
677
807
|
inputWrapper.appendChild(counter);
|
|
678
808
|
}
|
|
679
809
|
wrapper.appendChild(inputWrapper);
|
|
@@ -684,132 +814,83 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
684
814
|
const readonly = isElementReadonly(element, state, ctx);
|
|
685
815
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
686
816
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
687
|
-
const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
|
|
688
817
|
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
689
818
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
690
819
|
while (values.length < minCount) {
|
|
691
820
|
values.push(element.default || "");
|
|
692
821
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
822
|
+
ensureChipStyles(document);
|
|
823
|
+
const list = document.createElement("div");
|
|
824
|
+
list.className = "fb-chip-list";
|
|
825
|
+
wrapper.appendChild(list);
|
|
696
826
|
function updateIndices() {
|
|
697
|
-
const items =
|
|
698
|
-
items.forEach((
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
827
|
+
const items = list.querySelectorAll(".fb-chip-input");
|
|
828
|
+
items.forEach((input, index) => {
|
|
829
|
+
input.name = `${pathKey}[${index}]`;
|
|
830
|
+
const chip = input.closest(".fb-chip");
|
|
831
|
+
const sib = chip == null ? void 0 : chip.nextElementSibling;
|
|
832
|
+
if (sib && sib.classList.contains("error-message")) {
|
|
833
|
+
sib.id = `error-${input.name}`;
|
|
702
834
|
}
|
|
703
835
|
});
|
|
704
836
|
}
|
|
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
|
-
}
|
|
837
|
+
function addChip(value = "") {
|
|
838
|
+
const chip = document.createElement("div");
|
|
839
|
+
chip.className = "fb-chip";
|
|
840
|
+
const dot = document.createElement("span");
|
|
841
|
+
dot.className = "fb-chip-dot";
|
|
842
|
+
dot.setAttribute("aria-hidden", "true");
|
|
843
|
+
chip.appendChild(dot);
|
|
844
|
+
const input = document.createElement("input");
|
|
845
|
+
input.type = "text";
|
|
846
|
+
input.className = "fb-chip-input";
|
|
847
|
+
input.value = value;
|
|
848
|
+
input.placeholder = element.placeholder || t("placeholderText", state);
|
|
849
|
+
input.readOnly = readonly;
|
|
850
|
+
chip.appendChild(input);
|
|
749
851
|
if (!readonly && ctx.instance) {
|
|
750
852
|
const handleChange = () => {
|
|
751
|
-
|
|
752
|
-
|
|
853
|
+
ctx.instance.triggerOnChange(
|
|
854
|
+
input.name,
|
|
855
|
+
input.value === "" ? null : input.value
|
|
856
|
+
);
|
|
753
857
|
};
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
inputContainer.appendChild(textInput);
|
|
758
|
-
if (hasCharCounter) {
|
|
759
|
-
const counter = createCharCounter(element, textInput, false);
|
|
760
|
-
inputContainer.appendChild(counter);
|
|
858
|
+
input.addEventListener("blur", handleChange);
|
|
859
|
+
input.addEventListener("input", handleChange);
|
|
761
860
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
861
|
+
if (!readonly) {
|
|
862
|
+
const rem = document.createElement("button");
|
|
863
|
+
rem.type = "button";
|
|
864
|
+
rem.className = "fb-chip-remove";
|
|
865
|
+
rem.setAttribute("aria-label", t("removeElement", state));
|
|
866
|
+
rem.innerHTML = BIN_ICON_SVG;
|
|
867
|
+
rem.onclick = () => {
|
|
868
|
+
const chips = list.querySelectorAll(".fb-chip");
|
|
869
|
+
const idx = Array.prototype.indexOf.call(chips, chip);
|
|
870
|
+
if (idx < 0) return;
|
|
871
|
+
if (chips.length <= minCount) return;
|
|
872
|
+
values.splice(idx, 1);
|
|
873
|
+
const trailingError = chip.nextElementSibling;
|
|
874
|
+
if (trailingError && trailingError.classList.contains("error-message")) {
|
|
875
|
+
trailingError.remove();
|
|
876
|
+
}
|
|
877
|
+
chip.remove();
|
|
878
|
+
updateIndices();
|
|
879
|
+
updateAddButton();
|
|
880
|
+
updateRemoveButtons();
|
|
881
|
+
};
|
|
882
|
+
chip.appendChild(rem);
|
|
767
883
|
}
|
|
884
|
+
list.appendChild(chip);
|
|
768
885
|
updateIndices();
|
|
769
|
-
return
|
|
886
|
+
return chip;
|
|
770
887
|
}
|
|
771
888
|
function updateRemoveButtons() {
|
|
772
889
|
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";
|
|
890
|
+
const chipCount = list.querySelectorAll(".fb-chip").length;
|
|
891
|
+
const disabled = chipCount <= minCount;
|
|
892
|
+
list.querySelectorAll(".fb-chip-remove").forEach((btn) => {
|
|
893
|
+
btn.disabled = disabled;
|
|
813
894
|
});
|
|
814
895
|
}
|
|
815
896
|
let addUpdate = null;
|
|
@@ -818,7 +899,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
818
899
|
"text",
|
|
819
900
|
() => {
|
|
820
901
|
values.push(element.default || "");
|
|
821
|
-
|
|
902
|
+
addChip(element.default || "");
|
|
822
903
|
updateAddButton();
|
|
823
904
|
updateRemoveButtons();
|
|
824
905
|
},
|
|
@@ -831,7 +912,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
831
912
|
function updateAddButton() {
|
|
832
913
|
if (addUpdate) addUpdate(values.length, maxCount);
|
|
833
914
|
}
|
|
834
|
-
values.forEach((value) =>
|
|
915
|
+
values.forEach((value) => addChip(value));
|
|
835
916
|
updateAddButton();
|
|
836
917
|
updateRemoveButtons();
|
|
837
918
|
}
|
|
@@ -840,7 +921,7 @@ function validateTextElement(element, key, context) {
|
|
|
840
921
|
const errors = [];
|
|
841
922
|
const { scopeRoot, skipValidation } = context;
|
|
842
923
|
const markValidity = (input, errorMessage) => {
|
|
843
|
-
var _a2, _b2;
|
|
924
|
+
var _a2, _b2, _c2;
|
|
844
925
|
if (!input) return;
|
|
845
926
|
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
846
927
|
let errorElement = document.getElementById(errorId);
|
|
@@ -856,10 +937,12 @@ function validateTextElement(element, key, context) {
|
|
|
856
937
|
font-size: var(--fb-font-size-small);
|
|
857
938
|
margin-top: 0.25rem;
|
|
858
939
|
`;
|
|
859
|
-
|
|
860
|
-
|
|
940
|
+
const chipAncestor = (_a2 = input.closest) == null ? void 0 : _a2.call(input, ".fb-chip");
|
|
941
|
+
const anchor = chipAncestor || input;
|
|
942
|
+
if (anchor.nextSibling) {
|
|
943
|
+
(_b2 = anchor.parentNode) == null ? void 0 : _b2.insertBefore(errorElement, anchor.nextSibling);
|
|
861
944
|
} else {
|
|
862
|
-
(
|
|
945
|
+
(_c2 = anchor.parentNode) == null ? void 0 : _c2.appendChild(errorElement);
|
|
863
946
|
}
|
|
864
947
|
}
|
|
865
948
|
errorElement.textContent = errorMessage;
|
|
@@ -908,7 +991,7 @@ function validateTextElement(element, key, context) {
|
|
|
908
991
|
}
|
|
909
992
|
};
|
|
910
993
|
if (element.multiple) {
|
|
911
|
-
const inputs = scopeRoot.querySelectorAll(`[name^="${key}["]`);
|
|
994
|
+
const inputs = scopeRoot.querySelectorAll(`[name^="${key}\\["]`);
|
|
912
995
|
const values = [];
|
|
913
996
|
const rawValues = [];
|
|
914
997
|
inputs.forEach((input, index) => {
|
|
@@ -958,12 +1041,14 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
958
1041
|
);
|
|
959
1042
|
return;
|
|
960
1043
|
}
|
|
961
|
-
const inputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}["]`);
|
|
1044
|
+
const inputs = scopeRoot.querySelectorAll(`[name^="${fieldPath}\\["]`);
|
|
962
1045
|
inputs.forEach((input, index) => {
|
|
963
1046
|
if (index < value.length) {
|
|
964
1047
|
input.value = value[index] != null ? String(value[index]) : "";
|
|
965
1048
|
input.classList.remove("invalid");
|
|
966
1049
|
input.title = "";
|
|
1050
|
+
clearFieldError(input);
|
|
1051
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
967
1052
|
}
|
|
968
1053
|
});
|
|
969
1054
|
if (value.length !== inputs.length) {
|
|
@@ -977,26 +1062,15 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
977
1062
|
input.value = value != null ? String(value) : "";
|
|
978
1063
|
input.classList.remove("invalid");
|
|
979
1064
|
input.title = "";
|
|
1065
|
+
clearFieldError(input);
|
|
1066
|
+
if (input instanceof HTMLTextAreaElement) {
|
|
1067
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1068
|
+
}
|
|
980
1069
|
}
|
|
981
1070
|
}
|
|
982
1071
|
}
|
|
983
1072
|
|
|
984
1073
|
// 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
1074
|
function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
1001
1075
|
const state = ctx.state;
|
|
1002
1076
|
const readonly = isElementReadonly(element, state, ctx);
|
|
@@ -1027,7 +1101,7 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1027
1101
|
}
|
|
1028
1102
|
textareaWrapper.appendChild(textareaInput);
|
|
1029
1103
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1030
|
-
const counter = createCharCounter(element, textareaInput
|
|
1104
|
+
const counter = createCharCounter(element, textareaInput);
|
|
1031
1105
|
textareaWrapper.appendChild(counter);
|
|
1032
1106
|
}
|
|
1033
1107
|
wrapper.appendChild(textareaWrapper);
|
|
@@ -1084,7 +1158,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1084
1158
|
}
|
|
1085
1159
|
textareaContainer.appendChild(textareaInput);
|
|
1086
1160
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1087
|
-
const counter = createCharCounter(element, textareaInput
|
|
1161
|
+
const counter = createCharCounter(element, textareaInput);
|
|
1088
1162
|
textareaContainer.appendChild(counter);
|
|
1089
1163
|
}
|
|
1090
1164
|
itemWrapper.appendChild(textareaContainer);
|
|
@@ -1178,6 +1252,91 @@ function updateTextareaField(element, fieldPath, value, context) {
|
|
|
1178
1252
|
}
|
|
1179
1253
|
|
|
1180
1254
|
// src/components/number.ts
|
|
1255
|
+
function ensureStepperStyles(doc) {
|
|
1256
|
+
const ID = "fb-number-stepper-styles";
|
|
1257
|
+
if (doc.getElementById(ID)) return;
|
|
1258
|
+
const style = doc.createElement("style");
|
|
1259
|
+
style.id = ID;
|
|
1260
|
+
style.textContent = `
|
|
1261
|
+
.fb-stepper-input::-webkit-outer-spin-button,
|
|
1262
|
+
.fb-stepper-input::-webkit-inner-spin-button {
|
|
1263
|
+
-webkit-appearance: none;
|
|
1264
|
+
margin: 0;
|
|
1265
|
+
}
|
|
1266
|
+
.fb-stepper-input { -moz-appearance: textfield; }
|
|
1267
|
+
`;
|
|
1268
|
+
doc.head.appendChild(style);
|
|
1269
|
+
}
|
|
1270
|
+
function buildStepper(input, element, readonly) {
|
|
1271
|
+
var _a;
|
|
1272
|
+
ensureStepperStyles(input.ownerDocument);
|
|
1273
|
+
const step = (_a = element.step) != null ? _a : 1;
|
|
1274
|
+
const min = element.min;
|
|
1275
|
+
const max = element.max;
|
|
1276
|
+
const wrap = document.createElement("div");
|
|
1277
|
+
wrap.className = "fb-stepper";
|
|
1278
|
+
wrap.style.cssText = `
|
|
1279
|
+
display: inline-flex;
|
|
1280
|
+
align-items: stretch;
|
|
1281
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
1282
|
+
border-radius: var(--fb-border-radius);
|
|
1283
|
+
overflow: hidden;
|
|
1284
|
+
background: var(--fb-background-color);
|
|
1285
|
+
`;
|
|
1286
|
+
const makeBtn = (label, delta) => {
|
|
1287
|
+
const b = document.createElement("button");
|
|
1288
|
+
b.type = "button";
|
|
1289
|
+
b.textContent = label;
|
|
1290
|
+
b.tabIndex = -1;
|
|
1291
|
+
b.style.cssText = `
|
|
1292
|
+
width: 32px;
|
|
1293
|
+
border: none;
|
|
1294
|
+
background: transparent;
|
|
1295
|
+
color: var(--fb-text-color);
|
|
1296
|
+
font-size: var(--fb-font-size);
|
|
1297
|
+
font-family: var(--fb-font-family);
|
|
1298
|
+
cursor: ${readonly ? "default" : "pointer"};
|
|
1299
|
+
user-select: none;
|
|
1300
|
+
`;
|
|
1301
|
+
if (readonly) {
|
|
1302
|
+
b.disabled = true;
|
|
1303
|
+
b.style.opacity = "0.5";
|
|
1304
|
+
} else {
|
|
1305
|
+
b.addEventListener("click", (e) => {
|
|
1306
|
+
var _a2;
|
|
1307
|
+
e.preventDefault();
|
|
1308
|
+
const current = parseFloat(input.value);
|
|
1309
|
+
const base = Number.isFinite(current) ? current : (_a2 = min != null ? min : element.default) != null ? _a2 : 0;
|
|
1310
|
+
let next = parseFloat((base + delta * step).toPrecision(12));
|
|
1311
|
+
if (min != null) next = Math.max(min, next);
|
|
1312
|
+
if (max != null) next = Math.min(max, next);
|
|
1313
|
+
input.value = String(next);
|
|
1314
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1315
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
return b;
|
|
1319
|
+
};
|
|
1320
|
+
input.classList.add("fb-stepper-input");
|
|
1321
|
+
input.style.cssText = `
|
|
1322
|
+
width: 56px;
|
|
1323
|
+
border: none;
|
|
1324
|
+
border-left: var(--fb-border-width) solid var(--fb-border-color);
|
|
1325
|
+
border-right: var(--fb-border-width) solid var(--fb-border-color);
|
|
1326
|
+
padding: var(--fb-input-padding-y) 0;
|
|
1327
|
+
font-size: var(--fb-font-size);
|
|
1328
|
+
font-family: var(--fb-font-family);
|
|
1329
|
+
text-align: center;
|
|
1330
|
+
background: transparent;
|
|
1331
|
+
color: var(--fb-text-color);
|
|
1332
|
+
-moz-appearance: textfield;
|
|
1333
|
+
box-sizing: border-box;
|
|
1334
|
+
`;
|
|
1335
|
+
wrap.appendChild(makeBtn("\u2212", -1));
|
|
1336
|
+
wrap.appendChild(input);
|
|
1337
|
+
wrap.appendChild(makeBtn("+", 1));
|
|
1338
|
+
return wrap;
|
|
1339
|
+
}
|
|
1181
1340
|
function createNumberRangeHint(element, input) {
|
|
1182
1341
|
const hint = document.createElement("span");
|
|
1183
1342
|
hint.className = "number-range-hint";
|
|
@@ -1224,14 +1383,6 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1224
1383
|
inputWrapper.style.cssText = "position: relative;";
|
|
1225
1384
|
const numberInput = document.createElement("input");
|
|
1226
1385
|
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
1386
|
numberInput.name = pathKey;
|
|
1236
1387
|
numberInput.placeholder = element.placeholder || "0";
|
|
1237
1388
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
@@ -1239,6 +1390,16 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1239
1390
|
if (element.step !== void 0) numberInput.step = element.step.toString();
|
|
1240
1391
|
numberInput.value = ctx.prefill[element.key] || element.default || "";
|
|
1241
1392
|
numberInput.readOnly = readonly;
|
|
1393
|
+
if (!element.stepper) {
|
|
1394
|
+
numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1395
|
+
numberInput.style.cssText = `
|
|
1396
|
+
padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1397
|
+
font-size: var(--fb-font-size);
|
|
1398
|
+
font-family: var(--fb-font-family);
|
|
1399
|
+
width: 100%;
|
|
1400
|
+
box-sizing: border-box;
|
|
1401
|
+
`;
|
|
1402
|
+
}
|
|
1242
1403
|
if (!readonly && ctx.instance) {
|
|
1243
1404
|
const handleChange = () => {
|
|
1244
1405
|
const value = numberInput.value ? parseFloat(numberInput.value) : null;
|
|
@@ -1247,10 +1408,14 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1247
1408
|
numberInput.addEventListener("blur", handleChange);
|
|
1248
1409
|
numberInput.addEventListener("input", handleChange);
|
|
1249
1410
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
inputWrapper.appendChild(
|
|
1411
|
+
if (element.stepper) {
|
|
1412
|
+
inputWrapper.appendChild(buildStepper(numberInput, element, readonly));
|
|
1413
|
+
} else {
|
|
1414
|
+
inputWrapper.appendChild(numberInput);
|
|
1415
|
+
if (!readonly && (element.min != null || element.max != null)) {
|
|
1416
|
+
const counter = createNumberRangeHint(element, numberInput);
|
|
1417
|
+
inputWrapper.appendChild(counter);
|
|
1418
|
+
}
|
|
1254
1419
|
}
|
|
1255
1420
|
wrapper.appendChild(inputWrapper);
|
|
1256
1421
|
}
|
|
@@ -1508,13 +1673,14 @@ function updateNumberField(element, fieldPath, value, context) {
|
|
|
1508
1673
|
return;
|
|
1509
1674
|
}
|
|
1510
1675
|
const inputs = scopeRoot.querySelectorAll(
|
|
1511
|
-
`[name^="${fieldPath}["]`
|
|
1676
|
+
`[name^="${fieldPath}\\["]`
|
|
1512
1677
|
);
|
|
1513
1678
|
inputs.forEach((input, index) => {
|
|
1514
1679
|
if (index < value.length) {
|
|
1515
1680
|
input.value = value[index] != null ? String(value[index]) : "";
|
|
1516
1681
|
input.classList.remove("invalid");
|
|
1517
1682
|
input.title = "";
|
|
1683
|
+
clearFieldError(input);
|
|
1518
1684
|
}
|
|
1519
1685
|
});
|
|
1520
1686
|
if (value.length !== inputs.length) {
|
|
@@ -1530,6 +1696,7 @@ function updateNumberField(element, fieldPath, value, context) {
|
|
|
1530
1696
|
input.value = value != null ? String(value) : "";
|
|
1531
1697
|
input.classList.remove("invalid");
|
|
1532
1698
|
input.title = "";
|
|
1699
|
+
clearFieldError(input);
|
|
1533
1700
|
}
|
|
1534
1701
|
}
|
|
1535
1702
|
}
|
|
@@ -1746,7 +1913,7 @@ function validateSelectElement(element, key, context) {
|
|
|
1746
1913
|
};
|
|
1747
1914
|
if ("multiple" in element && element.multiple) {
|
|
1748
1915
|
const inputs = scopeRoot.querySelectorAll(
|
|
1749
|
-
`[name^="${key}["]`
|
|
1916
|
+
`[name^="${key}\\["]`
|
|
1750
1917
|
);
|
|
1751
1918
|
const values = [];
|
|
1752
1919
|
inputs.forEach((input) => {
|
|
@@ -1783,7 +1950,7 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1783
1950
|
return;
|
|
1784
1951
|
}
|
|
1785
1952
|
const selects = scopeRoot.querySelectorAll(
|
|
1786
|
-
`[name^="${fieldPath}["]`
|
|
1953
|
+
`[name^="${fieldPath}\\["]`
|
|
1787
1954
|
);
|
|
1788
1955
|
selects.forEach((select, index) => {
|
|
1789
1956
|
if (index < value.length) {
|
|
@@ -1794,6 +1961,7 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1794
1961
|
});
|
|
1795
1962
|
select.classList.remove("invalid");
|
|
1796
1963
|
select.title = "";
|
|
1964
|
+
clearFieldError(select);
|
|
1797
1965
|
}
|
|
1798
1966
|
});
|
|
1799
1967
|
if (value.length !== selects.length) {
|
|
@@ -1813,72 +1981,147 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1813
1981
|
});
|
|
1814
1982
|
select.classList.remove("invalid");
|
|
1815
1983
|
select.title = "";
|
|
1984
|
+
clearFieldError(select);
|
|
1816
1985
|
}
|
|
1817
1986
|
}
|
|
1818
1987
|
}
|
|
1819
1988
|
|
|
1820
1989
|
// src/components/switcher.ts
|
|
1821
|
-
function applySelectedStyle(btn) {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1990
|
+
function applySelectedStyle(btn, isPreset) {
|
|
1991
|
+
if (isPreset) {
|
|
1992
|
+
btn.style.backgroundColor = "var(--fb-primary-soft-color)";
|
|
1993
|
+
btn.style.color = "var(--fb-primary-color)";
|
|
1994
|
+
btn.style.borderColor = "var(--fb-primary-color)";
|
|
1995
|
+
} else {
|
|
1996
|
+
btn.style.backgroundColor = "var(--fb-primary-color)";
|
|
1997
|
+
btn.style.color = "#ffffff";
|
|
1998
|
+
btn.style.borderColor = "var(--fb-primary-color)";
|
|
1999
|
+
}
|
|
1825
2000
|
}
|
|
1826
|
-
function applyUnselectedStyle(btn) {
|
|
1827
|
-
btn.style.backgroundColor = "transparent";
|
|
2001
|
+
function applyUnselectedStyle(btn, isPreset) {
|
|
2002
|
+
btn.style.backgroundColor = isPreset ? "var(--fb-background-color)" : "transparent";
|
|
1828
2003
|
btn.style.color = "var(--fb-text-color)";
|
|
1829
2004
|
btn.style.borderColor = "var(--fb-border-color)";
|
|
1830
2005
|
}
|
|
2006
|
+
function isPresetButton(btn) {
|
|
2007
|
+
return btn.classList.contains("fb-switcher-preset");
|
|
2008
|
+
}
|
|
2009
|
+
function buildPresetCard(option, readonly) {
|
|
2010
|
+
const btn = document.createElement("button");
|
|
2011
|
+
btn.type = "button";
|
|
2012
|
+
btn.className = "fb-switcher-btn fb-switcher-preset";
|
|
2013
|
+
btn.dataset.value = option.value;
|
|
2014
|
+
btn.style.cssText = `
|
|
2015
|
+
display: inline-flex;
|
|
2016
|
+
align-items: center;
|
|
2017
|
+
gap: 8px;
|
|
2018
|
+
padding: 7px 12px 7px 10px;
|
|
2019
|
+
border-width: var(--fb-border-width);
|
|
2020
|
+
border-style: solid;
|
|
2021
|
+
border-radius: 999px;
|
|
2022
|
+
background: var(--fb-background-color);
|
|
2023
|
+
font-size: var(--fb-font-size);
|
|
2024
|
+
font-family: var(--fb-font-family);
|
|
2025
|
+
line-height: 1.25;
|
|
2026
|
+
cursor: ${readonly ? "default" : "pointer"};
|
|
2027
|
+
transition: background-color var(--fb-transition-duration), color var(--fb-transition-duration), border-color var(--fb-transition-duration);
|
|
2028
|
+
outline: none;
|
|
2029
|
+
`;
|
|
2030
|
+
if (option.iconUrl) {
|
|
2031
|
+
const icon = document.createElement("img");
|
|
2032
|
+
icon.className = "fb-switcher-icon";
|
|
2033
|
+
icon.src = option.iconUrl;
|
|
2034
|
+
icon.alt = "";
|
|
2035
|
+
icon.setAttribute("aria-hidden", "true");
|
|
2036
|
+
icon.style.cssText = `
|
|
2037
|
+
display: block;
|
|
2038
|
+
flex: 0 0 auto;
|
|
2039
|
+
width: 20px;
|
|
2040
|
+
height: 20px;
|
|
2041
|
+
object-fit: contain;
|
|
2042
|
+
`;
|
|
2043
|
+
btn.appendChild(icon);
|
|
2044
|
+
}
|
|
2045
|
+
const name = document.createElement("span");
|
|
2046
|
+
name.className = "fb-switcher-name";
|
|
2047
|
+
name.textContent = option.label;
|
|
2048
|
+
name.style.cssText = "font-weight: 600;";
|
|
2049
|
+
btn.appendChild(name);
|
|
2050
|
+
if (option.subtitle) {
|
|
2051
|
+
const sub = document.createElement("span");
|
|
2052
|
+
sub.className = "fb-switcher-subtitle";
|
|
2053
|
+
sub.textContent = option.subtitle;
|
|
2054
|
+
sub.style.cssText = `
|
|
2055
|
+
font-size: var(--fb-font-size-small);
|
|
2056
|
+
opacity: 0.7;
|
|
2057
|
+
font-variant-numeric: tabular-nums;
|
|
2058
|
+
`;
|
|
2059
|
+
btn.appendChild(sub);
|
|
2060
|
+
}
|
|
2061
|
+
return btn;
|
|
2062
|
+
}
|
|
1831
2063
|
function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onChange) {
|
|
1832
2064
|
const options = element.options || [];
|
|
2065
|
+
const isPresetMode = options.some((o) => o.subtitle || o.iconUrl);
|
|
1833
2066
|
const group = document.createElement("div");
|
|
1834
2067
|
group.className = "fb-switcher-group";
|
|
1835
|
-
group.style.cssText = `
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2068
|
+
group.style.cssText = isPresetMode ? `
|
|
2069
|
+
display: flex;
|
|
2070
|
+
flex-direction: row;
|
|
2071
|
+
flex-wrap: wrap;
|
|
2072
|
+
gap: 6px;
|
|
2073
|
+
` : `
|
|
2074
|
+
display: inline-flex;
|
|
2075
|
+
flex-direction: row;
|
|
2076
|
+
flex-wrap: nowrap;
|
|
2077
|
+
`;
|
|
1840
2078
|
const buttons = [];
|
|
1841
2079
|
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";
|
|
2080
|
+
let btn;
|
|
2081
|
+
if (isPresetMode) {
|
|
2082
|
+
btn = buildPresetCard(option, readonly);
|
|
1865
2083
|
} else {
|
|
1866
|
-
btn
|
|
1867
|
-
btn.
|
|
2084
|
+
btn = document.createElement("button");
|
|
2085
|
+
btn.type = "button";
|
|
2086
|
+
btn.className = "fb-switcher-btn";
|
|
2087
|
+
btn.dataset.value = option.value;
|
|
2088
|
+
btn.textContent = option.label;
|
|
2089
|
+
btn.style.cssText = `
|
|
2090
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
2091
|
+
font-size: var(--fb-font-size);
|
|
2092
|
+
border-width: var(--fb-border-width);
|
|
2093
|
+
border-style: solid;
|
|
2094
|
+
cursor: ${readonly ? "default" : "pointer"};
|
|
2095
|
+
transition: background-color var(--fb-transition-duration), color var(--fb-transition-duration), border-color var(--fb-transition-duration);
|
|
2096
|
+
white-space: nowrap;
|
|
2097
|
+
line-height: 1.25;
|
|
2098
|
+
outline: none;
|
|
2099
|
+
`;
|
|
2100
|
+
if (options.length === 1) {
|
|
2101
|
+
btn.style.borderRadius = "var(--fb-border-radius)";
|
|
2102
|
+
} else if (index === 0) {
|
|
2103
|
+
btn.style.borderRadius = "var(--fb-border-radius) 0 0 var(--fb-border-radius)";
|
|
2104
|
+
btn.style.borderRightWidth = "0";
|
|
2105
|
+
} else if (index === options.length - 1) {
|
|
2106
|
+
btn.style.borderRadius = "0 var(--fb-border-radius) var(--fb-border-radius) 0";
|
|
2107
|
+
} else {
|
|
2108
|
+
btn.style.borderRadius = "0";
|
|
2109
|
+
btn.style.borderRightWidth = "0";
|
|
2110
|
+
}
|
|
1868
2111
|
}
|
|
1869
2112
|
if (option.value === currentValue) {
|
|
1870
|
-
applySelectedStyle(btn);
|
|
2113
|
+
applySelectedStyle(btn, isPresetMode);
|
|
1871
2114
|
} else {
|
|
1872
|
-
applyUnselectedStyle(btn);
|
|
2115
|
+
applyUnselectedStyle(btn, isPresetMode);
|
|
1873
2116
|
}
|
|
1874
2117
|
if (!readonly) {
|
|
1875
2118
|
btn.addEventListener("click", () => {
|
|
1876
2119
|
hiddenInput.value = option.value;
|
|
1877
2120
|
buttons.forEach((b) => {
|
|
1878
2121
|
if (b.dataset.value === option.value) {
|
|
1879
|
-
applySelectedStyle(b);
|
|
2122
|
+
applySelectedStyle(b, isPresetMode);
|
|
1880
2123
|
} else {
|
|
1881
|
-
applyUnselectedStyle(b);
|
|
2124
|
+
applyUnselectedStyle(b, isPresetMode);
|
|
1882
2125
|
}
|
|
1883
2126
|
});
|
|
1884
2127
|
if (onChange) {
|
|
@@ -1892,7 +2135,7 @@ function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onCha
|
|
|
1892
2135
|
});
|
|
1893
2136
|
btn.addEventListener("mouseleave", () => {
|
|
1894
2137
|
if (hiddenInput.value !== option.value) {
|
|
1895
|
-
btn.style.backgroundColor = "transparent";
|
|
2138
|
+
btn.style.backgroundColor = isPresetMode ? "var(--fb-background-color)" : "transparent";
|
|
1896
2139
|
}
|
|
1897
2140
|
});
|
|
1898
2141
|
}
|
|
@@ -2117,7 +2360,7 @@ function validateSwitcherElement(element, key, context) {
|
|
|
2117
2360
|
);
|
|
2118
2361
|
if ("multiple" in element && element.multiple) {
|
|
2119
2362
|
const inputs = scopeRoot.querySelectorAll(
|
|
2120
|
-
`input[type="hidden"][name^="${key}["]`
|
|
2363
|
+
`input[type="hidden"][name^="${key}\\["]`
|
|
2121
2364
|
);
|
|
2122
2365
|
const values = [];
|
|
2123
2366
|
inputs.forEach((input) => {
|
|
@@ -2166,7 +2409,7 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2166
2409
|
return;
|
|
2167
2410
|
}
|
|
2168
2411
|
const inputs = scopeRoot.querySelectorAll(
|
|
2169
|
-
`input[type="hidden"][name^="${fieldPath}["]`
|
|
2412
|
+
`input[type="hidden"][name^="${fieldPath}\\["]`
|
|
2170
2413
|
);
|
|
2171
2414
|
inputs.forEach((input, index) => {
|
|
2172
2415
|
var _a2;
|
|
@@ -2176,15 +2419,17 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2176
2419
|
const group = (_a2 = input.parentElement) == null ? void 0 : _a2.querySelector(".fb-switcher-group");
|
|
2177
2420
|
if (group) {
|
|
2178
2421
|
group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
|
|
2422
|
+
const isPreset = isPresetButton(btn);
|
|
2179
2423
|
if (btn.dataset.value === newVal) {
|
|
2180
|
-
applySelectedStyle(btn);
|
|
2424
|
+
applySelectedStyle(btn, isPreset);
|
|
2181
2425
|
} else {
|
|
2182
|
-
applyUnselectedStyle(btn);
|
|
2426
|
+
applyUnselectedStyle(btn, isPreset);
|
|
2183
2427
|
}
|
|
2184
2428
|
});
|
|
2185
2429
|
}
|
|
2186
2430
|
input.classList.remove("invalid");
|
|
2187
2431
|
input.title = "";
|
|
2432
|
+
clearFieldError(input);
|
|
2188
2433
|
}
|
|
2189
2434
|
});
|
|
2190
2435
|
if (value.length !== inputs.length) {
|
|
@@ -2202,19 +2447,215 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2202
2447
|
const group = (_a = input.parentElement) == null ? void 0 : _a.querySelector(".fb-switcher-group");
|
|
2203
2448
|
if (group) {
|
|
2204
2449
|
group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
|
|
2450
|
+
const isPreset = isPresetButton(btn);
|
|
2205
2451
|
if (btn.dataset.value === newVal) {
|
|
2206
|
-
applySelectedStyle(btn);
|
|
2452
|
+
applySelectedStyle(btn, isPreset);
|
|
2207
2453
|
} else {
|
|
2208
|
-
applyUnselectedStyle(btn);
|
|
2454
|
+
applyUnselectedStyle(btn, isPreset);
|
|
2209
2455
|
}
|
|
2210
2456
|
});
|
|
2211
2457
|
}
|
|
2212
2458
|
input.classList.remove("invalid");
|
|
2213
2459
|
input.title = "";
|
|
2460
|
+
clearFieldError(input);
|
|
2214
2461
|
}
|
|
2215
2462
|
}
|
|
2216
2463
|
}
|
|
2217
2464
|
|
|
2465
|
+
// src/components/boolean.ts
|
|
2466
|
+
var TOGGLE_W = 36;
|
|
2467
|
+
var TOGGLE_H = 20;
|
|
2468
|
+
var KNOB = 16;
|
|
2469
|
+
function ensureStyles(doc) {
|
|
2470
|
+
const ID = "fb-boolean-styles";
|
|
2471
|
+
if (doc.getElementById(ID)) return;
|
|
2472
|
+
const style = doc.createElement("style");
|
|
2473
|
+
style.id = ID;
|
|
2474
|
+
style.textContent = `
|
|
2475
|
+
.fb-toggle {
|
|
2476
|
+
position: relative;
|
|
2477
|
+
display: inline-block;
|
|
2478
|
+
width: ${TOGGLE_W}px;
|
|
2479
|
+
height: ${TOGGLE_H}px;
|
|
2480
|
+
border-radius: ${TOGGLE_H}px;
|
|
2481
|
+
background: var(--fb-border-color);
|
|
2482
|
+
transition: background-color var(--fb-transition-duration);
|
|
2483
|
+
flex-shrink: 0;
|
|
2484
|
+
}
|
|
2485
|
+
.fb-toggle::after {
|
|
2486
|
+
content: "";
|
|
2487
|
+
position: absolute;
|
|
2488
|
+
top: ${(TOGGLE_H - KNOB) / 2}px;
|
|
2489
|
+
left: ${(TOGGLE_H - KNOB) / 2}px;
|
|
2490
|
+
width: ${KNOB}px;
|
|
2491
|
+
height: ${KNOB}px;
|
|
2492
|
+
border-radius: 50%;
|
|
2493
|
+
background: #ffffff;
|
|
2494
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|
2495
|
+
transition: transform var(--fb-transition-duration);
|
|
2496
|
+
}
|
|
2497
|
+
.fb-toggle.fb-on {
|
|
2498
|
+
background: var(--fb-primary-color);
|
|
2499
|
+
}
|
|
2500
|
+
.fb-toggle.fb-on::after {
|
|
2501
|
+
transform: translateX(${TOGGLE_W - KNOB - (TOGGLE_H - KNOB)}px);
|
|
2502
|
+
}
|
|
2503
|
+
.fb-toggle-row {
|
|
2504
|
+
display: flex;
|
|
2505
|
+
align-items: center;
|
|
2506
|
+
gap: 12px;
|
|
2507
|
+
padding: 12px 14px;
|
|
2508
|
+
background: var(--fb-surface-soft-color);
|
|
2509
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
2510
|
+
border-radius: var(--fb-border-radius);
|
|
2511
|
+
cursor: pointer;
|
|
2512
|
+
user-select: none;
|
|
2513
|
+
}
|
|
2514
|
+
.fb-toggle-row[aria-disabled="true"] {
|
|
2515
|
+
cursor: default;
|
|
2516
|
+
opacity: 0.7;
|
|
2517
|
+
}
|
|
2518
|
+
.fb-toggle-row:focus-visible {
|
|
2519
|
+
outline: var(--fb-focus-ring-width) solid var(--fb-focus-ring-color);
|
|
2520
|
+
outline-offset: var(--fb-focus-ring-offset);
|
|
2521
|
+
}
|
|
2522
|
+
.fb-toggle-text { flex: 1; min-width: 0; }
|
|
2523
|
+
.fb-toggle-title {
|
|
2524
|
+
display: flex;
|
|
2525
|
+
align-items: center;
|
|
2526
|
+
gap: 4px;
|
|
2527
|
+
font-size: var(--fb-font-size);
|
|
2528
|
+
font-weight: 500;
|
|
2529
|
+
color: var(--fb-text-color);
|
|
2530
|
+
line-height: 1.3;
|
|
2531
|
+
}
|
|
2532
|
+
.fb-toggle-subtitle {
|
|
2533
|
+
font-size: var(--fb-font-size-small);
|
|
2534
|
+
color: var(--fb-text-secondary-color);
|
|
2535
|
+
margin-top: 2px;
|
|
2536
|
+
line-height: 1.35;
|
|
2537
|
+
}
|
|
2538
|
+
.fb-toggle-info {
|
|
2539
|
+
flex: 0 0 14px;
|
|
2540
|
+
display: inline-flex;
|
|
2541
|
+
align-items: center;
|
|
2542
|
+
justify-content: center;
|
|
2543
|
+
width: 14px;
|
|
2544
|
+
height: 14px;
|
|
2545
|
+
border-radius: 50%;
|
|
2546
|
+
background: var(--fb-border-color);
|
|
2547
|
+
color: #fff;
|
|
2548
|
+
font-size: 10px;
|
|
2549
|
+
font-weight: 700;
|
|
2550
|
+
font-style: italic;
|
|
2551
|
+
font-family: serif;
|
|
2552
|
+
cursor: help;
|
|
2553
|
+
}
|
|
2554
|
+
`;
|
|
2555
|
+
doc.head.appendChild(style);
|
|
2556
|
+
}
|
|
2557
|
+
function parseBool(v) {
|
|
2558
|
+
if (typeof v === "boolean") return v;
|
|
2559
|
+
if (typeof v === "string") return v === "true" || v === "on" || v === "1";
|
|
2560
|
+
return false;
|
|
2561
|
+
}
|
|
2562
|
+
function renderBooleanElement(element, ctx, wrapper, pathKey) {
|
|
2563
|
+
var _a;
|
|
2564
|
+
ensureStyles(document);
|
|
2565
|
+
const state = ctx.state;
|
|
2566
|
+
const readonly = isElementReadonly(element, state, ctx);
|
|
2567
|
+
const prefillRaw = ctx.prefill[element.key];
|
|
2568
|
+
const initial = prefillRaw !== void 0 ? parseBool(prefillRaw) : parseBool(element.default);
|
|
2569
|
+
const hiddenInput = document.createElement("input");
|
|
2570
|
+
hiddenInput.type = "hidden";
|
|
2571
|
+
hiddenInput.name = pathKey;
|
|
2572
|
+
hiddenInput.value = initial ? "true" : "false";
|
|
2573
|
+
const row = document.createElement("div");
|
|
2574
|
+
row.className = "fb-toggle-row";
|
|
2575
|
+
row.setAttribute("role", "switch");
|
|
2576
|
+
row.setAttribute("aria-checked", initial ? "true" : "false");
|
|
2577
|
+
if (readonly) {
|
|
2578
|
+
row.setAttribute("aria-disabled", "true");
|
|
2579
|
+
} else {
|
|
2580
|
+
row.tabIndex = 0;
|
|
2581
|
+
}
|
|
2582
|
+
const pill = document.createElement("span");
|
|
2583
|
+
pill.className = "fb-toggle" + (initial ? " fb-on" : "");
|
|
2584
|
+
pill.setAttribute("aria-hidden", "true");
|
|
2585
|
+
row.appendChild(pill);
|
|
2586
|
+
const textBlock = document.createElement("div");
|
|
2587
|
+
textBlock.className = "fb-toggle-text";
|
|
2588
|
+
const titleEl = document.createElement("div");
|
|
2589
|
+
titleEl.className = "fb-toggle-title";
|
|
2590
|
+
titleEl.appendChild(document.createTextNode((_a = element.label) != null ? _a : ""));
|
|
2591
|
+
if (element.description) {
|
|
2592
|
+
const info = document.createElement("span");
|
|
2593
|
+
info.className = "fb-toggle-info";
|
|
2594
|
+
info.textContent = "i";
|
|
2595
|
+
info.title = element.description;
|
|
2596
|
+
titleEl.appendChild(info);
|
|
2597
|
+
}
|
|
2598
|
+
textBlock.appendChild(titleEl);
|
|
2599
|
+
if (element.hint) {
|
|
2600
|
+
const subtitle = document.createElement("div");
|
|
2601
|
+
subtitle.className = "fb-toggle-subtitle";
|
|
2602
|
+
subtitle.textContent = element.hint;
|
|
2603
|
+
textBlock.appendChild(subtitle);
|
|
2604
|
+
}
|
|
2605
|
+
row.appendChild(textBlock);
|
|
2606
|
+
if (!readonly) {
|
|
2607
|
+
const toggle = () => {
|
|
2608
|
+
const next = hiddenInput.value !== "true";
|
|
2609
|
+
hiddenInput.value = next ? "true" : "false";
|
|
2610
|
+
pill.classList.toggle("fb-on", next);
|
|
2611
|
+
row.setAttribute("aria-checked", next ? "true" : "false");
|
|
2612
|
+
if (ctx.instance) ctx.instance.triggerOnChange(pathKey, next);
|
|
2613
|
+
};
|
|
2614
|
+
row.addEventListener("click", (e) => {
|
|
2615
|
+
var _a2;
|
|
2616
|
+
if ((_a2 = e.target) == null ? void 0 : _a2.classList.contains("fb-toggle-info")) {
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
toggle();
|
|
2620
|
+
});
|
|
2621
|
+
row.addEventListener("keydown", (e) => {
|
|
2622
|
+
if (e.key === " " || e.key === "Enter") {
|
|
2623
|
+
e.preventDefault();
|
|
2624
|
+
toggle();
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
wrapper.appendChild(hiddenInput);
|
|
2629
|
+
wrapper.appendChild(row);
|
|
2630
|
+
}
|
|
2631
|
+
function validateBooleanElement(element, key, context) {
|
|
2632
|
+
var _a;
|
|
2633
|
+
const { scopeRoot } = context;
|
|
2634
|
+
const input = scopeRoot.querySelector(
|
|
2635
|
+
`input[type="hidden"][name="${key}"]`
|
|
2636
|
+
);
|
|
2637
|
+
const raw = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
2638
|
+
const value = parseBool(raw);
|
|
2639
|
+
const errors = [];
|
|
2640
|
+
return { value, errors };
|
|
2641
|
+
}
|
|
2642
|
+
function updateBooleanField(_element, fieldPath, value, context) {
|
|
2643
|
+
var _a;
|
|
2644
|
+
const { scopeRoot } = context;
|
|
2645
|
+
const input = scopeRoot.querySelector(
|
|
2646
|
+
`input[type="hidden"][name="${fieldPath}"]`
|
|
2647
|
+
);
|
|
2648
|
+
if (!input) return;
|
|
2649
|
+
const bool = parseBool(value);
|
|
2650
|
+
input.value = bool ? "true" : "false";
|
|
2651
|
+
const row = (_a = input.parentElement) == null ? void 0 : _a.querySelector(".fb-toggle-row");
|
|
2652
|
+
if (row) {
|
|
2653
|
+
row.setAttribute("aria-checked", bool ? "true" : "false");
|
|
2654
|
+
const pill = row.querySelector(".fb-toggle");
|
|
2655
|
+
if (pill) pill.classList.toggle("fb-on", bool);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2218
2659
|
// src/components/file/constraints.ts
|
|
2219
2660
|
function getAllowedExtensions(accept) {
|
|
2220
2661
|
if (!accept) return [];
|
|
@@ -2317,14 +2758,20 @@ function ensureFileStyles() {
|
|
|
2317
2758
|
}
|
|
2318
2759
|
|
|
2319
2760
|
/* \u2500\u2500\u2500 Wide single-file add tile (empty state) \u2500\u2500\u2500 */
|
|
2761
|
+
/* Flex-wraps: side-by-side when wide enough, stacks upload/library
|
|
2762
|
+
vertically when narrow (e.g. inside a 50/50 container column). */
|
|
2320
2763
|
.fb-wide-tile {
|
|
2321
2764
|
width: 100%;
|
|
2765
|
+
box-sizing: border-box;
|
|
2322
2766
|
border-radius: 0.75rem;
|
|
2323
2767
|
border: 1px dashed #60a5fa;
|
|
2324
2768
|
background: rgba(239,246,255,0.5);
|
|
2325
2769
|
display: flex;
|
|
2770
|
+
flex-wrap: wrap;
|
|
2771
|
+
align-items: stretch;
|
|
2772
|
+
gap: 0;
|
|
2326
2773
|
overflow: hidden;
|
|
2327
|
-
height: 180px;
|
|
2774
|
+
min-height: 180px;
|
|
2328
2775
|
transition: border-color 150ms, background 150ms, box-shadow 150ms;
|
|
2329
2776
|
cursor: pointer;
|
|
2330
2777
|
}
|
|
@@ -2338,9 +2785,12 @@ function ensureFileStyles() {
|
|
|
2338
2785
|
box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
|
|
2339
2786
|
}
|
|
2340
2787
|
|
|
2341
|
-
/* Upload zone inside wide tile
|
|
2788
|
+
/* Upload zone inside wide tile.
|
|
2789
|
+
flex: 1 1 220px \u2014 wants at least 220px; if the container can't fit
|
|
2790
|
+
upload + library on one row (~220 + 176), library wraps below. */
|
|
2342
2791
|
.fb-wide-tile-upload {
|
|
2343
|
-
flex: 1;
|
|
2792
|
+
flex: 1 1 220px;
|
|
2793
|
+
min-height: 140px;
|
|
2344
2794
|
display: flex;
|
|
2345
2795
|
flex-direction: column;
|
|
2346
2796
|
align-items: center;
|
|
@@ -2353,24 +2803,21 @@ function ensureFileStyles() {
|
|
|
2353
2803
|
background: transparent;
|
|
2354
2804
|
border: none;
|
|
2355
2805
|
font-family: inherit;
|
|
2806
|
+
/* Dashed separator from library: right side when in a row, bottom when
|
|
2807
|
+
wrapped (the line then sits between the two stacked cards). */
|
|
2808
|
+
border-right: 1px dashed rgba(96,165,250,0.5);
|
|
2356
2809
|
}
|
|
2357
2810
|
.fb-wide-tile-upload:hover {
|
|
2358
2811
|
background: rgba(191,219,254,0.25);
|
|
2359
2812
|
}
|
|
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 */
|
|
2813
|
+
/* Library zone inside wide tile.
|
|
2814
|
+
flex: 0 0 176px \u2014 fixed 176px, never grows. Upload fills the rest in
|
|
2815
|
+
row layout. When the tile wraps to two rows on narrow containers,
|
|
2816
|
+
library stays 176px wide on its own row (left-aligned), preserving the
|
|
2817
|
+
visual hierarchy "upload > library" in both layouts. */
|
|
2371
2818
|
.fb-wide-tile-library {
|
|
2372
|
-
|
|
2373
|
-
|
|
2819
|
+
flex: 0 0 176px;
|
|
2820
|
+
min-height: 120px;
|
|
2374
2821
|
display: flex;
|
|
2375
2822
|
flex-direction: column;
|
|
2376
2823
|
align-items: center;
|
|
@@ -2387,6 +2834,10 @@ function ensureFileStyles() {
|
|
|
2387
2834
|
.fb-wide-tile-library:hover {
|
|
2388
2835
|
background: rgba(191,219,254,0.25);
|
|
2389
2836
|
}
|
|
2837
|
+
/* Narrow-tile mode lives in a separate <style> tag (see below) \u2014 the
|
|
2838
|
+
@container rule is appended only when the runtime actually supports
|
|
2839
|
+
container queries, so jsdom (which doesn't) never sees it and stays
|
|
2840
|
+
quiet in test logs. */
|
|
2390
2841
|
|
|
2391
2842
|
/* \u2500\u2500\u2500 Multi-file outer grid container \u2500\u2500\u2500 */
|
|
2392
2843
|
.fb-multi-outer {
|
|
@@ -2765,6 +3216,39 @@ function ensureFileStyles() {
|
|
|
2765
3216
|
}
|
|
2766
3217
|
`;
|
|
2767
3218
|
document.head.appendChild(style);
|
|
3219
|
+
if (typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("container-type", "inline-size")) {
|
|
3220
|
+
const cq = document.createElement("style");
|
|
3221
|
+
cq.setAttribute("data-fb-file-styles-cq", "true");
|
|
3222
|
+
cq.textContent = `
|
|
3223
|
+
.fb-wide-tile { container-type: inline-size; }
|
|
3224
|
+
@container (max-width: 408px) {
|
|
3225
|
+
.fb-wide-tile-upload {
|
|
3226
|
+
border-right: none;
|
|
3227
|
+
border-bottom: 1px dashed rgba(96,165,250,0.5);
|
|
3228
|
+
}
|
|
3229
|
+
.fb-wide-tile-library {
|
|
3230
|
+
flex: 1 0 100%;
|
|
3231
|
+
min-height: 0;
|
|
3232
|
+
flex-direction: row;
|
|
3233
|
+
gap: 6px;
|
|
3234
|
+
padding: 8px 12px;
|
|
3235
|
+
font-size: 12px;
|
|
3236
|
+
}
|
|
3237
|
+
.fb-wide-tile-library .fb-wide-tile-library-icon {
|
|
3238
|
+
width: 16px;
|
|
3239
|
+
height: 16px;
|
|
3240
|
+
}
|
|
3241
|
+
.fb-wide-tile-library .fb-wide-tile-library-label {
|
|
3242
|
+
font-size: 12px;
|
|
3243
|
+
font-weight: 500;
|
|
3244
|
+
}
|
|
3245
|
+
.fb-wide-tile-library .fb-wide-tile-library-hint {
|
|
3246
|
+
display: none;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
`;
|
|
3250
|
+
document.head.appendChild(cq);
|
|
3251
|
+
}
|
|
2768
3252
|
}
|
|
2769
3253
|
|
|
2770
3254
|
// src/components/file/dom.ts
|
|
@@ -4134,21 +4618,21 @@ function buildWideTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragO
|
|
|
4134
4618
|
};
|
|
4135
4619
|
outer.appendChild(uploadBtn);
|
|
4136
4620
|
if (hasLibrary && onLibraryClick) {
|
|
4137
|
-
const divider = document.createElement("div");
|
|
4138
|
-
divider.className = "fb-wide-tile-divider";
|
|
4139
|
-
outer.appendChild(divider);
|
|
4140
4621
|
const libBtn = document.createElement("button");
|
|
4141
4622
|
libBtn.type = "button";
|
|
4142
4623
|
libBtn.className = "fb-wide-tile-library fb-file-library-card";
|
|
4143
4624
|
const libIcon = document.createElement("span");
|
|
4625
|
+
libIcon.className = "fb-wide-tile-library-icon";
|
|
4144
4626
|
libIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
|
|
4145
4627
|
libIcon.innerHTML = ICON_LIBRARY2;
|
|
4146
4628
|
libBtn.appendChild(libIcon);
|
|
4147
4629
|
const libLabel = document.createElement("div");
|
|
4630
|
+
libLabel.className = "fb-wide-tile-library-label";
|
|
4148
4631
|
libLabel.style.cssText = "font-size:13px;font-weight:600;text-align:center;";
|
|
4149
4632
|
libLabel.textContent = t("fromLibrary", state);
|
|
4150
4633
|
libBtn.appendChild(libLabel);
|
|
4151
4634
|
const libHint = document.createElement("div");
|
|
4635
|
+
libHint.className = "fb-wide-tile-library-hint";
|
|
4152
4636
|
libHint.style.cssText = "font-size:11px;opacity:0.75;text-align:center;";
|
|
4153
4637
|
libHint.textContent = t("libraryHint", state);
|
|
4154
4638
|
libBtn.appendChild(libHint);
|
|
@@ -5411,7 +5895,7 @@ function validateColourElement(element, key, context) {
|
|
|
5411
5895
|
};
|
|
5412
5896
|
if (element.multiple) {
|
|
5413
5897
|
const hexInputs = scopeRoot.querySelectorAll(
|
|
5414
|
-
`[name^="${key}["].colour-hex-input`
|
|
5898
|
+
`[name^="${key}\\["].colour-hex-input`
|
|
5415
5899
|
);
|
|
5416
5900
|
const values = [];
|
|
5417
5901
|
hexInputs.forEach((input, index) => {
|
|
@@ -5461,7 +5945,7 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
5461
5945
|
return;
|
|
5462
5946
|
}
|
|
5463
5947
|
const hexInputs = scopeRoot.querySelectorAll(
|
|
5464
|
-
`[name^="${fieldPath}["].colour-hex-input`
|
|
5948
|
+
`[name^="${fieldPath}\\["].colour-hex-input`
|
|
5465
5949
|
);
|
|
5466
5950
|
hexInputs.forEach((hexInput, index) => {
|
|
5467
5951
|
if (index < value.length) {
|
|
@@ -5469,6 +5953,7 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
5469
5953
|
hexInput.value = normalized;
|
|
5470
5954
|
hexInput.classList.remove("invalid");
|
|
5471
5955
|
hexInput.title = "";
|
|
5956
|
+
clearFieldError(hexInput);
|
|
5472
5957
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
5473
5958
|
if (wrapper) {
|
|
5474
5959
|
const swatch = wrapper.querySelector(".colour-swatch");
|
|
@@ -5498,6 +5983,7 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
5498
5983
|
hexInput.value = normalized;
|
|
5499
5984
|
hexInput.classList.remove("invalid");
|
|
5500
5985
|
hexInput.title = "";
|
|
5986
|
+
clearFieldError(hexInput);
|
|
5501
5987
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
5502
5988
|
if (wrapper) {
|
|
5503
5989
|
const swatch = wrapper.querySelector(".colour-swatch");
|
|
@@ -5905,7 +6391,7 @@ function validateSliderElement(element, key, context) {
|
|
|
5905
6391
|
};
|
|
5906
6392
|
if (element.multiple) {
|
|
5907
6393
|
const sliders = scopeRoot.querySelectorAll(
|
|
5908
|
-
`input[type="range"][name^="${key}["]`
|
|
6394
|
+
`input[type="range"][name^="${key}\\["]`
|
|
5909
6395
|
);
|
|
5910
6396
|
const values = [];
|
|
5911
6397
|
sliders.forEach((slider, index) => {
|
|
@@ -5957,7 +6443,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
5957
6443
|
return;
|
|
5958
6444
|
}
|
|
5959
6445
|
const sliders = scopeRoot.querySelectorAll(
|
|
5960
|
-
`input[type="range"][name^="${fieldPath}["]`
|
|
6446
|
+
`input[type="range"][name^="${fieldPath}\\["]`
|
|
5961
6447
|
);
|
|
5962
6448
|
sliders.forEach((slider, index) => {
|
|
5963
6449
|
if (index < value.length && value[index] !== null) {
|
|
@@ -5985,6 +6471,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
5985
6471
|
}
|
|
5986
6472
|
slider.classList.remove("invalid");
|
|
5987
6473
|
slider.title = "";
|
|
6474
|
+
clearFieldError(slider);
|
|
5988
6475
|
}
|
|
5989
6476
|
});
|
|
5990
6477
|
if (value.length !== sliders.length) {
|
|
@@ -6021,6 +6508,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
6021
6508
|
}
|
|
6022
6509
|
slider.classList.remove("invalid");
|
|
6023
6510
|
slider.title = "";
|
|
6511
|
+
clearFieldError(slider);
|
|
6024
6512
|
}
|
|
6025
6513
|
}
|
|
6026
6514
|
}
|
|
@@ -6145,34 +6633,24 @@ function getChildWrapperClass(isSlides, columns) {
|
|
|
6145
6633
|
const cols = columns || 1;
|
|
6146
6634
|
return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
|
|
6147
6635
|
}
|
|
6148
|
-
function mountRemoveButton(item, onRemove) {
|
|
6636
|
+
function mountRemoveButton(item, onRemove, state) {
|
|
6149
6637
|
const rem = document.createElement("button");
|
|
6150
6638
|
rem.type = "button";
|
|
6151
6639
|
rem.className = "fb-item-remove";
|
|
6640
|
+
rem.setAttribute("aria-label", t("removeElement", state));
|
|
6152
6641
|
rem.style.cssText = `
|
|
6153
|
-
width:
|
|
6154
|
-
height:
|
|
6642
|
+
width: 24px;
|
|
6643
|
+
height: 24px;
|
|
6155
6644
|
display: inline-flex;
|
|
6156
6645
|
align-items: center;
|
|
6157
6646
|
justify-content: center;
|
|
6158
6647
|
padding: 0;
|
|
6159
|
-
line-height: 1;
|
|
6160
|
-
font-size: 14px;
|
|
6161
|
-
color: var(--fb-error-color);
|
|
6162
|
-
background-color: transparent;
|
|
6163
6648
|
border: 0;
|
|
6164
6649
|
border-radius: 4px;
|
|
6165
6650
|
cursor: pointer;
|
|
6166
6651
|
flex-shrink: 0;
|
|
6167
|
-
transition: background-color var(--fb-transition-duration);
|
|
6168
6652
|
`;
|
|
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
|
-
});
|
|
6653
|
+
rem.innerHTML = BIN_ICON_SVG;
|
|
6176
6654
|
rem.onclick = onRemove;
|
|
6177
6655
|
const labelRow = item.querySelector("[data-fb-label-row]");
|
|
6178
6656
|
if (labelRow) {
|
|
@@ -6199,7 +6677,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6199
6677
|
itemsWrap.className = "fb-container-slides";
|
|
6200
6678
|
const slideCols = element.columns;
|
|
6201
6679
|
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:
|
|
6680
|
+
itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:var(--fb-slides-gap, 14px);align-items:start;`;
|
|
6203
6681
|
} else {
|
|
6204
6682
|
itemsWrap.className = "space-y-2";
|
|
6205
6683
|
}
|
|
@@ -6230,6 +6708,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6230
6708
|
const item = document.createElement("div");
|
|
6231
6709
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6232
6710
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6711
|
+
if (isSlides) {
|
|
6712
|
+
item.setAttribute("data-fb-slide-card", "");
|
|
6713
|
+
}
|
|
6233
6714
|
const childWrapper = document.createElement("div");
|
|
6234
6715
|
childWrapper.className = getChildWrapperClass(isSlides, element.columns);
|
|
6235
6716
|
element.elements.forEach((child) => {
|
|
@@ -6247,7 +6728,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6247
6728
|
});
|
|
6248
6729
|
item.appendChild(childWrapper);
|
|
6249
6730
|
if (!containerIsReadonly) {
|
|
6250
|
-
mountRemoveButton(item, () => handleRemoveItem(item));
|
|
6731
|
+
mountRemoveButton(item, () => handleRemoveItem(item), state);
|
|
6251
6732
|
}
|
|
6252
6733
|
if (slideAddTile && slideAddTile.parentElement === itemsWrap) {
|
|
6253
6734
|
itemsWrap.insertBefore(item, slideAddTile);
|
|
@@ -6294,6 +6775,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6294
6775
|
const item = document.createElement("div");
|
|
6295
6776
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6296
6777
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6778
|
+
if (isSlides) {
|
|
6779
|
+
item.setAttribute("data-fb-slide-card", "");
|
|
6780
|
+
}
|
|
6297
6781
|
const childWrapper = document.createElement("div");
|
|
6298
6782
|
if (isSlides) {
|
|
6299
6783
|
childWrapper.className = "space-y-2";
|
|
@@ -6318,7 +6802,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6318
6802
|
});
|
|
6319
6803
|
item.appendChild(childWrapper);
|
|
6320
6804
|
if (!containerIsReadonly) {
|
|
6321
|
-
mountRemoveButton(item, () => handleRemoveItem(item));
|
|
6805
|
+
mountRemoveButton(item, () => handleRemoveItem(item), ctx.state);
|
|
6322
6806
|
}
|
|
6323
6807
|
itemsWrap.appendChild(item);
|
|
6324
6808
|
});
|
|
@@ -6338,6 +6822,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6338
6822
|
const item = document.createElement("div");
|
|
6339
6823
|
item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
|
|
6340
6824
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
6825
|
+
if (isSlides) {
|
|
6826
|
+
item.setAttribute("data-fb-slide-card", "");
|
|
6827
|
+
}
|
|
6341
6828
|
const childWrapper = document.createElement("div");
|
|
6342
6829
|
if (isSlides) {
|
|
6343
6830
|
childWrapper.className = "space-y-2";
|
|
@@ -6363,11 +6850,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6363
6850
|
}
|
|
6364
6851
|
});
|
|
6365
6852
|
item.appendChild(childWrapper);
|
|
6366
|
-
mountRemoveButton(
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6853
|
+
mountRemoveButton(
|
|
6854
|
+
item,
|
|
6855
|
+
() => {
|
|
6856
|
+
if (countItems() > min) {
|
|
6857
|
+
handleRemoveItem(item);
|
|
6858
|
+
}
|
|
6859
|
+
},
|
|
6860
|
+
ctx.state
|
|
6861
|
+
);
|
|
6371
6862
|
itemsWrap.appendChild(item);
|
|
6372
6863
|
}
|
|
6373
6864
|
}
|
|
@@ -9060,7 +9551,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9060
9551
|
if (element.minLength != null || element.maxLength != null) {
|
|
9061
9552
|
const counterRow = document.createElement("div");
|
|
9062
9553
|
counterRow.style.cssText = "position: relative; padding: 2px 10px 4px; text-align: right;";
|
|
9063
|
-
const counter = createCharCounter(element, textarea
|
|
9554
|
+
const counter = createCharCounter(element, textarea);
|
|
9064
9555
|
counter.style.cssText = `
|
|
9065
9556
|
position: static;
|
|
9066
9557
|
display: inline-block;
|
|
@@ -9592,6 +10083,118 @@ function validateMarkdown(_element, _key, _context) {
|
|
|
9592
10083
|
function updateMarkdown(_element, _fieldPath, _value, _context) {
|
|
9593
10084
|
}
|
|
9594
10085
|
|
|
10086
|
+
// src/components/registry.ts
|
|
10087
|
+
function validateHiddenElement(element, key, context) {
|
|
10088
|
+
var _a;
|
|
10089
|
+
const { scopeRoot } = context;
|
|
10090
|
+
const input = scopeRoot.querySelector(
|
|
10091
|
+
`input[type="hidden"][data-hidden-field="true"][name="${key}"]`
|
|
10092
|
+
);
|
|
10093
|
+
const raw = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
10094
|
+
if (raw === "") {
|
|
10095
|
+
const defaultVal = "default" in element ? element.default : null;
|
|
10096
|
+
return { value: defaultVal !== void 0 ? defaultVal : null, errors: [] };
|
|
10097
|
+
}
|
|
10098
|
+
return { value: deserializeHiddenValue(raw), errors: [] };
|
|
10099
|
+
}
|
|
10100
|
+
function updateHiddenField(_element, fieldPath, value, context) {
|
|
10101
|
+
const { scopeRoot } = context;
|
|
10102
|
+
const input = scopeRoot.querySelector(
|
|
10103
|
+
`input[type="hidden"][data-hidden-field="true"][name="${fieldPath}"]`
|
|
10104
|
+
);
|
|
10105
|
+
if (!input) return;
|
|
10106
|
+
input.value = serializeHiddenValue(value);
|
|
10107
|
+
}
|
|
10108
|
+
var componentRegistry = {
|
|
10109
|
+
text: {
|
|
10110
|
+
validate: validateTextElement,
|
|
10111
|
+
update: updateTextField
|
|
10112
|
+
},
|
|
10113
|
+
textarea: {
|
|
10114
|
+
validate: validateTextareaElement,
|
|
10115
|
+
update: updateTextareaField
|
|
10116
|
+
},
|
|
10117
|
+
number: {
|
|
10118
|
+
validate: validateNumberElement,
|
|
10119
|
+
update: updateNumberField
|
|
10120
|
+
},
|
|
10121
|
+
select: {
|
|
10122
|
+
validate: validateSelectElement,
|
|
10123
|
+
update: updateSelectField
|
|
10124
|
+
},
|
|
10125
|
+
switcher: {
|
|
10126
|
+
validate: validateSwitcherElement,
|
|
10127
|
+
update: updateSwitcherField
|
|
10128
|
+
},
|
|
10129
|
+
boolean: {
|
|
10130
|
+
validate: validateBooleanElement,
|
|
10131
|
+
update: updateBooleanField,
|
|
10132
|
+
ownsLabel: true
|
|
10133
|
+
},
|
|
10134
|
+
file: {
|
|
10135
|
+
validate: validateFileElement,
|
|
10136
|
+
update: updateFileField
|
|
10137
|
+
},
|
|
10138
|
+
files: {
|
|
10139
|
+
// Legacy type - delegates to file
|
|
10140
|
+
validate: validateFileElement,
|
|
10141
|
+
update: updateFileField
|
|
10142
|
+
},
|
|
10143
|
+
colour: {
|
|
10144
|
+
validate: validateColourElement,
|
|
10145
|
+
update: updateColourField
|
|
10146
|
+
},
|
|
10147
|
+
slider: {
|
|
10148
|
+
validate: validateSliderElement,
|
|
10149
|
+
update: updateSliderField
|
|
10150
|
+
},
|
|
10151
|
+
container: {
|
|
10152
|
+
validate: validateContainerElement,
|
|
10153
|
+
update: updateContainerField
|
|
10154
|
+
},
|
|
10155
|
+
group: {
|
|
10156
|
+
// Deprecated type - delegates to container
|
|
10157
|
+
validate: validateGroupElement,
|
|
10158
|
+
update: updateGroupField
|
|
10159
|
+
},
|
|
10160
|
+
table: {
|
|
10161
|
+
validate: validateTableElement,
|
|
10162
|
+
update: updateTableField
|
|
10163
|
+
},
|
|
10164
|
+
richinput: {
|
|
10165
|
+
validate: validateRichInputElement,
|
|
10166
|
+
update: updateRichInputField
|
|
10167
|
+
},
|
|
10168
|
+
hidden: {
|
|
10169
|
+
// Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
|
|
10170
|
+
validate: validateHiddenElement,
|
|
10171
|
+
update: updateHiddenField
|
|
10172
|
+
},
|
|
10173
|
+
markdown: {
|
|
10174
|
+
// Display-only element — no value, no errors, skip from form data
|
|
10175
|
+
validate: validateMarkdown,
|
|
10176
|
+
update: updateMarkdown
|
|
10177
|
+
}
|
|
10178
|
+
};
|
|
10179
|
+
function getComponentOperations(elementType) {
|
|
10180
|
+
return componentRegistry[elementType] || null;
|
|
10181
|
+
}
|
|
10182
|
+
function validateElementWithComponent(element, key, context) {
|
|
10183
|
+
const ops = getComponentOperations(element.type);
|
|
10184
|
+
if (ops && ops.validate) {
|
|
10185
|
+
return ops.validate(element, key, context);
|
|
10186
|
+
}
|
|
10187
|
+
return null;
|
|
10188
|
+
}
|
|
10189
|
+
function updateElementWithComponent(element, fieldPath, value, context) {
|
|
10190
|
+
const ops = getComponentOperations(element.type);
|
|
10191
|
+
if (ops && ops.update) {
|
|
10192
|
+
ops.update(element, fieldPath, value, context);
|
|
10193
|
+
return true;
|
|
10194
|
+
}
|
|
10195
|
+
return false;
|
|
10196
|
+
}
|
|
10197
|
+
|
|
9595
10198
|
// src/components/index.ts
|
|
9596
10199
|
function showTooltip(tooltipId, button) {
|
|
9597
10200
|
const tooltip = document.getElementById(tooltipId);
|
|
@@ -9904,6 +10507,9 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
9904
10507
|
renderSwitcherElement(element, ctx, wrapper, pathKey);
|
|
9905
10508
|
}
|
|
9906
10509
|
break;
|
|
10510
|
+
case "boolean":
|
|
10511
|
+
renderBooleanElement(element, ctx, wrapper, pathKey);
|
|
10512
|
+
break;
|
|
9907
10513
|
case "file":
|
|
9908
10514
|
if (isMultiple) {
|
|
9909
10515
|
renderMultipleFileElement(element, ctx, wrapper, pathKey);
|
|
@@ -9982,8 +10588,11 @@ function renderElement2(element, ctx) {
|
|
|
9982
10588
|
const wrapper = document.createElement("div");
|
|
9983
10589
|
wrapper.className = "mb-2 fb-field-wrapper";
|
|
9984
10590
|
wrapper.setAttribute("data-field-key", element.key);
|
|
9985
|
-
const
|
|
9986
|
-
|
|
10591
|
+
const ops = getComponentOperations(element.type);
|
|
10592
|
+
if (!(ops == null ? void 0 : ops.ownsLabel)) {
|
|
10593
|
+
const label = createLabelContainer(element);
|
|
10594
|
+
wrapper.appendChild(label);
|
|
10595
|
+
}
|
|
9987
10596
|
const pathKey = pathJoin(ctx.path, element.key);
|
|
9988
10597
|
dispatchToRenderer(element, ctx, wrapper, pathKey);
|
|
9989
10598
|
if (initiallyDisabled) {
|
|
@@ -10216,28 +10825,52 @@ var defaultTheme = {
|
|
|
10216
10825
|
// blue-500
|
|
10217
10826
|
primaryHoverColor: "#2563eb",
|
|
10218
10827
|
// blue-600
|
|
10828
|
+
primarySoftColor: "#dbeafe",
|
|
10829
|
+
// blue-100
|
|
10830
|
+
primarySoftHoverColor: "#bfdbfe",
|
|
10831
|
+
// blue-200
|
|
10219
10832
|
errorColor: "#ef4444",
|
|
10220
10833
|
// red-500
|
|
10221
10834
|
errorHoverColor: "#dc2626",
|
|
10222
10835
|
// red-600
|
|
10223
10836
|
successColor: "#10b981",
|
|
10224
10837
|
// green-500
|
|
10838
|
+
accentColor: "#f59e0b",
|
|
10839
|
+
// amber-500
|
|
10840
|
+
accentSoftColor: "#fef3c7",
|
|
10841
|
+
// amber-100
|
|
10842
|
+
accentBorderColor: "#fde68a",
|
|
10843
|
+
// amber-200
|
|
10844
|
+
accentTextColor: "#92400e",
|
|
10845
|
+
// amber-800
|
|
10225
10846
|
borderColor: "#d1d5db",
|
|
10226
10847
|
// gray-300
|
|
10227
10848
|
borderHoverColor: "#9ca3af",
|
|
10228
10849
|
// gray-400
|
|
10229
10850
|
borderFocusColor: "#3b82f6",
|
|
10230
10851
|
// blue-500
|
|
10852
|
+
borderStrongColor: "#9ca3af",
|
|
10853
|
+
// gray-400
|
|
10231
10854
|
backgroundColor: "#ffffff",
|
|
10232
10855
|
// white
|
|
10233
10856
|
backgroundHoverColor: "#f9fafb",
|
|
10234
10857
|
// gray-50
|
|
10235
10858
|
backgroundReadonlyColor: "#f3f4f6",
|
|
10236
10859
|
// gray-100
|
|
10860
|
+
pageBackgroundColor: "#f9fafb",
|
|
10861
|
+
// gray-50
|
|
10862
|
+
surfaceSoftColor: "#eff6ff",
|
|
10863
|
+
// blue-50
|
|
10864
|
+
surfaceTintColor: "#f8fafc",
|
|
10865
|
+
// slate-50
|
|
10237
10866
|
textColor: "#1f2937",
|
|
10238
10867
|
// gray-800
|
|
10239
10868
|
textSecondaryColor: "#6b7280",
|
|
10240
10869
|
// gray-500
|
|
10870
|
+
textMutedColor: "#9ca3af",
|
|
10871
|
+
// gray-400
|
|
10872
|
+
textFaintColor: "#cbd5e1",
|
|
10873
|
+
// slate-300
|
|
10241
10874
|
textPlaceholderColor: "#9ca3af",
|
|
10242
10875
|
// gray-400
|
|
10243
10876
|
textDisabledColor: "#d1d5db",
|
|
@@ -10280,6 +10913,12 @@ var defaultTheme = {
|
|
|
10280
10913
|
// 4px (compact density v2)
|
|
10281
10914
|
borderRadius: "0.5rem",
|
|
10282
10915
|
// rounded-lg (8px)
|
|
10916
|
+
borderRadiusSmall: "0.375rem",
|
|
10917
|
+
// 6px
|
|
10918
|
+
borderRadiusLarge: "0.75rem",
|
|
10919
|
+
// 12px
|
|
10920
|
+
borderRadiusXLarge: "1rem",
|
|
10921
|
+
// 16px
|
|
10283
10922
|
borderWidth: "1px",
|
|
10284
10923
|
// Typography
|
|
10285
10924
|
fontSize: "0.875rem",
|
|
@@ -10291,13 +10930,31 @@ var defaultTheme = {
|
|
|
10291
10930
|
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
10292
10931
|
fontWeightNormal: "400",
|
|
10293
10932
|
fontWeightMedium: "500",
|
|
10933
|
+
lineHeight: "1.5",
|
|
10934
|
+
// Shadows
|
|
10935
|
+
shadowCard: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
|
|
10936
|
+
shadowPopover: "0 12px 32px -12px rgba(15,23,42,.18), 0 4px 12px -6px rgba(15,23,42,.08)",
|
|
10294
10937
|
// Focus ring
|
|
10295
10938
|
focusRingWidth: "2px",
|
|
10296
10939
|
focusRingColor: "#3b82f6",
|
|
10297
10940
|
// blue-500
|
|
10298
10941
|
focusRingOpacity: "0.5",
|
|
10299
10942
|
// Transitions
|
|
10300
|
-
transitionDuration: "200ms"
|
|
10943
|
+
transitionDuration: "200ms",
|
|
10944
|
+
// Slide-card defaults — flat-white to match every other item card. The
|
|
10945
|
+
// Picaz theme overrides this with a gradient + shadow to lift the slides.
|
|
10946
|
+
slideCardBg: "#ffffff",
|
|
10947
|
+
slideCardShadow: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
|
|
10948
|
+
slideCardRadius: "0.5rem",
|
|
10949
|
+
// matches borderRadius
|
|
10950
|
+
slideCardMinHeight: "0",
|
|
10951
|
+
slideCardPadding: "12px",
|
|
10952
|
+
// Section-label defaults — same look as the regular field label. Themes
|
|
10953
|
+
// that want the "ПРЕИМУЩЕСТВА" caps style override these three vars.
|
|
10954
|
+
labelSectionFontSize: "0.875rem",
|
|
10955
|
+
// matches fontSize
|
|
10956
|
+
labelSectionLetterSpacing: "normal",
|
|
10957
|
+
labelSectionTextTransform: "none"
|
|
10301
10958
|
};
|
|
10302
10959
|
function generateCSSVariables(theme) {
|
|
10303
10960
|
const mergedTheme = { ...defaultTheme, ...theme };
|
|
@@ -10309,6 +10966,7 @@ function generateCSSVariables(theme) {
|
|
|
10309
10966
|
return cssVars.join("\n");
|
|
10310
10967
|
}
|
|
10311
10968
|
function injectThemeVariables(container, theme) {
|
|
10969
|
+
ensureThemingHooks(container.ownerDocument || document);
|
|
10312
10970
|
const cssVariables = generateCSSVariables(theme);
|
|
10313
10971
|
let styleTag = container.querySelector(
|
|
10314
10972
|
"style[data-fb-theme]"
|
|
@@ -10371,115 +11029,50 @@ var exampleThemes = {
|
|
|
10371
11029
|
fontSize: "16px",
|
|
10372
11030
|
fontSizeSmall: "14px",
|
|
10373
11031
|
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
11032
|
},
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
11033
|
+
// Picaz wizard design tokens — derived from the Picaz Wizard mockups.
|
|
11034
|
+
// Pairs with the host-side .card / .section-num / .lede chrome that wraps the form.
|
|
11035
|
+
picaz: {
|
|
11036
|
+
...defaultTheme,
|
|
11037
|
+
primaryColor: "#2f5bea",
|
|
11038
|
+
primaryHoverColor: "#2349c8",
|
|
11039
|
+
primarySoftColor: "#eaf0ff",
|
|
11040
|
+
primarySoftHoverColor: "#d6e0ff",
|
|
11041
|
+
errorColor: "#ef4444",
|
|
11042
|
+
successColor: "#16a34a",
|
|
11043
|
+
accentColor: "#ffb020",
|
|
11044
|
+
accentSoftColor: "#fff7e6",
|
|
11045
|
+
accentBorderColor: "#fde7b5",
|
|
11046
|
+
accentTextColor: "#92400e",
|
|
11047
|
+
borderColor: "#e3e8f0",
|
|
11048
|
+
borderHoverColor: "#cdd6e3",
|
|
11049
|
+
borderFocusColor: "#2f5bea",
|
|
11050
|
+
borderStrongColor: "#cdd6e3",
|
|
11051
|
+
backgroundColor: "#ffffff",
|
|
11052
|
+
backgroundHoverColor: "#f3f7ff",
|
|
11053
|
+
pageBackgroundColor: "#f6f8fb",
|
|
11054
|
+
surfaceSoftColor: "#eef4ff",
|
|
11055
|
+
surfaceTintColor: "#f3f7ff",
|
|
11056
|
+
textColor: "#0f172a",
|
|
11057
|
+
textSecondaryColor: "#334155",
|
|
11058
|
+
textMutedColor: "#64748b",
|
|
11059
|
+
textFaintColor: "#94a3b8",
|
|
11060
|
+
textPlaceholderColor: "#94a3b8",
|
|
11061
|
+
buttonBgColor: "#2f5bea",
|
|
11062
|
+
buttonHoverBgColor: "#2349c8",
|
|
11063
|
+
fileUploadBgColor: "#fafcff",
|
|
11064
|
+
fileUploadBorderColor: "#cdd6e3",
|
|
11065
|
+
fileUploadHoverBorderColor: "#2f5bea",
|
|
11066
|
+
borderRadius: "12px",
|
|
11067
|
+
borderRadiusSmall: "8px",
|
|
11068
|
+
borderRadiusLarge: "16px",
|
|
11069
|
+
borderRadiusXLarge: "22px",
|
|
11070
|
+
fontFamily: '"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
11071
|
+
shadowCard: "0 1px 2px rgba(15,23,42,.04), 0 1px 0 rgba(15,23,42,.02)",
|
|
11072
|
+
shadowPopover: "0 12px 32px -12px rgba(15,23,42,.18), 0 4px 12px -6px rgba(15,23,42,.08)",
|
|
11073
|
+
focusRingColor: "#2f5bea"
|
|
10463
11074
|
}
|
|
10464
11075
|
};
|
|
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
11076
|
|
|
10484
11077
|
// src/instance/FormBuilderInstance.ts
|
|
10485
11078
|
var FormBuilderInstance = class {
|
|
@@ -10604,26 +11197,37 @@ var FormBuilderInstance = class {
|
|
|
10604
11197
|
}
|
|
10605
11198
|
}
|
|
10606
11199
|
/**
|
|
10607
|
-
* Find the DOM element corresponding to a field path (instance-scoped)
|
|
11200
|
+
* Find the DOM element corresponding to a field path (instance-scoped).
|
|
11201
|
+
*
|
|
11202
|
+
* Strategy:
|
|
11203
|
+
* 1. Try a `[name="…"]` lookup first — works for any field that renders
|
|
11204
|
+
* an input/hidden with the path as its name, in either mode. Some
|
|
11205
|
+
* readonly renderers still emit a hidden input (boolean, switcher),
|
|
11206
|
+
* so this path must run regardless of `state.config.readonly`. A
|
|
11207
|
+
* prior version gated this on edit mode only, which made
|
|
11208
|
+
* `updateField` / `setFormData` silently miss readonly boolean
|
|
11209
|
+
* fields whose component also opts out of the standard label row
|
|
11210
|
+
* (`ownsLabel: true`).
|
|
11211
|
+
* 2. If no input matched, fall back to locating the field wrapper by
|
|
11212
|
+
* its visible label text — needed for readonly previews that don't
|
|
11213
|
+
* emit any `name=` attribute (e.g. file/markdown previews).
|
|
10608
11214
|
*/
|
|
10609
11215
|
findFormElementByFieldPath(fieldPath) {
|
|
10610
11216
|
if (!this.state.formRoot) return null;
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
11217
|
+
let element = this.state.formRoot.querySelector(
|
|
11218
|
+
`[name="${fieldPath}"]`
|
|
11219
|
+
);
|
|
11220
|
+
if (element) return element;
|
|
11221
|
+
const variations = [
|
|
11222
|
+
fieldPath,
|
|
11223
|
+
fieldPath.replace(/\[(\d+)\]/g, "[$1]"),
|
|
11224
|
+
fieldPath.replace(/\./g, "[") + "]".repeat((fieldPath.match(/\./g) || []).length)
|
|
11225
|
+
];
|
|
11226
|
+
for (const variation of variations) {
|
|
11227
|
+
element = this.state.formRoot.querySelector(
|
|
11228
|
+
`[name="${variation}"]`
|
|
10614
11229
|
);
|
|
10615
11230
|
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
11231
|
}
|
|
10628
11232
|
const schemaElement = this.findSchemaElement(fieldPath);
|
|
10629
11233
|
if (!schemaElement) return null;
|