@dmitryvim/form-builder 0.2.5 → 0.2.7

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
@@ -56,9 +56,6 @@ function validateSchema(schema) {
56
56
  errors.push("Schema must be an object");
57
57
  return errors;
58
58
  }
59
- if (!schema.version) {
60
- errors.push("Schema missing version");
61
- }
62
59
  if (!Array.isArray(schema.elements)) {
63
60
  errors.push("Schema missing elements array");
64
61
  return errors;
@@ -72,6 +69,20 @@ function validateSchema(schema) {
72
69
  if (!element.key) {
73
70
  errors.push(`${elementPath}: missing key`);
74
71
  }
72
+ if (element.displayIf) {
73
+ const displayIf = element.displayIf;
74
+ if (!displayIf.key || typeof displayIf.key !== "string") {
75
+ errors.push(
76
+ `${elementPath}: displayIf must have a 'key' property of type string`
77
+ );
78
+ }
79
+ const hasOperator = "equals" in displayIf;
80
+ if (!hasOperator) {
81
+ errors.push(
82
+ `${elementPath}: displayIf must have at least one operator (equals, etc.)`
83
+ );
84
+ }
85
+ }
75
86
  if (element.type === "group" && "elements" in element && element.elements) {
76
87
  validateElements(element.elements, `${elementPath}.elements`);
77
88
  }
@@ -109,6 +120,66 @@ function clear(node) {
109
120
  while (node.firstChild) node.removeChild(node.firstChild);
110
121
  }
111
122
 
123
+ // src/utils/display-conditions.ts
124
+ function getValueByPath(data, path) {
125
+ if (!data || typeof data !== "object") {
126
+ return void 0;
127
+ }
128
+ const segments = path.match(/[^.[\]]+|\[\d+\]/g);
129
+ if (!segments || segments.length === 0) {
130
+ return void 0;
131
+ }
132
+ let current = data;
133
+ for (const segment of segments) {
134
+ if (current === void 0 || current === null) {
135
+ return void 0;
136
+ }
137
+ if (segment.startsWith("[") && segment.endsWith("]")) {
138
+ const index = parseInt(segment.slice(1, -1), 10);
139
+ if (!Array.isArray(current) || isNaN(index)) {
140
+ return void 0;
141
+ }
142
+ current = current[index];
143
+ } else {
144
+ current = current[segment];
145
+ }
146
+ }
147
+ return current;
148
+ }
149
+ function evaluateDisplayCondition(condition, formData) {
150
+ if (!condition || !condition.key) {
151
+ throw new Error(
152
+ "Invalid displayIf condition: must have a 'key' property"
153
+ );
154
+ }
155
+ const actualValue = getValueByPath(formData, condition.key);
156
+ if ("equals" in condition) {
157
+ return deepEqual(actualValue, condition.equals);
158
+ }
159
+ throw new Error(
160
+ `Invalid displayIf condition: no recognized operator (equals, etc.)`
161
+ );
162
+ }
163
+ function deepEqual(a, b) {
164
+ if (a === b) return true;
165
+ if (a == null || b == null) return a === b;
166
+ if (typeof a !== typeof b) return false;
167
+ if (typeof a === "object" && typeof b === "object") {
168
+ try {
169
+ return JSON.stringify(a) === JSON.stringify(b);
170
+ } catch (e) {
171
+ if (e instanceof TypeError && (e.message.includes("circular") || e.message.includes("cyclic"))) {
172
+ console.warn(
173
+ "deepEqual: Circular reference detected in displayIf comparison, using reference equality"
174
+ );
175
+ return a === b;
176
+ }
177
+ throw e;
178
+ }
179
+ }
180
+ return a === b;
181
+ }
182
+
112
183
  // src/components/text.ts
113
184
  function renderTextElement(element, ctx, wrapper, pathKey) {
114
185
  const state = ctx.state;
@@ -1151,6 +1222,172 @@ function t(key, state) {
1151
1222
  }
1152
1223
 
1153
1224
  // src/components/file.ts
1225
+ function renderLocalImagePreview(container, file, fileName) {
1226
+ const img = document.createElement("img");
1227
+ img.className = "w-full h-full object-contain";
1228
+ img.alt = fileName || "Preview";
1229
+ const reader = new FileReader();
1230
+ reader.onload = (e) => {
1231
+ img.src = e.target?.result || "";
1232
+ };
1233
+ reader.readAsDataURL(file);
1234
+ container.appendChild(img);
1235
+ }
1236
+ function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
1237
+ const videoUrl = URL.createObjectURL(file);
1238
+ container.onclick = null;
1239
+ const newContainer = container.cloneNode(false);
1240
+ if (container.parentNode) {
1241
+ container.parentNode.replaceChild(newContainer, container);
1242
+ }
1243
+ newContainer.innerHTML = `
1244
+ <div class="relative group h-full">
1245
+ <video class="w-full h-full object-contain" controls preload="auto" muted>
1246
+ <source src="${videoUrl}" type="${videoType}">
1247
+ Your browser does not support the video tag.
1248
+ </video>
1249
+ <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
1250
+ <button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
1251
+ ${t("removeElement", state)}
1252
+ </button>
1253
+ <button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
1254
+ Change
1255
+ </button>
1256
+ </div>
1257
+ </div>
1258
+ `;
1259
+ attachVideoButtonHandlers(newContainer, resourceId, state, deps);
1260
+ return newContainer;
1261
+ }
1262
+ function attachVideoButtonHandlers(container, resourceId, state, deps) {
1263
+ const changeBtn = container.querySelector(".change-file-btn");
1264
+ if (changeBtn) {
1265
+ changeBtn.onclick = (e) => {
1266
+ e.stopPropagation();
1267
+ if (deps?.picker) {
1268
+ deps.picker.click();
1269
+ }
1270
+ };
1271
+ }
1272
+ const deleteBtn = container.querySelector(".delete-file-btn");
1273
+ if (deleteBtn) {
1274
+ deleteBtn.onclick = (e) => {
1275
+ e.stopPropagation();
1276
+ handleVideoDelete(container, resourceId, state, deps);
1277
+ };
1278
+ }
1279
+ }
1280
+ function handleVideoDelete(container, resourceId, state, deps) {
1281
+ state.resourceIndex.delete(resourceId);
1282
+ const hiddenInput = container.parentElement?.querySelector(
1283
+ 'input[type="hidden"]'
1284
+ );
1285
+ if (hiddenInput) {
1286
+ hiddenInput.value = "";
1287
+ }
1288
+ if (deps?.fileUploadHandler) {
1289
+ container.onclick = deps.fileUploadHandler;
1290
+ }
1291
+ if (deps?.dragHandler) {
1292
+ setupDragAndDrop(container, deps.dragHandler);
1293
+ }
1294
+ container.innerHTML = `
1295
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1296
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1297
+ <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
1298
+ </svg>
1299
+ <div class="text-sm text-center">${t("clickDragText", state)}</div>
1300
+ </div>
1301
+ `;
1302
+ }
1303
+ function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
1304
+ const video = document.createElement("video");
1305
+ video.className = "w-full h-full object-contain";
1306
+ video.controls = true;
1307
+ video.preload = "metadata";
1308
+ video.muted = true;
1309
+ const source = document.createElement("source");
1310
+ source.src = thumbnailUrl;
1311
+ source.type = videoType;
1312
+ video.appendChild(source);
1313
+ video.appendChild(document.createTextNode("Your browser does not support the video tag."));
1314
+ container.appendChild(video);
1315
+ }
1316
+ function renderDeleteButton(container, resourceId, state) {
1317
+ addDeleteButton(container, state, () => {
1318
+ state.resourceIndex.delete(resourceId);
1319
+ const hiddenInput = container.parentElement?.querySelector(
1320
+ 'input[type="hidden"]'
1321
+ );
1322
+ if (hiddenInput) {
1323
+ hiddenInput.value = "";
1324
+ }
1325
+ container.innerHTML = `
1326
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1327
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1328
+ <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
1329
+ </svg>
1330
+ <div class="text-sm text-center">${t("clickDragText", state)}</div>
1331
+ </div>
1332
+ `;
1333
+ });
1334
+ }
1335
+ async function renderLocalFilePreview(container, meta, fileName, resourceId, isReadonly, state, deps) {
1336
+ if (!meta.file || !(meta.file instanceof File)) {
1337
+ return;
1338
+ }
1339
+ if (meta.type && meta.type.startsWith("image/")) {
1340
+ renderLocalImagePreview(container, meta.file, fileName);
1341
+ } else if (meta.type && meta.type.startsWith("video/")) {
1342
+ const newContainer = renderLocalVideoPreview(
1343
+ container,
1344
+ meta.file,
1345
+ meta.type,
1346
+ resourceId,
1347
+ state,
1348
+ deps
1349
+ );
1350
+ container = newContainer;
1351
+ } else {
1352
+ container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">\u{1F4C1}</div><div class="text-sm">${fileName}</div></div>`;
1353
+ }
1354
+ if (!isReadonly && !(meta.type && meta.type.startsWith("video/"))) {
1355
+ renderDeleteButton(container, resourceId, state);
1356
+ }
1357
+ }
1358
+ async function renderUploadedFilePreview(container, resourceId, fileName, meta, state) {
1359
+ if (!state.config.getThumbnail) {
1360
+ setEmptyFileContainer(container, state);
1361
+ return;
1362
+ }
1363
+ try {
1364
+ const thumbnailUrl = await state.config.getThumbnail(resourceId);
1365
+ if (thumbnailUrl) {
1366
+ clear(container);
1367
+ if (meta && meta.type && meta.type.startsWith("video/")) {
1368
+ renderUploadedVideoPreview(container, thumbnailUrl, meta.type);
1369
+ } else {
1370
+ const img = document.createElement("img");
1371
+ img.className = "w-full h-full object-contain";
1372
+ img.alt = fileName || "Preview";
1373
+ img.src = thumbnailUrl;
1374
+ container.appendChild(img);
1375
+ }
1376
+ } else {
1377
+ setEmptyFileContainer(container, state);
1378
+ }
1379
+ } catch (error) {
1380
+ console.error("Failed to get thumbnail:", error);
1381
+ container.innerHTML = `
1382
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1383
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1384
+ <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
1385
+ </svg>
1386
+ <div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
1387
+ </div>
1388
+ `;
1389
+ }
1390
+ }
1154
1391
  async function renderFilePreview(container, resourceId, state, options = {}) {
1155
1392
  const { fileName = "", isReadonly = false, deps = null } = options;
1156
1393
  if (!isReadonly && deps && (!deps.picker || !deps.fileUploadHandler || !deps.dragHandler)) {
@@ -1162,141 +1399,19 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
1162
1399
  if (isReadonly) {
1163
1400
  container.classList.add("cursor-pointer");
1164
1401
  }
1165
- const img = document.createElement("img");
1166
- img.className = "w-full h-full object-contain";
1167
- img.alt = fileName || "Preview";
1168
1402
  const meta = state.resourceIndex.get(resourceId);
1169
1403
  if (meta && meta.file && meta.file instanceof File) {
1170
- if (meta.type && meta.type.startsWith("image/")) {
1171
- const reader = new FileReader();
1172
- reader.onload = (e) => {
1173
- img.src = e.target?.result || "";
1174
- };
1175
- reader.readAsDataURL(meta.file);
1176
- container.appendChild(img);
1177
- } else if (meta.type && meta.type.startsWith("video/")) {
1178
- const videoUrl = URL.createObjectURL(meta.file);
1179
- container.onclick = null;
1180
- const newContainer = container.cloneNode(false);
1181
- if (container.parentNode) {
1182
- container.parentNode.replaceChild(newContainer, container);
1183
- }
1184
- container = newContainer;
1185
- container.innerHTML = `
1186
- <div class="relative group h-full">
1187
- <video class="w-full h-full object-contain" controls preload="auto" muted>
1188
- <source src="${videoUrl}" type="${meta.type}">
1189
- Your browser does not support the video tag.
1190
- </video>
1191
- <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
1192
- <button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
1193
- ${t("removeElement", state)}
1194
- </button>
1195
- <button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
1196
- Change
1197
- </button>
1198
- </div>
1199
- </div>
1200
- `;
1201
- const changeBtn = container.querySelector(
1202
- ".change-file-btn"
1203
- );
1204
- if (changeBtn) {
1205
- changeBtn.onclick = (e) => {
1206
- e.stopPropagation();
1207
- if (deps?.picker) {
1208
- deps.picker.click();
1209
- }
1210
- };
1211
- }
1212
- const deleteBtn = container.querySelector(
1213
- ".delete-file-btn"
1214
- );
1215
- if (deleteBtn) {
1216
- deleteBtn.onclick = (e) => {
1217
- e.stopPropagation();
1218
- state.resourceIndex.delete(resourceId);
1219
- const hiddenInput = container.parentElement?.querySelector(
1220
- 'input[type="hidden"]'
1221
- );
1222
- if (hiddenInput) {
1223
- hiddenInput.value = "";
1224
- }
1225
- if (deps?.fileUploadHandler) {
1226
- container.onclick = deps.fileUploadHandler;
1227
- }
1228
- if (deps?.dragHandler) {
1229
- setupDragAndDrop(container, deps.dragHandler);
1230
- }
1231
- container.innerHTML = `
1232
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1233
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1234
- <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
1235
- </svg>
1236
- <div class="text-sm text-center">${t("clickDragText", state)}</div>
1237
- </div>
1238
- `;
1239
- };
1240
- }
1241
- } else {
1242
- container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">\u{1F4C1}</div><div class="text-sm">${fileName}</div></div>`;
1243
- }
1244
- if (!isReadonly && !(meta && meta.type && meta.type.startsWith("video/"))) {
1245
- addDeleteButton(container, state, () => {
1246
- state.resourceIndex.delete(resourceId);
1247
- const hiddenInput = container.parentElement?.querySelector(
1248
- 'input[type="hidden"]'
1249
- );
1250
- if (hiddenInput) {
1251
- hiddenInput.value = "";
1252
- }
1253
- container.innerHTML = `
1254
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1255
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1256
- <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
1257
- </svg>
1258
- <div class="text-sm text-center">${t("clickDragText", state)}</div>
1259
- </div>
1260
- `;
1261
- });
1262
- }
1263
- } else if (state.config.getThumbnail) {
1264
- try {
1265
- const thumbnailUrl = await state.config.getThumbnail(resourceId);
1266
- if (thumbnailUrl) {
1267
- clear(container);
1268
- if (meta && meta.type && meta.type.startsWith("video/")) {
1269
- const video = document.createElement("video");
1270
- video.className = "w-full h-full object-contain";
1271
- video.controls = true;
1272
- video.preload = "metadata";
1273
- video.muted = true;
1274
- const source = document.createElement("source");
1275
- source.src = thumbnailUrl;
1276
- source.type = meta.type;
1277
- video.appendChild(source);
1278
- video.appendChild(document.createTextNode("Your browser does not support the video tag."));
1279
- container.appendChild(video);
1280
- } else {
1281
- img.src = thumbnailUrl;
1282
- container.appendChild(img);
1283
- }
1284
- } else {
1285
- setEmptyFileContainer(container, state);
1286
- }
1287
- } catch (error) {
1288
- console.error("Failed to get thumbnail:", error);
1289
- container.innerHTML = `
1290
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1291
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1292
- <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
1293
- </svg>
1294
- <div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
1295
- </div>
1296
- `;
1297
- }
1404
+ await renderLocalFilePreview(
1405
+ container,
1406
+ meta,
1407
+ fileName,
1408
+ resourceId,
1409
+ isReadonly,
1410
+ state,
1411
+ deps
1412
+ );
1298
1413
  } else {
1299
- setEmptyFileContainer(container, state);
1414
+ await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
1300
1415
  }
1301
1416
  }
1302
1417
  async function renderFilePreviewReadonly(resourceId, state, fileName) {
@@ -2197,6 +2312,460 @@ function updateFileField(element, fieldPath, value, context) {
2197
2312
  }
2198
2313
  }
2199
2314
 
2315
+ // src/components/colour.ts
2316
+ function normalizeColourValue(value) {
2317
+ if (!value) return "#000000";
2318
+ return value.toUpperCase();
2319
+ }
2320
+ function isValidHexColour(value) {
2321
+ return /^#[0-9A-F]{6}$/i.test(value) || /^#[0-9A-F]{3}$/i.test(value);
2322
+ }
2323
+ function expandHexColour(value) {
2324
+ if (/^#[0-9A-F]{3}$/i.test(value)) {
2325
+ const r = value[1];
2326
+ const g = value[2];
2327
+ const b = value[3];
2328
+ return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
2329
+ }
2330
+ return value.toUpperCase();
2331
+ }
2332
+ function createReadonlyColourUI(value) {
2333
+ const container = document.createElement("div");
2334
+ container.className = "flex items-center gap-2";
2335
+ const normalizedValue = normalizeColourValue(value);
2336
+ const swatch = document.createElement("div");
2337
+ swatch.style.cssText = `
2338
+ width: 32px;
2339
+ height: 32px;
2340
+ border-radius: var(--fb-border-radius);
2341
+ border: var(--fb-border-width) solid var(--fb-border-color);
2342
+ background-color: ${normalizedValue};
2343
+ `;
2344
+ const hexText = document.createElement("span");
2345
+ hexText.style.cssText = `
2346
+ font-size: var(--fb-font-size);
2347
+ color: var(--fb-text-color);
2348
+ font-family: var(--fb-font-family-mono, monospace);
2349
+ `;
2350
+ hexText.textContent = normalizedValue;
2351
+ container.appendChild(swatch);
2352
+ container.appendChild(hexText);
2353
+ return container;
2354
+ }
2355
+ function createEditColourUI(value, pathKey, ctx) {
2356
+ const normalizedValue = normalizeColourValue(value);
2357
+ const pickerWrapper = document.createElement("div");
2358
+ pickerWrapper.className = "colour-picker-wrapper";
2359
+ pickerWrapper.style.cssText = `
2360
+ display: flex;
2361
+ align-items: center;
2362
+ gap: 8px;
2363
+ `;
2364
+ const swatch = document.createElement("div");
2365
+ swatch.className = "colour-swatch";
2366
+ swatch.style.cssText = `
2367
+ width: 40px;
2368
+ height: 40px;
2369
+ border-radius: var(--fb-border-radius);
2370
+ border: var(--fb-border-width) solid var(--fb-border-color);
2371
+ background-color: ${normalizedValue};
2372
+ cursor: pointer;
2373
+ transition: border-color var(--fb-transition-duration) ease-in-out;
2374
+ flex-shrink: 0;
2375
+ `;
2376
+ const hexInput = document.createElement("input");
2377
+ hexInput.type = "text";
2378
+ hexInput.className = "colour-hex-input";
2379
+ hexInput.name = pathKey;
2380
+ hexInput.value = normalizedValue;
2381
+ hexInput.placeholder = "#000000";
2382
+ hexInput.style.cssText = `
2383
+ width: 100px;
2384
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
2385
+ border: var(--fb-border-width) solid var(--fb-border-color);
2386
+ border-radius: var(--fb-border-radius);
2387
+ background-color: var(--fb-background-color);
2388
+ color: var(--fb-text-color);
2389
+ font-size: var(--fb-font-size);
2390
+ font-family: var(--fb-font-family-mono, monospace);
2391
+ transition: all var(--fb-transition-duration) ease-in-out;
2392
+ `;
2393
+ const colourInput = document.createElement("input");
2394
+ colourInput.type = "color";
2395
+ colourInput.className = "colour-picker-hidden";
2396
+ colourInput.value = normalizedValue.toLowerCase();
2397
+ colourInput.style.cssText = `
2398
+ position: absolute;
2399
+ opacity: 0;
2400
+ pointer-events: none;
2401
+ `;
2402
+ hexInput.addEventListener("input", () => {
2403
+ const inputValue = hexInput.value.trim();
2404
+ if (isValidHexColour(inputValue)) {
2405
+ const expanded = expandHexColour(inputValue);
2406
+ swatch.style.backgroundColor = expanded;
2407
+ colourInput.value = expanded.toLowerCase();
2408
+ hexInput.classList.remove("invalid");
2409
+ if (ctx.instance) {
2410
+ ctx.instance.triggerOnChange(pathKey, expanded);
2411
+ }
2412
+ } else {
2413
+ hexInput.classList.add("invalid");
2414
+ }
2415
+ });
2416
+ hexInput.addEventListener("blur", () => {
2417
+ const inputValue = hexInput.value.trim();
2418
+ if (isValidHexColour(inputValue)) {
2419
+ const expanded = expandHexColour(inputValue);
2420
+ hexInput.value = expanded;
2421
+ swatch.style.backgroundColor = expanded;
2422
+ colourInput.value = expanded.toLowerCase();
2423
+ hexInput.classList.remove("invalid");
2424
+ }
2425
+ });
2426
+ colourInput.addEventListener("change", () => {
2427
+ const normalized = normalizeColourValue(colourInput.value);
2428
+ hexInput.value = normalized;
2429
+ swatch.style.backgroundColor = normalized;
2430
+ if (ctx.instance) {
2431
+ ctx.instance.triggerOnChange(pathKey, normalized);
2432
+ }
2433
+ });
2434
+ swatch.addEventListener("click", () => {
2435
+ colourInput.click();
2436
+ });
2437
+ swatch.addEventListener("mouseenter", () => {
2438
+ swatch.style.borderColor = "var(--fb-border-hover-color)";
2439
+ });
2440
+ swatch.addEventListener("mouseleave", () => {
2441
+ swatch.style.borderColor = "var(--fb-border-color)";
2442
+ });
2443
+ hexInput.addEventListener("focus", () => {
2444
+ hexInput.style.borderColor = "var(--fb-border-focus-color)";
2445
+ hexInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
2446
+ hexInput.style.outlineOffset = "0";
2447
+ });
2448
+ hexInput.addEventListener("blur", () => {
2449
+ hexInput.style.borderColor = "var(--fb-border-color)";
2450
+ hexInput.style.outline = "none";
2451
+ });
2452
+ hexInput.addEventListener("mouseenter", () => {
2453
+ if (document.activeElement !== hexInput) {
2454
+ hexInput.style.borderColor = "var(--fb-border-hover-color)";
2455
+ }
2456
+ });
2457
+ hexInput.addEventListener("mouseleave", () => {
2458
+ if (document.activeElement !== hexInput) {
2459
+ hexInput.style.borderColor = "var(--fb-border-color)";
2460
+ }
2461
+ });
2462
+ pickerWrapper.appendChild(swatch);
2463
+ pickerWrapper.appendChild(hexInput);
2464
+ pickerWrapper.appendChild(colourInput);
2465
+ return pickerWrapper;
2466
+ }
2467
+ function renderColourElement(element, ctx, wrapper, pathKey) {
2468
+ const state = ctx.state;
2469
+ const initialValue = ctx.prefill[element.key] || element.default || "#000000";
2470
+ if (state.config.readonly) {
2471
+ const readonlyUI = createReadonlyColourUI(initialValue);
2472
+ wrapper.appendChild(readonlyUI);
2473
+ } else {
2474
+ const editUI = createEditColourUI(initialValue, pathKey, ctx);
2475
+ wrapper.appendChild(editUI);
2476
+ }
2477
+ const colourHint = document.createElement("p");
2478
+ colourHint.className = "mt-1";
2479
+ colourHint.style.cssText = `
2480
+ font-size: var(--fb-font-size-small);
2481
+ color: var(--fb-text-secondary-color);
2482
+ `;
2483
+ colourHint.textContent = makeFieldHint(element);
2484
+ wrapper.appendChild(colourHint);
2485
+ }
2486
+ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
2487
+ const state = ctx.state;
2488
+ const prefillValues = ctx.prefill[element.key] || [];
2489
+ const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
2490
+ const minCount = element.minCount ?? 1;
2491
+ const maxCount = element.maxCount ?? Infinity;
2492
+ while (values.length < minCount) {
2493
+ values.push(element.default || "#000000");
2494
+ }
2495
+ const container = document.createElement("div");
2496
+ container.className = "space-y-2";
2497
+ wrapper.appendChild(container);
2498
+ function updateIndices() {
2499
+ const items = container.querySelectorAll(".multiple-colour-item");
2500
+ items.forEach((item, index) => {
2501
+ const input = item.querySelector("input");
2502
+ if (input) {
2503
+ input.name = `${pathKey}[${index}]`;
2504
+ }
2505
+ });
2506
+ }
2507
+ function addColourItem(value = "#000000", index = -1) {
2508
+ const itemWrapper = document.createElement("div");
2509
+ itemWrapper.className = "multiple-colour-item flex items-center gap-2";
2510
+ if (state.config.readonly) {
2511
+ const readonlyUI = createReadonlyColourUI(value);
2512
+ while (readonlyUI.firstChild) {
2513
+ itemWrapper.appendChild(readonlyUI.firstChild);
2514
+ }
2515
+ } else {
2516
+ const tempPathKey = `${pathKey}[${container.children.length}]`;
2517
+ const editUI = createEditColourUI(value, tempPathKey, ctx);
2518
+ editUI.style.flex = "1";
2519
+ itemWrapper.appendChild(editUI);
2520
+ }
2521
+ if (index === -1) {
2522
+ container.appendChild(itemWrapper);
2523
+ } else {
2524
+ container.insertBefore(itemWrapper, container.children[index]);
2525
+ }
2526
+ updateIndices();
2527
+ return itemWrapper;
2528
+ }
2529
+ function updateRemoveButtons() {
2530
+ if (state.config.readonly) return;
2531
+ const items = container.querySelectorAll(".multiple-colour-item");
2532
+ const currentCount = items.length;
2533
+ items.forEach((item) => {
2534
+ let removeBtn = item.querySelector(
2535
+ ".remove-item-btn"
2536
+ );
2537
+ if (!removeBtn) {
2538
+ removeBtn = document.createElement("button");
2539
+ removeBtn.type = "button";
2540
+ removeBtn.className = "remove-item-btn px-2 py-1 rounded";
2541
+ removeBtn.style.cssText = `
2542
+ color: var(--fb-error-color);
2543
+ background-color: transparent;
2544
+ transition: background-color var(--fb-transition-duration);
2545
+ `;
2546
+ removeBtn.innerHTML = "\u2715";
2547
+ removeBtn.addEventListener("mouseenter", () => {
2548
+ removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2549
+ });
2550
+ removeBtn.addEventListener("mouseleave", () => {
2551
+ removeBtn.style.backgroundColor = "transparent";
2552
+ });
2553
+ removeBtn.onclick = () => {
2554
+ const currentIndex = Array.from(container.children).indexOf(
2555
+ item
2556
+ );
2557
+ if (container.children.length > minCount) {
2558
+ values.splice(currentIndex, 1);
2559
+ item.remove();
2560
+ updateIndices();
2561
+ updateAddButton();
2562
+ updateRemoveButtons();
2563
+ }
2564
+ };
2565
+ item.appendChild(removeBtn);
2566
+ }
2567
+ const disabled = currentCount <= minCount;
2568
+ removeBtn.disabled = disabled;
2569
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
2570
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
2571
+ });
2572
+ }
2573
+ function updateAddButton() {
2574
+ const existingAddBtn = wrapper.querySelector(".add-colour-btn");
2575
+ if (existingAddBtn) existingAddBtn.remove();
2576
+ if (!state.config.readonly && values.length < maxCount) {
2577
+ const addBtn = document.createElement("button");
2578
+ addBtn.type = "button";
2579
+ addBtn.className = "add-colour-btn mt-2 px-3 py-1 rounded";
2580
+ addBtn.style.cssText = `
2581
+ color: var(--fb-primary-color);
2582
+ border: var(--fb-border-width) solid var(--fb-primary-color);
2583
+ background-color: transparent;
2584
+ font-size: var(--fb-font-size);
2585
+ transition: all var(--fb-transition-duration);
2586
+ `;
2587
+ addBtn.textContent = `+ Add ${element.label || "Colour"}`;
2588
+ addBtn.addEventListener("mouseenter", () => {
2589
+ addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2590
+ });
2591
+ addBtn.addEventListener("mouseleave", () => {
2592
+ addBtn.style.backgroundColor = "transparent";
2593
+ });
2594
+ addBtn.onclick = () => {
2595
+ const defaultColour = element.default || "#000000";
2596
+ values.push(defaultColour);
2597
+ addColourItem(defaultColour);
2598
+ updateAddButton();
2599
+ updateRemoveButtons();
2600
+ };
2601
+ wrapper.appendChild(addBtn);
2602
+ }
2603
+ }
2604
+ values.forEach((value) => addColourItem(value));
2605
+ updateAddButton();
2606
+ updateRemoveButtons();
2607
+ const hint = document.createElement("p");
2608
+ hint.className = "mt-1";
2609
+ hint.style.cssText = `
2610
+ font-size: var(--fb-font-size-small);
2611
+ color: var(--fb-text-secondary-color);
2612
+ `;
2613
+ hint.textContent = makeFieldHint(element);
2614
+ wrapper.appendChild(hint);
2615
+ }
2616
+ function validateColourElement(element, key, context) {
2617
+ const errors = [];
2618
+ const { scopeRoot, skipValidation } = context;
2619
+ const markValidity = (input, errorMessage) => {
2620
+ if (!input) return;
2621
+ const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
2622
+ let errorElement = document.getElementById(errorId);
2623
+ if (errorMessage) {
2624
+ input.classList.add("invalid");
2625
+ input.title = errorMessage;
2626
+ if (!errorElement) {
2627
+ errorElement = document.createElement("div");
2628
+ errorElement.id = errorId;
2629
+ errorElement.className = "error-message";
2630
+ errorElement.style.cssText = `
2631
+ color: var(--fb-error-color);
2632
+ font-size: var(--fb-font-size-small);
2633
+ margin-top: 0.25rem;
2634
+ `;
2635
+ if (input.nextSibling) {
2636
+ input.parentNode?.insertBefore(errorElement, input.nextSibling);
2637
+ } else {
2638
+ input.parentNode?.appendChild(errorElement);
2639
+ }
2640
+ }
2641
+ errorElement.textContent = errorMessage;
2642
+ errorElement.style.display = "block";
2643
+ } else {
2644
+ input.classList.remove("invalid");
2645
+ input.title = "";
2646
+ if (errorElement) {
2647
+ errorElement.remove();
2648
+ }
2649
+ }
2650
+ };
2651
+ const validateColourValue = (input, val, fieldKey) => {
2652
+ if (!val) {
2653
+ if (!skipValidation && element.required) {
2654
+ errors.push(`${fieldKey}: required`);
2655
+ markValidity(input, "required");
2656
+ return "";
2657
+ }
2658
+ markValidity(input, null);
2659
+ return "";
2660
+ }
2661
+ const normalized = normalizeColourValue(val);
2662
+ if (!skipValidation && !isValidHexColour(normalized)) {
2663
+ errors.push(`${fieldKey}: invalid hex colour format`);
2664
+ markValidity(input, "invalid hex colour format");
2665
+ return val;
2666
+ }
2667
+ markValidity(input, null);
2668
+ return normalized;
2669
+ };
2670
+ if (element.multiple) {
2671
+ const hexInputs = scopeRoot.querySelectorAll(
2672
+ `.colour-hex-input`
2673
+ );
2674
+ const values = [];
2675
+ hexInputs.forEach((input, index) => {
2676
+ const val = input?.value ?? "";
2677
+ const validated = validateColourValue(input, val, `${key}[${index}]`);
2678
+ values.push(validated);
2679
+ });
2680
+ if (!skipValidation) {
2681
+ const minCount = element.minCount ?? 1;
2682
+ const maxCount = element.maxCount ?? Infinity;
2683
+ const filteredValues = values.filter((v) => v !== "");
2684
+ if (element.required && filteredValues.length === 0) {
2685
+ errors.push(`${key}: required`);
2686
+ }
2687
+ if (filteredValues.length < minCount) {
2688
+ errors.push(`${key}: minimum ${minCount} items required`);
2689
+ }
2690
+ if (filteredValues.length > maxCount) {
2691
+ errors.push(`${key}: maximum ${maxCount} items allowed`);
2692
+ }
2693
+ }
2694
+ return { value: values, errors };
2695
+ } else {
2696
+ const hexInput = scopeRoot.querySelector(
2697
+ `[name="${key}"].colour-hex-input`
2698
+ );
2699
+ const val = hexInput?.value ?? "";
2700
+ if (!skipValidation && element.required && val === "") {
2701
+ errors.push(`${key}: required`);
2702
+ markValidity(hexInput, "required");
2703
+ return { value: "", errors };
2704
+ }
2705
+ const validated = validateColourValue(hexInput, val, key);
2706
+ return { value: validated, errors };
2707
+ }
2708
+ }
2709
+ function updateColourField(element, fieldPath, value, context) {
2710
+ const { scopeRoot } = context;
2711
+ if (element.multiple) {
2712
+ if (!Array.isArray(value)) {
2713
+ console.warn(
2714
+ `updateColourField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
2715
+ );
2716
+ return;
2717
+ }
2718
+ const hexInputs = scopeRoot.querySelectorAll(
2719
+ `.colour-hex-input`
2720
+ );
2721
+ hexInputs.forEach((hexInput, index) => {
2722
+ if (index < value.length) {
2723
+ const normalized = normalizeColourValue(value[index]);
2724
+ hexInput.value = normalized;
2725
+ hexInput.classList.remove("invalid");
2726
+ hexInput.title = "";
2727
+ const wrapper = hexInput.closest(".colour-picker-wrapper");
2728
+ if (wrapper) {
2729
+ const swatch = wrapper.querySelector(".colour-swatch");
2730
+ const colourInput = wrapper.querySelector(".colour-picker-hidden");
2731
+ if (swatch) {
2732
+ swatch.style.backgroundColor = normalized;
2733
+ }
2734
+ if (colourInput) {
2735
+ colourInput.value = normalized.toLowerCase();
2736
+ }
2737
+ }
2738
+ }
2739
+ });
2740
+ if (value.length !== hexInputs.length) {
2741
+ console.warn(
2742
+ `updateColourField: Multiple field "${fieldPath}" has ${hexInputs.length} inputs but received ${value.length} values. Consider re-rendering for add/remove.`
2743
+ );
2744
+ }
2745
+ } else {
2746
+ const hexInput = scopeRoot.querySelector(
2747
+ `[name="${fieldPath}"].colour-hex-input`
2748
+ );
2749
+ if (hexInput) {
2750
+ const normalized = normalizeColourValue(value);
2751
+ hexInput.value = normalized;
2752
+ hexInput.classList.remove("invalid");
2753
+ hexInput.title = "";
2754
+ const wrapper = hexInput.closest(".colour-picker-wrapper");
2755
+ if (wrapper) {
2756
+ const swatch = wrapper.querySelector(".colour-swatch");
2757
+ const colourInput = wrapper.querySelector(".colour-picker-hidden");
2758
+ if (swatch) {
2759
+ swatch.style.backgroundColor = normalized;
2760
+ }
2761
+ if (colourInput) {
2762
+ colourInput.value = normalized.toLowerCase();
2763
+ }
2764
+ }
2765
+ }
2766
+ }
2767
+ }
2768
+
2200
2769
  // src/components/container.ts
2201
2770
  var renderElementFunc = null;
2202
2771
  function setRenderElement(fn) {
@@ -2225,6 +2794,9 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2225
2794
  const subCtx = {
2226
2795
  path: pathJoin(ctx.path, element.key),
2227
2796
  prefill: ctx.prefill?.[element.key] || {},
2797
+ // Sliced data for value population
2798
+ formData: ctx.formData ?? ctx.prefill,
2799
+ // Complete root data for displayIf evaluation
2228
2800
  state: ctx.state
2229
2801
  };
2230
2802
  element.elements.forEach((child) => {
@@ -2268,7 +2840,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2268
2840
  const subCtx = {
2269
2841
  state: ctx.state,
2270
2842
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2271
- prefill: {}
2843
+ prefill: {},
2844
+ formData: ctx.formData ?? ctx.prefill
2845
+ // Complete root data for displayIf
2272
2846
  };
2273
2847
  const item = document.createElement("div");
2274
2848
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2313,7 +2887,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2313
2887
  const subCtx = {
2314
2888
  state: ctx.state,
2315
2889
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2316
- prefill: prefillObj || {}
2890
+ prefill: prefillObj || {},
2891
+ formData: ctx.formData ?? ctx.prefill
2892
+ // Complete root data for displayIf
2317
2893
  };
2318
2894
  const item = document.createElement("div");
2319
2895
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2344,7 +2920,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2344
2920
  const subCtx = {
2345
2921
  state: ctx.state,
2346
2922
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2347
- prefill: {}
2923
+ prefill: {},
2924
+ formData: ctx.formData ?? ctx.prefill
2925
+ // Complete root data for displayIf
2348
2926
  };
2349
2927
  const item = document.createElement("div");
2350
2928
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2624,11 +3202,33 @@ if (typeof document !== "undefined") {
2624
3202
  }
2625
3203
  });
2626
3204
  }
2627
- function renderElement2(element, ctx) {
2628
- const wrapper = document.createElement("div");
2629
- wrapper.className = "mb-6 fb-field-wrapper";
2630
- const label = document.createElement("div");
2631
- label.className = "flex items-center mb-2";
3205
+ function checkDisplayCondition(element, ctx) {
3206
+ if (!element.displayIf) {
3207
+ return null;
3208
+ }
3209
+ try {
3210
+ const dataForCondition = ctx.formData ?? ctx.prefill;
3211
+ const shouldDisplay = evaluateDisplayCondition(
3212
+ element.displayIf,
3213
+ dataForCondition
3214
+ );
3215
+ if (!shouldDisplay) {
3216
+ const hiddenWrapper = document.createElement("div");
3217
+ hiddenWrapper.className = "fb-field-wrapper-hidden";
3218
+ hiddenWrapper.style.display = "none";
3219
+ hiddenWrapper.setAttribute("data-field-key", element.key);
3220
+ hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
3221
+ return hiddenWrapper;
3222
+ }
3223
+ } catch (error) {
3224
+ console.error(
3225
+ `Error evaluating displayIf for field "${element.key}":`,
3226
+ error
3227
+ );
3228
+ }
3229
+ return null;
3230
+ }
3231
+ function createFieldLabel(element) {
2632
3232
  const title = document.createElement("label");
2633
3233
  title.className = "text-sm font-medium text-gray-900";
2634
3234
  title.textContent = element.label || element.key;
@@ -2638,59 +3238,71 @@ function renderElement2(element, ctx) {
2638
3238
  req.textContent = "*";
2639
3239
  title.appendChild(req);
2640
3240
  }
3241
+ return title;
3242
+ }
3243
+ function createInfoButton(element) {
3244
+ const infoBtn = document.createElement("button");
3245
+ infoBtn.type = "button";
3246
+ infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
3247
+ infoBtn.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>';
3248
+ const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
3249
+ const tooltip = document.createElement("div");
3250
+ tooltip.id = tooltipId;
3251
+ tooltip.className = "hidden absolute z-50 bg-gray-200 text-gray-900 text-sm rounded-lg p-3 max-w-sm border border-gray-300 shadow-lg";
3252
+ tooltip.style.position = "fixed";
3253
+ tooltip.textContent = element.description || element.hint || "Field information";
3254
+ document.body.appendChild(tooltip);
3255
+ infoBtn.onclick = (e) => {
3256
+ e.preventDefault();
3257
+ e.stopPropagation();
3258
+ showTooltip(tooltipId, infoBtn);
3259
+ };
3260
+ return infoBtn;
3261
+ }
3262
+ function createLabelContainer(element) {
3263
+ const label = document.createElement("div");
3264
+ label.className = "flex items-center mb-2";
3265
+ const title = createFieldLabel(element);
2641
3266
  label.appendChild(title);
2642
3267
  if (element.description || element.hint) {
2643
- const infoBtn = document.createElement("button");
2644
- infoBtn.type = "button";
2645
- infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
2646
- infoBtn.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>';
2647
- const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
2648
- const tooltip = document.createElement("div");
2649
- tooltip.id = tooltipId;
2650
- tooltip.className = "hidden absolute z-50 bg-gray-200 text-gray-900 text-sm rounded-lg p-3 max-w-sm border border-gray-300 shadow-lg";
2651
- tooltip.style.position = "fixed";
2652
- tooltip.textContent = element.description || element.hint || "Field information";
2653
- document.body.appendChild(tooltip);
2654
- infoBtn.onclick = (e) => {
2655
- e.preventDefault();
2656
- e.stopPropagation();
2657
- showTooltip(tooltipId, infoBtn);
2658
- };
3268
+ const infoBtn = createInfoButton(element);
2659
3269
  label.appendChild(infoBtn);
2660
3270
  }
2661
- wrapper.appendChild(label);
2662
- const pathKey = pathJoin(ctx.path, element.key);
3271
+ return label;
3272
+ }
3273
+ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
3274
+ const isMultiple = "multiple" in element && element.multiple;
2663
3275
  switch (element.type) {
2664
3276
  case "text":
2665
- if ("multiple" in element && element.multiple) {
3277
+ if (isMultiple) {
2666
3278
  renderMultipleTextElement(element, ctx, wrapper, pathKey);
2667
3279
  } else {
2668
3280
  renderTextElement(element, ctx, wrapper, pathKey);
2669
3281
  }
2670
3282
  break;
2671
3283
  case "textarea":
2672
- if ("multiple" in element && element.multiple) {
3284
+ if (isMultiple) {
2673
3285
  renderMultipleTextareaElement(element, ctx, wrapper, pathKey);
2674
3286
  } else {
2675
3287
  renderTextareaElement(element, ctx, wrapper, pathKey);
2676
3288
  }
2677
3289
  break;
2678
3290
  case "number":
2679
- if ("multiple" in element && element.multiple) {
3291
+ if (isMultiple) {
2680
3292
  renderMultipleNumberElement(element, ctx, wrapper, pathKey);
2681
3293
  } else {
2682
3294
  renderNumberElement(element, ctx, wrapper, pathKey);
2683
3295
  }
2684
3296
  break;
2685
3297
  case "select":
2686
- if ("multiple" in element && element.multiple) {
3298
+ if (isMultiple) {
2687
3299
  renderMultipleSelectElement(element, ctx, wrapper, pathKey);
2688
3300
  } else {
2689
3301
  renderSelectElement(element, ctx, wrapper, pathKey);
2690
3302
  }
2691
3303
  break;
2692
3304
  case "file":
2693
- if ("multiple" in element && element.multiple) {
3305
+ if (isMultiple) {
2694
3306
  renderMultipleFileElement(element, ctx, wrapper, pathKey);
2695
3307
  } else {
2696
3308
  renderFileElement(element, ctx, wrapper, pathKey);
@@ -2699,11 +3311,18 @@ function renderElement2(element, ctx) {
2699
3311
  case "files":
2700
3312
  renderFilesElement(element, ctx, wrapper, pathKey);
2701
3313
  break;
3314
+ case "colour":
3315
+ if (isMultiple) {
3316
+ renderMultipleColourElement(element, ctx, wrapper, pathKey);
3317
+ } else {
3318
+ renderColourElement(element, ctx, wrapper, pathKey);
3319
+ }
3320
+ break;
2702
3321
  case "group":
2703
3322
  renderGroupElement(element, ctx, wrapper, pathKey);
2704
3323
  break;
2705
3324
  case "container":
2706
- if ("multiple" in element && element.multiple) {
3325
+ if (isMultiple) {
2707
3326
  renderMultipleContainerElement(element, ctx, wrapper);
2708
3327
  } else {
2709
3328
  renderSingleContainerElement(element, ctx, wrapper, pathKey);
@@ -2716,6 +3335,19 @@ function renderElement2(element, ctx) {
2716
3335
  wrapper.appendChild(unsupported);
2717
3336
  }
2718
3337
  }
3338
+ }
3339
+ function renderElement2(element, ctx) {
3340
+ const hiddenElement = checkDisplayCondition(element, ctx);
3341
+ if (hiddenElement) {
3342
+ return hiddenElement;
3343
+ }
3344
+ const wrapper = document.createElement("div");
3345
+ wrapper.className = "mb-6 fb-field-wrapper";
3346
+ wrapper.setAttribute("data-field-key", element.key);
3347
+ const label = createLabelContainer(element);
3348
+ wrapper.appendChild(label);
3349
+ const pathKey = pathJoin(ctx.path, element.key);
3350
+ dispatchToRenderer(element, ctx, wrapper, pathKey);
2719
3351
  return wrapper;
2720
3352
  }
2721
3353
  setRenderElement(renderElement2);
@@ -2999,6 +3631,10 @@ var componentRegistry = {
2999
3631
  validate: validateFileElement,
3000
3632
  update: updateFileField
3001
3633
  },
3634
+ colour: {
3635
+ validate: validateColourElement,
3636
+ update: updateColourField
3637
+ },
3002
3638
  container: {
3003
3639
  validate: validateContainerElement,
3004
3640
  update: updateContainerField
@@ -3119,6 +3755,7 @@ var FormBuilderInstance = class {
3119
3755
  }
3120
3756
  this.state.debounceTimer = setTimeout(() => {
3121
3757
  const formData = this.validateForm(true);
3758
+ this.reevaluateConditionalFields();
3122
3759
  if (this.state.config.onChange) {
3123
3760
  this.state.config.onChange(formData);
3124
3761
  }
@@ -3378,6 +4015,8 @@ var FormBuilderInstance = class {
3378
4015
  const block = renderElement2(element, {
3379
4016
  path: "",
3380
4017
  prefill: prefill || {},
4018
+ formData: prefill || {},
4019
+ // Pass complete root data for displayIf evaluation
3381
4020
  state: this.state,
3382
4021
  instance: this
3383
4022
  });
@@ -3422,6 +4061,19 @@ var FormBuilderInstance = class {
3422
4061
  };
3423
4062
  setValidateElement(validateElement2);
3424
4063
  this.state.schema.elements.forEach((element) => {
4064
+ if (element.displayIf) {
4065
+ try {
4066
+ const shouldDisplay = evaluateDisplayCondition(element.displayIf, data);
4067
+ if (!shouldDisplay) {
4068
+ return;
4069
+ }
4070
+ } catch (error) {
4071
+ console.error(
4072
+ `Error evaluating displayIf for field "${element.key}" during validation:`,
4073
+ error
4074
+ );
4075
+ }
4076
+ }
3425
4077
  if (element.hidden) {
3426
4078
  data[element.key] = element.default !== void 0 ? element.default : null;
3427
4079
  } else {
@@ -3578,6 +4230,70 @@ var FormBuilderInstance = class {
3578
4230
  );
3579
4231
  }
3580
4232
  }
4233
+ /**
4234
+ * Re-evaluate all conditional fields (displayIf) based on current form data
4235
+ * This is called automatically when form data changes (via onChange events)
4236
+ */
4237
+ reevaluateConditionalFields() {
4238
+ if (!this.state.schema || !this.state.formRoot) return;
4239
+ const formData = this.validateForm(true).data;
4240
+ const checkElements = (elements, currentPath) => {
4241
+ elements.forEach((element) => {
4242
+ const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
4243
+ if (element.displayIf) {
4244
+ const fieldWrappers = this.state.formRoot.querySelectorAll(
4245
+ `[data-field-key="${element.key}"]`
4246
+ );
4247
+ fieldWrappers.forEach((wrapper) => {
4248
+ try {
4249
+ const shouldDisplay = evaluateDisplayCondition(
4250
+ element.displayIf,
4251
+ formData
4252
+ // Use complete formData for condition evaluation
4253
+ );
4254
+ const isCurrentlyHidden = wrapper.getAttribute("data-conditionally-hidden") === "true";
4255
+ if (shouldDisplay && isCurrentlyHidden) {
4256
+ const newWrapper = renderElement2(element, {
4257
+ path: fullPath,
4258
+ // Use accumulated path
4259
+ prefill: formData,
4260
+ // Use complete formData for root-level elements
4261
+ formData,
4262
+ // Pass complete formData for displayIf evaluation
4263
+ state: this.state,
4264
+ instance: this
4265
+ });
4266
+ wrapper.parentNode?.replaceChild(newWrapper, wrapper);
4267
+ } else if (!shouldDisplay && !isCurrentlyHidden) {
4268
+ const hiddenWrapper = document.createElement("div");
4269
+ hiddenWrapper.className = "fb-field-wrapper-hidden";
4270
+ hiddenWrapper.style.display = "none";
4271
+ hiddenWrapper.setAttribute("data-field-key", element.key);
4272
+ hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
4273
+ wrapper.parentNode?.replaceChild(hiddenWrapper, wrapper);
4274
+ }
4275
+ } catch (error) {
4276
+ console.error(
4277
+ `Error re-evaluating displayIf for field "${element.key}":`,
4278
+ error
4279
+ );
4280
+ }
4281
+ });
4282
+ }
4283
+ if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
4284
+ const containerData = formData?.[element.key];
4285
+ if (Array.isArray(containerData)) {
4286
+ containerData.forEach((_, index) => {
4287
+ checkElements(element.elements, `${fullPath}[${index}]`);
4288
+ });
4289
+ } else {
4290
+ checkElements(element.elements, fullPath);
4291
+ }
4292
+ }
4293
+ });
4294
+ };
4295
+ checkElements(this.state.schema.elements, "");
4296
+ }
3581
4297
  /**
3582
4298
  * Destroy instance and clean up resources
3583
4299
  */