@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/dist/esm/index.js CHANGED
@@ -87,6 +87,43 @@ function validateSchema(schema) {
87
87
  validateElements(element.elements, `${elementPath}.elements`);
88
88
  }
89
89
  if (element.type === "container" && element.elements) {
90
+ if ("columns" in element && element.columns !== void 0) {
91
+ const columns = element.columns;
92
+ const validColumns = [1, 2, 3, 4];
93
+ if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
94
+ errors.push(
95
+ `${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
96
+ );
97
+ }
98
+ }
99
+ if ("prefillHints" in element && element.prefillHints) {
100
+ const prefillHints = element.prefillHints;
101
+ if (Array.isArray(prefillHints)) {
102
+ prefillHints.forEach((hint, hintIndex) => {
103
+ if (!hint.label || typeof hint.label !== "string") {
104
+ errors.push(
105
+ `${elementPath}: prefillHints[${hintIndex}] must have a 'label' property of type string`
106
+ );
107
+ }
108
+ if (!hint.values || typeof hint.values !== "object") {
109
+ errors.push(
110
+ `${elementPath}: prefillHints[${hintIndex}] must have a 'values' property of type object`
111
+ );
112
+ } else {
113
+ for (const fieldKey in hint.values) {
114
+ const fieldExists = element.elements.some(
115
+ (childElement) => childElement.key === fieldKey
116
+ );
117
+ if (!fieldExists) {
118
+ errors.push(
119
+ `container "${element.key}": prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
120
+ );
121
+ }
122
+ }
123
+ }
124
+ });
125
+ }
126
+ }
90
127
  validateElements(element.elements, `${elementPath}.elements`);
91
128
  }
92
129
  if (element.type === "select" && element.options) {
@@ -2766,6 +2803,516 @@ function updateColourField(element, fieldPath, value, context) {
2766
2803
  }
2767
2804
  }
2768
2805
 
