@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.
@@ -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) {
@@ -380,7 +417,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
380
417
  font-size: var(--fb-font-size);
381
418
  transition: all var(--fb-transition-duration);
382
419
  `;
383
- addBtn.textContent = `+ Add ${element.label || "Text"}`;
420
+ addBtn.textContent = "+";
384
421
  addBtn.addEventListener("mouseenter", () => {
385
422
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
386
423
  });
@@ -633,7 +670,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
633
670
  removeBtn = document.createElement("button");
634
671
  removeBtn.type = "button";
635
672
  removeBtn.className = "remove-item-btn mt-1 px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm";
636
- removeBtn.innerHTML = "\u2715 Remove";
673
+ removeBtn.innerHTML = "\u2715";
637
674
  removeBtn.onclick = () => {
638
675
  const currentIndex = Array.from(container.children).indexOf(
639
676
  item
@@ -661,7 +698,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
661
698
  const addBtn = document.createElement("button");
662
699
  addBtn.type = "button";
663
700
  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";
664
- addBtn.textContent = `+ Add ${element.label || "Textarea"}`;
701
+ addBtn.textContent = "+";
665
702
  addBtn.onclick = () => {
666
703
  values.push(element.default || "");
667
704
  addTextareaItem(element.default || "");
@@ -804,7 +841,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
804
841
  const addBtn = document.createElement("button");
805
842
  addBtn.type = "button";
806
843
  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";
807
- addBtn.textContent = `+ Add ${element.label || "Number"}`;
844
+ addBtn.textContent = "+";
808
845
  addBtn.onclick = () => {
809
846
  values.push(element.default || "");
810
847
  addNumberItem(element.default || "");
@@ -1090,7 +1127,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1090
1127
  const addBtn = document.createElement("button");
1091
1128
  addBtn.type = "button";
1092
1129
  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";
1093
- addBtn.textContent = `+ Add ${element.label || "Selection"}`;
1130
+ addBtn.textContent = "+";
1094
1131
  addBtn.onclick = () => {
1095
1132
  var _a2, _b2;
1096
1133
  const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
@@ -2623,7 +2660,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
2623
2660
  font-size: var(--fb-font-size);
2624
2661
  transition: all var(--fb-transition-duration);
2625
2662
  `;
2626
- addBtn.textContent = `+ Add ${element.label || "Colour"}`;
2663
+ addBtn.textContent = "+";
2627
2664
  addBtn.addEventListener("mouseenter", () => {
2628
2665
  addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2629
2666
  });
@@ -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 = "+";
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
- itemsWrap.className = "space-y-4";
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]) || {},
@@ -2860,14 +3442,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2860
3442
  header.className = "flex justify-between items-center mb-4";
2861
3443
  const left = document.createElement("div");
2862
3444
  left.className = "flex-1";
2863
- const right = document.createElement("div");
2864
- right.className = "flex gap-2";
2865
3445
  const itemsWrap = document.createElement("div");
2866
3446
  itemsWrap.className = "space-y-4";
2867
3447
  containerWrap.appendChild(header);
2868
3448
  header.appendChild(left);
