@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.
@@ -62,9 +62,6 @@ function validateSchema(schema) {
62
62
  errors.push("Schema must be an object");
63
63
  return errors;
64
64
  }
65
- if (!schema.version) {
66
- errors.push("Schema missing version");
67
- }
68
65
  if (!Array.isArray(schema.elements)) {
69
66
  errors.push("Schema missing elements array");
70
67
  return errors;
@@ -78,6 +75,20 @@ function validateSchema(schema) {
78
75
  if (!element.key) {
79
76
  errors.push(`${elementPath}: missing key`);
80
77
  }
78
+ if (element.displayIf) {
79
+ const displayIf = element.displayIf;
80
+ if (!displayIf.key || typeof displayIf.key !== "string") {
81
+ errors.push(
82
+ `${elementPath}: displayIf must have a 'key' property of type string`
83
+ );
84
+ }
85
+ const hasOperator = "equals" in displayIf;
86
+ if (!hasOperator) {
87
+ errors.push(
88
+ `${elementPath}: displayIf must have at least one operator (equals, etc.)`
89
+ );
90
+ }
91
+ }
81
92
  if (element.type === "group" && "elements" in element && element.elements) {
82
93
  validateElements(element.elements, `${elementPath}.elements`);
83
94
  }
@@ -115,6 +126,66 @@ function clear(node) {
115
126
  while (node.firstChild) node.removeChild(node.firstChild);
116
127
  }
117
128
 
129
+ // src/utils/display-conditions.ts
130
+ function getValueByPath(data, path) {
131
+ if (!data || typeof data !== "object") {
132
+ return void 0;
133
+ }
134
+ const segments = path.match(/[^.[\]]+|\[\d+\]/g);
135
+ if (!segments || segments.length === 0) {
136
+ return void 0;
137
+ }
138
+ let current = data;
139
+ for (const segment of segments) {
140
+ if (current === void 0 || current === null) {
141
+ return void 0;
142
+ }
143
+ if (segment.startsWith("[") && segment.endsWith("]")) {
144
+ const index = parseInt(segment.slice(1, -1), 10);
145
+ if (!Array.isArray(current) || isNaN(index)) {
146
+ return void 0;
147
+ }
148
+ current = current[index];
149
+ } else {
150
+ current = current[segment];
151
+ }
152
+ }
153
+ return current;
154
+ }
155
+ function evaluateDisplayCondition(condition, formData) {
156
+ if (!condition || !condition.key) {
157
+ throw new Error(
158
+ "Invalid displayIf condition: must have a 'key' property"
159
+ );
160
+ }
161
+ const actualValue = getValueByPath(formData, condition.key);
162
+ if ("equals" in condition) {
163
+ return deepEqual(actualValue, condition.equals);
164
+ }
165
+ throw new Error(
166
+ `Invalid displayIf condition: no recognized operator (equals, etc.)`
167
+ );
168
+ }
169
+ function deepEqual(a, b) {
170
+ if (a === b) return true;
171
+ if (a == null || b == null) return a === b;
172
+ if (typeof a !== typeof b) return false;
173
+ if (typeof a === "object" && typeof b === "object") {
174
+ try {
175
+ return JSON.stringify(a) === JSON.stringify(b);
176
+ } catch (e) {
177
+ if (e instanceof TypeError && (e.message.includes("circular") || e.message.includes("cyclic"))) {
178
+ console.warn(
179
+ "deepEqual: Circular reference detected in displayIf comparison, using reference equality"
180
+ );
181
+ return a === b;
182
+ }
183
+ throw e;
184
+ }
185
+ }
186
+ return a === b;
187
+ }
188
+
118
189
  // src/components/text.ts
119
190
  function renderTextElement(element, ctx, wrapper, pathKey) {
120
191
  const state = ctx.state;
@@ -1172,6 +1243,175 @@ function t(key, state) {
1172
1243
  }
1173
1244
 
1174
1245
  // src/components/file.ts
1246
+ function renderLocalImagePreview(container, file, fileName) {
1247
+ const img = document.createElement("img");
1248
+ img.className = "w-full h-full object-contain";
1249
+ img.alt = fileName || "Preview";
1250
+ const reader = new FileReader();
1251
+ reader.onload = (e) => {
1252
+ var _a;
1253
+ img.src = ((_a = e.target) == null ? void 0 : _a.result) || "";
1254
+ };
1255
+ reader.readAsDataURL(file);
1256
+ container.appendChild(img);
1257
+ }
1258
+ function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
1259
+ const videoUrl = URL.createObjectURL(file);
1260
+ container.onclick = null;
1261
+ const newContainer = container.cloneNode(false);
1262
+ if (container.parentNode) {
1263
+ container.parentNode.replaceChild(newContainer, container);
1264
+ }
1265
+ newContainer.innerHTML = `
1266
+ <div class="relative group h-full">
1267
+ <video class="w-full h-full object-contain" controls preload="auto" muted>
1268
+ <source src="${videoUrl}" type="${videoType}">
1269
+ Your browser does not support the video tag.
1270
+ </video>
1271
+ <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
1272
+ <button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
1273
+ ${t("removeElement", state)}
1274
+ </button>
1275
+ <button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
1276
+ Change
1277
+ </button>
1278
+ </div>
1279
+ </div>
1280
+ `;
1281
+ attachVideoButtonHandlers(newContainer, resourceId, state, deps);
1282
+ return newContainer;
1283
+ }
1284
+ function attachVideoButtonHandlers(container, resourceId, state, deps) {
1285
+ const changeBtn = container.querySelector(".change-file-btn");
1286
+ if (changeBtn) {
1287
+ changeBtn.onclick = (e) => {
1288
+ e.stopPropagation();
1289
+ if (deps == null ? void 0 : deps.picker) {
1290
+ deps.picker.click();
1291
+ }
1292
+ };
1293
+ }
1294
+ const deleteBtn = container.querySelector(".delete-file-btn");
1295
+ if (deleteBtn) {
1296
+ deleteBtn.onclick = (e) => {
1297
+ e.stopPropagation();
1298
+ handleVideoDelete(container, resourceId, state, deps);
1299
+ };
1300
+ }
1301
+ }
1302
+ function handleVideoDelete(container, resourceId, state, deps) {
1303
+ var _a;
1304
+ state.resourceIndex.delete(resourceId);
1305
+ const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1306
+ 'input[type="hidden"]'
1307
+ );
1308
+ if (hiddenInput) {
1309
+ hiddenInput.value = "";
1310
+ }
1311
+ if (deps == null ? void 0 : deps.fileUploadHandler) {
1312
+ container.onclick = deps.fileUploadHandler;
1313
+ }
1314
+ if (deps == null ? void 0 : deps.dragHandler) {
1315
+ setupDragAndDrop(container, deps.dragHandler);
1316
+ }
1317
+ container.innerHTML = `
1318
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1319
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1320
+ <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"/>
1321
+ </svg>
1322
+ <div class="text-sm text-center">${t("clickDragText", state)}</div>
1323
+ </div>
1324
+ `;
1325
+ }
1326
+ function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
1327
+ const video = document.createElement("video");
1328
+ video.className = "w-full h-full object-contain";
1329
+ video.controls = true;
1330
+ video.preload = "metadata";
1331
+ video.muted = true;
1332
+ const source = document.createElement("source");
1333
+ source.src = thumbnailUrl;
1334
+ source.type = videoType;
1335
+ video.appendChild(source);
1336
+ video.appendChild(document.createTextNode("Your browser does not support the video tag."));
1337
+ container.appendChild(video);
1338
+ }
1339
+ function renderDeleteButton(container, resourceId, state) {
1340
+ addDeleteButton(container, state, () => {
1341
+ var _a;
1342
+ state.resourceIndex.delete(resourceId);
1343
+ const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1344
+ 'input[type="hidden"]'
1345
+ );
1346
+ if (hiddenInput) {
1347
+ hiddenInput.value = "";
1348
+ }
1349
+ container.innerHTML = `
1350
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1351
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1352
+ <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"/>
1353
+ </svg>
1354
+ <div class="text-sm text-center">${t("clickDragText", state)}</div>
1355
+ </div>
1356
+ `;
1357
+ });
1358
+ }
1359
+ async function renderLocalFilePreview(container, meta, fileName, resourceId, isReadonly, state, deps) {
1360
+ if (!meta.file || !(meta.file instanceof File)) {
1361
+ return;
1362
+ }
1363
+ if (meta.type && meta.type.startsWith("image/")) {
1364
+ renderLocalImagePreview(container, meta.file, fileName);
1365
+ } else if (meta.type && meta.type.startsWith("video/")) {
1366
+ const newContainer = renderLocalVideoPreview(
1367
+ container,
1368
+ meta.file,
1369
+ meta.type,
1370
+ resourceId,
1371
+ state,
1372
+ deps
1373
+ );
1374
+ container = newContainer;
1375
+ } else {
1376
+ 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>`;
1377
+ }
1378
+ if (!isReadonly && !(meta.type && meta.type.startsWith("video/"))) {
1379
+ renderDeleteButton(container, resourceId, state);
1380
+ }
1381
+ }
1382
+ async function renderUploadedFilePreview(container, resourceId, fileName, meta, state) {
1383
+ if (!state.config.getThumbnail) {
1384
+ setEmptyFileContainer(container, state);
1385
+ return;
1386
+ }
1387
+ try {
1388
+ const thumbnailUrl = await state.config.getThumbnail(resourceId);
1389
+ if (thumbnailUrl) {
1390
+ clear(container);
1391
+ if (meta && meta.type && meta.type.startsWith("video/")) {
1392
+ renderUploadedVideoPreview(container, thumbnailUrl, meta.type);
1393
+ } else {
1394
+ const img = document.createElement("img");
1395
+ img.className = "w-full h-full object-contain";
1396
+ img.alt = fileName || "Preview";
1397
+ img.src = thumbnailUrl;
1398
+ container.appendChild(img);
1399
+ }
1400
+ } else {
1401
+ setEmptyFileContainer(container, state);
1402
+ }
1403
+ } catch (error) {
1404
+ console.error("Failed to get thumbnail:", error);
1405
+ container.innerHTML = `
1406
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1407
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1408
+ <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"/>
1409
+ </svg>
1410
+ <div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
1411
+ </div>
1412
+ `;
1413
+ }
1414
+ }
1175
1415
  async function renderFilePreview(container, resourceId, state, options = {}) {
1176
1416
  const { fileName = "", isReadonly = false, deps = null } = options;
1177
1417
  if (!isReadonly && deps && (!deps.picker || !deps.fileUploadHandler || !deps.dragHandler)) {
@@ -1183,144 +1423,19 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
1183
1423
  if (isReadonly) {
1184
1424
  container.classList.add("cursor-pointer");
1185
1425
  }
1186
- const img = document.createElement("img");
1187
- img.className = "w-full h-full object-contain";
1188
- img.alt = fileName || "Preview";
1189
1426
  const meta = state.resourceIndex.get(resourceId);
1190
1427
  if (meta && meta.file && meta.file instanceof File) {
1191
- if (meta.type && meta.type.startsWith("image/")) {
1192
- const reader = new FileReader();
1193
- reader.onload = (e) => {
1194
- var _a;
1195
- img.src = ((_a = e.target) == null ? void 0 : _a.result) || "";
1196
- };
1197
- reader.readAsDataURL(meta.file);
1198
- container.appendChild(img);
1199
- } else if (meta.type && meta.type.startsWith("video/")) {
1200
- const videoUrl = URL.createObjectURL(meta.file);
1201
- container.onclick = null;
1202
- const newContainer = container.cloneNode(false);
1203
- if (container.parentNode) {
1204
- container.parentNode.replaceChild(newContainer, container);
1205
- }
1206
- container = newContainer;
1207
- container.innerHTML = `
1208
- <div class="relative group h-full">
1209
- <video class="w-full h-full object-contain" controls preload="auto" muted>
1210
- <source src="${videoUrl}" type="${meta.type}">
1211
- Your browser does not support the video tag.
1212
- </video>
1213
- <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
1214
- <button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
1215
- ${t("removeElement", state)}
1216
- </button>
1217
- <button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
1218
- Change
1219
- </button>
1220
- </div>
1221
- </div>
1222
- `;
1223
- const changeBtn = container.querySelector(
1224
- ".change-file-btn"
1225
- );
1226
- if (changeBtn) {
1227
- changeBtn.onclick = (e) => {
1228
- e.stopPropagation();
1229
- if (deps == null ? void 0 : deps.picker) {
1230
- deps.picker.click();
1231
- }
1232
- };
1233
- }
1234
- const deleteBtn = container.querySelector(
1235
- ".delete-file-btn"
1236
- );
1237
- if (deleteBtn) {
1238
- deleteBtn.onclick = (e) => {
1239
- var _a;
1240
- e.stopPropagation();
1241
- state.resourceIndex.delete(resourceId);
1242
- const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1243
- 'input[type="hidden"]'
1244
- );
1245
- if (hiddenInput) {
1246
- hiddenInput.value = "";
1247
- }
1248
- if (deps == null ? void 0 : deps.fileUploadHandler) {
1249
- container.onclick = deps.fileUploadHandler;
1250
- }
1251
- if (deps == null ? void 0 : deps.dragHandler) {
1252
- setupDragAndDrop(container, deps.dragHandler);
1253
- }
1254
- container.innerHTML = `
1255
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1256
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1257
- <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"/>
1258
- </svg>
1259
- <div class="text-sm text-center">${t("clickDragText", state)}</div>
1260
- </div>
1261
- `;
1262
- };
1263
- }
1264
- } else {
1265
- 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>`;
1266
- }
1267
- if (!isReadonly && !(meta && meta.type && meta.type.startsWith("video/"))) {
1268
- addDeleteButton(container, state, () => {
1269
- var _a;
1270
- state.resourceIndex.delete(resourceId);
1271
- const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1272
- 'input[type="hidden"]'
1273
- );
1274
- if (hiddenInput) {
1275
- hiddenInput.value = "";
1276
- }
1277
- container.innerHTML = `
1278
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1279
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1280
- <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"/>
1281
- </svg>
1282
- <div class="text-sm text-center">${t("clickDragText", state)}</div>
1283
- </div>
1284
- `;
1285
- });
1286
- }
1287
- } else if (state.config.getThumbnail) {
1288
- try {
1289
- const thumbnailUrl = await state.config.getThumbnail(resourceId);
1290
- if (thumbnailUrl) {
1291
- clear(container);
1292
- if (meta && meta.type && meta.type.startsWith("video/")) {
1293
- const video = document.createElement("video");
1294
- video.className = "w-full h-full object-contain";
1295
- video.controls = true;
1296
- video.preload = "metadata";
1297
- video.muted = true;
1298
- const source = document.createElement("source");
1299
- source.src = thumbnailUrl;
1300
- source.type = meta.type;
1301
- video.appendChild(source);
1302
- video.appendChild(document.createTextNode("Your browser does not support the video tag."));
1303
- container.appendChild(video);
1304
- } else {
1305
- img.src = thumbnailUrl;
1306
- container.appendChild(img);
1307
- }
1308
- } else {
1309
- setEmptyFileContainer(container, state);
1310
- }
1311
- } catch (error) {
1312
- console.error("Failed to get thumbnail:", error);
1313
- container.innerHTML = `
1314
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1315
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1316
- <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"/>
1317
- </svg>
1318
- <div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
1319
- </div>
1320
- `;
1321
- }
1428
+ await renderLocalFilePreview(
1429
+ container,
1430
+ meta,
1431
+ fileName,
1432
+ resourceId,
1433
+ isReadonly,
1434
+ state,
1435
+ deps
1436
+ );
1322
1437
  } else {
1323
- setEmptyFileContainer(container, state);
1438
+ await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
1324
1439
  }
