@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.
@@ -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
- 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]) || {},
@@ -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
- item.appendChild(renderElement(child, subCtx));
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
- item.appendChild(renderElement(child, subCtx));
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
- item.appendChild(renderElement(child, subCtx));
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
  }