2806
+ // src/components/slider.ts
2807
+ function positionToExponential(position, min, max) {
2808
+ if (min <= 0) {
2809
+ throw new Error("Exponential scale requires min > 0");
2810
+ }
2811
+ const logMin = Math.log(min);
2812
+ const logMax = Math.log(max);
2813
+ return Math.exp(logMin + position * (logMax - logMin));
2814
+ }
2815
+ function exponentialToPosition(value, min, max) {
2816
+ if (min <= 0) {
2817
+ throw new Error("Exponential scale requires min > 0");
2818
+ }
2819
+ const logMin = Math.log(min);
2820
+ const logMax = Math.log(max);
2821
+ const logValue = Math.log(value);
2822
+ return (logValue - logMin) / (logMax - logMin);
2823
+ }
2824
+ function alignToStep(value, step) {
2825
+ return Math.round(value / step) * step;
2826
+ }
2827
+ function createSliderUI(value, pathKey, element, ctx, readonly) {
2828
+ const container = document.createElement("div");
2829
+ container.className = "slider-container space-y-2";
2830
+ const sliderRow = document.createElement("div");
2831
+ sliderRow.className = "flex items-center gap-3";
2832
+ const slider = document.createElement("input");
2833
+ slider.type = "range";
2834
+ slider.name = pathKey;
2835
+ slider.className = "slider-input flex-1";
2836
+ slider.disabled = readonly;
2837
+ const scale = element.scale || "linear";
2838
+ const min = element.min;
2839
+ const max = element.max;
2840
+ const step = element.step ?? 1;
2841
+ if (scale === "exponential") {
2842
+ if (min <= 0) {
2843
+ throw new Error(
2844
+ `Slider "${element.key}": exponential scale requires min > 0 (got ${min})`
2845
+ );
2846
+ }
2847
+ slider.min = "0";
2848
+ slider.max = "1000";
2849
+ slider.step = "1";
2850
+ const position = exponentialToPosition(value, min, max);
2851
+ slider.value = (position * 1e3).toString();
2852
+ } else {
2853
+ slider.min = min.toString();
2854
+ slider.max = max.toString();
2855
+ slider.step = step.toString();
2856
+ slider.value = value.toString();
2857
+ }
2858
+ slider.style.cssText = `
2859
+ height: 6px;
2860
+ border-radius: 3px;
2861
+ background: linear-gradient(
2862
+ to right,
2863
+ var(--fb-primary-color) 0%,
2864
+ var(--fb-primary-color) ${(value - min) / (max - min) * 100}%,
2865
+ var(--fb-border-color) ${(value - min) / (max - min) * 100}%,
2866
+ var(--fb-border-color) 100%
2867
+ );
2868
+ outline: none;
2869
+ transition: background 0.1s ease-in-out;
2870
+ cursor: ${readonly ? "not-allowed" : "pointer"};
2871
+ opacity: ${readonly ? "0.6" : "1"};
2872
+ `;
2873
+ const valueDisplay = document.createElement("span");
2874
+ valueDisplay.className = "slider-value";
2875
+ valueDisplay.style.cssText = `
2876
+ min-width: 60px;
2877
+ text-align: right;
2878
+ font-size: var(--fb-font-size);
2879
+ color: var(--fb-text-color);
2880
+ font-family: var(--fb-font-family-mono, monospace);
2881
+ font-weight: 500;
2882
+ `;
2883
+ valueDisplay.textContent = value.toFixed(step < 1 ? 2 : 0);
2884
+ sliderRow.appendChild(slider);
2885
+ sliderRow.appendChild(valueDisplay);
2886
+ container.appendChild(sliderRow);
2887
+ const labelsRow = document.createElement("div");
2888
+ labelsRow.className = "flex justify-between";
2889
+ labelsRow.style.cssText = `
2890
+ font-size: var(--fb-font-size-small);
2891
+ color: var(--fb-text-secondary-color);
2892
+ `;
2893
+ const minLabel = document.createElement("span");
2894
+ minLabel.textContent = min.toString();
2895
+ const maxLabel = document.createElement("span");
2896
+ maxLabel.textContent = max.toString();
2897
+ labelsRow.appendChild(minLabel);
2898
+ labelsRow.appendChild(maxLabel);
2899
+ container.appendChild(labelsRow);
2900
+ if (!readonly) {
2901
+ const updateValue = () => {
2902
+ let displayValue;
2903
+ if (scale === "exponential") {
2904
+ const position = parseFloat(slider.value) / 1e3;
2905
+ displayValue = positionToExponential(position, min, max);
2906
+ displayValue = alignToStep(displayValue, step);
2907
+ displayValue = Math.max(min, Math.min(max, displayValue));
2908
+ } else {
2909
+ displayValue = parseFloat(slider.value);
2910
+ displayValue = alignToStep(displayValue, step);
2911
+ }
2912
+ valueDisplay.textContent = displayValue.toFixed(step < 1 ? 2 : 0);
2913
+ const percentage = (displayValue - min) / (max - min) * 100;
2914
+ slider.style.background = `linear-gradient(
2915
+ to right,
2916
+ var(--fb-primary-color) 0%,
2917
+ var(--fb-primary-color) ${percentage}%,
2918
+ var(--fb-border-color) ${percentage}%,
2919
+ var(--fb-border-color) 100%
2920
+ )`;
2921
+ if (ctx.instance) {
2922
+ ctx.instance.triggerOnChange(pathKey, displayValue);
2923
+ }
2924
+ };
2925
+ slider.addEventListener("input", updateValue);
2926
+ slider.addEventListener("change", updateValue);
2927
+ }
2928
+ return container;
2929
+ }
2930
+ function renderSliderElement(element, ctx, wrapper, pathKey) {
2931
+ if (element.min === void 0 || element.min === null) {
2932
+ throw new Error(
2933
+ `Slider field "${element.key}" requires "min" property`
2934
+ );
2935
+ }
2936
+ if (element.max === void 0 || element.max === null) {
2937
+ throw new Error(
2938
+ `Slider field "${element.key}" requires "max" property`
2939
+ );
2940
+ }
2941
+ if (element.min >= element.max) {
2942
+ throw new Error(
2943
+ `Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
2944
+ );
2945
+ }
2946
+ const state = ctx.state;
2947
+ const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
2948
+ const initialValue = ctx.prefill[element.key] ?? defaultValue;
2949
+ const sliderUI = createSliderUI(
2950
+ initialValue,
2951
+ pathKey,
2952
+ element,
2953
+ ctx,
2954
+ state.config.readonly
2955
+ );
2956
+ wrapper.appendChild(sliderUI);
2957
+ const hint = document.createElement("p");
2958
+ hint.className = "mt-1";
2959
+ hint.style.cssText = `
2960
+ font-size: var(--fb-font-size-small);
2961
+ color: var(--fb-text-secondary-color);
2962
+ `;
2963
+ hint.textContent = makeFieldHint(element);
2964
+ wrapper.appendChild(hint);
2965
+ }
2966
+ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
2967
+ if (element.min === void 0 || element.min === null) {
2968
+ throw new Error(
2969
+ `Slider field "${element.key}" requires "min" property`
2970
+ );
2971
+ }
2972
+ if (element.max === void 0 || element.max === null) {
2973
+ throw new Error(
2974
+ `Slider field "${element.key}" requires "max" property`
2975
+ );
2976
+ }
2977
+ if (element.min >= element.max) {
2978
+ throw new Error(
2979
+ `Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
2980
+ );
2981
+ }
2982
+ const state = ctx.state;
2983
+ const prefillValues = ctx.prefill[element.key] || [];
2984
+ const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
2985
+ const minCount = element.minCount ?? 1;
2986
+ const maxCount = element.maxCount ?? Infinity;
2987
+ const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
2988
+ while (values.length < minCount) {
2989
+ values.push(defaultValue);
2990
+ }
2991
+ const container = document.createElement("div");
2992
+ container.className = "space-y-3";
2993
+ wrapper.appendChild(container);
2994
+ function updateIndices() {
2995
+ const items = container.querySelectorAll(".multiple-slider-item");
2996
+ items.forEach((item, index) => {
2997
+ const slider = item.querySelector("input[type=range]");
2998
+ if (slider) {
2999
+ slider.setAttribute("name", `${pathKey}[${index}]`);
3000
+ }
3001
+ });
3002
+ }
3003
+ function addSliderItem(value = defaultValue, index = -1) {
3004
+ const itemWrapper = document.createElement("div");
3005
+ itemWrapper.className = "multiple-slider-item flex items-start gap-2";
3006
+ const tempPathKey = `${pathKey}[${container.children.length}]`;
3007
+ const sliderUI = createSliderUI(
3008
+ value,
3009
+ tempPathKey,
3010
+ element,
3011
+ ctx,
3012
+ state.config.readonly
3013
+ );
3014
+ sliderUI.style.flex = "1";
3015
+ itemWrapper.appendChild(sliderUI);
3016
+ if (index === -1) {
3017
+ container.appendChild(itemWrapper);
3018
+ } else {
3019
+ container.insertBefore(itemWrapper, container.children[index]);
3020
+ }
3021
+ updateIndices();
3022
+ return itemWrapper;
3023
+ }
3024
+ function updateRemoveButtons() {
3025
+ if (state.config.readonly) return;
3026
+ const items = container.querySelectorAll(".multiple-slider-item");
3027
+ const currentCount = items.length;
3028
+ items.forEach((item) => {
3029
+ let removeBtn = item.querySelector(
3030
+ ".remove-item-btn"
3031
+ );
3032
+ if (!removeBtn) {
3033
+ removeBtn = document.createElement("button");
3034
+ removeBtn.type = "button";
3035
+ removeBtn.className = "remove-item-btn px-2 py-1 rounded";
3036
+ removeBtn.style.cssText = `
3037
+ color: var(--fb-error-color);
3038
+ background-color: transparent;
3039
+ transition: background-color var(--fb-transition-duration);
3040
+ margin-top: 8px;
3041
+ `;
3042
+ removeBtn.innerHTML = "\u2715";
3043
+ removeBtn.addEventListener("mouseenter", () => {
3044
+ removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
3045
+ });
3046
+ removeBtn.addEventListener("mouseleave", () => {
3047
+ removeBtn.style.backgroundColor = "transparent";
3048
+ });
3049
+ removeBtn.onclick = () => {
3050
+ const currentIndex = Array.from(container.children).indexOf(
3051
+ item
3052
+ );
3053
+ if (container.children.length > minCount) {
3054
+ values.splice(currentIndex, 1);
3055
+ item.remove();
3056
+ updateIndices();
3057
+ updateAddButton();
3058
+ updateRemoveButtons();
3059
+ }
3060
+ };
3061
+ item.appendChild(removeBtn);
3062
+ }
3063
+ const disabled = currentCount <= minCount;
3064
+ removeBtn.disabled = disabled;
3065
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
3066
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
3067
+ });
3068
+ }
3069
+ function updateAddButton() {
3070
+ const existingAddBtn = wrapper.querySelector(".add-slider-btn");
3071
+ if (existingAddBtn) existingAddBtn.remove();
3072
+ if (!state.config.readonly && values.length < maxCount) {
3073
+ const addBtn = document.createElement("button");
3074
+ addBtn.type = "button";
3075
+ addBtn.className = "add-slider-btn mt-2 px-3 py-1 rounded";
3076
+ addBtn.style.cssText = `
3077
+ color: var(--fb-primary-color);
3078
+ border: var(--fb-border-width) solid var(--fb-primary-color);
3079
+ background-color: transparent;
3080
+ font-size: var(--fb-font-size);
3081
+ transition: all var(--fb-transition-duration);
3082
+ `;
3083
+ addBtn.textContent = `+ Add ${element.label || "Slider"}`;
3084
+ addBtn.addEventListener("mouseenter", () => {
3085
+ addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
3086
+ });
3087
+ addBtn.addEventListener("mouseleave", () => {
3088
+ addBtn.style.backgroundColor = "transparent";
3089
+ });
3090
+ addBtn.onclick = () => {
3091
+ values.push(defaultValue);
3092
+ addSliderItem(defaultValue);
3093
+ updateAddButton();
3094
+ updateRemoveButtons();
3095
+ };
3096
+ wrapper.appendChild(addBtn);
3097
+ }
3098
+ }
3099
+ values.forEach((value) => addSliderItem(value));
3100
+ updateAddButton();
3101
+ updateRemoveButtons();
3102
+ const hint = document.createElement("p");
3103
+ hint.className = "mt-1";
3104
+ hint.style.cssText = `
3105
+ font-size: var(--fb-font-size-small);
3106
+ color: var(--fb-text-secondary-color);
3107
+ `;
3108
+ hint.textContent = makeFieldHint(element);
3109
+ wrapper.appendChild(hint);
3110
+ }
3111
+ function validateSliderElement(element, key, context) {
3112
+ const errors = [];
3113
+ const { scopeRoot, skipValidation } = context;
3114
+ if (element.min === void 0 || element.min === null) {
3115
+ throw new Error(
3116
+ `Slider validation: field "${key}" requires "min" property`
3117
+ );
3118
+ }
3119
+ if (element.max === void 0 || element.max === null) {
3120
+ throw new Error(
3121
+ `Slider validation: field "${key}" requires "max" property`
3122
+ );
3123
+ }
3124
+ const min = element.min;
3125
+ const max = element.max;
3126
+ const step = element.step ?? 1;
3127
+ const scale = element.scale || "linear";
3128
+ const markValidity = (input, errorMessage) => {
3129
+ if (!input) return;
3130
+ const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
3131
+ let errorElement = document.getElementById(errorId);
3132
+ if (errorMessage) {
3133
+ input.classList.add("invalid");
3134
+ input.title = errorMessage;
3135
+ if (!errorElement) {
3136
+ errorElement = document.createElement("div");
3137
+ errorElement.id = errorId;
3138
+ errorElement.className = "error-message";
3139
+ errorElement.style.cssText = `
3140
+ color: var(--fb-error-color);
3141
+ font-size: var(--fb-font-size-small);
3142
+ margin-top: 0.25rem;
3143
+ `;
3144
+ const sliderContainer = input.closest(".slider-container");
3145
+ if (sliderContainer && sliderContainer.nextSibling) {
3146
+ sliderContainer.parentNode?.insertBefore(errorElement, sliderContainer.nextSibling);
3147
+ } else if (sliderContainer) {
3148
+ sliderContainer.parentNode?.appendChild(errorElement);
3149
+ }
3150
+ }
3151
+ errorElement.textContent = errorMessage;
3152
+ errorElement.style.display = "block";
3153
+ } else {
3154
+ input.classList.remove("invalid");
3155
+ input.title = "";
3156
+ if (errorElement) {
3157
+ errorElement.remove();
3158
+ }
3159
+ }
3160
+ };
3161
+ const validateSliderValue = (slider, fieldKey) => {
3162
+ const rawValue = slider.value;
3163
+ if (!rawValue) {
3164
+ if (!skipValidation && element.required) {
3165
+ errors.push(`${fieldKey}: required`);
3166
+ markValidity(slider, "required");
3167
+ return null;
3168
+ }
3169
+ markValidity(slider, null);
3170
+ return null;
3171
+ }
3172
+ let value;
3173
+ if (scale === "exponential") {
3174
+ const position = parseFloat(rawValue) / 1e3;
3175
+ value = positionToExponential(position, min, max);
3176
+ value = alignToStep(value, step);
3177
+ } else {
3178
+ value = parseFloat(rawValue);
3179
+ value = alignToStep(value, step);
3180
+ }
3181
+ if (!skipValidation) {
3182
+ if (value < min) {
3183
+ errors.push(`${fieldKey}: value ${value} < min ${min}`);
3184
+ markValidity(slider, `value must be >= ${min}`);
3185
+ return value;
3186
+ }
3187
+ if (value > max) {
3188
+ errors.push(`${fieldKey}: value ${value} > max ${max}`);
3189
+ markValidity(slider, `value must be <= ${max}`);
3190
+ return value;
3191
+ }
3192
+ }
3193
+ markValidity(slider, null);
3194
+ return value;
3195
+ };
3196
+ if (element.multiple) {
3197
+ const sliders = scopeRoot.querySelectorAll(
3198
+ `input[type="range"][name^="${key}["]`
3199
+ );
3200
+ const values = [];
3201
+ sliders.forEach((slider, index) => {
3202
+ const value = validateSliderValue(slider, `${key}[${index}]`);
3203
+ values.push(value);
3204
+ });
3205
+ if (!skipValidation) {
3206
+ const minCount = element.minCount ?? 1;
3207
+ const maxCount = element.maxCount ?? Infinity;
3208
+ const filteredValues = values.filter((v) => v !== null);
3209
+ if (element.required && filteredValues.length === 0) {
3210
+ errors.push(`${key}: required`);
3211
+ }
3212
+ if (filteredValues.length < minCount) {
3213
+ errors.push(`${key}: minimum ${minCount} items required`);
3214
+ }
3215
+ if (filteredValues.length > maxCount) {
3216
+ errors.push(`${key}: maximum ${maxCount} items allowed`);
3217
+ }
3218
+ }
3219
+ return { value: values, errors };
3220
+ } else {
3221
+ const slider = scopeRoot.querySelector(
3222
+ `input[type="range"][name="${key}"]`
3223
+ );
3224
+ if (!slider) {
3225
+ if (!skipValidation && element.required) {
3226
+ errors.push(`${key}: required`);
3227
+ }
3228
+ return { value: null, errors };
3229
+ }
3230
+ const value = validateSliderValue(slider, key);
3231
+ return { value, errors };
3232
+ }
3233
+ }
3234
+ function updateSliderField(element, fieldPath, value, context) {
3235
+ const { scopeRoot } = context;
3236
+ const min = element.min;
3237
+ const max = element.max;
3238
+ const step = element.step ?? 1;
3239
+ const scale = element.scale || "linear";
3240
+ if (element.multiple) {
3241
+ if (!Array.isArray(value)) {
3242
+ console.warn(
3243
+ `updateSliderField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
3244
+ );
3245
+ return;
3246
+ }
3247
+ const sliders = scopeRoot.querySelectorAll(
3248
+ `input[type="range"][name^="${fieldPath}["]`
3249
+ );
3250
+ sliders.forEach((slider, index) => {
3251
+ if (index < value.length && value[index] !== null) {
3252
+ const numValue = Number(value[index]);
3253
+ if (scale === "exponential") {
3254
+ const position = exponentialToPosition(numValue, min, max);
3255
+ slider.value = (position * 1e3).toString();
3256
+ } else {
3257
+ slider.value = numValue.toString();
3258
+ }
3259
+ const sliderContainer = slider.closest(".slider-container");
3260
+ if (sliderContainer) {
3261
+ const valueDisplay = sliderContainer.querySelector(".slider-value");
3262
+ if (valueDisplay) {
3263
+ valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
3264
+ }
3265
+ const percentage = (numValue - min) / (max - min) * 100;
3266
+ slider.style.background = `linear-gradient(
3267
+ to right,
3268
+ var(--fb-primary-color) 0%,
3269
+ var(--fb-primary-color) ${percentage}%,
3270
+ var(--fb-border-color) ${percentage}%,
3271
+ var(--fb-border-color) 100%
3272
+ )`;
3273
+ }
3274
+ slider.classList.remove("invalid");
3275
+ slider.title = "";
3276
+ }
3277
+ });
3278
+ if (value.length !== sliders.length) {
3279
+ console.warn(
3280
+ `updateSliderField: Multiple field "${fieldPath}" has ${sliders.length} sliders but received ${value.length} values. Consider re-rendering for add/remove.`
3281
+ );
3282
+ }
3283
+ } else {
3284
+ const slider = scopeRoot.querySelector(
3285
+ `input[type="range"][name="${fieldPath}"]`
3286
+ );
3287
+ if (slider && value !== null && value !== void 0) {
3288
+ const numValue = Number(value);
3289
+ if (scale === "exponential") {
3290
+ const position = exponentialToPosition(numValue, min, max);
3291
+ slider.value = (position * 1e3).toString();
3292
+ } else {
3293
+ slider.value = numValue.toString();
3294
+ }
3295
+ const sliderContainer = slider.closest(".slider-container");
3296
+ if (sliderContainer) {
3297
+ const valueDisplay = sliderContainer.querySelector(".slider-value");
3298
+ if (valueDisplay) {
3299
+ valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
3300
+ }
3301
+ const percentage = (numValue - min) / (max - min) * 100;
3302
+ slider.style.background = `linear-gradient(
3303
+ to right,
3304
+ var(--fb-primary-color) 0%,
3305
+ var(--fb-primary-color) ${percentage}%,
3306
+ var(--fb-border-color) ${percentage}%,
3307
+ var(--fb-border-color) 100%
3308
+ )`;
3309
+ }
3310
+ slider.classList.remove("invalid");
3311
+ slider.title = "";
3312
+ }
3313
+ }
3314
+ }
3315
+
2769
3316
  // src/components/container.ts
2770
3317
  var renderElementFunc = null;
2771
3318
  function setRenderElement(fn) {
@@ -2779,6 +3326,24 @@ function renderElement(element, ctx) {
2779
3326
  }
2780
3327
  return renderElementFunc(element, ctx);
2781
3328
  }
3329
+ function createPrefillHints(element, pathKey) {
3330
+ if (!element.prefillHints || element.prefillHints.length === 0) {
3331
+ return null;
3332
+ }
3333
+ const hintsContainer = document.createElement("div");
3334
+ hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
3335
+ element.prefillHints.forEach((hint, index) => {
3336
+ const hintButton = document.createElement("button");
3337
+ hintButton.type = "button";
3338
+ hintButton.className = "fb-prefill-hint";
3339
+ hintButton.textContent = hint.label;
3340
+ hintButton.setAttribute("data-hint-values", JSON.stringify(hint.values));
3341
+ hintButton.setAttribute("data-container-key", pathKey);
3342
+ hintButton.setAttribute("data-hint-index", String(index));
3343
+ hintsContainer.appendChild(hintButton);
3344
+ });
3345
+ return hintsContainer;
3346
+ }
2782
3347
  function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2783
3348
  const containerWrap = document.createElement("div");
2784
3349
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
@@ -2788,9 +3353,20 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2788
3353
  const left = document.createElement("div");
2789
3354
  left.className = "flex-1";
2790
3355
  const itemsWrap = document.createElement("div");
2791
- itemsWrap.className = "space-y-4";
3356
+ const columns = element.columns || 1;
3357
+ if (columns === 1) {
3358
+ itemsWrap.className = "space-y-4";
3359
+ } else {
3360
+ itemsWrap.className = `grid grid-cols-${columns} gap-4`;
3361
+ }
2792
3362
  containerWrap.appendChild(header);
2793
3363
  header.appendChild(left);
3364
+ if (!ctx.state.config.readonly) {
3365
+ const hintsElement = createPrefillHints(element, pathKey);
3366
+ if (hintsElement) {
3367
+ containerWrap.appendChild(hintsElement);
3368
+ }
3369
+ }
2794
3370
  const subCtx = {
2795
3371
  path: pathJoin(ctx.path, element.key),
2796
3372
  prefill: ctx.prefill?.[element.key] || {},
@@ -2825,6 +3401,12 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2825
3401
  if (!state.config.readonly) {
2826
3402
  header.appendChild(right);
2827
3403
  }
3404
+ if (!ctx.state.config.readonly) {
3405
+ const hintsElement = createPrefillHints(element, element.key);
3406
+ if (hintsElement) {
3407
+ containerWrap.appendChild(hintsElement);
3408
+ }
3409
+ }
2828
3410
  const min = element.minCount ?? 0;
2829
3411
  const max = element.maxCount ?? Infinity;
2830
3412
  const pre = Array.isArray(ctx.prefill?.[element.key]) ? ctx.prefill[element.key] : null;
@@ -2847,11 +3429,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2847
3429
  const item = document.createElement("div");
2848
3430
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2849
3431
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3432
+ const childWrapper = document.createElement("div");
3433
+ const columns = element.columns || 1;
3434
+ if (columns === 1) {
3435
+ childWrapper.className = "space-y-4";
3436
+ } else {
3437
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3438
+ }
2850
3439
  element.elements.forEach((child) => {
2851
3440
  if (!child.hidden) {
2852
- item.appendChild(renderElement(child, subCtx));
3441
+ childWrapper.appendChild(renderElement(child, subCtx));
2853
3442
  }
2854
3443
  });
3444
+ item.appendChild(childWrapper);
2855
3445
  if (!state.config.readonly) {
2856
3446
  const rem = document.createElement("button");
2857
3447
  rem.type = "button";
@@ -2894,11 +3484,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2894
3484
  const item = document.createElement("div");
2895
3485
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2896
3486
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3487
+ const childWrapper = document.createElement("div");
3488
+ const columns = element.columns || 1;
3489
+ if (columns === 1) {
3490
+ childWrapper.className = "space-y-4";
3491
+ } else {
3492
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3493
+ }
2897
3494
  element.elements.forEach((child) => {
2898
3495
  if (!child.hidden) {
2899
- item.appendChild(renderElement(child, subCtx));
3496
+ childWrapper.appendChild(renderElement(child, subCtx));
2900
3497
  }
2901
3498
  });
3499
+ item.appendChild(childWrapper);
2902
3500
  if (!state.config.readonly) {
2903
3501
  const rem = document.createElement("button");
2904
3502
  rem.type = "button";
@@ -2927,11 +3525,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2927
3525
  const item = document.createElement("div");
2928
3526
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2929
3527
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3528
+ const childWrapper = document.createElement("div");
3529
+ const columns = element.columns || 1;
3530
+ if (columns === 1) {
3531
+ childWrapper.className = "space-y-4";
3532
+ } else {
3533
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3534
+ }
2930
3535
  element.elements.forEach((child) => {
2931
3536
  if (!child.hidden) {
2932
- item.appendChild(renderElement(child, subCtx));
3537
+ childWrapper.appendChild(renderElement(child, subCtx));
2933
3538
  }
2934
3539
  });
3540
+ item.appendChild(childWrapper);
2935
3541
  const rem = document.createElement("button");
2936
3542
  rem.type = "button";
2937
3543
  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";
@@ -3318,6 +3924,13 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
3318
3924
  renderColourElement(element, ctx, wrapper, pathKey);
3319
3925
  }
3320
3926
  break;
3927
+ case "slider":
3928
+ if (isMultiple) {
3929
+ renderMultipleSliderElement(element, ctx, wrapper, pathKey);
3930
+ } else {
3931
+ renderSliderElement(element, ctx, wrapper, pathKey);
3932
+ }
3933
+ break;
3321
3934
  case "group":
3322
3935
  renderGroupElement(element, ctx, wrapper, pathKey);
3323
3936
  break;
@@ -3635,6 +4248,10 @@ var componentRegistry = {
3635
4248
  validate: validateColourElement,
3636
4249
  update: updateColourField
3637
4250
  },
4251
+ slider: {
4252
+ validate: validateSliderElement,
4253
+ update: updateSliderField
4254
+ },
3638
4255
  container: {
3639
4256
  validate: validateContainerElement,
3640
4257
  update: updateContainerField
@@ -3991,6 +4608,33 @@ var FormBuilderInstance = class {
3991
4608
  this.renderFormLevelActions(allFormLevelActions, trueFormLevelActions);
3992
4609
  }
3993
4610
  }
4611
+ /**
4612
+ * Handle prefill hint click - updates container fields with hint values
4613
+ */
4614
+ handlePrefillHintClick(event) {
4615
+ const target = event.target;
4616
+ if (!target.classList.contains("fb-prefill-hint")) {
4617
+ return;
4618
+ }
4619
+ event.preventDefault();
4620
+ event.stopPropagation();
4621
+ const hintValuesJson = target.getAttribute("data-hint-values");
4622
+ const containerKey = target.getAttribute("data-container-key");
4623
+ if (!hintValuesJson || !containerKey) {
4624
+ console.warn("Prefill hint missing required data attributes");
4625
+ return;
4626
+ }
4627
+ try {
4628
+ const hintValues = JSON.parse(hintValuesJson);
4629
+ for (const fieldKey in hintValues) {
4630
+ const fullPath = `${containerKey}.${fieldKey}`;
4631
+ const value = hintValues[fieldKey];
4632
+ this.updateField(fullPath, value);
4633
+ }
4634
+ } catch (error) {
4635
+ console.error("Error parsing prefill hint values:", error);
4636
+ }
4637
+ }
3994
4638
  /**
3995
4639
  * Render form from schema
3996
4640
  */
@@ -4023,6 +4667,9 @@ var FormBuilderInstance = class {
4023
4667
  formEl.appendChild(block);
4024
4668
  });
4025
4669
  root.appendChild(formEl);
4670
+ if (!this.state.config.readonly) {
4671
+ root.addEventListener("click", this.handlePrefillHintClick.bind(this));
4672
+ }
4026
4673
  if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
4027
4674
  this.renderExternalActions();
4028
4675
  }