@dmitryvim/form-builder 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,6 +93,43 @@ function validateSchema(schema) {
93
93
  validateElements(element.elements, `${elementPath}.elements`);
94
94
  }
95
95
  if (element.type === "container" && element.elements) {
96
+ if ("columns" in element && element.columns !== void 0) {
97
+ const columns = element.columns;
98
+ const validColumns = [1, 2, 3, 4];
99
+ if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
100
+ errors.push(
101
+ `${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
102
+ );
103
+ }
104
+ }
105
+ if ("prefillHints" in element && element.prefillHints) {
106
+ const prefillHints = element.prefillHints;
107
+ if (Array.isArray(prefillHints)) {
108
+ prefillHints.forEach((hint, hintIndex) => {
109
+ if (!hint.label || typeof hint.label !== "string") {
110
+ errors.push(
111
+ `${elementPath}: prefillHints[${hintIndex}] must have a 'label' property of type string`
112
+ );
113
+ }
114
+ if (!hint.values || typeof hint.values !== "object") {
115
+ errors.push(
116
+ `${elementPath}: prefillHints[${hintIndex}] must have a 'values' property of type object`
117
+ );
118
+ } else {
119
+ for (const fieldKey in hint.values) {
120
+ const fieldExists = element.elements.some(
121
+ (childElement) => childElement.key === fieldKey
122
+ );
123
+ if (!fieldExists) {
124
+ errors.push(
125
+ `container "${element.key}": prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
126
+ );
127
+ }
128
+ }
129
+ }
130
+ });
131
+ }
132
+ }
96
133
  validateElements(element.elements, `${elementPath}.elements`);
97
134
  }
98
135
  if (element.type === "select" && element.options) {
@@ -1243,6 +1280,175 @@ function t(key, state) {
1243
1280
  }
1244
1281
 
1245
1282
  // src/components/file.ts
1283
+ function renderLocalImagePreview(container, file, fileName) {
1284
+ const img = document.createElement("img");
1285
+ img.className = "w-full h-full object-contain";
1286
+ img.alt = fileName || "Preview";
1287
+ const reader = new FileReader();
1288
+ reader.onload = (e) => {
1289
+ var _a;
1290
+ img.src = ((_a = e.target) == null ? void 0 : _a.result) || "";
1291
+ };
1292
+ reader.readAsDataURL(file);
1293
+ container.appendChild(img);
1294
+ }
1295
+ function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
1296
+ const videoUrl = URL.createObjectURL(file);
1297
+ container.onclick = null;
1298
+ const newContainer = container.cloneNode(false);
1299
+ if (container.parentNode) {
1300
+ container.parentNode.replaceChild(newContainer, container);
1301
+ }
1302
+ newContainer.innerHTML = `
1303
+ <div class="relative group h-full">
1304
+ <video class="w-full h-full object-contain" controls preload="auto" muted>
1305
+ <source src="${videoUrl}" type="${videoType}">
1306
+ Your browser does not support the video tag.
1307
+ </video>
1308
+ <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
1309
+ <button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
1310
+ ${t("removeElement", state)}
1311
+ </button>
1312
+ <button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
1313
+ Change
1314
+ </button>
1315
+ </div>
1316
+ </div>
1317
+ `;
1318
+ attachVideoButtonHandlers(newContainer, resourceId, state, deps);
1319
+ return newContainer;
1320
+ }
1321
+ function attachVideoButtonHandlers(container, resourceId, state, deps) {
1322
+ const changeBtn = container.querySelector(".change-file-btn");
1323
+ if (changeBtn) {
1324
+ changeBtn.onclick = (e) => {
1325
+ e.stopPropagation();
1326
+ if (deps == null ? void 0 : deps.picker) {
1327
+ deps.picker.click();
1328
+ }
1329
+ };
1330
+ }
1331
+ const deleteBtn = container.querySelector(".delete-file-btn");
1332
+ if (deleteBtn) {
1333
+ deleteBtn.onclick = (e) => {
1334
+ e.stopPropagation();
1335
+ handleVideoDelete(container, resourceId, state, deps);
1336
+ };
1337
+ }
1338
+ }
1339
+ function handleVideoDelete(container, resourceId, state, deps) {
1340
+ var _a;
1341
+ state.resourceIndex.delete(resourceId);
1342
+ const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1343
+ 'input[type="hidden"]'
1344
+ );
1345
+ if (hiddenInput) {
1346
+ hiddenInput.value = "";
1347
+ }
1348
+ if (deps == null ? void 0 : deps.fileUploadHandler) {
1349
+ container.onclick = deps.fileUploadHandler;
1350
+ }
1351
+ if (deps == null ? void 0 : deps.dragHandler) {
1352
+ setupDragAndDrop(container, deps.dragHandler);
1353
+ }
1354
+ container.innerHTML = `
1355
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1356
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1357
+ <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"/>
1358
+ </svg>
1359
+ <div class="text-sm text-center">${t("clickDragText", state)}</div>
1360
+ </div>
1361
+ `;
1362
+ }
1363
+ function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
1364
+ const video = document.createElement("video");
1365
+ video.className = "w-full h-full object-contain";
1366
+ video.controls = true;
1367
+ video.preload = "metadata";
1368
+ video.muted = true;
1369
+ const source = document.createElement("source");
1370
+ source.src = thumbnailUrl;
1371
+ source.type = videoType;
1372
+ video.appendChild(source);
1373
+ video.appendChild(document.createTextNode("Your browser does not support the video tag."));
1374
+ container.appendChild(video);
1375
+ }
1376
+ function renderDeleteButton(container, resourceId, state) {
1377
+ addDeleteButton(container, state, () => {
1378
+ var _a;
1379
+ state.resourceIndex.delete(resourceId);
1380
+ const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1381
+ 'input[type="hidden"]'
1382
+ );
1383
+ if (hiddenInput) {
1384
+ hiddenInput.value = "";
1385
+ }
1386
+ container.innerHTML = `
1387
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1388
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1389
+ <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"/>
1390
+ </svg>
1391
+ <div class="text-sm text-center">${t("clickDragText", state)}</div>
1392
+ </div>
1393
+ `;
1394
+ });
1395
+ }
1396
+ async function renderLocalFilePreview(container, meta, fileName, resourceId, isReadonly, state, deps) {
1397
+ if (!meta.file || !(meta.file instanceof File)) {
1398
+ return;
1399
+ }
1400
+ if (meta.type && meta.type.startsWith("image/")) {
1401
+ renderLocalImagePreview(container, meta.file, fileName);
1402
+ } else if (meta.type && meta.type.startsWith("video/")) {
1403
+ const newContainer = renderLocalVideoPreview(
1404
+ container,
1405
+ meta.file,
1406
+ meta.type,
1407
+ resourceId,
1408
+ state,
1409
+ deps
1410
+ );
1411
+ container = newContainer;
1412
+ } else {
1413
+ 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>`;
1414
+ }
1415
+ if (!isReadonly && !(meta.type && meta.type.startsWith("video/"))) {
1416
+ renderDeleteButton(container, resourceId, state);
1417
+ }
1418
+ }
1419
+ async function renderUploadedFilePreview(container, resourceId, fileName, meta, state) {
1420
+ if (!state.config.getThumbnail) {
1421
+ setEmptyFileContainer(container, state);
1422
+ return;
1423
+ }
1424
+ try {
1425
+ const thumbnailUrl = await state.config.getThumbnail(resourceId);
1426
+ if (thumbnailUrl) {
1427
+ clear(container);
1428
+ if (meta && meta.type && meta.type.startsWith("video/")) {
1429
+ renderUploadedVideoPreview(container, thumbnailUrl, meta.type);
1430
+ } else {
1431
+ const img = document.createElement("img");
1432
+ img.className = "w-full h-full object-contain";
1433
+ img.alt = fileName || "Preview";
1434
+ img.src = thumbnailUrl;
1435
+ container.appendChild(img);
1436
+ }
1437
+ } else {
1438
+ setEmptyFileContainer(container, state);
1439
+ }
1440
+ } catch (error) {
1441
+ console.error("Failed to get thumbnail:", error);
1442
+ container.innerHTML = `
1443
+ <div class="flex flex-col items-center justify-center h-full text-gray-400">
1444
+ <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1445
+ <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"/>
1446
+ </svg>
1447
+ <div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
1448
+ </div>
1449
+ `;
1450
+ }
1451
+ }
1246
1452
  async function renderFilePreview(container, resourceId, state, options = {}) {
1247
1453
  const { fileName = "", isReadonly = false, deps = null } = options;
1248
1454
  if (!isReadonly && deps && (!deps.picker || !deps.fileUploadHandler || !deps.dragHandler)) {
@@ -1254,144 +1460,19 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
1254
1460
  if (isReadonly) {
1255
1461
  container.classList.add("cursor-pointer");
1256
1462
  }
1257
- const img = document.createElement("img");
1258
- img.className = "w-full h-full object-contain";
1259
- img.alt = fileName || "Preview";
1260
1463
  const meta = state.resourceIndex.get(resourceId);
1261
1464
  if (meta && meta.file && meta.file instanceof File) {
1262
- if (meta.type && meta.type.startsWith("image/")) {
1263
- const reader = new FileReader();
1264
- reader.onload = (e) => {
1265
- var _a;
1266
- img.src = ((_a = e.target) == null ? void 0 : _a.result) || "";
1267
- };
1268
- reader.readAsDataURL(meta.file);
1269
- container.appendChild(img);
1270
- } else if (meta.type && meta.type.startsWith("video/")) {
1271
- const videoUrl = URL.createObjectURL(meta.file);
1272
- container.onclick = null;
1273
- const newContainer = container.cloneNode(false);
1274
- if (container.parentNode) {
1275
- container.parentNode.replaceChild(newContainer, container);
1276
- }
1277
- container = newContainer;
1278
- container.innerHTML = `
1279
- <div class="relative group h-full">
1280
- <video class="w-full h-full object-contain" controls preload="auto" muted>
1281
- <source src="${videoUrl}" type="${meta.type}">
1282
- Your browser does not support the video tag.
1283
- </video>
1284
- <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
1285
- <button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
1286
- ${t("removeElement", state)}
1287
- </button>
1288
- <button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
1289
- Change
1290
- </button>
1291
- </div>
1292
- </div>
1293
- `;
1294
- const changeBtn = container.querySelector(
1295
- ".change-file-btn"
1296
- );
1297
- if (changeBtn) {
1298
- changeBtn.onclick = (e) => {
1299
- e.stopPropagation();
1300
- if (deps == null ? void 0 : deps.picker) {
1301
- deps.picker.click();
1302
- }
1303
- };
1304
- }
1305
- const deleteBtn = container.querySelector(
1306
- ".delete-file-btn"
1307
- );
1308
- if (deleteBtn) {
1309
- deleteBtn.onclick = (e) => {
1310
- var _a;
1311
- e.stopPropagation();
1312
- state.resourceIndex.delete(resourceId);
1313
- const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1314
- 'input[type="hidden"]'
1315
- );
1316
- if (hiddenInput) {
1317
- hiddenInput.value = "";
1318
- }
1319
- if (deps == null ? void 0 : deps.fileUploadHandler) {
1320
- container.onclick = deps.fileUploadHandler;
1321
- }
1322
- if (deps == null ? void 0 : deps.dragHandler) {
1323
- setupDragAndDrop(container, deps.dragHandler);
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
- } else {
1336
- 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>`;
1337
- }
1338
- if (!isReadonly && !(meta && meta.type && meta.type.startsWith("video/"))) {
1339
- addDeleteButton(container, state, () => {
1340
- var _a;
1341
- state.resourceIndex.delete(resourceId);
1342
- const hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
1343
- 'input[type="hidden"]'
1344
- );
1345
- if (hiddenInput) {
1346
- hiddenInput.value = "";
1347
- }
1348
- container.innerHTML = `
1349
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1350
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1351
- <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"/>
1352
- </svg>
1353
- <div class="text-sm text-center">${t("clickDragText", state)}</div>
1354
- </div>
1355
- `;
1356
- });
1357
- }
1358
- } else if (state.config.getThumbnail) {
1359
- try {
1360
- const thumbnailUrl = await state.config.getThumbnail(resourceId);
1361
- if (thumbnailUrl) {
1362
- clear(container);
1363
- if (meta && meta.type && meta.type.startsWith("video/")) {
1364
- const video = document.createElement("video");
1365
- video.className = "w-full h-full object-contain";
1366
- video.controls = true;
1367
- video.preload = "metadata";
1368
- video.muted = true;
1369
- const source = document.createElement("source");
1370
- source.src = thumbnailUrl;
1371
- source.type = meta.type;
1372
- video.appendChild(source);
1373
- video.appendChild(document.createTextNode("Your browser does not support the video tag."));
1374
- container.appendChild(video);
1375
- } else {
1376
- img.src = thumbnailUrl;
1377
- container.appendChild(img);
1378
- }
1379
- } else {
1380
- setEmptyFileContainer(container, state);
1381
- }
1382
- } catch (error) {
1383
- console.error("Failed to get thumbnail:", error);
1384
- container.innerHTML = `
1385
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
1386
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
1387
- <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"/>
1388
- </svg>
1389
- <div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
1390
- </div>
1391
- `;
1392
- }
1465
+ await renderLocalFilePreview(
1466
+ container,
1467
+ meta,
1468
+ fileName,
1469
+ resourceId,
1470
+ isReadonly,
1471
+ state,
1472
+ deps
1473
+ );
1393
1474
  } else {
1394
- setEmptyFileContainer(container, state);
1475
+ await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
1395
1476
  }
