@dmitryvim/form-builder 0.2.7 → 0.2.8
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 +104 -41
- package/dist/browser/formbuilder.v0.2.8.min.js +304 -0
- package/dist/cjs/index.cjs +657 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +651 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +104 -41
- 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/cjs/index.cjs
CHANGED
|
@@ -93,6 +93,43 @@ function validateSchema(schema) {
|
|
|
93
93
|
validateElements(element.elements, `${elementPath}.elements`);
|
|
94
94
|
}
|
|
95
95
|
if (element.type === "container" && element.elements) {
|
|
96
|
+
if ("columns" in element && element.columns !== void 0) {
|
|
97
|
+
const columns = element.columns;
|
|
98
|
+
const validColumns = [1, 2, 3, 4];
|
|
99
|
+
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
100
|
+
errors.push(
|
|
101
|
+
`${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if ("prefillHints" in element && element.prefillHints) {
|
|
106
|
+
const prefillHints = element.prefillHints;
|
|
107
|
+
if (Array.isArray(prefillHints)) {
|
|
108
|
+
prefillHints.forEach((hint, hintIndex) => {
|
|
109
|
+
if (!hint.label || typeof hint.label !== "string") {
|
|
110
|
+
errors.push(
|
|
111
|
+
`${elementPath}: prefillHints[${hintIndex}] must have a 'label' property of type string`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (!hint.values || typeof hint.values !== "object") {
|
|
115
|
+
errors.push(
|
|
116
|
+
`${elementPath}: prefillHints[${hintIndex}] must have a 'values' property of type object`
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
for (const fieldKey in hint.values) {
|
|
120
|
+
const fieldExists = element.elements.some(
|
|
121
|
+
(childElement) => childElement.key === fieldKey
|
|
122
|
+
);
|
|
123
|
+
if (!fieldExists) {
|
|
124
|
+
errors.push(
|
|
125
|
+
`container "${element.key}": prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
96
133
|
validateElements(element.elements, `${elementPath}.elements`);
|
|
97
134
|
}
|
|
98
135
|
if (element.type === "select" && element.options) {
|
|
@@ -2808,6 +2845,522 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
2808
2845
|
}
|
|
2809
2846
|
}
|
|
2810
2847
|
|
|
2848
|
+
// src/components/slider.ts
|
|
2849
|
+
function positionToExponential(position, min, max) {
|
|
2850
|
+
if (min <= 0) {
|
|
2851
|
+
throw new Error("Exponential scale requires min > 0");
|
|
2852
|
+
}
|
|
2853
|
+
const logMin = Math.log(min);
|
|
2854
|
+
const logMax = Math.log(max);
|
|
2855
|
+
return Math.exp(logMin + position * (logMax - logMin));
|
|
2856
|
+
}
|
|
2857
|
+
function exponentialToPosition(value, min, max) {
|
|
2858
|
+
if (min <= 0) {
|
|
2859
|
+
throw new Error("Exponential scale requires min > 0");
|
|
2860
|
+
}
|
|
2861
|
+
const logMin = Math.log(min);
|
|
2862
|
+
const logMax = Math.log(max);
|
|
2863
|
+
const logValue = Math.log(value);
|
|
2864
|
+
return (logValue - logMin) / (logMax - logMin);
|
|
2865
|
+
}
|
|
2866
|
+
function alignToStep(value, step) {
|
|
2867
|
+
return Math.round(value / step) * step;
|
|
2868
|
+
}
|
|
2869
|
+
function createSliderUI(value, pathKey, element, ctx, readonly) {
|
|
2870
|
+
var _a;
|
|
2871
|
+
const container = document.createElement("div");
|
|
2872
|
+
container.className = "slider-container space-y-2";
|
|
2873
|
+
const sliderRow = document.createElement("div");
|
|
2874
|
+
sliderRow.className = "flex items-center gap-3";
|
|
2875
|
+
const slider = document.createElement("input");
|
|
2876
|
+
slider.type = "range";
|
|
2877
|
+
slider.name = pathKey;
|
|
2878
|
+
slider.className = "slider-input flex-1";
|
|
2879
|
+
slider.disabled = readonly;
|
|
2880
|
+
const scale = element.scale || "linear";
|
|
2881
|
+
const min = element.min;
|
|
2882
|
+
const max = element.max;
|
|
2883
|
+
const step = (_a = element.step) != null ? _a : 1;
|
|
2884
|
+
if (scale === "exponential") {
|
|
2885
|
+
if (min <= 0) {
|
|
2886
|
+
throw new Error(
|
|
2887
|
+
`Slider "${element.key}": exponential scale requires min > 0 (got ${min})`
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
slider.min = "0";
|
|
2891
|
+
slider.max = "1000";
|
|
2892
|
+
slider.step = "1";
|
|
2893
|
+
const position = exponentialToPosition(value, min, max);
|
|
2894
|
+
slider.value = (position * 1e3).toString();
|
|
2895
|
+
} else {
|
|
2896
|
+
slider.min = min.toString();
|
|
2897
|
+
slider.max = max.toString();
|
|
2898
|
+
slider.step = step.toString();
|
|
2899
|
+
slider.value = value.toString();
|
|
2900
|
+
}
|
|
2901
|
+
slider.style.cssText = `
|
|
2902
|
+
height: 6px;
|
|
2903
|
+
border-radius: 3px;
|
|
2904
|
+
background: linear-gradient(
|
|
2905
|
+
to right,
|
|
2906
|
+
var(--fb-primary-color) 0%,
|
|
2907
|
+
var(--fb-primary-color) ${(value - min) / (max - min) * 100}%,
|
|
2908
|
+
var(--fb-border-color) ${(value - min) / (max - min) * 100}%,
|
|
2909
|
+
var(--fb-border-color) 100%
|
|
2910
|
+
);
|
|
2911
|
+
outline: none;
|
|
2912
|
+
transition: background 0.1s ease-in-out;
|
|
2913
|
+
cursor: ${readonly ? "not-allowed" : "pointer"};
|
|
2914
|
+
opacity: ${readonly ? "0.6" : "1"};
|
|
2915
|
+
`;
|
|
2916
|
+
const valueDisplay = document.createElement("span");
|
|
2917
|
+
valueDisplay.className = "slider-value";
|
|
2918
|
+
valueDisplay.style.cssText = `
|
|
2919
|
+
min-width: 60px;
|
|
2920
|
+
text-align: right;
|
|
2921
|
+
font-size: var(--fb-font-size);
|
|
2922
|
+
color: var(--fb-text-color);
|
|
2923
|
+
font-family: var(--fb-font-family-mono, monospace);
|
|
2924
|
+
font-weight: 500;
|
|
2925
|
+
`;
|
|
2926
|
+
valueDisplay.textContent = value.toFixed(step < 1 ? 2 : 0);
|
|
2927
|
+
sliderRow.appendChild(slider);
|
|
2928
|
+
sliderRow.appendChild(valueDisplay);
|
|
2929
|
+
container.appendChild(sliderRow);
|
|
2930
|
+
const labelsRow = document.createElement("div");
|
|
2931
|
+
labelsRow.className = "flex justify-between";
|
|
2932
|
+
labelsRow.style.cssText = `
|
|
2933
|
+
font-size: var(--fb-font-size-small);
|
|
2934
|
+
color: var(--fb-text-secondary-color);
|
|
2935
|
+
`;
|
|
2936
|
+
const minLabel = document.createElement("span");
|
|
2937
|
+
minLabel.textContent = min.toString();
|
|
2938
|
+
const maxLabel = document.createElement("span");
|
|
2939
|
+
maxLabel.textContent = max.toString();
|
|
2940
|
+
labelsRow.appendChild(minLabel);
|
|
2941
|
+
labelsRow.appendChild(maxLabel);
|
|
2942
|
+
container.appendChild(labelsRow);
|
|
2943
|
+
if (!readonly) {
|
|
2944
|
+
const updateValue = () => {
|
|
2945
|
+
let displayValue;
|
|
2946
|
+
if (scale === "exponential") {
|
|
2947
|
+
const position = parseFloat(slider.value) / 1e3;
|
|
2948
|
+
displayValue = positionToExponential(position, min, max);
|
|
2949
|
+
displayValue = alignToStep(displayValue, step);
|
|
2950
|
+
displayValue = Math.max(min, Math.min(max, displayValue));
|
|
2951
|
+
} else {
|
|
2952
|
+
displayValue = parseFloat(slider.value);
|
|
2953
|
+
displayValue = alignToStep(displayValue, step);
|
|
2954
|
+
}
|
|
2955
|
+
valueDisplay.textContent = displayValue.toFixed(step < 1 ? 2 : 0);
|
|
2956
|
+
const percentage = (displayValue - min) / (max - min) * 100;
|
|
2957
|
+
slider.style.background = `linear-gradient(
|
|
2958
|
+
to right,
|
|
2959
|
+
var(--fb-primary-color) 0%,
|
|
2960
|
+
var(--fb-primary-color) ${percentage}%,
|
|
2961
|
+
var(--fb-border-color) ${percentage}%,
|
|
2962
|
+
var(--fb-border-color) 100%
|
|
2963
|
+
)`;
|
|
2964
|
+
if (ctx.instance) {
|
|
2965
|
+
ctx.instance.triggerOnChange(pathKey, displayValue);
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2968
|
+
slider.addEventListener("input", updateValue);
|
|
2969
|
+
slider.addEventListener("change", updateValue);
|
|
2970
|
+
}
|
|
2971
|
+
return container;
|
|
2972
|
+
}
|
|
2973
|
+
function renderSliderElement(element, ctx, wrapper, pathKey) {
|
|
2974
|
+
var _a;
|
|
2975
|
+
if (element.min === void 0 || element.min === null) {
|
|
2976
|
+
throw new Error(
|
|
2977
|
+
`Slider field "${element.key}" requires "min" property`
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
if (element.max === void 0 || element.max === null) {
|
|
2981
|
+
throw new Error(
|
|
2982
|
+
`Slider field "${element.key}" requires "max" property`
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
if (element.min >= element.max) {
|
|
2986
|
+
throw new Error(
|
|
2987
|
+
`Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
const state = ctx.state;
|
|
2991
|
+
const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
|
|
2992
|
+
const initialValue = (_a = ctx.prefill[element.key]) != null ? _a : defaultValue;
|
|
2993
|
+
const sliderUI = createSliderUI(
|
|
2994
|
+
initialValue,
|
|
2995
|
+
pathKey,
|
|
2996
|
+
element,
|
|
2997
|
+
ctx,
|
|
2998
|
+
state.config.readonly
|
|
2999
|
+
);
|
|
3000
|
+
wrapper.appendChild(sliderUI);
|
|
3001
|
+
const hint = document.createElement("p");
|
|
3002
|
+
hint.className = "mt-1";
|
|
3003
|
+
hint.style.cssText = `
|
|
3004
|
+
font-size: var(--fb-font-size-small);
|
|
3005
|
+
color: var(--fb-text-secondary-color);
|
|
3006
|
+
`;
|
|
3007
|
+
hint.textContent = makeFieldHint(element);
|
|
3008
|
+
wrapper.appendChild(hint);
|
|
3009
|
+
}
|
|
3010
|
+
function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
3011
|
+
var _a, _b;
|
|
3012
|
+
if (element.min === void 0 || element.min === null) {
|
|
3013
|
+
throw new Error(
|
|
3014
|
+
`Slider field "${element.key}" requires "min" property`
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
if (element.max === void 0 || element.max === null) {
|
|
3018
|
+
throw new Error(
|
|
3019
|
+
`Slider field "${element.key}" requires "max" property`
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
if (element.min >= element.max) {
|
|
3023
|
+
throw new Error(
|
|
3024
|
+
`Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
const state = ctx.state;
|
|
3028
|
+
const prefillValues = ctx.prefill[element.key] || [];
|
|
3029
|
+
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
3030
|
+
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
3031
|
+
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
3032
|
+
const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
|
|
3033
|
+
while (values.length < minCount) {
|
|
3034
|
+
values.push(defaultValue);
|
|
3035
|
+
}
|
|
3036
|
+
const container = document.createElement("div");
|
|
3037
|
+
container.className = "space-y-3";
|
|
3038
|
+
wrapper.appendChild(container);
|
|
3039
|
+
function updateIndices() {
|
|
3040
|
+
const items = container.querySelectorAll(".multiple-slider-item");
|
|
3041
|
+
items.forEach((item, index) => {
|
|
3042
|
+
const slider = item.querySelector("input[type=range]");
|
|
3043
|
+
if (slider) {
|
|
3044
|
+
slider.setAttribute("name", `${pathKey}[${index}]`);
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
3048
|
+
function addSliderItem(value = defaultValue, index = -1) {
|
|
3049
|
+
const itemWrapper = document.createElement("div");
|
|
3050
|
+
itemWrapper.className = "multiple-slider-item flex items-start gap-2";
|
|
3051
|
+
const tempPathKey = `${pathKey}[${container.children.length}]`;
|
|
3052
|
+
const sliderUI = createSliderUI(
|
|
3053
|
+
value,
|
|
3054
|
+
tempPathKey,
|
|
3055
|
+
element,
|
|
3056
|
+
ctx,
|
|
3057
|
+
state.config.readonly
|
|
3058
|
+
);
|
|
3059
|
+
sliderUI.style.flex = "1";
|
|
3060
|
+
itemWrapper.appendChild(sliderUI);
|
|
3061
|
+
if (index === -1) {
|
|
3062
|
+
container.appendChild(itemWrapper);
|
|
3063
|
+
} else {
|
|
3064
|
+
container.insertBefore(itemWrapper, container.children[index]);
|
|
3065
|
+
}
|
|
3066
|
+
updateIndices();
|
|
3067
|
+
return itemWrapper;
|
|
3068
|
+
}
|
|
3069
|
+
function updateRemoveButtons() {
|
|
3070
|
+
if (state.config.readonly) return;
|
|
3071
|
+
const items = container.querySelectorAll(".multiple-slider-item");
|
|
3072
|
+
const currentCount = items.length;
|
|
3073
|
+
items.forEach((item) => {
|
|
3074
|
+
let removeBtn = item.querySelector(
|
|
3075
|
+
".remove-item-btn"
|
|
3076
|
+
);
|
|
3077
|
+
if (!removeBtn) {
|
|
3078
|
+
removeBtn = document.createElement("button");
|
|
3079
|
+
removeBtn.type = "button";
|
|
3080
|
+
removeBtn.className = "remove-item-btn px-2 py-1 rounded";
|
|
3081
|
+
removeBtn.style.cssText = `
|
|
3082
|
+
color: var(--fb-error-color);
|
|
3083
|
+
background-color: transparent;
|
|
3084
|
+
transition: background-color var(--fb-transition-duration);
|
|
3085
|
+
margin-top: 8px;
|
|
3086
|
+
`;
|
|
3087
|
+
removeBtn.innerHTML = "\u2715";
|
|
3088
|
+
removeBtn.addEventListener("mouseenter", () => {
|
|
3089
|
+
removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3090
|
+
});
|
|
3091
|
+
removeBtn.addEventListener("mouseleave", () => {
|
|
3092
|
+
removeBtn.style.backgroundColor = "transparent";
|
|
3093
|
+
});
|
|
3094
|
+
removeBtn.onclick = () => {
|
|
3095
|
+
const currentIndex = Array.from(container.children).indexOf(
|
|
3096
|
+
item
|
|
3097
|
+
);
|
|
3098
|
+
if (container.children.length > minCount) {
|
|
3099
|
+
values.splice(currentIndex, 1);
|
|
3100
|
+
item.remove();
|
|
3101
|
+
updateIndices();
|
|
3102
|
+
updateAddButton();
|
|
3103
|
+
updateRemoveButtons();
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
item.appendChild(removeBtn);
|
|
3107
|
+
}
|
|
3108
|
+
const disabled = currentCount <= minCount;
|
|
3109
|
+
removeBtn.disabled = disabled;
|
|
3110
|
+
removeBtn.style.opacity = disabled ? "0.5" : "1";
|
|
3111
|
+
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
function updateAddButton() {
|
|
3115
|
+
const existingAddBtn = wrapper.querySelector(".add-slider-btn");
|
|
3116
|
+
if (existingAddBtn) existingAddBtn.remove();
|
|
3117
|
+
if (!state.config.readonly && values.length < maxCount) {
|
|
3118
|
+
const addBtn = document.createElement("button");
|
|
3119
|
+
addBtn.type = "button";
|
|
3120
|
+
addBtn.className = "add-slider-btn mt-2 px-3 py-1 rounded";
|
|
3121
|
+
addBtn.style.cssText = `
|
|
3122
|
+
color: var(--fb-primary-color);
|
|
3123
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3124
|
+
background-color: transparent;
|
|
3125
|
+
font-size: var(--fb-font-size);
|
|
3126
|
+
transition: all var(--fb-transition-duration);
|
|
3127
|
+
`;
|
|
3128
|
+
addBtn.textContent = `+ Add ${element.label || "Slider"}`;
|
|
3129
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
3130
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3131
|
+
});
|
|
3132
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
3133
|
+
addBtn.style.backgroundColor = "transparent";
|
|
3134
|
+
});
|
|
3135
|
+
addBtn.onclick = () => {
|
|
3136
|
+
values.push(defaultValue);
|
|
3137
|
+
addSliderItem(defaultValue);
|
|
3138
|
+
updateAddButton();
|
|
3139
|
+
updateRemoveButtons();
|
|
3140
|
+
};
|
|
3141
|
+
wrapper.appendChild(addBtn);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
values.forEach((value) => addSliderItem(value));
|
|
3145
|
+
updateAddButton();
|
|
3146
|
+
updateRemoveButtons();
|
|
3147
|
+
const hint = document.createElement("p");
|
|
3148
|
+
hint.className = "mt-1";
|
|
3149
|
+
hint.style.cssText = `
|
|
3150
|
+
font-size: var(--fb-font-size-small);
|
|
3151
|
+
color: var(--fb-text-secondary-color);
|
|
3152
|
+
`;
|
|
3153
|
+
hint.textContent = makeFieldHint(element);
|
|
3154
|
+
wrapper.appendChild(hint);
|
|
3155
|
+
}
|
|
3156
|
+
function validateSliderElement(element, key, context) {
|
|
3157
|
+
var _a, _b, _c;
|
|
3158
|
+
const errors = [];
|
|
3159
|
+
const { scopeRoot, skipValidation } = context;
|
|
3160
|
+
if (element.min === void 0 || element.min === null) {
|
|
3161
|
+
throw new Error(
|
|
3162
|
+
`Slider validation: field "${key}" requires "min" property`
|
|
3163
|
+
);
|
|
3164
|
+
}
|
|
3165
|
+
if (element.max === void 0 || element.max === null) {
|
|
3166
|
+
throw new Error(
|
|
3167
|
+
`Slider validation: field "${key}" requires "max" property`
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
const min = element.min;
|
|
3171
|
+
const max = element.max;
|
|
3172
|
+
const step = (_a = element.step) != null ? _a : 1;
|
|
3173
|
+
const scale = element.scale || "linear";
|
|
3174
|
+
const markValidity = (input, errorMessage) => {
|
|
3175
|
+
var _a2, _b2;
|
|
3176
|
+
if (!input) return;
|
|
3177
|
+
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
3178
|
+
let errorElement = document.getElementById(errorId);
|
|
3179
|
+
if (errorMessage) {
|
|
3180
|
+
input.classList.add("invalid");
|
|
3181
|
+
input.title = errorMessage;
|
|
3182
|
+
if (!errorElement) {
|
|
3183
|
+
errorElement = document.createElement("div");
|
|
3184
|
+
errorElement.id = errorId;
|
|
3185
|
+
errorElement.className = "error-message";
|
|
3186
|
+
errorElement.style.cssText = `
|
|
3187
|
+
color: var(--fb-error-color);
|
|
3188
|
+
font-size: var(--fb-font-size-small);
|
|
3189
|
+
margin-top: 0.25rem;
|
|
3190
|
+
`;
|
|
3191
|
+
const sliderContainer = input.closest(".slider-container");
|
|
3192
|
+
if (sliderContainer && sliderContainer.nextSibling) {
|
|
3193
|
+
(_a2 = sliderContainer.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, sliderContainer.nextSibling);
|
|
3194
|
+
} else if (sliderContainer) {
|
|
3195
|
+
(_b2 = sliderContainer.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
errorElement.textContent = errorMessage;
|
|
3199
|
+
errorElement.style.display = "block";
|
|
3200
|
+
} else {
|
|
3201
|
+
input.classList.remove("invalid");
|
|
3202
|
+
input.title = "";
|
|
3203
|
+
if (errorElement) {
|
|
3204
|
+
errorElement.remove();
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
};
|
|
3208
|
+
const validateSliderValue = (slider, fieldKey) => {
|
|
3209
|
+
const rawValue = slider.value;
|
|
3210
|
+
if (!rawValue) {
|
|
3211
|
+
if (!skipValidation && element.required) {
|
|
3212
|
+
errors.push(`${fieldKey}: required`);
|
|
3213
|
+
markValidity(slider, "required");
|
|
3214
|
+
return null;
|
|
3215
|
+
}
|
|
3216
|
+
markValidity(slider, null);
|
|
3217
|
+
return null;
|
|
3218
|
+
}
|
|
3219
|
+
let value;
|
|
3220
|
+
if (scale === "exponential") {
|
|
3221
|
+
const position = parseFloat(rawValue) / 1e3;
|
|
3222
|
+
value = positionToExponential(position, min, max);
|
|
3223
|
+
value = alignToStep(value, step);
|
|
3224
|
+
} else {
|
|
3225
|
+
value = parseFloat(rawValue);
|
|
3226
|
+
value = alignToStep(value, step);
|
|
3227
|
+
}
|
|
3228
|
+
if (!skipValidation) {
|
|
3229
|
+
if (value < min) {
|
|
3230
|
+
errors.push(`${fieldKey}: value ${value} < min ${min}`);
|
|
3231
|
+
markValidity(slider, `value must be >= ${min}`);
|
|
3232
|
+
return value;
|
|
3233
|
+
}
|
|
3234
|
+
if (value > max) {
|
|
3235
|
+
errors.push(`${fieldKey}: value ${value} > max ${max}`);
|
|
3236
|
+
markValidity(slider, `value must be <= ${max}`);
|
|
3237
|
+
return value;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
markValidity(slider, null);
|
|
3241
|
+
return value;
|
|
3242
|
+
};
|
|
3243
|
+
if (element.multiple) {
|
|
3244
|
+
const sliders = scopeRoot.querySelectorAll(
|
|
3245
|
+
`input[type="range"][name^="${key}["]`
|
|
3246
|
+
);
|
|
3247
|
+
const values = [];
|
|
3248
|
+
sliders.forEach((slider, index) => {
|
|
3249
|
+
const value = validateSliderValue(slider, `${key}[${index}]`);
|
|
3250
|
+
values.push(value);
|
|
3251
|
+
});
|
|
3252
|
+
if (!skipValidation) {
|
|
3253
|
+
const minCount = (_b = element.minCount) != null ? _b : 1;
|
|
3254
|
+
const maxCount = (_c = element.maxCount) != null ? _c : Infinity;
|
|
3255
|
+
const filteredValues = values.filter((v) => v !== null);
|
|
3256
|
+
if (element.required && filteredValues.length === 0) {
|
|
3257
|
+
errors.push(`${key}: required`);
|
|
3258
|
+
}
|
|
3259
|
+
if (filteredValues.length < minCount) {
|
|
3260
|
+
errors.push(`${key}: minimum ${minCount} items required`);
|
|
3261
|
+
}
|
|
3262
|
+
if (filteredValues.length > maxCount) {
|
|
3263
|
+
errors.push(`${key}: maximum ${maxCount} items allowed`);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
return { value: values, errors };
|
|
3267
|
+
} else {
|
|
3268
|
+
const slider = scopeRoot.querySelector(
|
|
3269
|
+
`input[type="range"][name="${key}"]`
|
|
3270
|
+
);
|
|
3271
|
+
if (!slider) {
|
|
3272
|
+
if (!skipValidation && element.required) {
|
|
3273
|
+
errors.push(`${key}: required`);
|
|
3274
|
+
}
|
|
3275
|
+
return { value: null, errors };
|
|
3276
|
+
}
|
|
3277
|
+
const value = validateSliderValue(slider, key);
|
|
3278
|
+
return { value, errors };
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
function updateSliderField(element, fieldPath, value, context) {
|
|
3282
|
+
var _a;
|
|
3283
|
+
const { scopeRoot } = context;
|
|
3284
|
+
const min = element.min;
|
|
3285
|
+
const max = element.max;
|
|
3286
|
+
const step = (_a = element.step) != null ? _a : 1;
|
|
3287
|
+
const scale = element.scale || "linear";
|
|
3288
|
+
if (element.multiple) {
|
|
3289
|
+
if (!Array.isArray(value)) {
|
|
3290
|
+
console.warn(
|
|
3291
|
+
`updateSliderField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
|
|
3292
|
+
);
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
const sliders = scopeRoot.querySelectorAll(
|
|
3296
|
+
`input[type="range"][name^="${fieldPath}["]`
|
|
3297
|
+
);
|
|
3298
|
+
sliders.forEach((slider, index) => {
|
|
3299
|
+
if (index < value.length && value[index] !== null) {
|
|
3300
|
+
const numValue = Number(value[index]);
|
|
3301
|
+
if (scale === "exponential") {
|
|
3302
|
+
const position = exponentialToPosition(numValue, min, max);
|
|
3303
|
+
slider.value = (position * 1e3).toString();
|
|
3304
|
+
} else {
|
|
3305
|
+
slider.value = numValue.toString();
|
|
3306
|
+
}
|
|
3307
|
+
const sliderContainer = slider.closest(".slider-container");
|
|
3308
|
+
if (sliderContainer) {
|
|
3309
|
+
const valueDisplay = sliderContainer.querySelector(".slider-value");
|
|
3310
|
+
if (valueDisplay) {
|
|
3311
|
+
valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
|
|
3312
|
+
}
|
|
3313
|
+
const percentage = (numValue - min) / (max - min) * 100;
|
|
3314
|
+
slider.style.background = `linear-gradient(
|
|
3315
|
+
to right,
|
|
3316
|
+
var(--fb-primary-color) 0%,
|
|
3317
|
+
var(--fb-primary-color) ${percentage}%,
|
|
3318
|
+
var(--fb-border-color) ${percentage}%,
|
|
3319
|
+
var(--fb-border-color) 100%
|
|
3320
|
+
)`;
|
|
3321
|
+
}
|
|
3322
|
+
slider.classList.remove("invalid");
|
|
3323
|
+
slider.title = "";
|
|
3324
|
+
}
|
|
3325
|
+
});
|
|
3326
|
+
if (value.length !== sliders.length) {
|
|
3327
|
+
console.warn(
|
|
3328
|
+
`updateSliderField: Multiple field "${fieldPath}" has ${sliders.length} sliders but received ${value.length} values. Consider re-rendering for add/remove.`
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
} else {
|
|
3332
|
+
const slider = scopeRoot.querySelector(
|
|
3333
|
+
`input[type="range"][name="${fieldPath}"]`
|
|
3334
|
+
);
|
|
3335
|
+
if (slider && value !== null && value !== void 0) {
|
|
3336
|
+
const numValue = Number(value);
|
|
3337
|
+
if (scale === "exponential") {
|
|
3338
|
+
const position = exponentialToPosition(numValue, min, max);
|
|
3339
|
+
slider.value = (position * 1e3).toString();
|
|
3340
|
+
} else {
|
|
3341
|
+
slider.value = numValue.toString();
|
|
3342
|
+
}
|
|
3343
|
+
const sliderContainer = slider.closest(".slider-container");
|
|
3344
|
+
if (sliderContainer) {
|
|
3345
|
+
const valueDisplay = sliderContainer.querySelector(".slider-value");
|
|
3346
|
+
if (valueDisplay) {
|
|
3347
|
+
valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
|
|
3348
|
+
}
|
|
3349
|
+
const percentage = (numValue - min) / (max - min) * 100;
|
|
3350
|
+
slider.style.background = `linear-gradient(
|
|
3351
|
+
to right,
|
|
3352
|
+
var(--fb-primary-color) 0%,
|
|
3353
|
+
var(--fb-primary-color) ${percentage}%,
|
|
3354
|
+
var(--fb-border-color) ${percentage}%,
|
|
3355
|
+
var(--fb-border-color) 100%
|
|
3356
|
+
)`;
|
|
3357
|
+
}
|
|
3358
|
+
slider.classList.remove("invalid");
|
|
3359
|
+
slider.title = "";
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
2811
3364
|
// src/components/container.ts
|
|
2812
3365
|
var renderElementFunc = null;
|
|
2813
3366
|
function setRenderElement(fn) {
|
|
@@ -2821,6 +3374,24 @@ function renderElement(element, ctx) {
|
|
|
2821
3374
|
}
|
|
2822
3375
|
return renderElementFunc(element, ctx);
|
|
2823
3376
|
}
|
|
3377
|
+
function createPrefillHints(element, pathKey) {
|
|
3378
|
+
if (!element.prefillHints || element.prefillHints.length === 0) {
|
|
3379
|
+
return null;
|
|
3380
|
+
}
|
|
3381
|
+
const hintsContainer = document.createElement("div");
|
|
3382
|
+
hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
|
|
3383
|
+
element.prefillHints.forEach((hint, index) => {
|
|
3384
|
+
const hintButton = document.createElement("button");
|
|
3385
|
+
hintButton.type = "button";
|
|
3386
|
+
hintButton.className = "fb-prefill-hint";
|
|
3387
|
+
hintButton.textContent = hint.label;
|
|
3388
|
+
hintButton.setAttribute("data-hint-values", JSON.stringify(hint.values));
|
|
3389
|
+
hintButton.setAttribute("data-container-key", pathKey);
|
|
3390
|
+
hintButton.setAttribute("data-hint-index", String(index));
|
|
3391
|
+
hintsContainer.appendChild(hintButton);
|
|
3392
|
+
});
|
|
3393
|
+
return hintsContainer;
|
|
3394
|
+
}
|
|
2824
3395
|
function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
2825
3396
|
var _a, _b;
|
|
2826
3397
|
const containerWrap = document.createElement("div");
|
|
@@ -2831,9 +3402,20 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
2831
3402
|
const left = document.createElement("div");
|
|
2832
3403
|
left.className = "flex-1";
|
|
2833
3404
|
const itemsWrap = document.createElement("div");
|
|
2834
|
-
|
|
3405
|
+
const columns = element.columns || 1;
|
|
3406
|
+
if (columns === 1) {
|
|
3407
|
+
itemsWrap.className = "space-y-4";
|
|
3408
|
+
} else {
|
|
3409
|
+
itemsWrap.className = `grid grid-cols-${columns} gap-4`;
|
|
3410
|
+
}
|
|
2835
3411
|
containerWrap.appendChild(header);
|
|
2836
3412
|
header.appendChild(left);
|
|
3413
|
+
if (!ctx.state.config.readonly) {
|
|
3414
|
+
const hintsElement = createPrefillHints(element, pathKey);
|
|
3415
|
+
if (hintsElement) {
|
|
3416
|
+
containerWrap.appendChild(hintsElement);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
2837
3419
|
const subCtx = {
|
|
2838
3420
|
path: pathJoin(ctx.path, element.key),
|
|
2839
3421
|
prefill: ((_a = ctx.prefill) == null ? void 0 : _a[element.key]) || {},
|
|
@@ -2869,6 +3451,12 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2869
3451
|
if (!state.config.readonly) {
|
|
2870
3452
|
header.appendChild(right);
|
|
2871
3453
|
}
|
|
3454
|
+
if (!ctx.state.config.readonly) {
|
|
3455
|
+
const hintsElement = createPrefillHints(element, element.key);
|
|
3456
|
+
if (hintsElement) {
|
|
3457
|
+
containerWrap.appendChild(hintsElement);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
2872
3460
|
const min = (_a = element.minCount) != null ? _a : 0;
|
|
2873
3461
|
const max = (_b = element.maxCount) != null ? _b : Infinity;
|
|
2874
3462
|
const pre = Array.isArray((_c = ctx.prefill) == null ? void 0 : _c[element.key]) ? ctx.prefill[element.key] : null;
|
|
@@ -2892,11 +3480,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2892
3480
|
const item = document.createElement("div");
|
|
2893
3481
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2894
3482
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3483
|
+
const childWrapper = document.createElement("div");
|
|
3484
|
+
const columns = element.columns || 1;
|
|
3485
|
+
if (columns === 1) {
|
|
3486
|
+
childWrapper.className = "space-y-4";
|
|
3487
|
+
} else {
|
|
3488
|
+
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
3489
|
+
}
|
|
2895
3490
|
element.elements.forEach((child) => {
|
|
2896
3491
|
if (!child.hidden) {
|
|
2897
|
-
|
|
3492
|
+
childWrapper.appendChild(renderElement(child, subCtx));
|
|
2898
3493
|
}
|
|
2899
3494
|
});
|
|
3495
|
+
item.appendChild(childWrapper);
|
|
2900
3496
|
if (!state.config.readonly) {
|
|
2901
3497
|
const rem = document.createElement("button");
|
|
2902
3498
|
rem.type = "button";
|
|
@@ -2940,11 +3536,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2940
3536
|
const item = document.createElement("div");
|
|
2941
3537
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2942
3538
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3539
|
+
const childWrapper = document.createElement("div");
|
|
3540
|
+
const columns = element.columns || 1;
|
|
3541
|
+
if (columns === 1) {
|
|
3542
|
+
childWrapper.className = "space-y-4";
|
|
3543
|
+
} else {
|
|
3544
|
+
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
3545
|
+
}
|
|
2943
3546
|
element.elements.forEach((child) => {
|
|
2944
3547
|
if (!child.hidden) {
|
|
2945
|
-
|
|
3548
|
+
childWrapper.appendChild(renderElement(child, subCtx));
|
|
2946
3549
|
}
|
|
2947
3550
|
});
|
|
3551
|
+
item.appendChild(childWrapper);
|
|
2948
3552
|
if (!state.config.readonly) {
|
|
2949
3553
|
const rem = document.createElement("button");
|
|
2950
3554
|
rem.type = "button";
|
|
@@ -2973,11 +3577,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
2973
3577
|
const item = document.createElement("div");
|
|
2974
3578
|
item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
|
|
2975
3579
|
item.setAttribute("data-container-item", `${element.key}[${idx}]`);
|
|
3580
|
+
const childWrapper = document.createElement("div");
|
|
3581
|
+
const columns = element.columns || 1;
|
|
3582
|
+
if (columns === 1) {
|
|
3583
|
+
childWrapper.className = "space-y-4";
|
|
3584
|
+
} else {
|
|
3585
|
+
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
3586
|
+
}
|
|
2976
3587
|
element.elements.forEach((child) => {
|
|
2977
3588
|
if (!child.hidden) {
|
|
2978
|
-
|
|
3589
|
+
childWrapper.appendChild(renderElement(child, subCtx));
|
|
2979
3590
|
}
|
|
2980
3591
|
});
|
|
3592
|
+
item.appendChild(childWrapper);
|
|
2981
3593
|
const rem = document.createElement("button");
|
|
2982
3594
|
rem.type = "button";
|
|
2983
3595
|
rem.className = "absolute top-2 right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 transition-colors";
|
|
@@ -3368,6 +3980,13 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
3368
3980
|
renderColourElement(element, ctx, wrapper, pathKey);
|
|
3369
3981
|
}
|
|
3370
3982
|
break;
|
|
3983
|
+
case "slider":
|
|
3984
|
+
if (isMultiple) {
|
|
3985
|
+
renderMultipleSliderElement(element, ctx, wrapper, pathKey);
|
|
3986
|
+
} else {
|
|
3987
|
+
renderSliderElement(element, ctx, wrapper, pathKey);
|
|
3988
|
+
}
|
|
3989
|
+
break;
|
|
3371
3990
|
case "group":
|
|
3372
3991
|
renderGroupElement(element, ctx, wrapper, pathKey);
|
|
3373
3992
|
break;
|
|
@@ -3685,6 +4304,10 @@ var componentRegistry = {
|
|
|
3685
4304
|
validate: validateColourElement,
|
|
3686
4305
|
update: updateColourField
|
|
3687
4306
|
},
|
|
4307
|
+
slider: {
|
|
4308
|
+
validate: validateSliderElement,
|
|
4309
|
+
update: updateSliderField
|
|
4310
|
+
},
|
|
3688
4311
|
container: {
|
|
3689
4312
|
validate: validateContainerElement,
|
|
3690
4313
|
update: updateContainerField
|
|
@@ -4041,6 +4664,33 @@ var FormBuilderInstance = class {
|
|
|
4041
4664
|
this.renderFormLevelActions(allFormLevelActions, trueFormLevelActions);
|
|
4042
4665
|
}
|
|
4043
4666
|
}
|
|
4667
|
+
/**
|
|
4668
|
+
* Handle prefill hint click - updates container fields with hint values
|
|
4669
|
+
*/
|
|
4670
|
+
handlePrefillHintClick(event) {
|
|
4671
|
+
const target = event.target;
|
|
4672
|
+
if (!target.classList.contains("fb-prefill-hint")) {
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
event.preventDefault();
|
|
4676
|
+
event.stopPropagation();
|
|
4677
|
+
const hintValuesJson = target.getAttribute("data-hint-values");
|
|
4678
|
+
const containerKey = target.getAttribute("data-container-key");
|
|
4679
|
+
if (!hintValuesJson || !containerKey) {
|
|
4680
|
+
console.warn("Prefill hint missing required data attributes");
|
|
4681
|
+
return;
|
|
4682
|
+
}
|
|
4683
|
+
try {
|
|
4684
|
+
const hintValues = JSON.parse(hintValuesJson);
|
|
4685
|
+
for (const fieldKey in hintValues) {
|
|
4686
|
+
const fullPath = `${containerKey}.${fieldKey}`;
|
|
4687
|
+
const value = hintValues[fieldKey];
|
|
4688
|
+
this.updateField(fullPath, value);
|
|
4689
|
+
}
|
|
4690
|
+
} catch (error) {
|
|
4691
|
+
console.error("Error parsing prefill hint values:", error);
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4044
4694
|
/**
|
|
4045
4695
|
* Render form from schema
|
|
4046
4696
|
*/
|
|
@@ -4073,6 +4723,9 @@ var FormBuilderInstance = class {
|
|
|
4073
4723
|
formEl.appendChild(block);
|
|
4074
4724
|
});
|
|
4075
4725
|
root.appendChild(formEl);
|
|
4726
|
+
if (!this.state.config.readonly) {
|
|
4727
|
+
root.addEventListener("click", this.handlePrefillHintClick.bind(this));
|
|
4728
|
+
}
|
|
4076
4729
|
if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
|
|
4077
4730
|
this.renderExternalActions();
|
|
4078
4731
|
}
|