1325
1440
  }
1326
1441
  async function renderFilePreviewReadonly(resourceId, state, fileName) {
@@ -2235,6 +2350,464 @@ function updateFileField(element, fieldPath, value, context) {
2235
2350
  }
2236
2351
  }
2237
2352
 
2353
+ // src/components/colour.ts
2354
+ function normalizeColourValue(value) {
2355
+ if (!value) return "#000000";
2356
+ return value.toUpperCase();
2357
+ }
2358
+ function isValidHexColour(value) {
2359
+ return /^#[0-9A-F]{6}$/i.test(value) || /^#[0-9A-F]{3}$/i.test(value);
2360
+ }
2361
+ function expandHexColour(value) {
2362
+ if (/^#[0-9A-F]{3}$/i.test(value)) {
2363
+ const r = value[1];
2364
+ const g = value[2];
2365
+ const b = value[3];
2366
+ return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
2367
+ }
2368
+ return value.toUpperCase();
2369
+ }
2370
+ function createReadonlyColourUI(value) {
2371
+ const container = document.createElement("div");
2372
+ container.className = "flex items-center gap-2";
2373
+ const normalizedValue = normalizeColourValue(value);
2374
+ const swatch = document.createElement("div");
2375
+ swatch.style.cssText = `
2376
+ width: 32px;
2377
+ height: 32px;
2378
+ border-radius: var(--fb-border-radius);
2379
+ border: var(--fb-border-width) solid var(--fb-border-color);
2380
+ background-color: ${normalizedValue};
2381
+ `;
2382
+ const hexText = document.createElement("span");
2383
+ hexText.style.cssText = `
2384
+ font-size: var(--fb-font-size);
2385
+ color: var(--fb-text-color);
2386
+ font-family: var(--fb-font-family-mono, monospace);
2387
+ `;
2388
+ hexText.textContent = normalizedValue;
2389
+ container.appendChild(swatch);
2390
+ container.appendChild(hexText);
2391
+ return container;
2392
+ }
2393
+ function createEditColourUI(value, pathKey, ctx) {
2394
+ const normalizedValue = normalizeColourValue(value);
2395
+ const pickerWrapper = document.createElement("div");
2396
+ pickerWrapper.className = "colour-picker-wrapper";
2397
+ pickerWrapper.style.cssText = `
2398
+ display: flex;
2399
+ align-items: center;
2400
+ gap: 8px;
2401
+ `;
2402
+ const swatch = document.createElement("div");
2403
+ swatch.className = "colour-swatch";
2404
+ swatch.style.cssText = `
2405
+ width: 40px;
2406
+ height: 40px;
2407
+ border-radius: var(--fb-border-radius);
2408
+ border: var(--fb-border-width) solid var(--fb-border-color);
2409
+ background-color: ${normalizedValue};
2410
+ cursor: pointer;
2411
+ transition: border-color var(--fb-transition-duration) ease-in-out;
2412
+ flex-shrink: 0;
2413
+ `;
2414
+ const hexInput = document.createElement("input");
2415
+ hexInput.type = "text";
2416
+ hexInput.className = "colour-hex-input";
2417
+ hexInput.name = pathKey;
2418
+ hexInput.value = normalizedValue;
2419
+ hexInput.placeholder = "#000000";
2420
+ hexInput.style.cssText = `
2421
+ width: 100px;
2422
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
2423
+ border: var(--fb-border-width) solid var(--fb-border-color);
2424
+ border-radius: var(--fb-border-radius);
2425
+ background-color: var(--fb-background-color);
2426
+ color: var(--fb-text-color);
2427
+ font-size: var(--fb-font-size);
2428
+ font-family: var(--fb-font-family-mono, monospace);
2429
+ transition: all var(--fb-transition-duration) ease-in-out;
2430
+ `;
2431
+ const colourInput = document.createElement("input");
2432
+ colourInput.type = "color";
2433
+ colourInput.className = "colour-picker-hidden";
2434
+ colourInput.value = normalizedValue.toLowerCase();
2435
+ colourInput.style.cssText = `
2436
+ position: absolute;
2437
+ opacity: 0;
2438
+ pointer-events: none;
2439
+ `;
2440
+ hexInput.addEventListener("input", () => {
2441
+ const inputValue = hexInput.value.trim();
2442
+ if (isValidHexColour(inputValue)) {
2443
+ const expanded = expandHexColour(inputValue);
2444
+ swatch.style.backgroundColor = expanded;
2445
+ colourInput.value = expanded.toLowerCase();
2446
+ hexInput.classList.remove("invalid");
2447
+ if (ctx.instance) {
2448
+ ctx.instance.triggerOnChange(pathKey, expanded);
2449
+ }
2450
+ } else {
2451
+ hexInput.classList.add("invalid");
2452
+ }
2453
+ });
2454
+ hexInput.addEventListener("blur", () => {
2455
+ const inputValue = hexInput.value.trim();
2456
+ if (isValidHexColour(inputValue)) {
2457
+ const expanded = expandHexColour(inputValue);
2458
+ hexInput.value = expanded;
2459
+ swatch.style.backgroundColor = expanded;
2460
+ colourInput.value = expanded.toLowerCase();
2461
+ hexInput.classList.remove("invalid");
2462
+ }
2463
+ });
2464
+ colourInput.addEventListener("change", () => {
2465
+ const normalized = normalizeColourValue(colourInput.value);
2466
+ hexInput.value = normalized;
2467
+ swatch.style.backgroundColor = normalized;
2468
+ if (ctx.instance) {
2469
+ ctx.instance.triggerOnChange(pathKey, normalized);
2470
+ }
2471
+ });
2472
+ swatch.addEventListener("click", () => {
2473
+ colourInput.click();
2474
+ });
2475
+ swatch.addEventListener("mouseenter", () => {
2476
+ swatch.style.borderColor = "var(--fb-border-hover-color)";
2477
+ });
2478
+ swatch.addEventListener("mouseleave", () => {
2479
+ swatch.style.borderColor = "var(--fb-border-color)";
2480
+ });
2481
+ hexInput.addEventListener("focus", () => {
2482
+ hexInput.style.borderColor = "var(--fb-border-focus-color)";
2483
+ hexInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
2484
+ hexInput.style.outlineOffset = "0";
2485
+ });
2486
+ hexInput.addEventListener("blur", () => {
2487
+ hexInput.style.borderColor = "var(--fb-border-color)";
2488
+ hexInput.style.outline = "none";
2489
+ });
2490
+ hexInput.addEventListener("mouseenter", () => {
2491
+ if (document.activeElement !== hexInput) {
2492
+ hexInput.style.borderColor = "var(--fb-border-hover-color)";
2493
+ }
2494
+ });
2495
+ hexInput.addEventListener("mouseleave", () => {
2496
+ if (document.activeElement !== hexInput) {
2497
+ hexInput.style.borderColor = "var(--fb-border-color)";
2498
+ }
2499
+ });
2500
+ pickerWrapper.appendChild(swatch);
2501
+ pickerWrapper.appendChild(hexInput);
2502
+ pickerWrapper.appendChild(colourInput);
2503
+ return pickerWrapper;
2504
+ }
2505
+ function renderColourElement(element, ctx, wrapper, pathKey) {
2506
+ const state = ctx.state;
2507
+ const initialValue = ctx.prefill[element.key] || element.default || "#000000";
2508
+ if (state.config.readonly) {
2509
+ const readonlyUI = createReadonlyColourUI(initialValue);
2510
+ wrapper.appendChild(readonlyUI);
2511
+ } else {
2512
+ const editUI = createEditColourUI(initialValue, pathKey, ctx);
2513
+ wrapper.appendChild(editUI);
2514
+ }
2515
+ const colourHint = document.createElement("p");
2516
+ colourHint.className = "mt-1";
2517
+ colourHint.style.cssText = `
2518
+ font-size: var(--fb-font-size-small);
2519
+ color: var(--fb-text-secondary-color);
2520
+ `;
2521
+ colourHint.textContent = makeFieldHint(element);
2522
+ wrapper.appendChild(colourHint);
2523
+ }
2524
+ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
2525
+ var _a, _b;
2526
+ const state = ctx.state;
2527
+ const prefillValues = ctx.prefill[element.key] || [];
2528
+ const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
2529
+ const minCount = (_a = element.minCount) != null ? _a : 1;
2530
+ const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
2531
+ while (values.length < minCount) {
2532
+ values.push(element.default || "#000000");
2533
+ }
2534
+ const container = document.createElement("div");
2535
+ container.className = "space-y-2";
2536
+ wrapper.appendChild(container);
2537
+ function updateIndices() {
2538
+ const items = container.querySelectorAll(".multiple-colour-item");
2539
+ items.forEach((item, index) => {
2540
+ const input = item.querySelector("input");
2541
+ if (input) {
2542
+ input.name = `${pathKey}[${index}]`;
2543
+ }
2544
+ });
2545
+ }
2546
+ function addColourItem(value = "#000000", index = -1) {
2547
+ const itemWrapper = document.createElement("div");
2548
+ itemWrapper.className = "multiple-colour-item flex items-center gap-2";
2549
+ if (state.config.readonly) {
2550
+ const readonlyUI = createReadonlyColourUI(value);
2551
+ while (readonlyUI.firstChild) {
2552
+ itemWrapper.appendChild(readonlyUI.firstChild);
2553
+ }
2554
+ } else {
2555
+ const tempPathKey = `${pathKey}[${container.children.length}]`;
2556
+ const editUI = createEditColourUI(value, tempPathKey, ctx);
2557
+ editUI.style.flex = "1";
2558
+ itemWrapper.appendChild(editUI);
2559
+ }
2560
+ if (index === -1) {
2561
+ container.appendChild(itemWrapper);
2562
+ } else {
2563
+ container.insertBefore(itemWrapper, container.children[index]);
2564
+ }
2565
+ updateIndices();
2566
+ return itemWrapper;
2567
+ }
2568
+ function updateRemoveButtons() {
2569
+ if (state.config.readonly) return;
2570
+ const items = container.querySelectorAll(".multiple-colour-item");
2571
+ const currentCount = items.length;
2572
+ items.forEach((item) => {
2573
+ let removeBtn = item.querySelector(
2574
+ ".remove-item-btn"
2575
+ );
2576
+ if (!removeBtn) {
2577
+ removeBtn = document.createElement("button");
2578
+ removeBtn.type = "button";
2579
+ removeBtn.className = "remove-item-btn px-2 py-1 rounded";
2580
+ removeBtn.style.cssText = `
2581
+ color: var(--fb-error-color);
2582
+ background-color: transparent;
2583
+ transition: background-color var(--fb-transition-duration);
2584
+ `;
2585
+ removeBtn.innerHTML = "\u2715";
2586
+ removeBtn.addEventListener("mouseenter", () => {
2587
+ removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2588
+ });
2589
+ removeBtn.addEventListener("mouseleave", () => {
2590
+ removeBtn.style.backgroundColor = "transparent";
2591
+ });
2592
+ removeBtn.onclick = () => {
2593
+ const currentIndex = Array.from(container.children).indexOf(
2594
+ item
2595
+ );
2596
+ if (container.children.length > minCount) {
2597
+ values.splice(currentIndex, 1);
2598
+ item.remove();
2599
+ updateIndices();
2600
+ updateAddButton();
2601
+ updateRemoveButtons();
2602
+ }
2603
+ };
2604
+ item.appendChild(removeBtn);
2605
+ }
2606
+ const disabled = currentCount <= minCount;
2607
+ removeBtn.disabled = disabled;
2608
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
2609
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
2610
+ });
2611
+ }
2612
+ function updateAddButton() {
2613
+ const existingAddBtn = wrapper.querySelector(".add-colour-btn");
2614
+ if (existingAddBtn) existingAddBtn.remove();
2615
+ if (!state.config.readonly && values.length < maxCount) {
2616
+ const addBtn = document.createElement("button");
2617
+ addBtn.type = "button";
2618
+ addBtn.className = "add-colour-btn mt-2 px-3 py-1 rounded";
2619
+ addBtn.style.cssText = `
2620
+ color: var(--fb-primary-color);
2621
+ border: var(--fb-border-width) solid var(--fb-primary-color);
2622
+ background-color: transparent;
2623
+ font-size: var(--fb-font-size);
2624
+ transition: all var(--fb-transition-duration);
2625
+ `;
2626
+ addBtn.textContent = `+ Add ${element.label || "Colour"}`;
2627
+ addBtn.addEventListener("mouseenter", () => {
2628
+ addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2629
+ });
2630
+ addBtn.addEventListener("mouseleave", () => {
2631
+ addBtn.style.backgroundColor = "transparent";
2632
+ });
2633
+ addBtn.onclick = () => {
2634
+ const defaultColour = element.default || "#000000";
2635
+ values.push(defaultColour);
2636
+ addColourItem(defaultColour);
2637
+ updateAddButton();
2638
+ updateRemoveButtons();
2639
+ };
2640
+ wrapper.appendChild(addBtn);
2641
+ }
2642
+ }
2643
+ values.forEach((value) => addColourItem(value));
2644
+ updateAddButton();
2645
+ updateRemoveButtons();
2646
+ const hint = document.createElement("p");
2647
+ hint.className = "mt-1";
2648
+ hint.style.cssText = `
2649
+ font-size: var(--fb-font-size-small);
2650
+ color: var(--fb-text-secondary-color);
2651
+ `;
2652
+ hint.textContent = makeFieldHint(element);
2653
+ wrapper.appendChild(hint);
2654
+ }
2655
+ function validateColourElement(element, key, context) {
2656
+ var _a, _b, _c;
2657
+ const errors = [];
2658
+ const { scopeRoot, skipValidation } = context;
2659
+ const markValidity = (input, errorMessage) => {
2660
+ var _a2, _b2;
2661
+ if (!input) return;
2662
+ const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
2663
+ let errorElement = document.getElementById(errorId);
2664
+ if (errorMessage) {
2665
+ input.classList.add("invalid");
2666
+ input.title = errorMessage;
2667
+ if (!errorElement) {
2668
+ errorElement = document.createElement("div");
2669
+ errorElement.id = errorId;
2670
+ errorElement.className = "error-message";
2671
+ errorElement.style.cssText = `
2672
+ color: var(--fb-error-color);
2673
+ font-size: var(--fb-font-size-small);
2674
+ margin-top: 0.25rem;
2675
+ `;
2676
+ if (input.nextSibling) {
2677
+ (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
2678
+ } else {
2679
+ (_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
2680
+ }
2681
+ }
2682
+ errorElement.textContent = errorMessage;
2683
+ errorElement.style.display = "block";
2684
+ } else {
2685
+ input.classList.remove("invalid");
2686
+ input.title = "";
2687
+ if (errorElement) {
2688
+ errorElement.remove();
2689
+ }
2690
+ }
2691
+ };
2692
+ const validateColourValue = (input, val, fieldKey) => {
2693
+ if (!val) {
2694
+ if (!skipValidation && element.required) {
2695
+ errors.push(`${fieldKey}: required`);
2696
+ markValidity(input, "required");
2697
+ return "";
2698
+ }
2699
+ markValidity(input, null);
2700
+ return "";
2701
+ }
2702
+ const normalized = normalizeColourValue(val);
2703
+ if (!skipValidation && !isValidHexColour(normalized)) {
2704
+ errors.push(`${fieldKey}: invalid hex colour format`);
2705
+ markValidity(input, "invalid hex colour format");
2706
+ return val;
2707
+ }
2708
+ markValidity(input, null);
2709
+ return normalized;
2710
+ };
2711
+ if (element.multiple) {
2712
+ const hexInputs = scopeRoot.querySelectorAll(
2713
+ `.colour-hex-input`
2714
+ );
2715
+ const values = [];
2716
+ hexInputs.forEach((input, index) => {
2717
+ var _a2;
2718
+ const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
2719
+ const validated = validateColourValue(input, val, `${key}[${index}]`);
2720
+ values.push(validated);
2721
+ });
2722
+ if (!skipValidation) {
2723
+ const minCount = (_a = element.minCount) != null ? _a : 1;
2724
+ const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
2725
+ const filteredValues = values.filter((v) => v !== "");
2726
+ if (element.required && filteredValues.length === 0) {
2727
+ errors.push(`${key}: required`);
2728
+ }
2729
+ if (filteredValues.length < minCount) {
2730
+ errors.push(`${key}: minimum ${minCount} items required`);
2731
+ }
2732
+ if (filteredValues.length > maxCount) {
2733
+ errors.push(`${key}: maximum ${maxCount} items allowed`);
2734
+ }
2735
+ }
2736
+ return { value: values, errors };
2737
+ } else {
2738
+ const hexInput = scopeRoot.querySelector(
2739
+ `[name="${key}"].colour-hex-input`
2740
+ );
2741
+ const val = (_c = hexInput == null ? void 0 : hexInput.value) != null ? _c : "";
2742
+ if (!skipValidation && element.required && val === "") {
2743
+ errors.push(`${key}: required`);
2744
+ markValidity(hexInput, "required");
2745
+ return { value: "", errors };
2746
+ }
2747
+ const validated = validateColourValue(hexInput, val, key);
2748
+ return { value: validated, errors };
2749
+ }
2750
+ }
2751
+ function updateColourField(element, fieldPath, value, context) {
2752
+ const { scopeRoot } = context;
2753
+ if (element.multiple) {
2754
+ if (!Array.isArray(value)) {
2755
+ console.warn(
2756
+ `updateColourField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
2757
+ );
2758
+ return;
2759
+ }
2760
+ const hexInputs = scopeRoot.querySelectorAll(
2761
+ `.colour-hex-input`
2762
+ );
2763
+ hexInputs.forEach((hexInput, index) => {
2764
+ if (index < value.length) {
2765
+ const normalized = normalizeColourValue(value[index]);
2766
+ hexInput.value = normalized;
2767
+ hexInput.classList.remove("invalid");
2768
+ hexInput.title = "";
2769
+ const wrapper = hexInput.closest(".colour-picker-wrapper");
2770
+ if (wrapper) {
2771
+ const swatch = wrapper.querySelector(".colour-swatch");
2772
+ const colourInput = wrapper.querySelector(".colour-picker-hidden");
2773
+ if (swatch) {
2774
+ swatch.style.backgroundColor = normalized;
2775
+ }
2776
+ if (colourInput) {
2777
+ colourInput.value = normalized.toLowerCase();
2778
+ }
2779
+ }
2780
+ }
2781
+ });
2782
+ if (value.length !== hexInputs.length) {
2783
+ console.warn(
2784
+ `updateColourField: Multiple field "${fieldPath}" has ${hexInputs.length} inputs but received ${value.length} values. Consider re-rendering for add/remove.`
2785
+ );
2786
+ }
2787
+ } else {
2788
+ const hexInput = scopeRoot.querySelector(
2789
+ `[name="${fieldPath}"].colour-hex-input`
2790
+ );
2791
+ if (hexInput) {
2792
+ const normalized = normalizeColourValue(value);
2793
+ hexInput.value = normalized;
2794
+ hexInput.classList.remove("invalid");
2795
+ hexInput.title = "";
2796
+ const wrapper = hexInput.closest(".colour-picker-wrapper");
2797
+ if (wrapper) {
2798
+ const swatch = wrapper.querySelector(".colour-swatch");
2799
+ const colourInput = wrapper.querySelector(".colour-picker-hidden");
2800
+ if (swatch) {
2801
+ swatch.style.backgroundColor = normalized;
2802
+ }
2803
+ if (colourInput) {
2804
+ colourInput.value = normalized.toLowerCase();
2805
+ }
2806
+ }
2807
+ }
2808
+ }
2809
+ }
2810
+
2238
2811
  // src/components/container.ts
2239
2812
  var renderElementFunc = null;
2240
2813
  function setRenderElement(fn) {
@@ -2249,7 +2822,7 @@ function renderElement(element, ctx) {
2249
2822
  return renderElementFunc(element, ctx);
2250
2823
  }
2251
2824
  function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2252
- var _a;
2825
+ var _a, _b;
2253
2826
  const containerWrap = document.createElement("div");
2254
2827
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
2255
2828
  containerWrap.setAttribute("data-container", pathKey);
@@ -2264,6 +2837,9 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2264
2837
  const subCtx = {
2265
2838
  path: pathJoin(ctx.path, element.key),
2266
2839
  prefill: ((_a = ctx.prefill) == null ? void 0 : _a[element.key]) || {},
2840
+ // Sliced data for value population
2841
+ formData: (_b = ctx.formData) != null ? _b : ctx.prefill,
2842
+ // Complete root data for displayIf evaluation
2267
2843
  state: ctx.state
2268
2844
  };
2269
2845
  element.elements.forEach((child) => {
@@ -2276,7 +2852,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2276
2852
  wrapper.appendChild(containerWrap);
2277
2853
  }
2278
2854
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2279
- var _a, _b, _c;
2855
+ var _a, _b, _c, _d;
2280
2856
  const state = ctx.state;
2281
2857
  const containerWrap = document.createElement("div");
2282
2858
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
@@ -2303,12 +2879,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2303
2879
  add.className = "px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors";
2304
2880
  add.textContent = t("addElement", state);
2305
2881
  add.onclick = () => {
2882
+ var _a2;
2306
2883
  if (countItems() < max) {
2307
2884
  const idx = countItems();
2308
2885
  const subCtx = {
2309
2886
  state: ctx.state,
2310
2887
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2311
- prefill: {}
2888
+ prefill: {},
2889
+ formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
2890
+ // Complete root data for displayIf
2312
2891
  };
2313
2892
  const item = document.createElement("div");
2314
2893
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2350,10 +2929,13 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2350
2929
  }
2351
2930
  if (pre && Array.isArray(pre)) {
2352
2931
  pre.forEach((prefillObj, idx) => {
2932
+ var _a2;
2353
2933
  const subCtx = {
2354
2934
  state: ctx.state,
2355
2935
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2356
- prefill: prefillObj || {}
2936
+ prefill: prefillObj || {},
2937
+ formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
2938
+ // Complete root data for displayIf
2357
2939
  };
2358
2940
  const item = document.createElement("div");
2359
2941
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2384,7 +2966,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2384
2966
  const subCtx = {
2385
2967
  state: ctx.state,
2386
2968
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2387
- prefill: {}
2969
+ prefill: {},
2970
+ formData: (_d = ctx.formData) != null ? _d : ctx.prefill
2971
+ // Complete root data for displayIf
2388
2972
  };
2389
2973
  const item = document.createElement("div");
2390
2974
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2667,11 +3251,34 @@ if (typeof document !== "undefined") {
2667
3251
  }
2668
3252
  });
2669
3253
  }
2670
- function renderElement2(element, ctx) {
2671
- const wrapper = document.createElement("div");
2672
- wrapper.className = "mb-6 fb-field-wrapper";
2673
- const label = document.createElement("div");
2674
- label.className = "flex items-center mb-2";
3254
+ function checkDisplayCondition(element, ctx) {
3255
+ var _a;
3256
+ if (!element.displayIf) {
3257
+ return null;
3258
+ }
3259
+ try {
3260
+ const dataForCondition = (_a = ctx.formData) != null ? _a : ctx.prefill;
3261
+ const shouldDisplay = evaluateDisplayCondition(
3262
+ element.displayIf,
3263
+ dataForCondition
3264
+ );
3265
+ if (!shouldDisplay) {
3266
+ const hiddenWrapper = document.createElement("div");
3267
+ hiddenWrapper.className = "fb-field-wrapper-hidden";
3268
+ hiddenWrapper.style.display = "none";
3269
+ hiddenWrapper.setAttribute("data-field-key", element.key);
3270
+ hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
3271
+ return hiddenWrapper;
3272
+ }
3273
+ } catch (error) {
3274
+ console.error(
3275
+ `Error evaluating displayIf for field "${element.key}":`,
3276
+ error
3277
+ );
3278
+ }
3279
+ return null;
3280
+ }
3281
+ function createFieldLabel(element) {
2675
3282
  const title = document.createElement("label");
2676
3283
  title.className = "text-sm font-medium text-gray-900";
2677
3284
  title.textContent = element.label || element.key;
@@ -2681,59 +3288,71 @@ function renderElement2(element, ctx) {
2681
3288
  req.textContent = "*";
2682
3289
  title.appendChild(req);
2683
3290
  }
3291
+ return title;
3292
+ }
3293
+ function createInfoButton(element) {
3294
+ const infoBtn = document.createElement("button");
3295
+ infoBtn.type = "button";
3296
+ infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
3297
+ 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>';
3298
+ const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
3299
+ const tooltip = document.createElement("div");
3300
+ tooltip.id = tooltipId;
3301
+ 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";
3302
+ tooltip.style.position = "fixed";
3303
+ tooltip.textContent = element.description || element.hint || "Field information";
3304
+ document.body.appendChild(tooltip);
3305
+ infoBtn.onclick = (e) => {
3306
+ e.preventDefault();
3307
+ e.stopPropagation();
3308
+ showTooltip(tooltipId, infoBtn);
3309
+ };
3310
+ return infoBtn;
3311
+ }
3312
+ function createLabelContainer(element) {
3313
+ const label = document.createElement("div");
3314
+ label.className = "flex items-center mb-2";
3315
+ const title = createFieldLabel(element);
2684
3316
  label.appendChild(title);
2685
3317
  if (element.description || element.hint) {
2686
- const infoBtn = document.createElement("button");
2687
- infoBtn.type = "button";
2688
- infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
2689
- 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>';
2690
- const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
2691
- const tooltip = document.createElement("div");
2692
- tooltip.id = tooltipId;
2693
- 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";
2694
- tooltip.style.position = "fixed";
2695
- tooltip.textContent = element.description || element.hint || "Field information";
2696
- document.body.appendChild(tooltip);
2697
- infoBtn.onclick = (e) => {
2698
- e.preventDefault();
2699
- e.stopPropagation();
2700
- showTooltip(tooltipId, infoBtn);
2701
- };
3318
+ const infoBtn = createInfoButton(element);
2702
3319
  label.appendChild(infoBtn);
2703
3320
  }
2704
- wrapper.appendChild(label);
2705
- const pathKey = pathJoin(ctx.path, element.key);
3321
+ return label;
3322
+ }
3323
+ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
3324
+ const isMultiple = "multiple" in element && element.multiple;
2706
3325
  switch (element.type) {
2707
3326
  case "text":
2708
- if ("multiple" in element && element.multiple) {
3327
+ if (isMultiple) {
2709
3328
  renderMultipleTextElement(element, ctx, wrapper, pathKey);
2710
3329
  } else {
2711
3330
  renderTextElement(element, ctx, wrapper, pathKey);
2712
3331
  }
2713
3332
  break;
2714
3333
  case "textarea":
2715
- if ("multiple" in element && element.multiple) {
3334
+ if (isMultiple) {
2716
3335
  renderMultipleTextareaElement(element, ctx, wrapper, pathKey);
2717
3336
  } else {
2718
3337
  renderTextareaElement(element, ctx, wrapper, pathKey);
2719
3338
  }
2720
3339
  break;
2721
3340
  case "number":
2722
- if ("multiple" in element && element.multiple) {
3341
+ if (isMultiple) {
2723
3342
  renderMultipleNumberElement(element, ctx, wrapper, pathKey);
2724
3343
  } else {
2725
3344
  renderNumberElement(element, ctx, wrapper, pathKey);
2726
3345
  }
2727
3346
  break;
2728
3347
  case "select":
2729
- if ("multiple" in element && element.multiple) {
3348
+ if (isMultiple) {
2730
3349
  renderMultipleSelectElement(element, ctx, wrapper, pathKey);
2731
3350
  } else {
2732
3351
  renderSelectElement(element, ctx, wrapper, pathKey);
2733
3352
  }
2734
3353
  break;
2735
3354
  case "file":
2736
- if ("multiple" in element && element.multiple) {
3355
+ if (isMultiple) {
2737
3356
  renderMultipleFileElement(element, ctx, wrapper, pathKey);
2738
3357
  } else {
2739
3358
  renderFileElement(element, ctx, wrapper, pathKey);
@@ -2742,11 +3361,18 @@ function renderElement2(element, ctx) {
2742
3361
  case "files":
2743
3362
  renderFilesElement(element, ctx, wrapper, pathKey);
2744
3363
  break;
3364
+ case "colour":
3365
+ if (isMultiple) {
3366
+ renderMultipleColourElement(element, ctx, wrapper, pathKey);
3367
+ } else {
3368
+ renderColourElement(element, ctx, wrapper, pathKey);
3369
+ }
3370
+ break;
2745
3371
  case "group":
2746
3372
  renderGroupElement(element, ctx, wrapper, pathKey);
2747
3373
  break;
2748
3374
  case "container":
2749
- if ("multiple" in element && element.multiple) {
3375
+ if (isMultiple) {
2750
3376
  renderMultipleContainerElement(element, ctx, wrapper);
2751
3377
  } else {
2752
3378
  renderSingleContainerElement(element, ctx, wrapper, pathKey);
@@ -2759,6 +3385,19 @@ function renderElement2(element, ctx) {
2759
3385
  wrapper.appendChild(unsupported);
2760
3386
  }
2761
3387
  }
3388
+ }
3389
+ function renderElement2(element, ctx) {
3390
+ const hiddenElement = checkDisplayCondition(element, ctx);
3391
+ if (hiddenElement) {
3392
+ return hiddenElement;
3393
+ }
3394
+ const wrapper = document.createElement("div");
3395
+ wrapper.className = "mb-6 fb-field-wrapper";
3396
+ wrapper.setAttribute("data-field-key", element.key);
3397
+ const label = createLabelContainer(element);
3398
+ wrapper.appendChild(label);
3399
+ const pathKey = pathJoin(ctx.path, element.key);
3400
+ dispatchToRenderer(element, ctx, wrapper, pathKey);
2762
3401
  return wrapper;
2763
3402
  }
2764
3403
  setRenderElement(renderElement2);
@@ -3042,6 +3681,10 @@ var componentRegistry = {
3042
3681
  validate: validateFileElement,
3043
3682
  update: updateFileField
3044
3683
  },
3684
+ colour: {
3685
+ validate: validateColourElement,
3686
+ update: updateColourField
3687
+ },
3045
3688
  container: {
3046
3689
  validate: validateContainerElement,
3047
3690
  update: updateContainerField
@@ -3162,6 +3805,7 @@ var FormBuilderInstance = class {
3162
3805
  }
3163
3806
  this.state.debounceTimer = setTimeout(() => {
3164
3807
  const formData = this.validateForm(true);
3808
+ this.reevaluateConditionalFields();
3165
3809
  if (this.state.config.onChange) {
3166
3810
  this.state.config.onChange(formData);
3167
3811
  }
@@ -3421,6 +4065,8 @@ var FormBuilderInstance = class {
3421
4065
  const block = renderElement2(element, {
3422
4066
  path: "",
3423
4067
  prefill: prefill || {},
4068
+ formData: prefill || {},
4069
+ // Pass complete root data for displayIf evaluation
3424
4070
  state: this.state,
3425
4071
  instance: this
3426
4072
  });
@@ -3465,6 +4111,19 @@ var FormBuilderInstance = class {
3465
4111
  };
3466
4112
  setValidateElement(validateElement2);
3467
4113
  this.state.schema.elements.forEach((element) => {
4114
+ if (element.displayIf) {
4115
+ try {
4116
+ const shouldDisplay = evaluateDisplayCondition(element.displayIf, data);
4117
+ if (!shouldDisplay) {
4118
+ return;
4119
+ }
4120
+ } catch (error) {
4121
+ console.error(
4122
+ `Error evaluating displayIf for field "${element.key}" during validation:`,
4123
+ error
4124
+ );
4125
+ }
4126
+ }
3468
4127
  if (element.hidden) {
3469
4128
  data[element.key] = element.default !== void 0 ? element.default : null;
3470
4129
  } else {
@@ -3621,6 +4280,71 @@ var FormBuilderInstance = class {
3621
4280
  );
3622
4281
  }
3623
4282
  }
4283
+ /**
4284
+ * Re-evaluate all conditional fields (displayIf) based on current form data
4285
+ * This is called automatically when form data changes (via onChange events)
4286
+ */
4287
+ reevaluateConditionalFields() {
4288
+ if (!this.state.schema || !this.state.formRoot) return;
4289
+ const formData = this.validateForm(true).data;
4290
+ const checkElements = (elements, currentPath) => {
4291
+ elements.forEach((element) => {
4292
+ const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
4293
+ if (element.displayIf) {
4294
+ const fieldWrappers = this.state.formRoot.querySelectorAll(
4295
+ `[data-field-key="${element.key}"]`
4296
+ );
4297
+ fieldWrappers.forEach((wrapper) => {
4298
+ var _a, _b;
4299
+ try {
4300
+ const shouldDisplay = evaluateDisplayCondition(
4301
+ element.displayIf,
4302
+ formData
4303
+ // Use complete formData for condition evaluation
4304
+ );
4305
+ const isCurrentlyHidden = wrapper.getAttribute("data-conditionally-hidden") === "true";
4306
+ if (shouldDisplay && isCurrentlyHidden) {
4307
+ const newWrapper = renderElement2(element, {
4308
+ path: fullPath,
4309
+ // Use accumulated path
4310
+ prefill: formData,
4311
+ // Use complete formData for root-level elements
4312
+ formData,
4313
+ // Pass complete formData for displayIf evaluation
4314
+ state: this.state,
4315
+ instance: this
4316
+ });
4317
+ (_a = wrapper.parentNode) == null ? void 0 : _a.replaceChild(newWrapper, wrapper);
4318
+ } else if (!shouldDisplay && !isCurrentlyHidden) {
4319
+ const hiddenWrapper = document.createElement("div");
4320
+ hiddenWrapper.className = "fb-field-wrapper-hidden";
4321
+ hiddenWrapper.style.display = "none";
4322
+ hiddenWrapper.setAttribute("data-field-key", element.key);
4323
+ hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
4324
+ (_b = wrapper.parentNode) == null ? void 0 : _b.replaceChild(hiddenWrapper, wrapper);
4325
+ }
4326
+ } catch (error) {
4327
+ console.error(
4328
+ `Error re-evaluating displayIf for field "${element.key}":`,
4329
+ error
4330
+ );
4331
+ }
4332
+ });
4333
+ }
4334
+ if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
4335
+ const containerData = formData == null ? void 0 : formData[element.key];
4336
+ if (Array.isArray(containerData)) {
4337
+ containerData.forEach((_, index) => {
4338
+ checkElements(element.elements, `${fullPath}[${index}]`);
4339
+ });
4340
+ } else {
4341
+ checkElements(element.elements, fullPath);
4342
+ }
4343
+ }
4344
+ });
4345
+ };
4346
+ checkElements(this.state.schema.elements, "");
4347
+ }
3624
4348
  /**
3625
4349
  * Destroy instance and clean up resources
3626
4350
  */