1396
1477
  }
1397
1478
  async function renderFilePreviewReadonly(resourceId, state, fileName) {
@@ -2306,113 +2387,1130 @@ function updateFileField(element, fieldPath, value, context) {
2306
2387
  }
2307
2388
  }
2308
2389
 
2309
- // src/components/container.ts
2310
- var renderElementFunc = null;
2311
- function setRenderElement(fn) {
2312
- renderElementFunc = fn;
2390
+ // src/components/colour.ts
2391
+ function normalizeColourValue(value) {
2392
+ if (!value) return "#000000";
2393
+ return value.toUpperCase();
2313
2394
  }
2314
- function renderElement(element, ctx) {
2315
- if (!renderElementFunc) {
2316
- throw new Error(
2317
- "renderElement not initialized. Import from components/index.ts"
2318
- );
2395
+ function isValidHexColour(value) {
2396
+ return /^#[0-9A-F]{6}$/i.test(value) || /^#[0-9A-F]{3}$/i.test(value);
2397
+ }
2398
+ function expandHexColour(value) {
2399
+ if (/^#[0-9A-F]{3}$/i.test(value)) {
2400
+ const r = value[1];
2401
+ const g = value[2];
2402
+ const b = value[3];
2403
+ return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
2319
2404
  }
2320
- return renderElementFunc(element, ctx);
2405
+ return value.toUpperCase();
2321
2406
  }
2322
- function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2323
- var _a, _b;
2324
- const containerWrap = document.createElement("div");
2325
- containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
2326
- containerWrap.setAttribute("data-container", pathKey);
2327
- const header = document.createElement("div");
2328
- header.className = "flex justify-between items-center mb-4";
2329
- const left = document.createElement("div");
2330
- left.className = "flex-1";
2331
- const itemsWrap = document.createElement("div");
2332
- itemsWrap.className = "space-y-4";
2333
- containerWrap.appendChild(header);
2334
- header.appendChild(left);
2335
- const subCtx = {
2336
- path: pathJoin(ctx.path, element.key),
2337
- prefill: ((_a = ctx.prefill) == null ? void 0 : _a[element.key]) || {},
2338
- // Sliced data for value population
2339
- formData: (_b = ctx.formData) != null ? _b : ctx.prefill,
2340
- // Complete root data for displayIf evaluation
2341
- state: ctx.state
2342
- };
2343
- element.elements.forEach((child) => {
2344
- if (!child.hidden) {
2345
- itemsWrap.appendChild(renderElement(child, subCtx));
2407
+ function createReadonlyColourUI(value) {
2408
+ const container = document.createElement("div");
2409
+ container.className = "flex items-center gap-2";
2410
+ const normalizedValue = normalizeColourValue(value);
2411
+ const swatch = document.createElement("div");
2412
+ swatch.style.cssText = `
2413
+ width: 32px;
2414
+ height: 32px;
2415
+ border-radius: var(--fb-border-radius);
2416
+ border: var(--fb-border-width) solid var(--fb-border-color);
2417
+ background-color: ${normalizedValue};
2418
+ `;
2419
+ const hexText = document.createElement("span");
2420
+ hexText.style.cssText = `
2421
+ font-size: var(--fb-font-size);
2422
+ color: var(--fb-text-color);
2423
+ font-family: var(--fb-font-family-mono, monospace);
2424
+ `;
2425
+ hexText.textContent = normalizedValue;
2426
+ container.appendChild(swatch);
2427
+ container.appendChild(hexText);
2428
+ return container;
2429
+ }
2430
+ function createEditColourUI(value, pathKey, ctx) {
2431
+ const normalizedValue = normalizeColourValue(value);
2432
+ const pickerWrapper = document.createElement("div");
2433
+ pickerWrapper.className = "colour-picker-wrapper";
2434
+ pickerWrapper.style.cssText = `
2435
+ display: flex;
2436
+ align-items: center;
2437
+ gap: 8px;
2438
+ `;
2439
+ const swatch = document.createElement("div");
2440
+ swatch.className = "colour-swatch";
2441
+ swatch.style.cssText = `
2442
+ width: 40px;
2443
+ height: 40px;
2444
+ border-radius: var(--fb-border-radius);
2445
+ border: var(--fb-border-width) solid var(--fb-border-color);
2446
+ background-color: ${normalizedValue};
2447
+ cursor: pointer;
2448
+ transition: border-color var(--fb-transition-duration) ease-in-out;
2449
+ flex-shrink: 0;
2450
+ `;
2451
+ const hexInput = document.createElement("input");
2452
+ hexInput.type = "text";
2453
+ hexInput.className = "colour-hex-input";
2454
+ hexInput.name = pathKey;
2455
+ hexInput.value = normalizedValue;
2456
+ hexInput.placeholder = "#000000";
2457
+ hexInput.style.cssText = `
2458
+ width: 100px;
2459
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
2460
+ border: var(--fb-border-width) solid var(--fb-border-color);
2461
+ border-radius: var(--fb-border-radius);
2462
+ background-color: var(--fb-background-color);
2463
+ color: var(--fb-text-color);
2464
+ font-size: var(--fb-font-size);
2465
+ font-family: var(--fb-font-family-mono, monospace);
2466
+ transition: all var(--fb-transition-duration) ease-in-out;
2467
+ `;
2468
+ const colourInput = document.createElement("input");
2469
+ colourInput.type = "color";
2470
+ colourInput.className = "colour-picker-hidden";
2471
+ colourInput.value = normalizedValue.toLowerCase();
2472
+ colourInput.style.cssText = `
2473
+ position: absolute;
2474
+ opacity: 0;
2475
+ pointer-events: none;
2476
+ `;
2477
+ hexInput.addEventListener("input", () => {
2478
+ const inputValue = hexInput.value.trim();
2479
+ if (isValidHexColour(inputValue)) {
2480
+ const expanded = expandHexColour(inputValue);
2481
+ swatch.style.backgroundColor = expanded;
2482
+ colourInput.value = expanded.toLowerCase();
2483
+ hexInput.classList.remove("invalid");
2484
+ if (ctx.instance) {
2485
+ ctx.instance.triggerOnChange(pathKey, expanded);
2486
+ }
2487
+ } else {
2488
+ hexInput.classList.add("invalid");
2346
2489
  }
2347
2490
  });
2348
- containerWrap.appendChild(itemsWrap);
2349
- left.innerHTML = `<span>${element.label || element.key}</span>`;
2350
- wrapper.appendChild(containerWrap);
2491
+ hexInput.addEventListener("blur", () => {
2492
+ const inputValue = hexInput.value.trim();
2493
+ if (isValidHexColour(inputValue)) {
2494
+ const expanded = expandHexColour(inputValue);
2495
+ hexInput.value = expanded;
2496
+ swatch.style.backgroundColor = expanded;
2497
+ colourInput.value = expanded.toLowerCase();
2498
+ hexInput.classList.remove("invalid");
2499
+ }
2500
+ });
2501
+ colourInput.addEventListener("change", () => {
2502
+ const normalized = normalizeColourValue(colourInput.value);
2503
+ hexInput.value = normalized;
2504
+ swatch.style.backgroundColor = normalized;
2505
+ if (ctx.instance) {
2506
+ ctx.instance.triggerOnChange(pathKey, normalized);
2507
+ }
2508
+ });
2509
+ swatch.addEventListener("click", () => {
2510
+ colourInput.click();
2511
+ });
2512
+ swatch.addEventListener("mouseenter", () => {
2513
+ swatch.style.borderColor = "var(--fb-border-hover-color)";
2514
+ });
2515
+ swatch.addEventListener("mouseleave", () => {
2516
+ swatch.style.borderColor = "var(--fb-border-color)";
2517
+ });
2518
+ hexInput.addEventListener("focus", () => {
2519
+ hexInput.style.borderColor = "var(--fb-border-focus-color)";
2520
+ hexInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
2521
+ hexInput.style.outlineOffset = "0";
2522
+ });
2523
+ hexInput.addEventListener("blur", () => {
2524
+ hexInput.style.borderColor = "var(--fb-border-color)";
2525
+ hexInput.style.outline = "none";
2526
+ });
2527
+ hexInput.addEventListener("mouseenter", () => {
2528
+ if (document.activeElement !== hexInput) {
2529
+ hexInput.style.borderColor = "var(--fb-border-hover-color)";
2530
+ }
2531
+ });
2532
+ hexInput.addEventListener("mouseleave", () => {
2533
+ if (document.activeElement !== hexInput) {
2534
+ hexInput.style.borderColor = "var(--fb-border-color)";
2535
+ }
2536
+ });
2537
+ pickerWrapper.appendChild(swatch);
2538
+ pickerWrapper.appendChild(hexInput);
2539
+ pickerWrapper.appendChild(colourInput);
2540
+ return pickerWrapper;
2351
2541
  }
2352
- function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2353
- var _a, _b, _c, _d;
2542
+ function renderColourElement(element, ctx, wrapper, pathKey) {
2354
2543
  const state = ctx.state;
2355
- const containerWrap = document.createElement("div");
2356
- containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
2357
- const header = document.createElement("div");
2358
- header.className = "flex justify-between items-center mb-4";
2359
- const left = document.createElement("div");
2360
- left.className = "flex-1";
2361
- const right = document.createElement("div");
2362
- right.className = "flex gap-2";
2363
- const itemsWrap = document.createElement("div");
2364
- itemsWrap.className = "space-y-4";
2365
- containerWrap.appendChild(header);
2366
- header.appendChild(left);
2367
- if (!state.config.readonly) {
2368
- header.appendChild(right);
2544
+ const initialValue = ctx.prefill[element.key] || element.default || "#000000";
2545
+ if (state.config.readonly) {
2546
+ const readonlyUI = createReadonlyColourUI(initialValue);
2547
+ wrapper.appendChild(readonlyUI);
2548
+ } else {
2549
+ const editUI = createEditColourUI(initialValue, pathKey, ctx);
2550
+ wrapper.appendChild(editUI);
2369
2551
  }
2370
- const min = (_a = element.minCount) != null ? _a : 0;
2371
- const max = (_b = element.maxCount) != null ? _b : Infinity;
2372
- const pre = Array.isArray((_c = ctx.prefill) == null ? void 0 : _c[element.key]) ? ctx.prefill[element.key] : null;
2373
- const countItems = () => itemsWrap.querySelectorAll(":scope > .containerItem").length;
2374
- const createAddButton = () => {
2375
- const add = document.createElement("button");
2376
- add.type = "button";
2377
- add.className = "px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors";
2378
- add.textContent = t("addElement", state);
2379
- add.onclick = () => {
2380
- var _a2;
2381
- if (countItems() < max) {
2382
- const idx = countItems();
2383
- const subCtx = {
2384
- state: ctx.state,
2385
- path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2386
- prefill: {},
2387
- formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
2388
- // Complete root data for displayIf
2389
- };
2390
- const item = document.createElement("div");
2391
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2392
- item.setAttribute("data-container-item", `${element.key}[${idx}]`);
2393
- element.elements.forEach((child) => {
2394
- if (!child.hidden) {
2395
- item.appendChild(renderElement(child, subCtx));
2396
- }
2397
- });
2398
- if (!state.config.readonly) {
2399
- const rem = document.createElement("button");
2400
- rem.type = "button";
2401
- rem.className = "absolute top-2 right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 transition-colors";
2402
- rem.textContent = "\xD7";
2403
- rem.onclick = () => {
2404
- item.remove();
2405
- updateAddButton();
2406
- };
2407
- item.style.position = "relative";
2408
- item.appendChild(rem);
2409
- }
2410
- itemsWrap.appendChild(item);
2411
- updateAddButton();
2412
- }
2413
- };
2414
- return add;
2415
- };
2552
+ const colourHint = document.createElement("p");
2553
+ colourHint.className = "mt-1";
2554
+ colourHint.style.cssText = `
2555
+ font-size: var(--fb-font-size-small);
2556
+ color: var(--fb-text-secondary-color);
2557
+ `;
2558
+ colourHint.textContent = makeFieldHint(element);
2559
+ wrapper.appendChild(colourHint);
2560
+ }
2561
+ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
2562
+ var _a, _b;
2563
+ const state = ctx.state;
2564
+ const prefillValues = ctx.prefill[element.key] || [];
2565
+ const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
2566
+ const minCount = (_a = element.minCount) != null ? _a : 1;
2567
+ const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
2568
+ while (values.length < minCount) {
2569
+ values.push(element.default || "#000000");
2570
+ }
2571
+ const container = document.createElement("div");
2572
+ container.className = "space-y-2";
2573
+ wrapper.appendChild(container);
2574
+ function updateIndices() {
2575
+ const items = container.querySelectorAll(".multiple-colour-item");
2576
+ items.forEach((item, index) => {
2577
+ const input = item.querySelector("input");
2578
+ if (input) {
2579
+ input.name = `${pathKey}[${index}]`;
2580
+ }
2581
+ });
2582
+ }
2583
+ function addColourItem(value = "#000000", index = -1) {
2584
+ const itemWrapper = document.createElement("div");
2585
+ itemWrapper.className = "multiple-colour-item flex items-center gap-2";
2586
+ if (state.config.readonly) {
2587
+ const readonlyUI = createReadonlyColourUI(value);
2588
+ while (readonlyUI.firstChild) {
2589
+ itemWrapper.appendChild(readonlyUI.firstChild);
2590
+ }
2591
+ } else {
2592
+ const tempPathKey = `${pathKey}[${container.children.length}]`;
2593
+ const editUI = createEditColourUI(value, tempPathKey, ctx);
2594
+ editUI.style.flex = "1";
2595
+ itemWrapper.appendChild(editUI);
2596
+ }
2597
+ if (index === -1) {
2598
+ container.appendChild(itemWrapper);
2599
+ } else {
2600
+ container.insertBefore(itemWrapper, container.children[index]);
2601
+ }
2602
+ updateIndices();
2603
+ return itemWrapper;
2604
+ }
2605
+ function updateRemoveButtons() {
2606
+ if (state.config.readonly) return;
2607
+ const items = container.querySelectorAll(".multiple-colour-item");
2608
+ const currentCount = items.length;
2609
+ items.forEach((item) => {
2610
+ let removeBtn = item.querySelector(
2611
+ ".remove-item-btn"
2612
+ );
2613
+ if (!removeBtn) {
2614
+ removeBtn = document.createElement("button");
2615
+ removeBtn.type = "button";
2616
+ removeBtn.className = "remove-item-btn px-2 py-1 rounded";
2617
+ removeBtn.style.cssText = `
2618
+ color: var(--fb-error-color);
2619
+ background-color: transparent;
2620
+ transition: background-color var(--fb-transition-duration);
2621
+ `;
2622
+ removeBtn.innerHTML = "\u2715";
2623
+ removeBtn.addEventListener("mouseenter", () => {
2624
+ removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2625
+ });
2626
+ removeBtn.addEventListener("mouseleave", () => {
2627
+ removeBtn.style.backgroundColor = "transparent";
2628
+ });
2629
+ removeBtn.onclick = () => {
2630
+ const currentIndex = Array.from(container.children).indexOf(
2631
+ item
2632
+ );
2633
+ if (container.children.length > minCount) {
2634
+ values.splice(currentIndex, 1);
2635
+ item.remove();
2636
+ updateIndices();
2637
+ updateAddButton();
2638
+ updateRemoveButtons();
2639
+ }
2640
+ };
2641
+ item.appendChild(removeBtn);
2642
+ }
2643
+ const disabled = currentCount <= minCount;
2644
+ removeBtn.disabled = disabled;
2645
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
2646
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
2647
+ });
2648
+ }
2649
+ function updateAddButton() {
2650
+ const existingAddBtn = wrapper.querySelector(".add-colour-btn");
2651
+ if (existingAddBtn) existingAddBtn.remove();
2652
+ if (!state.config.readonly && values.length < maxCount) {
2653
+ const addBtn = document.createElement("button");
2654
+ addBtn.type = "button";
2655
+ addBtn.className = "add-colour-btn mt-2 px-3 py-1 rounded";
2656
+ addBtn.style.cssText = `
2657
+ color: var(--fb-primary-color);
2658
+ border: var(--fb-border-width) solid var(--fb-primary-color);
2659
+ background-color: transparent;
2660
+ font-size: var(--fb-font-size);
2661
+ transition: all var(--fb-transition-duration);
2662
+ `;
2663
+ addBtn.textContent = `+ Add ${element.label || "Colour"}`;
2664
+ addBtn.addEventListener("mouseenter", () => {
2665
+ addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
2666
+ });
2667
+ addBtn.addEventListener("mouseleave", () => {
2668
+ addBtn.style.backgroundColor = "transparent";
2669
+ });
2670
+ addBtn.onclick = () => {
2671
+ const defaultColour = element.default || "#000000";
2672
+ values.push(defaultColour);
2673
+ addColourItem(defaultColour);
2674
+ updateAddButton();
2675
+ updateRemoveButtons();
2676
+ };
2677
+ wrapper.appendChild(addBtn);
2678
+ }
2679
+ }
2680
+ values.forEach((value) => addColourItem(value));
2681
+ updateAddButton();
2682
+ updateRemoveButtons();
2683
+ const hint = document.createElement("p");
2684
+ hint.className = "mt-1";
2685
+ hint.style.cssText = `
2686
+ font-size: var(--fb-font-size-small);
2687
+ color: var(--fb-text-secondary-color);
2688
+ `;
2689
+ hint.textContent = makeFieldHint(element);
2690
+ wrapper.appendChild(hint);
2691
+ }
2692
+ function validateColourElement(element, key, context) {
2693
+ var _a, _b, _c;
2694
+ const errors = [];
2695
+ const { scopeRoot, skipValidation } = context;
2696
+ const markValidity = (input, errorMessage) => {
2697
+ var _a2, _b2;
2698
+ if (!input) return;
2699
+ const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
2700
+ let errorElement = document.getElementById(errorId);
2701
+ if (errorMessage) {
2702
+ input.classList.add("invalid");
2703
+ input.title = errorMessage;
2704
+ if (!errorElement) {
2705
+ errorElement = document.createElement("div");
2706
+ errorElement.id = errorId;
2707
+ errorElement.className = "error-message";
2708
+ errorElement.style.cssText = `
2709
+ color: var(--fb-error-color);
2710
+ font-size: var(--fb-font-size-small);
2711
+ margin-top: 0.25rem;
2712
+ `;
2713
+ if (input.nextSibling) {
2714
+ (_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
2715
+ } else {
2716
+ (_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
2717
+ }
2718
+ }
2719
+ errorElement.textContent = errorMessage;
2720
+ errorElement.style.display = "block";
2721
+ } else {
2722
+ input.classList.remove("invalid");
2723
+ input.title = "";
2724
+ if (errorElement) {
2725
+ errorElement.remove();
2726
+ }
2727
+ }
2728
+ };
2729
+ const validateColourValue = (input, val, fieldKey) => {
2730
+ if (!val) {
2731
+ if (!skipValidation && element.required) {
2732
+ errors.push(`${fieldKey}: required`);
2733
+ markValidity(input, "required");
2734
+ return "";
2735
+ }
2736
+ markValidity(input, null);
2737
+ return "";
2738
+ }
2739
+ const normalized = normalizeColourValue(val);
2740
+ if (!skipValidation && !isValidHexColour(normalized)) {
2741
+ errors.push(`${fieldKey}: invalid hex colour format`);
2742
+ markValidity(input, "invalid hex colour format");
2743
+ return val;
2744
+ }
2745
+ markValidity(input, null);
2746
+ return normalized;
2747
+ };
2748
+ if (element.multiple) {
2749
+ const hexInputs = scopeRoot.querySelectorAll(
2750
+ `.colour-hex-input`
2751
+ );
2752
+ const values = [];
2753
+ hexInputs.forEach((input, index) => {
2754
+ var _a2;
2755
+ const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
2756
+ const validated = validateColourValue(input, val, `${key}[${index}]`);
2757
+ values.push(validated);
2758
+ });
2759
+ if (!skipValidation) {
2760
+ const minCount = (_a = element.minCount) != null ? _a : 1;
2761
+ const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
2762
+ const filteredValues = values.filter((v) => v !== "");
2763
+ if (element.required && filteredValues.length === 0) {
2764
+ errors.push(`${key}: required`);
2765
+ }
2766
+ if (filteredValues.length < minCount) {
2767
+ errors.push(`${key}: minimum ${minCount} items required`);
2768
+ }
2769
+ if (filteredValues.length > maxCount) {
2770
+ errors.push(`${key}: maximum ${maxCount} items allowed`);
2771
+ }
2772
+ }
2773
+ return { value: values, errors };
2774
+ } else {
2775
+ const hexInput = scopeRoot.querySelector(
2776
+ `[name="${key}"].colour-hex-input`
2777
+ );
2778
+ const val = (_c = hexInput == null ? void 0 : hexInput.value) != null ? _c : "";
2779
+ if (!skipValidation && element.required && val === "") {
2780
+ errors.push(`${key}: required`);
2781
+ markValidity(hexInput, "required");
2782
+ return { value: "", errors };
2783
+ }
2784
+ const validated = validateColourValue(hexInput, val, key);
2785
+ return { value: validated, errors };
2786
+ }
2787
+ }
2788
+ function updateColourField(element, fieldPath, value, context) {
2789
+ const { scopeRoot } = context;
2790
+ if (element.multiple) {
2791
+ if (!Array.isArray(value)) {
2792
+ console.warn(
2793
+ `updateColourField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
2794
+ );
2795
+ return;
2796
+ }
2797
+ const hexInputs = scopeRoot.querySelectorAll(
2798
+ `.colour-hex-input`
2799
+ );
2800
+ hexInputs.forEach((hexInput, index) => {
2801
+ if (index < value.length) {
2802
+ const normalized = normalizeColourValue(value[index]);
2803
+ hexInput.value = normalized;
2804
+ hexInput.classList.remove("invalid");
2805
+ hexInput.title = "";
2806
+ const wrapper = hexInput.closest(".colour-picker-wrapper");
2807
+ if (wrapper) {
2808
+ const swatch = wrapper.querySelector(".colour-swatch");
2809
+ const colourInput = wrapper.querySelector(".colour-picker-hidden");
2810
+ if (swatch) {
2811
+ swatch.style.backgroundColor = normalized;
2812
+ }
2813
+ if (colourInput) {
2814
+ colourInput.value = normalized.toLowerCase();
2815
+ }
2816
+ }
2817
+ }
2818
+ });
2819
+ if (value.length !== hexInputs.length) {
2820
+ console.warn(
2821
+ `updateColourField: Multiple field "${fieldPath}" has ${hexInputs.length} inputs but received ${value.length} values. Consider re-rendering for add/remove.`
2822
+ );
2823
+ }
2824
+ } else {
2825
+ const hexInput = scopeRoot.querySelector(
2826
+ `[name="${fieldPath}"].colour-hex-input`
2827
+ );
2828
+ if (hexInput) {
2829
+ const normalized = normalizeColourValue(value);
2830
+ hexInput.value = normalized;
2831
+ hexInput.classList.remove("invalid");
2832
+ hexInput.title = "";
2833
+ const wrapper = hexInput.closest(".colour-picker-wrapper");
2834
+ if (wrapper) {
2835
+ const swatch = wrapper.querySelector(".colour-swatch");
2836
+ const colourInput = wrapper.querySelector(".colour-picker-hidden");
2837
+ if (swatch) {
2838
+ swatch.style.backgroundColor = normalized;
2839
+ }
2840
+ if (colourInput) {
2841
+ colourInput.value = normalized.toLowerCase();
2842
+ }
2843
+ }
2844
+ }
2845
+ }
2846
+ }
2847
+
2848
+ // src/components/slider.ts
2849
+ function positionToExponential(position, min, max) {
2850
+ if (min <= 0) {
2851
+ throw new Error("Exponential scale requires min > 0");
2852
+ }
2853
+ const logMin = Math.log(min);
2854
+ const logMax = Math.log(max);
2855
+ return Math.exp(logMin + position * (logMax - logMin));
2856
+ }
2857
+ function exponentialToPosition(value, min, max) {
2858
+ if (min <= 0) {
2859
+ throw new Error("Exponential scale requires min > 0");
2860
+ }
2861
+ const logMin = Math.log(min);
2862
+ const logMax = Math.log(max);
2863
+ const logValue = Math.log(value);
2864
+ return (logValue - logMin) / (logMax - logMin);
2865
+ }
2866
+ function alignToStep(value, step) {
2867
+ return Math.round(value / step) * step;
2868
+ }
2869
+ function createSliderUI(value, pathKey, element, ctx, readonly) {
2870
+ var _a;
2871
+ const container = document.createElement("div");
2872
+ container.className = "slider-container space-y-2";
2873
+ const sliderRow = document.createElement("div");
2874
+ sliderRow.className = "flex items-center gap-3";
2875
+ const slider = document.createElement("input");
2876
+ slider.type = "range";
2877
+ slider.name = pathKey;
2878
+ slider.className = "slider-input flex-1";
2879
+ slider.disabled = readonly;
2880
+ const scale = element.scale || "linear";
2881
+ const min = element.min;
2882
+ const max = element.max;
2883
+ const step = (_a = element.step) != null ? _a : 1;
2884
+ if (scale === "exponential") {
2885
+ if (min <= 0) {
2886
+ throw new Error(
2887
+ `Slider "${element.key}": exponential scale requires min > 0 (got ${min})`
2888
+ );
2889
+ }
2890
+ slider.min = "0";
2891
+ slider.max = "1000";
2892
+ slider.step = "1";
2893
+ const position = exponentialToPosition(value, min, max);
2894
+ slider.value = (position * 1e3).toString();
2895
+ } else {
2896
+ slider.min = min.toString();
2897
+ slider.max = max.toString();
2898
+ slider.step = step.toString();
2899
+ slider.value = value.toString();
2900
+ }
2901
+ slider.style.cssText = `
2902
+ height: 6px;
2903
+ border-radius: 3px;
2904
+ background: linear-gradient(
2905
+ to right,
2906
+ var(--fb-primary-color) 0%,
2907
+ var(--fb-primary-color) ${(value - min) / (max - min) * 100}%,
2908
+ var(--fb-border-color) ${(value - min) / (max - min) * 100}%,
2909
+ var(--fb-border-color) 100%
2910
+ );
2911
+ outline: none;
2912
+ transition: background 0.1s ease-in-out;
2913
+ cursor: ${readonly ? "not-allowed" : "pointer"};
2914
+ opacity: ${readonly ? "0.6" : "1"};
2915
+ `;
2916
+ const valueDisplay = document.createElement("span");
2917
+ valueDisplay.className = "slider-value";
2918
+ valueDisplay.style.cssText = `
2919
+ min-width: 60px;
2920
+ text-align: right;
2921
+ font-size: var(--fb-font-size);
2922
+ color: var(--fb-text-color);
2923
+ font-family: var(--fb-font-family-mono, monospace);
2924
+ font-weight: 500;
2925
+ `;
2926
+ valueDisplay.textContent = value.toFixed(step < 1 ? 2 : 0);
2927
+ sliderRow.appendChild(slider);
2928
+ sliderRow.appendChild(valueDisplay);
2929
+ container.appendChild(sliderRow);
2930
+ const labelsRow = document.createElement("div");
2931
+ labelsRow.className = "flex justify-between";
2932
+ labelsRow.style.cssText = `
2933
+ font-size: var(--fb-font-size-small);
2934
+ color: var(--fb-text-secondary-color);
2935
+ `;
2936
+ const minLabel = document.createElement("span");
2937
+ minLabel.textContent = min.toString();
2938
+ const maxLabel = document.createElement("span");
2939
+ maxLabel.textContent = max.toString();
2940
+ labelsRow.appendChild(minLabel);
2941
+ labelsRow.appendChild(maxLabel);
2942
+ container.appendChild(labelsRow);
2943
+ if (!readonly) {
2944
+ const updateValue = () => {
2945
+ let displayValue;
2946
+ if (scale === "exponential") {
2947
+ const position = parseFloat(slider.value) / 1e3;
2948
+ displayValue = positionToExponential(position, min, max);
2949
+ displayValue = alignToStep(displayValue, step);
2950
+ displayValue = Math.max(min, Math.min(max, displayValue));
2951
+ } else {
2952
+ displayValue = parseFloat(slider.value);
2953
+ displayValue = alignToStep(displayValue, step);
2954
+ }
2955
+ valueDisplay.textContent = displayValue.toFixed(step < 1 ? 2 : 0);
2956
+ const percentage = (displayValue - min) / (max - min) * 100;
2957
+ slider.style.background = `linear-gradient(
2958
+ to right,
2959
+ var(--fb-primary-color) 0%,
2960
+ var(--fb-primary-color) ${percentage}%,
2961
+ var(--fb-border-color) ${percentage}%,
2962
+ var(--fb-border-color) 100%
2963
+ )`;
2964
+ if (ctx.instance) {
2965
+ ctx.instance.triggerOnChange(pathKey, displayValue);
2966
+ }
2967
+ };
2968
+ slider.addEventListener("input", updateValue);
2969
+ slider.addEventListener("change", updateValue);
2970
+ }
2971
+ return container;
2972
+ }
2973
+ function renderSliderElement(element, ctx, wrapper, pathKey) {
2974
+ var _a;
2975
+ if (element.min === void 0 || element.min === null) {
2976
+ throw new Error(
2977
+ `Slider field "${element.key}" requires "min" property`
2978
+ );
2979
+ }
2980
+ if (element.max === void 0 || element.max === null) {
2981
+ throw new Error(
2982
+ `Slider field "${element.key}" requires "max" property`
2983
+ );
2984
+ }
2985
+ if (element.min >= element.max) {
2986
+ throw new Error(
2987
+ `Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
2988
+ );
2989
+ }
2990
+ const state = ctx.state;
2991
+ const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
2992
+ const initialValue = (_a = ctx.prefill[element.key]) != null ? _a : defaultValue;
2993
+ const sliderUI = createSliderUI(
2994
+ initialValue,
2995
+ pathKey,
2996
+ element,
2997
+ ctx,
2998
+ state.config.readonly
2999
+ );
3000
+ wrapper.appendChild(sliderUI);
3001
+ const hint = document.createElement("p");
3002
+ hint.className = "mt-1";
3003
+ hint.style.cssText = `
3004
+ font-size: var(--fb-font-size-small);
3005
+ color: var(--fb-text-secondary-color);
3006
+ `;
3007
+ hint.textContent = makeFieldHint(element);
3008
+ wrapper.appendChild(hint);
3009
+ }
3010
+ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
3011
+ var _a, _b;
3012
+ if (element.min === void 0 || element.min === null) {
3013
+ throw new Error(
3014
+ `Slider field "${element.key}" requires "min" property`
3015
+ );
3016
+ }
3017
+ if (element.max === void 0 || element.max === null) {
3018
+ throw new Error(
3019
+ `Slider field "${element.key}" requires "max" property`
3020
+ );
3021
+ }
3022
+ if (element.min >= element.max) {
3023
+ throw new Error(
3024
+ `Slider field "${element.key}": min (${element.min}) must be less than max (${element.max})`
3025
+ );
3026
+ }
3027
+ const state = ctx.state;
3028
+ const prefillValues = ctx.prefill[element.key] || [];
3029
+ const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
3030
+ const minCount = (_a = element.minCount) != null ? _a : 1;
3031
+ const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
3032
+ const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
3033
+ while (values.length < minCount) {
3034
+ values.push(defaultValue);
3035
+ }
3036
+ const container = document.createElement("div");
3037
+ container.className = "space-y-3";
3038
+ wrapper.appendChild(container);
3039
+ function updateIndices() {
3040
+ const items = container.querySelectorAll(".multiple-slider-item");
3041
+ items.forEach((item, index) => {
3042
+ const slider = item.querySelector("input[type=range]");
3043
+ if (slider) {
3044
+ slider.setAttribute("name", `${pathKey}[${index}]`);
3045
+ }
3046
+ });
3047
+ }
3048
+ function addSliderItem(value = defaultValue, index = -1) {
3049
+ const itemWrapper = document.createElement("div");
3050
+ itemWrapper.className = "multiple-slider-item flex items-start gap-2";
3051
+ const tempPathKey = `${pathKey}[${container.children.length}]`;
3052
+ const sliderUI = createSliderUI(
3053
+ value,
3054
+ tempPathKey,
3055
+ element,
3056
+ ctx,
3057
+ state.config.readonly
3058
+ );
3059
+ sliderUI.style.flex = "1";
3060
+ itemWrapper.appendChild(sliderUI);
3061
+ if (index === -1) {
3062
+ container.appendChild(itemWrapper);
3063
+ } else {
3064
+ container.insertBefore(itemWrapper, container.children[index]);
3065
+ }
3066
+ updateIndices();
3067
+ return itemWrapper;
3068
+ }
3069
+ function updateRemoveButtons() {
3070
+ if (state.config.readonly) return;
3071
+ const items = container.querySelectorAll(".multiple-slider-item");
3072
+ const currentCount = items.length;
3073
+ items.forEach((item) => {
3074
+ let removeBtn = item.querySelector(
3075
+ ".remove-item-btn"
3076
+ );
3077
+ if (!removeBtn) {
3078
+ removeBtn = document.createElement("button");
3079
+ removeBtn.type = "button";
3080
+ removeBtn.className = "remove-item-btn px-2 py-1 rounded";
3081
+ removeBtn.style.cssText = `
3082
+ color: var(--fb-error-color);
3083
+ background-color: transparent;
3084
+ transition: background-color var(--fb-transition-duration);
3085
+ margin-top: 8px;
3086
+ `;
3087
+ removeBtn.innerHTML = "\u2715";
3088
+ removeBtn.addEventListener("mouseenter", () => {
3089
+ removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
3090
+ });
3091
+ removeBtn.addEventListener("mouseleave", () => {
3092
+ removeBtn.style.backgroundColor = "transparent";
3093
+ });
3094
+ removeBtn.onclick = () => {
3095
+ const currentIndex = Array.from(container.children).indexOf(
3096
+ item
3097
+ );
3098
+ if (container.children.length > minCount) {
3099
+ values.splice(currentIndex, 1);
3100
+ item.remove();
3101
+ updateIndices();
3102
+ updateAddButton();
3103
+ updateRemoveButtons();
3104
+ }
3105
+ };
3106
+ item.appendChild(removeBtn);
3107
+ }
3108
+ const disabled = currentCount <= minCount;
3109
+ removeBtn.disabled = disabled;
3110
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
3111
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
3112
+ });
3113
+ }
3114
+ function updateAddButton() {
3115
+ const existingAddBtn = wrapper.querySelector(".add-slider-btn");
3116
+ if (existingAddBtn) existingAddBtn.remove();
3117
+ if (!state.config.readonly && values.length < maxCount) {
3118
+ const addBtn = document.createElement("button");
3119
+ addBtn.type = "button";
3120
+ addBtn.className = "add-slider-btn mt-2 px-3 py-1 rounded";
3121
+ addBtn.style.cssText = `
3122
+ color: var(--fb-primary-color);
3123
+ border: var(--fb-border-width) solid var(--fb-primary-color);
3124
+ background-color: transparent;
3125
+ font-size: var(--fb-font-size);
3126
+ transition: all var(--fb-transition-duration);
3127
+ `;
3128
+ addBtn.textContent = `+ Add ${element.label || "Slider"}`;
3129
+ addBtn.addEventListener("mouseenter", () => {
3130
+ addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
3131
+ });
3132
+ addBtn.addEventListener("mouseleave", () => {
3133
+ addBtn.style.backgroundColor = "transparent";
3134
+ });
3135
+ addBtn.onclick = () => {
3136
+ values.push(defaultValue);
3137
+ addSliderItem(defaultValue);
3138
+ updateAddButton();
3139
+ updateRemoveButtons();
3140
+ };
3141
+ wrapper.appendChild(addBtn);
3142
+ }
3143
+ }
3144
+ values.forEach((value) => addSliderItem(value));
3145
+ updateAddButton();
3146
+ updateRemoveButtons();
3147
+ const hint = document.createElement("p");
3148
+ hint.className = "mt-1";
3149
+ hint.style.cssText = `
3150
+ font-size: var(--fb-font-size-small);
3151
+ color: var(--fb-text-secondary-color);
3152
+ `;
3153
+ hint.textContent = makeFieldHint(element);
3154
+ wrapper.appendChild(hint);
3155
+ }
3156
+ function validateSliderElement(element, key, context) {
3157
+ var _a, _b, _c;
3158
+ const errors = [];
3159
+ const { scopeRoot, skipValidation } = context;
3160
+ if (element.min === void 0 || element.min === null) {
3161
+ throw new Error(
3162
+ `Slider validation: field "${key}" requires "min" property`
3163
+ );
3164
+ }
3165
+ if (element.max === void 0 || element.max === null) {
3166
+ throw new Error(
3167
+ `Slider validation: field "${key}" requires "max" property`
3168
+ );
3169
+ }
3170
+ const min = element.min;
3171
+ const max = element.max;
3172
+ const step = (_a = element.step) != null ? _a : 1;
3173
+ const scale = element.scale || "linear";
3174
+ const markValidity = (input, errorMessage) => {
3175
+ var _a2, _b2;
3176
+ if (!input) return;
3177
+ const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
3178
+ let errorElement = document.getElementById(errorId);
3179
+ if (errorMessage) {
3180
+ input.classList.add("invalid");
3181
+ input.title = errorMessage;
3182
+ if (!errorElement) {
3183
+ errorElement = document.createElement("div");
3184
+ errorElement.id = errorId;
3185
+ errorElement.className = "error-message";
3186
+ errorElement.style.cssText = `
3187
+ color: var(--fb-error-color);
3188
+ font-size: var(--fb-font-size-small);
3189
+ margin-top: 0.25rem;
3190
+ `;
3191
+ const sliderContainer = input.closest(".slider-container");
3192
+ if (sliderContainer && sliderContainer.nextSibling) {
3193
+ (_a2 = sliderContainer.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, sliderContainer.nextSibling);
3194
+ } else if (sliderContainer) {
3195
+ (_b2 = sliderContainer.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
3196
+ }
3197
+ }
3198
+ errorElement.textContent = errorMessage;
3199
+ errorElement.style.display = "block";
3200
+ } else {
3201
+ input.classList.remove("invalid");
3202
+ input.title = "";
3203
+ if (errorElement) {
3204
+ errorElement.remove();
3205
+ }
3206
+ }
3207
+ };
3208
+ const validateSliderValue = (slider, fieldKey) => {
3209
+ const rawValue = slider.value;
3210
+ if (!rawValue) {
3211
+ if (!skipValidation && element.required) {
3212
+ errors.push(`${fieldKey}: required`);
3213
+ markValidity(slider, "required");
3214
+ return null;
3215
+ }
3216
+ markValidity(slider, null);
3217
+ return null;
3218
+ }
3219
+ let value;
3220
+ if (scale === "exponential") {
3221
+ const position = parseFloat(rawValue) / 1e3;
3222
+ value = positionToExponential(position, min, max);
3223
+ value = alignToStep(value, step);
3224
+ } else {
3225
+ value = parseFloat(rawValue);
3226
+ value = alignToStep(value, step);
3227
+ }
3228
+ if (!skipValidation) {
3229
+ if (value < min) {
3230
+ errors.push(`${fieldKey}: value ${value} < min ${min}`);
3231
+ markValidity(slider, `value must be >= ${min}`);
3232
+ return value;
3233
+ }
3234
+ if (value > max) {
3235
+ errors.push(`${fieldKey}: value ${value} > max ${max}`);
3236
+ markValidity(slider, `value must be <= ${max}`);
3237
+ return value;
3238
+ }
3239
+ }
3240
+ markValidity(slider, null);
3241
+ return value;
3242
+ };
3243
+ if (element.multiple) {
3244
+ const sliders = scopeRoot.querySelectorAll(
3245
+ `input[type="range"][name^="${key}["]`
3246
+ );
3247
+ const values = [];
3248
+ sliders.forEach((slider, index) => {
3249
+ const value = validateSliderValue(slider, `${key}[${index}]`);
3250
+ values.push(value);
3251
+ });
3252
+ if (!skipValidation) {
3253
+ const minCount = (_b = element.minCount) != null ? _b : 1;
3254
+ const maxCount = (_c = element.maxCount) != null ? _c : Infinity;
3255
+ const filteredValues = values.filter((v) => v !== null);
3256
+ if (element.required && filteredValues.length === 0) {
3257
+ errors.push(`${key}: required`);
3258
+ }
3259
+ if (filteredValues.length < minCount) {
3260
+ errors.push(`${key}: minimum ${minCount} items required`);
3261
+ }
3262
+ if (filteredValues.length > maxCount) {
3263
+ errors.push(`${key}: maximum ${maxCount} items allowed`);
3264
+ }
3265
+ }
3266
+ return { value: values, errors };
3267
+ } else {
3268
+ const slider = scopeRoot.querySelector(
3269
+ `input[type="range"][name="${key}"]`
3270
+ );
3271
+ if (!slider) {
3272
+ if (!skipValidation && element.required) {
3273
+ errors.push(`${key}: required`);
3274
+ }
3275
+ return { value: null, errors };
3276
+ }
3277
+ const value = validateSliderValue(slider, key);
3278
+ return { value, errors };
3279
+ }
3280
+ }
3281
+ function updateSliderField(element, fieldPath, value, context) {
3282
+ var _a;
3283
+ const { scopeRoot } = context;
3284
+ const min = element.min;
3285
+ const max = element.max;
3286
+ const step = (_a = element.step) != null ? _a : 1;
3287
+ const scale = element.scale || "linear";
3288
+ if (element.multiple) {
3289
+ if (!Array.isArray(value)) {
3290
+ console.warn(
3291
+ `updateSliderField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
3292
+ );
3293
+ return;
3294
+ }
3295
+ const sliders = scopeRoot.querySelectorAll(
3296
+ `input[type="range"][name^="${fieldPath}["]`
3297
+ );
3298
+ sliders.forEach((slider, index) => {
3299
+ if (index < value.length && value[index] !== null) {
3300
+ const numValue = Number(value[index]);
3301
+ if (scale === "exponential") {
3302
+ const position = exponentialToPosition(numValue, min, max);
3303
+ slider.value = (position * 1e3).toString();
3304
+ } else {
3305
+ slider.value = numValue.toString();
3306
+ }
3307
+ const sliderContainer = slider.closest(".slider-container");
3308
+ if (sliderContainer) {
3309
+ const valueDisplay = sliderContainer.querySelector(".slider-value");
3310
+ if (valueDisplay) {
3311
+ valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
3312
+ }
3313
+ const percentage = (numValue - min) / (max - min) * 100;
3314
+ slider.style.background = `linear-gradient(
3315
+ to right,
3316
+ var(--fb-primary-color) 0%,
3317
+ var(--fb-primary-color) ${percentage}%,
3318
+ var(--fb-border-color) ${percentage}%,
3319
+ var(--fb-border-color) 100%
3320
+ )`;
3321
+ }
3322
+ slider.classList.remove("invalid");
3323
+ slider.title = "";
3324
+ }
3325
+ });
3326
+ if (value.length !== sliders.length) {
3327
+ console.warn(
3328
+ `updateSliderField: Multiple field "${fieldPath}" has ${sliders.length} sliders but received ${value.length} values. Consider re-rendering for add/remove.`
3329
+ );
3330
+ }
3331
+ } else {
3332
+ const slider = scopeRoot.querySelector(
3333
+ `input[type="range"][name="${fieldPath}"]`
3334
+ );
3335
+ if (slider && value !== null && value !== void 0) {
3336
+ const numValue = Number(value);
3337
+ if (scale === "exponential") {
3338
+ const position = exponentialToPosition(numValue, min, max);
3339
+ slider.value = (position * 1e3).toString();
3340
+ } else {
3341
+ slider.value = numValue.toString();
3342
+ }
3343
+ const sliderContainer = slider.closest(".slider-container");
3344
+ if (sliderContainer) {
3345
+ const valueDisplay = sliderContainer.querySelector(".slider-value");
3346
+ if (valueDisplay) {
3347
+ valueDisplay.textContent = numValue.toFixed(step < 1 ? 2 : 0);
3348
+ }
3349
+ const percentage = (numValue - min) / (max - min) * 100;
3350
+ slider.style.background = `linear-gradient(
3351
+ to right,
3352
+ var(--fb-primary-color) 0%,
3353
+ var(--fb-primary-color) ${percentage}%,
3354
+ var(--fb-border-color) ${percentage}%,
3355
+ var(--fb-border-color) 100%
3356
+ )`;
3357
+ }
3358
+ slider.classList.remove("invalid");
3359
+ slider.title = "";
3360
+ }
3361
+ }
3362
+ }
3363
+
3364
+ // src/components/container.ts
3365
+ var renderElementFunc = null;
3366
+ function setRenderElement(fn) {
3367
+ renderElementFunc = fn;
3368
+ }
3369
+ function renderElement(element, ctx) {
3370
+ if (!renderElementFunc) {
3371
+ throw new Error(
3372
+ "renderElement not initialized. Import from components/index.ts"
3373
+ );
3374
+ }
3375
+ return renderElementFunc(element, ctx);
3376
+ }
3377
+ function createPrefillHints(element, pathKey) {
3378
+ if (!element.prefillHints || element.prefillHints.length === 0) {
3379
+ return null;
3380
+ }
3381
+ const hintsContainer = document.createElement("div");
3382
+ hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
3383
+ element.prefillHints.forEach((hint, index) => {
3384
+ const hintButton = document.createElement("button");
3385
+ hintButton.type = "button";
3386
+ hintButton.className = "fb-prefill-hint";
3387
+ hintButton.textContent = hint.label;
3388
+ hintButton.setAttribute("data-hint-values", JSON.stringify(hint.values));
3389
+ hintButton.setAttribute("data-container-key", pathKey);
3390
+ hintButton.setAttribute("data-hint-index", String(index));
3391
+ hintsContainer.appendChild(hintButton);
3392
+ });
3393
+ return hintsContainer;
3394
+ }
3395
+ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
3396
+ var _a, _b;
3397
+ const containerWrap = document.createElement("div");
3398
+ containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
3399
+ containerWrap.setAttribute("data-container", pathKey);
3400
+ const header = document.createElement("div");
3401
+ header.className = "flex justify-between items-center mb-4";
3402
+ const left = document.createElement("div");
3403
+ left.className = "flex-1";
3404
+ const itemsWrap = document.createElement("div");
3405
+ const columns = element.columns || 1;
3406
+ if (columns === 1) {
3407
+ itemsWrap.className = "space-y-4";
3408
+ } else {
3409
+ itemsWrap.className = `grid grid-cols-${columns} gap-4`;
3410
+ }
3411
+ containerWrap.appendChild(header);
3412
+ header.appendChild(left);
3413
+ if (!ctx.state.config.readonly) {
3414
+ const hintsElement = createPrefillHints(element, pathKey);
3415
+ if (hintsElement) {
3416
+ containerWrap.appendChild(hintsElement);
3417
+ }
3418
+ }
3419
+ const subCtx = {
3420
+ path: pathJoin(ctx.path, element.key),
3421
+ prefill: ((_a = ctx.prefill) == null ? void 0 : _a[element.key]) || {},
3422
+ // Sliced data for value population
3423
+ formData: (_b = ctx.formData) != null ? _b : ctx.prefill,
3424
+ // Complete root data for displayIf evaluation
3425
+ state: ctx.state
3426
+ };
3427
+ element.elements.forEach((child) => {
3428
+ if (!child.hidden) {
3429
+ itemsWrap.appendChild(renderElement(child, subCtx));
3430
+ }
3431
+ });
3432
+ containerWrap.appendChild(itemsWrap);
3433
+ left.innerHTML = `<span>${element.label || element.key}</span>`;
3434
+ wrapper.appendChild(containerWrap);
3435
+ }
3436
+ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3437
+ var _a, _b, _c, _d;
3438
+ const state = ctx.state;
3439
+ const containerWrap = document.createElement("div");
3440
+ containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
3441
+ const header = document.createElement("div");
3442
+ header.className = "flex justify-between items-center mb-4";
3443
+ const left = document.createElement("div");
3444
+ left.className = "flex-1";
3445
+ const right = document.createElement("div");
3446
+ right.className = "flex gap-2";
3447
+ const itemsWrap = document.createElement("div");
3448
+ itemsWrap.className = "space-y-4";
3449
+ containerWrap.appendChild(header);
3450
+ header.appendChild(left);
3451
+ if (!state.config.readonly) {
3452
+ header.appendChild(right);
3453
+ }
3454
+ if (!ctx.state.config.readonly) {
3455
+ const hintsElement = createPrefillHints(element, element.key);
3456
+ if (hintsElement) {
3457
+ containerWrap.appendChild(hintsElement);
3458
+ }
3459
+ }
3460
+ const min = (_a = element.minCount) != null ? _a : 0;
3461
+ const max = (_b = element.maxCount) != null ? _b : Infinity;
3462
+ const pre = Array.isArray((_c = ctx.prefill) == null ? void 0 : _c[element.key]) ? ctx.prefill[element.key] : null;
3463
+ const countItems = () => itemsWrap.querySelectorAll(":scope > .containerItem").length;
3464
+ const createAddButton = () => {
3465
+ const add = document.createElement("button");
3466
+ add.type = "button";
3467
+ add.className = "px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors";
3468
+ add.textContent = t("addElement", state);
3469
+ add.onclick = () => {
3470
+ var _a2;
3471
+ if (countItems() < max) {
3472
+ const idx = countItems();
3473
+ const subCtx = {
3474
+ state: ctx.state,
3475
+ path: pathJoin(ctx.path, `${element.key}[${idx}]`),
3476
+ prefill: {},
3477
+ formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
3478
+ // Complete root data for displayIf
3479
+ };
3480
+ const item = document.createElement("div");
3481
+ item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
3482
+ item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3483
+ const childWrapper = document.createElement("div");
3484
+ const columns = element.columns || 1;
3485
+ if (columns === 1) {
3486
+ childWrapper.className = "space-y-4";
3487
+ } else {
3488
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3489
+ }
3490
+ element.elements.forEach((child) => {
3491
+ if (!child.hidden) {
3492
+ childWrapper.appendChild(renderElement(child, subCtx));
3493
+ }
3494
+ });
3495
+ item.appendChild(childWrapper);
3496
+ if (!state.config.readonly) {
3497
+ const rem = document.createElement("button");
3498
+ rem.type = "button";
3499
+ rem.className = "absolute top-2 right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 transition-colors";
3500
+ rem.textContent = "\xD7";
3501
+ rem.onclick = () => {
3502
+ item.remove();
3503
+ updateAddButton();
3504
+ };
3505
+ item.style.position = "relative";
3506
+ item.appendChild(rem);
3507
+ }
3508
+ itemsWrap.appendChild(item);
3509
+ updateAddButton();
3510
+ }
3511
+ };
3512
+ return add;
3513
+ };
2416
3514
  const updateAddButton = () => {
2417
3515
  const currentCount = countItems();
2418
3516
  const addBtn = right.querySelector("button");
@@ -2438,11 +3536,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2438
3536
  const item = document.createElement("div");
2439
3537
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2440
3538
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3539
+ const childWrapper = document.createElement("div");
3540
+ const columns = element.columns || 1;
3541
+ if (columns === 1) {
3542
+ childWrapper.className = "space-y-4";
3543
+ } else {
3544
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3545
+ }
2441
3546
  element.elements.forEach((child) => {
2442
3547
  if (!child.hidden) {
2443
- item.appendChild(renderElement(child, subCtx));
3548
+ childWrapper.appendChild(renderElement(child, subCtx));
2444
3549
  }
2445
3550
  });
3551
+ item.appendChild(childWrapper);
2446
3552
  if (!state.config.readonly) {
2447
3553
  const rem = document.createElement("button");
2448
3554
  rem.type = "button";
@@ -2471,11 +3577,19 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2471
3577
  const item = document.createElement("div");
2472
3578
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
2473
3579
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
3580
+ const childWrapper = document.createElement("div");
3581
+ const columns = element.columns || 1;
3582
+ if (columns === 1) {
3583
+ childWrapper.className = "space-y-4";
3584
+ } else {
3585
+ childWrapper.className = `grid grid-cols-${columns} gap-4`;
3586
+ }
2474
3587
  element.elements.forEach((child) => {
2475
3588
  if (!child.hidden) {
2476
- item.appendChild(renderElement(child, subCtx));
3589
+ childWrapper.appendChild(renderElement(child, subCtx));
2477
3590
  }
2478
3591
  });
3592
+ item.appendChild(childWrapper);
2479
3593
  const rem = document.createElement("button");
2480
3594
  rem.type = "button";
2481
3595
  rem.className = "absolute top-2 right-2 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 transition-colors";
@@ -2749,35 +3863,34 @@ if (typeof document !== "undefined") {
2749
3863
  }
2750
3864
  });
2751
3865
  }
2752
- function renderElement2(element, ctx) {
3866
+ function checkDisplayCondition(element, ctx) {
2753
3867
  var _a;
2754
- if (element.displayIf) {
2755
- try {
2756
- const dataForCondition = (_a = ctx.formData) != null ? _a : ctx.prefill;
2757
- const shouldDisplay = evaluateDisplayCondition(
2758
- element.displayIf,
2759
- dataForCondition
2760
- );
2761
- if (!shouldDisplay) {
2762
- const hiddenWrapper = document.createElement("div");
2763
- hiddenWrapper.className = "fb-field-wrapper-hidden";
2764
- hiddenWrapper.style.display = "none";
2765
- hiddenWrapper.setAttribute("data-field-key", element.key);
2766
- hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
2767
- return hiddenWrapper;
2768
- }
2769
- } catch (error) {
2770
- console.error(
2771
- `Error evaluating displayIf for field "${element.key}":`,
2772
- error
2773
- );
3868
+ if (!element.displayIf) {
3869
+ return null;
3870
+ }
3871
+ try {
3872
+ const dataForCondition = (_a = ctx.formData) != null ? _a : ctx.prefill;
3873
+ const shouldDisplay = evaluateDisplayCondition(
3874
+ element.displayIf,
3875
+ dataForCondition
3876
+ );
3877
+ if (!shouldDisplay) {
3878
+ const hiddenWrapper = document.createElement("div");
3879
+ hiddenWrapper.className = "fb-field-wrapper-hidden";
3880
+ hiddenWrapper.style.display = "none";
3881
+ hiddenWrapper.setAttribute("data-field-key", element.key);
3882
+ hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
3883
+ return hiddenWrapper;
2774
3884
  }
3885
+ } catch (error) {
3886
+ console.error(
3887
+ `Error evaluating displayIf for field "${element.key}":`,
3888
+ error
3889
+ );
2775
3890
  }
2776
- const wrapper = document.createElement("div");
2777
- wrapper.className = "mb-6 fb-field-wrapper";
2778
- wrapper.setAttribute("data-field-key", element.key);
2779
- const label = document.createElement("div");
2780
- label.className = "flex items-center mb-2";
3891
+ return null;
3892
+ }
3893
+ function createFieldLabel(element) {
2781
3894
  const title = document.createElement("label");
2782
3895
  title.className = "text-sm font-medium text-gray-900";
2783
3896
  title.textContent = element.label || element.key;
@@ -2787,59 +3900,71 @@ function renderElement2(element, ctx) {
2787
3900
  req.textContent = "*";
2788
3901
  title.appendChild(req);
2789
3902
  }
3903
+ return title;
3904
+ }
3905
+ function createInfoButton(element) {
3906
+ const infoBtn = document.createElement("button");
3907
+ infoBtn.type = "button";
3908
+ infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
3909
+ 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>';
3910
+ const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
3911
+ const tooltip = document.createElement("div");
3912
+ tooltip.id = tooltipId;
3913
+ 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";
3914
+ tooltip.style.position = "fixed";
3915
+ tooltip.textContent = element.description || element.hint || "Field information";
3916
+ document.body.appendChild(tooltip);
3917
+ infoBtn.onclick = (e) => {
3918
+ e.preventDefault();
3919
+ e.stopPropagation();
3920
+ showTooltip(tooltipId, infoBtn);
3921
+ };
3922
+ return infoBtn;
3923
+ }
3924
+ function createLabelContainer(element) {
3925
+ const label = document.createElement("div");
3926
+ label.className = "flex items-center mb-2";
3927
+ const title = createFieldLabel(element);
2790
3928
  label.appendChild(title);
2791
3929
  if (element.description || element.hint) {
2792
- const infoBtn = document.createElement("button");
2793
- infoBtn.type = "button";
2794
- infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
2795
- 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>';
2796
- const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
2797
- const tooltip = document.createElement("div");
2798
- tooltip.id = tooltipId;
2799
- 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";
2800
- tooltip.style.position = "fixed";
2801
- tooltip.textContent = element.description || element.hint || "Field information";
2802
- document.body.appendChild(tooltip);
2803
- infoBtn.onclick = (e) => {
2804
- e.preventDefault();
2805
- e.stopPropagation();
2806
- showTooltip(tooltipId, infoBtn);
2807
- };
3930
+ const infoBtn = createInfoButton(element);
2808
3931
  label.appendChild(infoBtn);
2809
3932
  }
2810
- wrapper.appendChild(label);
2811
- const pathKey = pathJoin(ctx.path, element.key);
3933
+ return label;
3934
+ }
3935
+ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
3936
+ const isMultiple = "multiple" in element && element.multiple;
2812
3937
  switch (element.type) {
2813
3938
  case "text":
2814
- if ("multiple" in element && element.multiple) {
3939
+ if (isMultiple) {
2815
3940
  renderMultipleTextElement(element, ctx, wrapper, pathKey);
2816
3941
  } else {
2817
3942
  renderTextElement(element, ctx, wrapper, pathKey);
2818
3943
  }
2819
3944
  break;
2820
3945
  case "textarea":
2821
- if ("multiple" in element && element.multiple) {
3946
+ if (isMultiple) {
2822
3947
  renderMultipleTextareaElement(element, ctx, wrapper, pathKey);
2823
3948
  } else {
2824
3949
  renderTextareaElement(element, ctx, wrapper, pathKey);
2825
3950
  }
2826
3951
  break;
2827
3952
  case "number":
2828
- if ("multiple" in element && element.multiple) {
3953
+ if (isMultiple) {
2829
3954
  renderMultipleNumberElement(element, ctx, wrapper, pathKey);
2830
3955
  } else {
2831
3956
  renderNumberElement(element, ctx, wrapper, pathKey);
2832
3957
  }
2833
3958
  break;
2834
3959
  case "select":
2835
- if ("multiple" in element && element.multiple) {
3960
+ if (isMultiple) {
2836
3961
  renderMultipleSelectElement(element, ctx, wrapper, pathKey);
2837
3962
  } else {
2838
3963
  renderSelectElement(element, ctx, wrapper, pathKey);
2839
3964
  }
2840
3965
  break;
2841
3966
  case "file":
2842
- if ("multiple" in element && element.multiple) {
3967
+ if (isMultiple) {
2843
3968
  renderMultipleFileElement(element, ctx, wrapper, pathKey);
2844
3969
  } else {
2845
3970
  renderFileElement(element, ctx, wrapper, pathKey);
@@ -2848,11 +3973,25 @@ function renderElement2(element, ctx) {
2848
3973
  case "files":
2849
3974
  renderFilesElement(element, ctx, wrapper, pathKey);
2850
3975
  break;
3976
+ case "colour":
3977
+ if (isMultiple) {
3978
+ renderMultipleColourElement(element, ctx, wrapper, pathKey);
3979
+ } else {
3980
+ renderColourElement(element, ctx, wrapper, pathKey);
3981
+ }
3982
+ break;
3983
+ case "slider":
3984
+ if (isMultiple) {
3985
+ renderMultipleSliderElement(element, ctx, wrapper, pathKey);
3986
+ } else {
3987
+ renderSliderElement(element, ctx, wrapper, pathKey);
3988
+ }
3989
+ break;
2851
3990
  case "group":
2852
3991
  renderGroupElement(element, ctx, wrapper, pathKey);
2853
3992
  break;
2854
3993
  case "container":
2855
- if ("multiple" in element && element.multiple) {
3994
+ if (isMultiple) {
2856
3995
  renderMultipleContainerElement(element, ctx, wrapper);
2857
3996
  } else {
2858
3997
  renderSingleContainerElement(element, ctx, wrapper, pathKey);
@@ -2865,6 +4004,19 @@ function renderElement2(element, ctx) {
2865
4004
  wrapper.appendChild(unsupported);
2866
4005
  }
2867
4006
  }
4007
+ }
4008
+ function renderElement2(element, ctx) {
4009
+ const hiddenElement = checkDisplayCondition(element, ctx);
4010
+ if (hiddenElement) {
4011
+ return hiddenElement;
4012
+ }
4013
+ const wrapper = document.createElement("div");
4014
+ wrapper.className = "mb-6 fb-field-wrapper";
4015
+ wrapper.setAttribute("data-field-key", element.key);
4016
+ const label = createLabelContainer(element);
4017
+ wrapper.appendChild(label);
4018
+ const pathKey = pathJoin(ctx.path, element.key);
4019
+ dispatchToRenderer(element, ctx, wrapper, pathKey);
2868
4020
  return wrapper;
2869
4021
  }
2870
4022
  setRenderElement(renderElement2);
@@ -3148,6 +4300,14 @@ var componentRegistry = {
3148
4300
  validate: validateFileElement,
3149
4301
  update: updateFileField
3150
4302
  },
4303
+ colour: {
4304
+ validate: validateColourElement,
4305
+ update: updateColourField
4306
+ },
4307
+ slider: {
4308
+ validate: validateSliderElement,
4309
+ update: updateSliderField
4310
+ },
3151
4311
  container: {
3152
4312
  validate: validateContainerElement,
3153
4313
  update: updateContainerField
@@ -3504,6 +4664,33 @@ var FormBuilderInstance = class {
3504
4664
  this.renderFormLevelActions(allFormLevelActions, trueFormLevelActions);
3505
4665
  }
3506
4666
  }
4667
+ /**
4668
+ * Handle prefill hint click - updates container fields with hint values
4669
+ */
4670
+ handlePrefillHintClick(event) {
4671
+ const target = event.target;
4672
+ if (!target.classList.contains("fb-prefill-hint")) {
4673
+ return;
4674
+ }
4675
+ event.preventDefault();
4676
+ event.stopPropagation();
4677
+ const hintValuesJson = target.getAttribute("data-hint-values");
4678
+ const containerKey = target.getAttribute("data-container-key");
4679
+ if (!hintValuesJson || !containerKey) {
4680
+ console.warn("Prefill hint missing required data attributes");
4681
+ return;
4682
+ }
4683
+ try {
4684
+ const hintValues = JSON.parse(hintValuesJson);
4685
+ for (const fieldKey in hintValues) {
4686
+ const fullPath = `${containerKey}.${fieldKey}`;
4687
+ const value = hintValues[fieldKey];
4688
+ this.updateField(fullPath, value);
4689
+ }
4690
+ } catch (error) {
4691
+ console.error("Error parsing prefill hint values:", error);
4692
+ }
4693
+ }
3507
4694
  /**
3508
4695
  * Render form from schema
3509
4696
  */
@@ -3536,6 +4723,9 @@ var FormBuilderInstance = class {
3536
4723
  formEl.appendChild(block);
3537
4724
  });
3538
4725
  root.appendChild(formEl);
4726
+ if (!this.state.config.readonly) {
4727
+ root.addEventListener("click", this.handlePrefillHintClick.bind(this));
4728
+ }
3539
4729
  if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
3540
4730
  this.renderExternalActions();
3541
4731
  }