2869
- if (!state.config.readonly) {
2870
- header.appendChild(right);
3449
+ if (!ctx.state.config.readonly) {
3450
+ const hintsElement = createPrefillHints(element, element.key);
3451
+ if (hintsElement) {
3452
+ containerWrap.appendChild(hintsElement);
3453
+ }
2871
3454
  }
2872
3455
  const min = (_a = element.minCount) != null ? _a : 0;
2873
3456
  const max = (_b = element.maxCount) != null ? _b : Infinity;
@@ -2876,8 +3459,21 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2876
3459
  const createAddButton = () => {
2877
3460
  const add = document.createElement("button");
2878
3461
  add.type = "button";
2879
- add.className = "px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors";
2880
- add.textContent = t("addElement", state);
3462
+ add.className = "add-container-btn mt-2 px-3 py-1 rounded";
3463
+ add.style.cssText = `
3464
+ color: var(--fb-primary-color);
3465
+ border: var(--fb-border-width) solid var(--fb-primary-color);
3466
+ background-color: transparent;
3467
+ font-size: var(--fb-font-size);
3468
+ transition: all var(--fb-transition-duration);
3469
+ `;
3470
+ add.textContent = "+";
3471
+ add.addEventListener("mouseenter", () => {
3472
+ add.style.backgroundColor = "var(--fb-background-hover-color)";
3473
+ });
3474
+ add.addEventListener("mouseleave", () => {
3475
+ add.style.backgroundColor = "transparent";
3476
+ });
2881
3477
  add.onclick = () => {
2882
3478
  var _a2;
2883
3479
  if (countItems() < max) {
@@ -2892,16 +3488,35 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2892
3488
  const item = document.createElement("div");
2893
3489
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2894
3490
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3491
+ const childWrapper = document.createElement("div");
3492
+ const columns = element.columns || 1;
3493
+ if (columns === 1) {
3494
+ childWrapper.className = "space-y-4";
3495
+ } else {
3496
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3497
+ }
2895
3498
  element.elements.forEach((child) => {
2896
3499
  if (!child.hidden) {
2897
- item.appendChild(renderElement(child, subCtx));
3500
+ childWrapper.appendChild(renderElement(child, subCtx));
2898
3501
  }
2899
3502
  });
3503
+ item.appendChild(childWrapper);
2900
3504
  if (!state.config.readonly) {
2901
3505
  const rem = document.createElement("button");
2902
3506
  rem.type = "button";
2903
- 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";
2904
- rem.textContent = "\xD7";
3507
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3508
+ rem.style.cssText = `
3509
+ color: var(--fb-error-color);
3510
+ background-color: transparent;
3511
+ transition: background-color var(--fb-transition-duration);
3512
+ `;
3513
+ rem.textContent = "\u2715";
3514
+ rem.addEventListener("mouseenter", () => {
3515
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3516
+ });
3517
+ rem.addEventListener("mouseleave", () => {
3518
+ rem.style.backgroundColor = "transparent";
3519
+ });
2905
3520
  rem.onclick = () => {
2906
3521
  item.remove();
2907
3522
  updateAddButton();
@@ -2917,16 +3532,14 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2917
3532
  };
2918
3533
  const updateAddButton = () => {
2919
3534
  const currentCount = countItems();
2920
- const addBtn = right.querySelector("button");
2921
- if (addBtn) {
2922
- addBtn.disabled = currentCount >= max;
2923
- addBtn.style.opacity = currentCount >= max ? "0.5" : "1";
3535
+ const existingAddBtn = containerWrap.querySelector(".add-container-btn");
3536
+ if (existingAddBtn) {
3537
+ existingAddBtn.disabled = currentCount >= max;
3538
+ existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
3539
+ existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
2924
3540
  }
2925
3541
  left.innerHTML = `<span>${element.label || element.key}</span> <span class="text-sm text-gray-500">(${currentCount}/${max === Infinity ? "\u221E" : max})</span>`;
2926
3542
  };
2927
- if (!state.config.readonly) {
2928
- right.appendChild(createAddButton());
2929
- }
2930
3543
  if (pre && Array.isArray(pre)) {
2931
3544
  pre.forEach((prefillObj, idx) => {
2932
3545
  var _a2;
@@ -2940,16 +3553,35 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2940
3553
  const item = document.createElement("div");
2941
3554
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2942
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
+ }
2943
3563
  element.elements.forEach((child) => {
2944
3564
  if (!child.hidden) {
2945
- item.appendChild(renderElement(child, subCtx));
3565
+ childWrapper.appendChild(renderElement(child, subCtx));
2946
3566
  }
2947
3567
  });
3568
+ item.appendChild(childWrapper);
2948
3569
  if (!state.config.readonly) {
2949
3570
  const rem = document.createElement("button");
2950
3571
  rem.type = "button";
2951
- 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";
2952
- rem.textContent = "\xD7";
3572
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3573
+ rem.style.cssText = `
3574
+ color: var(--fb-error-color);
3575
+ background-color: transparent;
3576
+ transition: background-color var(--fb-transition-duration);
3577
+ `;
3578
+ rem.textContent = "\u2715";
3579
+ rem.addEventListener("mouseenter", () => {
3580
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3581
+ });
3582
+ rem.addEventListener("mouseleave", () => {
3583
+ rem.style.backgroundColor = "transparent";
3584
+ });
2953
3585
  rem.onclick = () => {
2954
3586
  item.remove();
2955
3587
  updateAddButton();
@@ -2973,15 +3605,34 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2973
3605
  const item = document.createElement("div");
2974
3606
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2975
3607
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3608
+ const childWrapper = document.createElement("div");
3609
+ const columns = element.columns || 1;
3610
+ if (columns === 1) {
3611
+ childWrapper.className = "space-y-4";
3612
+ } else {
3613
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3614
+ }
2976
3615
  element.elements.forEach((child) => {
2977
3616
  if (!child.hidden) {
2978
- item.appendChild(renderElement(child, subCtx));
3617
+ childWrapper.appendChild(renderElement(child, subCtx));
2979
3618
  }
2980
3619
  });
3620
+ item.appendChild(childWrapper);
2981
3621
  const rem = document.createElement("button");
2982
3622
  rem.type = "button";
2983
- 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";
2984
- rem.textContent = "\xD7";
3623
+ rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
3624
+ rem.style.cssText = `
3625
+ color: var(--fb-error-color);
3626
+ background-color: transparent;
3627
+ transition: background-color var(--fb-transition-duration);
3628
+ `;
3629
+ rem.textContent = "\u2715";
3630
+ rem.addEventListener("mouseenter", () => {
3631
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
3632
+ });
3633
+ rem.addEventListener("mouseleave", () => {
3634
+ rem.style.backgroundColor = "transparent";
3635
+ });
2985
3636
  rem.onclick = () => {
2986
3637
  if (countItems() > min) {
2987
3638
  item.remove();
@@ -2994,6 +3645,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2994
3645
  }
2995
3646
  }
2996
3647
  containerWrap.appendChild(itemsWrap);
3648
+ if (!state.config.readonly) {
3649
+ containerWrap.appendChild(createAddButton());
3650
+ }
2997
3651
  updateAddButton();
2998
3652
  wrapper.appendChild(containerWrap);
2999
3653
  }
@@ -3368,6 +4022,13 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
3368
4022
  renderColourElement(element, ctx, wrapper, pathKey);
3369
4023
  }
3370
4024
  break;
4025
+ case "slider":
4026
+ if (isMultiple) {
4027
+ renderMultipleSliderElement(element, ctx, wrapper, pathKey);
4028
+ } else {
4029
+ renderSliderElement(element, ctx, wrapper, pathKey);
4030
+ }
4031
+ break;
3371
4032
  case "group":
3372
4033
  renderGroupElement(element, ctx, wrapper, pathKey);
3373
4034
  break;
@@ -3685,6 +4346,10 @@ var componentRegistry = {
3685
4346
  validate: validateColourElement,
3686
4347
  update: updateColourField
3687
4348
  },
4349
+ slider: {
4350
+ validate: validateSliderElement,
4351
+ update: updateSliderField
4352
+ },
3688
4353
  container: {
3689
4354
  validate: validateContainerElement,
3690
4355
  update: updateContainerField
@@ -4041,6 +4706,33 @@ var FormBuilderInstance = class {
4041
4706
  this.renderFormLevelActions(allFormLevelActions, trueFormLevelActions);
4042
4707
  }
4043
4708
  }
4709
+ /**
4710
+ * Handle prefill hint click - updates container fields with hint values
4711
+ */
4712
+ handlePrefillHintClick(event) {
4713
+ const target = event.target;
4714
+ if (!target.classList.contains("fb-prefill-hint")) {
4715
+ return;
4716
+ }
4717
+ event.preventDefault();
4718
+ event.stopPropagation();
4719
+ const hintValuesJson = target.getAttribute("data-hint-values");
4720
+ const containerKey = target.getAttribute("data-container-key");
4721
+ if (!hintValuesJson || !containerKey) {
4722
+ console.warn("Prefill hint missing required data attributes");
4723
+ return;
4724
+ }
4725
+ try {
4726
+ const hintValues = JSON.parse(hintValuesJson);
4727
+ for (const fieldKey in hintValues) {
4728
+ const fullPath = `${containerKey}.${fieldKey}`;
4729
+ const value = hintValues[fieldKey];
4730
+ this.updateField(fullPath, value);
4731
+ }
4732
+ } catch (error) {
4733
+ console.error("Error parsing prefill hint values:", error);
4734
+ }
4735
+ }
4044
4736
  /**
4045
4737
  * Render form from schema
4046
4738
  */
@@ -4073,6 +4765,9 @@ var FormBuilderInstance = class {
4073
4765
  formEl.appendChild(block);
4074
4766
  });
4075
4767
  root.appendChild(formEl);
4768
+ if (!this.state.config.readonly) {
4769
+ root.addEventListener("click", this.handlePrefillHintClick.bind(this));
4770
+ }
4076
4771
  if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
4077
4772
  this.renderExternalActions();
4078
4773
  }