@dmitryvim/form-builder 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/browser/formbuilder.min.js +116 -35
- package/dist/browser/formbuilder.v0.2.9.min.js +322 -0
- package/dist/cjs/index.cjs +724 -29
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +718 -29
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +116 -35
- package/dist/types/components/index.d.ts +2 -1
- package/dist/types/components/slider.d.ts +11 -0
- package/dist/types/instance/FormBuilderInstance.d.ts +4 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +20 -1
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.7.min.js +0 -241
package/dist/esm/index.js
CHANGED
|
@@ -87,6 +87,43 @@ function validateSchema(schema) {
|
|
|
87
87
|
validateElements(element.elements, `${elementPath}.elements`);
|
|
88
88
|
}
|
|
89
89
|
if (element.type === "container" && element.elements) {
|
|
90
|
+
if ("columns" in element && element.columns !== void 0) {
|
|
91
|
+
const columns = element.columns;
|
|
92
|
+
const validColumns = [1, 2, 3, 4];
|
|
93
|
+
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
94
|
+
errors.push(
|
|
95
|
+
`${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if ("prefillHints" in element && element.prefillHints) {
|
|
100
|
+
const prefillHints = element.prefillHints;
|
|
101
|
+
if (Array.isArray(prefillHints)) {
|
|
102
|
+
prefillHints.forEach((hint, hintIndex) => {
|
|
103
|
+
if (!hint.label || typeof hint.label !== "string") {
|
|
104
|
+
errors.push(
|
|
105
|
+
`${elementPath}: prefillHints[${hintIndex}] must have a 'label' property of type string`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (!hint.values || typeof hint.values !== "object") {
|
|
109
|
+
errors.push(
|
|
110
|
+
`${elementPath}: prefillHints[${hintIndex}] must have a 'values' property of type object`
|
|
111
|
+
);
|
|
112
|
+
} else {
|
|
113
|
+
for (const fieldKey in hint.values) {
|
|
114
|
+
const fieldExists = element.elements.some(
|
|
115
|
+
(childElement) => childElement.key === fieldKey
|
|
116
|
+
);
|
|
117
|
+
if (!fieldExists) {
|
|
118
|
+
errors.push(
|
|
119
|
+
`container "${element.key}": prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
90
127
|
validateElements(element.elements, `${elementPath}.elements`);
|
|
91
128
|
}
|
|
92
129
|
if (element.type === "select" && element.options) {
|
|
@@ -373,7 +410,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
373
410
|
font-size: var(--fb-font-size);
|
|
374
411
|
transition: all var(--fb-transition-duration);
|
|
375
412
|
`;
|
|
376
|
-
addBtn.textContent =
|
|
413
|
+
addBtn.textContent = "+";
|
|
377
414
|
addBtn.addEventListener("mouseenter", () => {
|
|
378
415
|
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
379
416
|
});
|
|
@@ -622,7 +659,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
622
659
|
removeBtn = document.createElement("button");
|
|
623
660
|
removeBtn.type = "button";
|
|
624
661
|
removeBtn.className = "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
|
|
625
|
-
removeBtn.innerHTML = "\u2715
|
|
662
|
+
removeBtn.innerHTML = "\u2715";
|
|
626
663
|
removeBtn.onclick = () => {
|
|
627
664
|
const currentIndex = Array.from(container.children).indexOf(
|
|
628
665
|
item
|
|
@@ -650,7 +687,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
650
687
|
const addBtn = document.createElement("button");
|
|
651
688
|
addBtn.type = "button";
|
|
652
689
|
addBtn.className = "add-textarea-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
|
|
653
|
-
addBtn.textContent =
|
|
690
|
+
addBtn.textContent = "+";
|
|
654
691
|
addBtn.onclick = () => {
|
|
655
692
|
values.push(element.default || "");
|
|
656
693
|
addTextareaItem(element.default || "");
|
|
@@ -792,7 +829,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
792
829
|
const addBtn = document.createElement("button");
|
|
793
830
|
addBtn.type = "button";
|
|
794
831
|
addBtn.className = "add-number-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
|
|
795
|
-
addBtn.textContent =
|
|
832
|
+
addBtn.textContent = "+";
|
|
796
833
|
addBtn.onclick = () => {
|
|
797
834
|
values.push(element.default || "");
|
|
798
835
|
addNumberItem(element.default || "");
|
|
@@ -1074,7 +1111,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1074
1111
|
const addBtn = document.createElement("button");
|
|
1075
1112
|
addBtn.type = "button";
|
|
1076
1113
|
addBtn.className = "add-select-btn mt-2 px-3 py-1 text-blue-600 border border-blue-300 rounded hover:bg-blue-50 text-sm";
|
|
1077
|
-
addBtn.textContent =
|
|
1114
|
+
addBtn.textContent = "+";
|
|
1078
1115
|
addBtn.onclick = () => {
|
|
1079
1116
|
const defaultValue = element.default || element.options?.[0]?.value || "";
|
|
1080
1117
|
values.push(defaultValue);
|
|
@@ -2584,7 +2621,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
2584
2621
|
font-size: var(--fb-font-size);
|
|
2585
2622
|
transition: all var(--fb-transition-duration);
|
|
2586
2623
|
`;
|
|
2587
|
-
addBtn.textContent =
|
|
2624
|
+
addBtn.textContent = "+";
|
|
2588
2625
|
addBtn.addEventListener("mouseenter", () => {
|
|
2589
2626
|
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
2590
2627
|
});
|
|
@@ -2766,6 +2803,516 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
2766
2803
|
}
|
|
2767
2804
|
}
|
|
2768
2805
|
|
|
2806
|
+
// src/components/slider.ts
|
|
2807
|
+
function positionToExponential(position, min, max) {
|
|
2808
|
+
if (min <= 0) {
|
|
2809
|
+
throw new Error("Exponential scale requires min > 0");
|
|
2810
|
+
}
|
|
2811
|
+
const logMin = Math.log(min);
|
|
2812
|
+
const logMax = Math.log(max);
|
|
2813
|
+
return Math.exp(logMin + position * (logMax - logMin));
|
|
2814
|
+
}
|
|
2815
|
+
function exponentialToPosition(value, min, max) {
|
|
2816
|
+
if (min <= 0) {
|
|
2817
|
+
throw new Error("Exponential scale requires min > 0");
|
|
2818
|
+
}
|
|
2819
|
+
const logMin = Math.log(min);
|
|
2820
|
+
const logMax = Math.log(max);
|
|
2821
|
+
const logValue = Math.log(value);
|
|
2822
|
+
return (logValue - logMin) / (logMax - logMin);
|
|
2823
|
+
}
|
|
2824
|
+
function alignToStep(value, step) {
|
|
2825
|
+
return Math.round(value / step) * step;
|
|
2826
|
+
}
|
|
2827
|
+
function createSliderUI(value, pathKey, element, ctx, readonly) {
|
|
2828
|
+
const container = document.createElement("div");
|
|
2829
|
+
container.className = "slider-container space-y-2";
|
|
2830
|
+
const sliderRow = document.createElement("div");
|
|
2831
|
+
sliderRow.className = "flex items-center gap-3";
|
|
2832
|
+
const slider = document.createElement("input");
|
|
2833
|
+
slider.type = "range";
|
|
2834
|
+
slider.name = pathKey;
|
|
2835
|
+
slider.className = "slider-input flex-1";
|
|
2836
|
+
slider.disabled = readonly;
|
|
2837
|
+
const scale = element.scale || "linear";
|
|
2838
|
+
const min = element.min;
|
|
2839
|
+
const max = element.max;
|
|
2840
|
+
const step = element.step ?? 1;
|
|
2841
|
+
if (scale === "exponential") {
|
|
2842
|
+
if (min <= 0) {
|
|
2843
|
+
throw new Error(
|
|
2844
|
+
`Slider "${element.key}": exponential scale requires min > 0 (got ${min})`
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
slider.min = "0";
|
|
2848
|
+
slider.max = "1000";
|
|
2849
|
+
slider.step = "1";
|
|
2850
|
+
const position = exponentialToPosition(value, min, max);
|
|
2851
|
+
slider.value = (position * 1e3).toString();
|
|
2852
|
+
} else {
|
|
2853
|
+
slider.min = min.toString();
|
|
2854
|
+
slider.max = max.toString();
|
|
2855
|
+
slider.step = step.toString();
|
|
2856
|
+
slider.value = value.toString();
|
|
2857
|
+
}
|
|
2858
|
+
slider.style.cssText = `
|
|
2859
|
+
height: 6px;
|
|
2860
|
+
border-radius: 3px;
|
|
2861
|
+
background: linear-gradient(
|
|
2862
|
+
to right,
|
|
2863
|
+
var(--fb-primary-color) 0%,
|
|
2864
|
+
var(--fb-primary-color) ${(value - min) / (max - min) * 100}%,
|
|
2865
|
+
var(--fb-border-color) ${(value - min) / (max - min) * 100}%,
|
|
2866
|
+
var(--fb-border-color) 100%
|
|
2867
|
+
);
|
|
2868
|
+
outline: none;
|
|
2869
|
+
transition: background 0.1s ease-in-out;
|
|
2870
|
+
cursor: ${readonly ? "not-allowed" : "pointer"};
|
|
2871
|
+
opacity: ${readonly ? "0.6" : "1"};
|
|
2872
|
+
`;
|
|
2873
|
+
const valueDisplay = document.createElement("span");
|
|
2874
|
+
valueDisplay.className = "slider-value";
|
|
2875
|
+
valueDisplay.style.cssText = `
|
|
2876
|
+
min-width: 60px;
|
|
2877
|
+
text-align: right;
|
|
2878
|
+
font-size: var(--fb-font-size);
|
|
2879
|
+
color: var(--fb-text-color);
|
|
2880
|
+
font-family: var(--fb-font-family-mono, monospace);
|
|
2881
|
+
font-weight: 500;
|
|
2882
|
+
`;
|
|
2883
|
+
valueDisplay.textContent = value.toFixed(step < 1 ? 2 : 0);
|
|
2884
|
+
sliderRow.appendChild(slider);
|
|
2885
|
+
sliderRow.appendChild(valueDisplay);
|
|
2886
|
+
container.appendChild(sliderRow);
|
|
2887
|
+
const labelsRow = document.createElement("div");
|
|
2888
|
+
labelsRow.className = "flex justify-between";
|
|
2889
|
+
labelsRow.style.cssText = `
|
|
2890
|
+
font-size: var(--fb-font-size-small);
|
|
2891
|
+
color: var(--fb-text-secondary-color);
|
|
2892
|
+
`;
|
|
2893
|
+
const minLabel = document.createElement("span");
|
|
2894
|
+
minLabel.textContent = min.toString();
|
|
2895
|
+
const maxLabel = document.createElement("span");
|
|
2896
|
+
maxLabel.textContent = max.toString();
|
|
2897
|
+
labelsRow.appendChild(minLabel);
|
|
2898
|
+
labelsRow.appendChild(maxLabel);
|
|
2899
|
+
container.appendChild(labelsRow);
|
|
2900
|
+
if (!readonly) {
|
|
2901
|
+
const updateValue = () => {
|
|
2902
|
+
let displayValue;
|
|
2903
|
+
if (scale === "exponential") {
|
|
2904
|
+
const position = parseFloat(slider.value) / 1e3;
|
|
2905
|
+
displayValue = positionToExponential(position, min, max);
|
|
2906
|
+
displayValue = alignToStep(displayValue, step);
|
|
2907
|
+
displayValue = Math.max(min, Math.min(max, displayValue));
|
|
2908
|
+
} else {
|
|
2909
|
+
displayValue = parseFloat(slider.value);
|
|
2910
|
+
displayValue = alignToStep(displayValue, step);
|
|
2911
|
+
}
|
|
2912
|
+
valueDisplay.textContent = displayValue.toFixed(step < 1 ? 2 : 0);
|
|
2913
|
+
const percentage = (displayValue - min) / (max - min) * 100;
|
|
2914
|
+
slider.style.background = `linear-gradient(
|
|
2915
|
+
to right,
|
|
2916
|
+
var(--fb-primary-color) 0%,
|
|
2917
|
+
var(--fb-primary-color) ${percentage}%,
|
|
2918
|
+
var(--fb-border-color) ${percentage}%,
|
|
2919
|
+
var(--fb-border-color) 100%
|
|
2920
|
+
)`;
|
|
2921
|
+
if (ctx.instance) {
|
|
2922
|
+
ctx.instance.triggerOnChange(pathKey, displayValue);
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
slider.addEventListener("input", updateValue);
|
|
2926
|
+
slider.addEventListener("change", updateValue);
|
|
2927
|
+
}
|
|
2928
|
+
return container;
|
|
2929
|
+
}
|
|
2930
|
+
function renderSliderElement(element, ctx, wrapper, pathKey) {
|
|
2931
|
+
if (element.min === void 0 || element.min === null) {
|
|
2932
|
+
throw new Error(
|
|
2933
|
+
`Slider field "${element.key}" requires "min" property`
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
if (element.max === void 0 || element.max === null) {
|
|
2937
|
+
throw new Error(
|
|
2938
|
+
`Slider field "${element.key}" requires "max" property`
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
if (element.min >= element.max) {
|
|
2942
|
+
throw new Error(
|
|
2943
|
+
`Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
const state = ctx.state;
|
|
2947
|
+
const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
|
|
2948
|
+
const initialValue = ctx.prefill[element.key] ?? defaultValue;
|
|
2949
|
+
const sliderUI = createSliderUI(
|
|
2950
|
+
initialValue,
|
|
2951
|
+
pathKey,
|
|
2952
|
+
element,
|
|
2953
|
+
ctx,
|
|
2954
|
+
state.config.readonly
|
|
2955
|
+
);
|
|
2956
|
+
wrapper.appendChild(sliderUI);
|
|
2957
|
+
const hint = document.createElement("p");
|
|
2958
|
+
hint.className = "mt-1";
|
|
2959
|
+
hint.style.cssText = `
|
|
2960
|
+
font-size: var(--fb-font-size-small);
|
|
2961
|
+
color: var(--fb-text-secondary-color);
|
|
2962
|
+
`;
|
|
2963
|
+
hint.textContent = makeFieldHint(element);
|
|
2964
|
+
wrapper.appendChild(hint);
|
|
2965
|
+
}
|
|
2966
|
+
function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
2967
|
+
if (element.min === void 0 || element.min === null) {
|
|
2968
|
+
throw new Error(
|
|
2969
|
+
`Slider field "${element.key}" requires "min" property`
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
if (element.max === void 0 || element.max === null) {
|
|
2973
|
+
throw new Error(
|
|
2974
|
+
`Slider field "${element.key}" requires "max" property`
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
if (element.min >= element.max) {
|
|
2978
|
+
throw new Error(
|
|
2979
|
+
`Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
const state = ctx.state;
|
|
2983
|
+
const prefillValues = ctx.prefill[element.key] || [];
|
|
2984
|
+
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
2985
|
+
const minCount = element.minCount ?? 1;
|
|
2986
|
+
const maxCount = element.maxCount ?? Infinity;
|
|
2987
|
+
const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
|
|
2988
|
+
while (values.length < minCount) {
|
|
2989
|
+
values.push(defaultValue);
|
|
2990
|
+
}
|
|
2991
|
+
const container = document.createElement("div");
|
|
2992
|
+
container.className = "space-y-3";
|
|
2993
|
+
wrapper.appendChild(container);
|
|
2994
|
+
function updateIndices() {
|
|
2995
|
+
const items = container.querySelectorAll(".multiple-slider-item");
|
|
2996
|
+
items.forEach((item, index) => {
|
|
2997
|
+
const slider = item.querySelector("input[type=range]");
|
|
2998
|
+
if (slider) {
|
|
2999
|
+
slider.setAttribute("name", `${pathKey}[${index}]`);
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
function addSliderItem(value = defaultValue, index = -1) {
|
|
3004
|
+
const itemWrapper = document.createElement("div");
|
|
3005
|
+
itemWrapper.className = "multiple-slider-item flex items-start gap-2";
|
|
3006
|
+
const tempPathKey = `${pathKey}[${container.children.length}]`;
|
|
3007
|
+
const sliderUI = createSliderUI(
|
|
3008
|
+
value,
|
|
3009
|
+
tempPathKey,
|
|
3010
|
+
element,
|
|
3011
|
+
ctx,
|
|
3012
|
+
state.config.readonly
|
|
3013
|
+
);
|
|
3014
|
+
sliderUI.style.flex = "1";
|
|
3015
|
+
itemWrapper.appendChild(sliderUI);
|
|
3016
|
+
if (index === -1) {
|
|
3017
|
+
container.appendChild(itemWrapper);
|
|
3018
|
+
} else {
|
|
3019
|
+
container.insertBefore(itemWrapper, container.children[index]);
|
|
3020
|
+
}
|
|
3021
|
+
updateIndices();
|
|
3022
|
+
return itemWrapper;
|
|
3023
|
+
}
|
|
3024
|
+
function updateRemoveButtons() {
|
|
3025
|
+
if (state.config.readonly) return;
|
|
3026
|
+
const items = container.querySelectorAll(".multiple-slider-item");
|
|
3027
|
+
const currentCount = items.length;
|
|
3028
|
+
items.forEach((item) => {
|
|
3029
|
+
let removeBtn = item.querySelector(
|
|
3030
|
+
".remove-item-btn"
|
|
3031
|
+
);
|
|
3032
|
+
if (!removeBtn) {
|
|
3033
|
+
removeBtn = document.createElement("button");
|
|
3034
|
+
removeBtn.type = "button";
|
|
3035
|
+
removeBtn.className = "remove-item-btn px-2 py-1 rounded";
|
|
3036
|
+
removeBtn.style.cssText = `
|
|
3037
|
+
color: var(--fb-error-color);
|
|
3038
|
+
background-color: transparent;
|
|
3039
|
+
transition: background-color var(--fb-transition-duration);
|
|
3040
|
+
margin-top: 8px;
|
|
3041
|
+
`;
|
|
3042
|
+
removeBtn.innerHTML = "\u2715";
|
|
3043
|
+
removeBtn.addEventListener("mouseenter", () => {
|
|
3044
|
+
removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3045
|
+
});
|
|
3046
|
+
removeBtn.addEventListener("mouseleave", () => {
|
|
3047
|
+
removeBtn.style.backgroundColor = "transparent";
|
|
3048
|
+
});
|
|
3049
|
+
removeBtn.onclick = () => {
|
|
3050
|
+
const currentIndex = Array.from(container.children).indexOf(
|
|
3051
|
+
item
|
|
3052
|
+
);
|
|
3053
|
+
if (container.children.length > minCount) {
|
|
3054
|
+
values.splice(currentIndex, 1);
|
|
3055
|
+
item.remove();
|
|
3056
|
+
updateIndices();
|
|
3057
|
+
updateAddButton();
|
|
3058
|
+
updateRemoveButtons();
|
|
3059
|
+
}
|
|
3060
|
+
};
|
|
3061
|
+
item.appendChild(removeBtn);
|
|
3062
|
+
}
|
|
3063
|
+
const disabled = currentCount <= minCount;
|
|
3064
|
+
removeBtn.disabled = disabled;
|
|
3065
|
+
removeBtn.style.opacity = disabled ? "0.5" : "1";
|
|
3066
|
+
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
function updateAddButton() {
|
|
3070
|
+
const existingAddBtn = wrapper.querySelector(".add-slider-btn");
|
|
3071
|
+
if (existingAddBtn) existingAddBtn.remove();
|
|
3072
|
+
if (!state.config.readonly && values.length < maxCount) {
|
|
3073
|
+
const addBtn = document.createElement("button");
|
|
3074
|
+
addBtn.type = "button";
|
|
3075
|
+
addBtn.className = "add-slider-btn mt-2 px-3 py-1 rounded";
|
|
3076
|
+
addBtn.style.cssText = `
|
|
3077
|
+
color: var(--fb-primary-color);
|
|
3078
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3079
|
+
background-color: transparent;
|
|
3080
|
+
font-size: var(--fb-font-size);
|
|
3081
|
+
transition: all var(--fb-transition-duration);
|
|
3082
|
+
`;
|
|
3083
|
+
addBtn.textContent = "+";
|
|
3084
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
3085
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3086
|
+
});
|
|
3087
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
3088
|
+
addBtn.style.backgroundColor = "transparent";
|
|
3089
|
+
});
|
|
3090
|
+
addBtn.onclick = () => {
|
|
3091
|
+
values.push(defaultValue);
|
|
3092
|
+
addSliderItem(defaultValue);
|
|
3093
|
+
updateAddButton();
|
|
3094
|
+
updateRemoveButtons();
|
|
3095
|
+
};
|
|
3096
|
+
wrapper.appendChild(addBtn);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
values.forEach((value) => addSliderItem(value));
|
|
3100
|
+
updateAddButton();
|
|
3101
|
+
updateRemoveButtons();
|
|
3102
|
+
const hint = document.createElement("p");
|
|
3103
|
+
hint.className = "mt-1";
|
|
3104
|
+
hint.style.cssText = `
|
|
3105
|
+
font-size: var(--fb-font-size-small);
|
|
3106
|
+
color: var(--fb-text-secondary-color);
|
|
3107
|
+
`;
|
|
3108
|
+
hint.textContent = makeFieldHint(element);
|
|
3109
|
+
wrapper.appendChild(hint);
|
|
3110
|
+
}
|
|
3111
|
+
function validateSliderElement(element, key, context) {
|
|
3112
|
+
const errors = [];
|
|
3113
|
+
const { scopeRoot, skipValidation } = context;
|
|
3114
|
+
if (element.min === void 0 || element.min === null) {
|
|
3115
|
+
throw new Error(
|
|
3116
|
+
`Slider validation: field "${key}" requires "min" property`
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
if (element.max === void 0 || element.max === null) {
|
|
3120
|
+
throw new Error(
|
|
3121
|
+
`Slider validation: field "${key}" requires "max" property`
|
|
3122
|
+
);
|
|
3123
|
+
}
|
|
3124
|
+
const min = element.min;
|
|
3125
|
+
const max = element.max;
|
|
3126
|
+
const step = element.step ?? 1;
|
|
3127
|
+
const scale = element.scale || "linear";
|
|
3128
|
+
const markValidity = (input, errorMessage) => {
|
|
3129
|
+
if (!input) return;
|
|
3130
|
+
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
3131
|
+
let errorElement = document.getElementById(errorId);
|
|
3132
|
+
if (errorMessage) {
|
|
3133
|
+
input.classList.add("invalid");
|
|
3134
|
+
input.title = errorMessage;
|
|
3135
|
+
if (!errorElement) {
|
|
3136
|
+
errorElement = document.createElement("div");
|
|
3137
|
+
errorElement.id = errorId;
|
|
3138
|
+
errorElement.className = "error-message";
|
|
3139
|
+
errorElement.style.cssText = `
|
|
3140
|
+
color: var(--fb-error-color);
|
|
3141
|
+
font-size: var(--fb-font-size-small);
|
|
3142
|
+
margin-top: 0.25rem;
|
|
3143
|
+
`;
|
|
3144
|
+
const sliderContainer = input.closest(".slider-container");
|
|
3145
|
+
if (sliderContainer && sliderContainer.nextSibling) {
|
|
3146
|
+
sliderContainer.parentNode?.insertBefore(errorElement, sliderContainer.nextSibling);
|
|
3147
|
+
} else if (sliderContainer) {
|
|
3148
|
+
sliderContainer.parentNode?.appendChild(errorElement);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
errorElement.textContent = errorMessage;
|
|
3152
|
+
errorElement.style.display = "block";
|
|
3153
|
+
} else {
|
|
3154
|
+
input.classList.remove("invalid");
|
|
3155
|
+
input.title = "";
|
|
3156
|
+
if (errorElement) {
|
|
3157
|
+
errorElement.remove();
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
const validateSliderValue = (slider, fieldKey) => {
|
|
3162
|
+
const rawValue = slider.value;
|
|
3163
|
+
if (!rawValue) {
|
|
3164
|
+
if (!skipValidation && element.required) {
|
|
3165
|
+
errors.push(`${fieldKey}: required`);
|
|
3166
|
+
markValidity(slider, "required");
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
3169
|
+
markValidity(slider, null);
|
|
3170
|
+
return null;
|
|
3171
|
+
}
|
|
3172
|
+
let value;
|
|
3173
|
+
if (scale === "exponential") {
|
|
3174
|
+
const position = parseFloat(rawValue) / 1e3;
|
|
3175
|
+
value = positionToExponential(position, min, max);
|
|
3176
|
+
value = alignToStep(value, step);
|
|
3177
|
+
} else {
|
|
3178
|
+
value = parseFloat(rawValue);
|
|
3179
|
+
value = alignToStep(value, step);
|
|
3180
|
+
}
|
|
3181
|
+
if (!skipValidation) {
|
|
3182
|
+
if (value < min) {
|
|
3183
|
+
errors.push(`${fieldKey}: value ${value} < min ${min}`);
|
|
3184
|
+
markValidity(slider, `value must be >= ${min}`);
|
|
3185
|
+
return value;
|
|
3186
|
+
}
|
|
3187
|
+
if (value > max) {
|
|
3188
|
+
errors.push(`${fieldKey}: value ${value} > max ${max}`);
|
|
3189
|
+
markValidity(slider, `value must be <= ${max}`);
|
|
3190
|
+
return value;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
markValidity(slider, null);
|
|
3194
|
+
return value;
|
|
3195
|
+
};
|
|
3196
|
+
if (element.multiple) {
|
|
3197
|
+
const sliders = scopeRoot.querySelectorAll(
|
|
3198
|
+
`input[type="range"][name^="${key}["]`
|
|
3199
|
+
);
|
|
3200
|
+
const values = [];
|
|
3201
|
+
sliders.forEach((slider, index) => {
|
|
3202
|
+
const value = validateSliderValue(slider, `${key}[${index}]`);
|
|
3203
|
+
values.push(value);
|
|
3204
|
+
});
|
|
3205
|
+
if (!skipValidation) {
|
|
3206
|
+
const minCount = element.minCount ?? 1;
|
|
3207
|
+
const maxCount = element.maxCount ?? Infinity;
|
|
3208
|
+
const filteredValues = values.filter((v) => v !== null);
|
|
3209
|
+
if (element.required && filteredValues.length === 0) {
|
|
3210
|
+
errors.push(`${key}: required`);
|
|
3211
|
+
}
|
|
3212
|
+
if (filteredValues.length < minCount) {
|
|
3213
|
+
errors.push(`${key}: minimum ${minCount} items required`);
|
|
3214
|
+
}
|
|
3215
|
+
if (filteredValues.length > maxCount) {
|
|
3216
|
+
errors.push(`${key}: maximum ${maxCount} items allowed`);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
return { value: values, errors };
|
|
3220
|
+
} else {
|
|
3221
|
+
const slider = scopeRoot.querySelector(
|
|
3222
|
+
`input[type="range"][name="${key}"]`
|
|
3223
|
+
);
|
|
3224
|
+
if (!slider) {
|
|
3225
|
+
if (!skipValidation && element.required) {
|
|
3226
|
+
errors.push(`${key}: required`);
|
|
3227
|
+
}
|
|
3228
|
+
return { value: null, errors };
|
|
3229
|
+
}
|
|
3230
|
+
const value = validateSliderValue(slider, key);
|
|
3231
|
+
return { value, errors };
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
function updateSliderField(element, fieldPath, value, context) {
|
|
3235
|
+
const { scopeRoot } = context;
|
|
3236
|
+
const min = element.min;
|
|
3237
|
+
const max = element.max;
|
|
3238
|
+
const step = element.step ?? 1;
|
|
3239
|
+
const scale = element.scale || "linear";
|
|
3240
|
+
if (element.multiple) {
|
|
3241
|
+
if (!Array.isArray(value)) {
|
|
3242
|
+
console.warn(
|
|
3243
|
+
`updateSliderField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
|
|
3244
|
+
);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
const sliders = scopeRoot.querySelectorAll(
|
|
3248
|
+
`input[type="range"][name^="${fieldPath}["]`
|
|
3249
|
+
);
|
|
3250
|
+
sliders.forEach((slider, index) => {
|
|
3251
|
+
if (index < value.length && value[index] !== null) {
|
|
3252
|
+
const numValue = Number(value[index]);
|
|
3253
|
+
if (scale === "exponential") {
|
|
3254
|
+
const position = exponentialToPosition(numValue, min, max);
|
|
3255
|
+
slider.value = (position * 1e3).toString();
|
|
3256
|
+
} else {
|
|
3257
|
+
slider.value = numValue.toString();
|
|
3258
|
+
}
|
|
3259
|
+
const sliderContainer = slider.closest(".slider-container");
|
|
3260
|
+
if (sliderContainer) {
|
|
3261
|
+
const valueDisplay = sliderContainer.querySelector(".slider-value");
|
|
3262
|
+
if (valueDisplay) {
|
|
3263
|
+
valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
|
|
3264
|
+
}
|
|
3265
|
+
const percentage = (numValue - min) / (max - min) * 100;
|
|
3266
|
+
slider.style.background = `linear-gradient(
|
|
3267
|
+
to right,
|
|
3268
|
+
var(--fb-primary-color) 0%,
|
|
3269
|
+
var(--fb-primary-color) ${percentage}%,
|
|
3270
|
+
var(--fb-border-color) ${percentage}%,
|
|
3271
|
+
var(--fb-border-color) 100%
|
|
3272
|
+
)`;
|
|
3273
|
+
}
|
|
3274
|
+
slider.classList.remove("invalid");
|
|
3275
|
+
slider.title = "";
|
|
3276
|
+
}
|
|
3277
|
+
});
|
|
3278
|
+
if (value.length !== sliders.length) {
|
|
3279
|
+
console.warn(
|
|
3280
|
+
`updateSliderField: Multiple field "${fieldPath}" has ${sliders.length} sliders but received ${value.length} values. Consider re-rendering for add/remove.`
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
} else {
|
|
3284
|
+
const slider = scopeRoot.querySelector(
|
|
3285
|
+
`input[type="range"][name="${fieldPath}"]`
|
|
3286
|
+
);
|
|
3287
|
+
if (slider && value !== null && value !== void 0) {
|
|
3288
|
+
const numValue = Number(value);
|
|
3289
|
+
if (scale === "exponential") {
|
|
3290
|
+
const position = exponentialToPosition(numValue, min, max);
|
|
3291
|
+
slider.value = (position * 1e3).toString();
|
|
3292
|
+
} else {
|
|
3293
|
+
slider.value = numValue.toString();
|
|
3294
|
+
}
|
|
3295
|
+
const sliderContainer = slider.closest(".slider-container");
|
|
3296
|
+
if (sliderContainer) {
|
|
3297
|
+
const valueDisplay = sliderContainer.querySelector(".slider-value");
|
|
3298
|
+
if (valueDisplay) {
|
|
3299
|
+
valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
|
|
3300
|
+
}
|
|
3301
|
+
const percentage = (numValue - min) / (max - min) * 100;
|
|
3302
|
+
slider.style.background = `linear-gradient(
|
|
3303
|
+
to right,
|
|
3304
|
+
var(--fb-primary-color) 0%,
|
|
3305
|
+
var(--fb-primary-color) ${percentage}%,
|
|
3306
|
+
var(--fb-border-color) ${percentage}%,
|
|
3307
|
+
var(--fb-border-color) 100%
|
|
3308
|
+
)`;
|
|
3309
|
+
}
|
|
3310
|
+
slider.classList.remove("invalid");
|
|
3311
|
+
slider.title = "";
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
|
|
2769
3316
|
// src/components/container.ts
|
|
2770
3317
|
var renderElementFunc = null;
|
|
2771
3318
|
function setRenderElement(fn) {
|
|
@@ -2779,6 +3326,24 @@ function renderElement(element, ctx) {
|
|
|
2779
3326
|
}
|
|
2780
3327
|
return renderElementFunc(element, ctx);
|
|
2781
3328
|
}
|
|
3329
|
+
function createPrefillHints(element, pathKey) {
|
|
3330
|
+
if (!element.prefillHints || element.prefillHints.length === 0) {
|
|
3331
|
+
return null;
|
|
3332
|
+
}
|
|
3333
|
+
const hintsContainer = document.createElement("div");
|
|
3334
|
+
hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
|
|
3335
|
+
element.prefillHints.forEach((hint, index) => {
|
|
3336
|
+
const hintButton = document.createElement("button");
|
|
3337
|
+
hintButton.type = "button";
|
|
3338
|
+
hintButton.className = "fb-prefill-hint";
|
|
3339
|
+
hintButton.textContent = hint.label;
|
|
3340
|
+
hintButton.setAttribute("data-hint-values", JSON.stringify(hint.values));
|
|
3341
|
+
hintButton.setAttribute("data-container-key", pathKey);
|
|
3342
|
+
hintButton.setAttribute("data-hint-index", String(index));
|
|
3343
|
+
hintsContainer.appendChild(hintButton);
|
|
3344
|
+
});
|
|
3345
|
+
return hintsContainer;
|
|
3346
|
+
}
|
|
2782
3347
|
function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
2783
3348
|
const containerWrap = document.createElement("div");
|
|
2784
3349
|
containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
|
|
@@ -2788,9 +3353,20 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
2788
3353
|
const left = document.createElement("div");
|
|
2789
3354
|
left.className = "flex-1";
|
|
2790
3355
|
const itemsWrap = document.createElement("div");
|
|
2791
|
-
|
|
3356
|
+
const columns = element.columns || 1;
|
|
3357
|
+
if (columns === 1) {
|
|
3358
|
+
itemsWrap.className = "space-y-4";
|
|
3359
|
+
} else {
|
|
3360
|
+
itemsWrap.className = `grid grid-cols-${columns} gap-4`;
|
|
3361
|
+
}
|
|
2792
3362
|
containerWrap.appendChild(header);
|
|
2793
3363
|
header.appendChild(left);
|
|
3364
|
+
if (!ctx.state.config.readonly) {
|
|
3365
|
+
const hintsElement = createPrefillHints(element, pathKey);
|
|
3366
|
+
if (hintsElement) {
|
|
3367
|
+
containerWrap.appendChild(hintsElement);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
2794
3370
|
const subCtx = {
|
|
2795
3371
|
path: pathJoin(ctx.path, element.key),
|
|
2796
3372
|
prefill: ctx.prefill?.[element.key] || {},
|
|
@@ -2816,14 +3392,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2816
3392
|
header.className = "flex justify-between items-center mb-4";
|
|
2817
3393
|
const left = document.createElement("div");
|
|
2818
3394
|
left.className = "flex-1";
|
|
2819
|
-
const right = document.createElement("div");
|
|
2820
|
-
right.className = "flex gap-2";
|
|
2821
3395
|
const itemsWrap = document.createElement("div");
|
|
2822
3396
|
itemsWrap.className = "space-y-4";
|
|
2823
3397
|
containerWrap.appendChild(header);
|
|
2824
3398
|
header.appendChild(left);
|
|
2825
|
-
if (!state.config.readonly) {
|
|
2826
|
-
|
|
3399
|
+
if (!ctx.state.config.readonly) {
|
|
3400
|
+
const hintsElement = createPrefillHints(element, element.key);
|
|
3401
|
+
if (hintsElement) {
|
|
3402
|
+
containerWrap.appendChild(hintsElement);
|
|
3403
|
+
}
|
|
2827
3404
|
}
|
|
2828
3405
|
const min = element.minCount ?? 0;
|
|
2829
3406
|
const max = element.maxCount ?? Infinity;
|
|
@@ -2832,8 +3409,21 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2832
3409
|
const createAddButton = () => {
|
|
2833
3410
|
const add = document.createElement("button");
|
|
2834
3411
|
add.type = "button";
|
|
2835
|
-
add.className = "
|
|
2836
|
-
add.
|
|
3412
|
+
add.className = "add-container-btn mt-2 px-3 py-1 rounded";
|
|
3413
|
+
add.style.cssText = `
|
|
3414
|
+
color: var(--fb-primary-color);
|
|
3415
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3416
|
+
background-color: transparent;
|
|
3417
|
+
font-size: var(--fb-font-size);
|
|
3418
|
+
transition: all var(--fb-transition-duration);
|
|
3419
|
+
`;
|
|
3420
|
+
add.textContent = "+";
|
|
3421
|
+
add.addEventListener("mouseenter", () => {
|
|
3422
|
+
add.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3423
|
+
});
|
|
3424
|
+
add.addEventListener("mouseleave", () => {
|
|
3425
|
+
add.style.backgroundColor = "transparent";
|
|
3426
|
+
});
|
|
2837
3427
|
add.onclick = () => {
|
|
2838
3428
|
if (countItems() < max) {
|
|
2839
3429
|
const idx = countItems();
|
|
@@ -2847,16 +3437,35 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2847
3437
|
const item = document.createElement("div");
|
|
2848
3438
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2849
3439
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3440
|
+
const childWrapper = document.createElement("div");
|
|
3441
|
+
const columns = element.columns || 1;
|
|
3442
|
+
if (columns === 1) {
|
|
3443
|
+
childWrapper.className = "space-y-4";
|
|
3444
|
+
} else {
|
|
3445
|
+
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
3446
|
+
}
|
|
2850
3447
|
element.elements.forEach((child) => {
|
|
2851
3448
|
if (!child.hidden) {
|
|
2852
|
-
|
|
3449
|
+
childWrapper.appendChild(renderElement(child, subCtx));
|
|
2853
3450
|
}
|
|
2854
3451
|
});
|
|
3452
|
+
item.appendChild(childWrapper);
|
|
2855
3453
|
if (!state.config.readonly) {
|
|
2856
3454
|
const rem = document.createElement("button");
|
|
2857
3455
|
rem.type = "button";
|
|
2858
|
-
rem.className = "absolute top-2 right-2
|
|
2859
|
-
rem.
|
|
3456
|
+
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
3457
|
+
rem.style.cssText = `
|
|
3458
|
+
color: var(--fb-error-color);
|
|
3459
|
+
background-color: transparent;
|
|
3460
|
+
transition: background-color var(--fb-transition-duration);
|
|
3461
|
+
`;
|
|
3462
|
+
rem.textContent = "\u2715";
|
|
3463
|
+
rem.addEventListener("mouseenter", () => {
|
|
3464
|
+
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3465
|
+
});
|
|
3466
|
+
rem.addEventListener("mouseleave", () => {
|
|
3467
|
+
rem.style.backgroundColor = "transparent";
|
|
3468
|
+
});
|
|
2860
3469
|
rem.onclick = () => {
|
|
2861
3470
|
item.remove();
|
|
2862
3471
|
updateAddButton();
|
|
@@ -2872,16 +3481,14 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2872
3481
|
};
|
|
2873
3482
|
const updateAddButton = () => {
|
|
2874
3483
|
const currentCount = countItems();
|
|
2875
|
-
const
|
|
2876
|
-
if (
|
|
2877
|
-
|
|
2878
|
-
|
|
3484
|
+
const existingAddBtn = containerWrap.querySelector(".add-container-btn");
|
|
3485
|
+
if (existingAddBtn) {
|
|
3486
|
+
existingAddBtn.disabled = currentCount >= max;
|
|
3487
|
+
existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
|
|
3488
|
+
existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
|
|
2879
3489
|
}
|
|
2880
3490
|
left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-sm text-gray-500">(${currentCount}/${max === Infinity ? "\u221E" : max})</span>`;
|
|
2881
3491
|
};
|
|
2882
|
-
if (!state.config.readonly) {
|
|
2883
|
-
right.appendChild(createAddButton());
|
|
2884
|
-
}
|
|
2885
3492
|
if (pre && Array.isArray(pre)) {
|
|
2886
3493
|
pre.forEach((prefillObj, idx) => {
|
|
2887
3494
|
const subCtx = {
|
|
@@ -2894,16 +3501,35 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2894
3501
|
const item = document.createElement("div");
|
|
2895
3502
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2896
3503
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3504
|
+
const childWrapper = document.createElement("div");
|
|
3505
|
+
const columns = element.columns || 1;
|
|
3506
|
+
if (columns === 1) {
|
|
3507
|
+
childWrapper.className = "space-y-4";
|
|
3508
|
+
} else {
|
|
3509
|
+
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
3510
|
+
}
|
|
2897
3511
|
element.elements.forEach((child) => {
|
|
2898
3512
|
if (!child.hidden) {
|
|
2899
|
-
|
|
3513
|
+
childWrapper.appendChild(renderElement(child, subCtx));
|
|
2900
3514
|
}
|
|
2901
3515
|
});
|
|
3516
|
+
item.appendChild(childWrapper);
|
|
2902
3517
|
if (!state.config.readonly) {
|
|
2903
3518
|
const rem = document.createElement("button");
|
|
2904
3519
|
rem.type = "button";
|
|
2905
|
-
rem.className = "absolute top-2 right-2
|
|
2906
|
-
rem.
|
|
3520
|
+
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
3521
|
+
rem.style.cssText = `
|
|
3522
|
+
color: var(--fb-error-color);
|
|
3523
|
+
background-color: transparent;
|
|
3524
|
+
transition: background-color var(--fb-transition-duration);
|
|
3525
|
+
`;
|
|
3526
|
+
rem.textContent = "\u2715";
|
|
3527
|
+
rem.addEventListener("mouseenter", () => {
|
|
3528
|
+
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3529
|
+
});
|
|
3530
|
+
rem.addEventListener("mouseleave", () => {
|
|
3531
|
+
rem.style.backgroundColor = "transparent";
|
|
3532
|
+
});
|
|
2907
3533
|
rem.onclick = () => {
|
|
2908
3534
|
item.remove();
|
|
2909
3535
|
updateAddButton();
|
|
@@ -2927,15 +3553,34 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2927
3553
|
const item = document.createElement("div");
|
|
2928
3554
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2929
3555
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3556
|
+
const childWrapper = document.createElement("div");
|
|
3557
|
+
const columns = element.columns || 1;
|
|
3558
|
+
if (columns === 1) {
|
|
3559
|
+
childWrapper.className = "space-y-4";
|
|
3560
|
+
} else {
|
|
3561
|
+
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
3562
|
+
}
|
|
2930
3563
|
element.elements.forEach((child) => {
|
|
2931
3564
|
if (!child.hidden) {
|
|
2932
|
-
|
|
3565
|
+
childWrapper.appendChild(renderElement(child, subCtx));
|
|
2933
3566
|
}
|
|
2934
3567
|
});
|
|
3568
|
+
item.appendChild(childWrapper);
|
|
2935
3569
|
const rem = document.createElement("button");
|
|
2936
3570
|
rem.type = "button";
|
|
2937
|
-
rem.className = "absolute top-2 right-2
|
|
2938
|
-
rem.
|
|
3571
|
+
rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
|
|
3572
|
+
rem.style.cssText = `
|
|
3573
|
+
color: var(--fb-error-color);
|
|
3574
|
+
background-color: transparent;
|
|
3575
|
+
transition: background-color var(--fb-transition-duration);
|
|
3576
|
+
`;
|
|
3577
|
+
rem.textContent = "\u2715";
|
|
3578
|
+
rem.addEventListener("mouseenter", () => {
|
|
3579
|
+
rem.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3580
|
+
});
|
|
3581
|
+
rem.addEventListener("mouseleave", () => {
|
|
3582
|
+
rem.style.backgroundColor = "transparent";
|
|
3583
|
+
});
|
|
2939
3584
|
rem.onclick = () => {
|
|
2940
3585
|
if (countItems() > min) {
|
|
2941
3586
|
item.remove();
|
|
@@ -2948,6 +3593,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2948
3593
|
}
|
|
2949
3594
|
}
|
|
2950
3595
|
containerWrap.appendChild(itemsWrap);
|
|
3596
|
+
if (!state.config.readonly) {
|
|
3597
|
+
containerWrap.appendChild(createAddButton());
|
|
3598
|
+
}
|
|
2951
3599
|
updateAddButton();
|
|
2952
3600
|
wrapper.appendChild(containerWrap);
|
|
2953
3601
|
}
|
|
@@ -3318,6 +3966,13 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
3318
3966
|
renderColourElement(element, ctx, wrapper, pathKey);
|
|
3319
3967
|
}
|
|
3320
3968
|
break;
|
|
3969
|
+
case "slider":
|
|
3970
|
+
if (isMultiple) {
|
|
3971
|
+
renderMultipleSliderElement(element, ctx, wrapper, pathKey);
|
|
3972
|
+
} else {
|
|
3973
|
+
renderSliderElement(element, ctx, wrapper, pathKey);
|
|
3974
|
+
}
|
|
3975
|
+
break;
|
|
3321
3976
|
case "group":
|
|
3322
3977
|
renderGroupElement(element, ctx, wrapper, pathKey);
|
|
3323
3978
|
break;
|
|
@@ -3635,6 +4290,10 @@ var componentRegistry = {
|
|
|
3635
4290
|
validate: validateColourElement,
|
|
3636
4291
|
update: updateColourField
|
|
3637
4292
|
},
|
|
4293
|
+
slider: {
|
|
4294
|
+
validate: validateSliderElement,
|
|
4295
|
+
update: updateSliderField
|
|
4296
|
+
},
|
|
3638
4297
|
container: {
|
|
3639
4298
|
validate: validateContainerElement,
|
|
3640
4299
|
update: updateContainerField
|
|
@@ -3991,6 +4650,33 @@ var FormBuilderInstance = class {
|
|
|
3991
4650
|
this.renderFormLevelActions(allFormLevelActions, trueFormLevelActions);
|
|
3992
4651
|
}
|
|
3993
4652
|
}
|
|
4653
|
+
/**
|
|
4654
|
+
* Handle prefill hint click - updates container fields with hint values
|
|
4655
|
+
*/
|
|
4656
|
+
handlePrefillHintClick(event) {
|
|
4657
|
+
const target = event.target;
|
|
4658
|
+
if (!target.classList.contains("fb-prefill-hint")) {
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
event.preventDefault();
|
|
4662
|
+
event.stopPropagation();
|
|
4663
|
+
const hintValuesJson = target.getAttribute("data-hint-values");
|
|
4664
|
+
const containerKey = target.getAttribute("data-container-key");
|
|
4665
|
+
if (!hintValuesJson || !containerKey) {
|
|
4666
|
+
console.warn("Prefill hint missing required data attributes");
|
|
4667
|
+
return;
|
|
4668
|
+
}
|
|
4669
|
+
try {
|
|
4670
|
+
const hintValues = JSON.parse(hintValuesJson);
|
|
4671
|
+
for (const fieldKey in hintValues) {
|
|
4672
|
+
const fullPath = `${containerKey}.${fieldKey}`;
|
|
4673
|
+
const value = hintValues[fieldKey];
|
|
4674
|
+
this.updateField(fullPath, value);
|
|
4675
|
+
}
|
|
4676
|
+
} catch (error) {
|
|
4677
|
+
console.error("Error parsing prefill hint values:", error);
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
3994
4680
|
/**
|
|
3995
4681
|
* Render form from schema
|
|
3996
4682
|
*/
|
|
@@ -4023,6 +4709,9 @@ var FormBuilderInstance = class {
|
|
|
4023
4709
|
formEl.appendChild(block);
|
|
4024
4710
|
});
|
|
4025
4711
|
root.appendChild(formEl);
|
|
4712
|
+
if (!this.state.config.readonly) {
|
|
4713
|
+
root.addEventListener("click", this.handlePrefillHintClick.bind(this));
|
|
4714
|
+
}
|
|
4026
4715
|
if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
|
|
4027
4716
|
this.renderExternalActions();
|
|
4028
4717
|
}
|