@dmitryvim/form-builder 0.2.6 → 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.
@@ -1243,6 +1243,175 @@ function t(key, state) {
1243
1243
  }
1244
1244
 
1245
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
+ }
1246
1415
  async function renderFilePreview(container, resourceId, state, options = {}) {
1247
1416
  const { fileName = "", isReadonly = false, deps = null } = options;
1248
1417
  if (!isReadonly && deps && (!deps.picker || !deps.fileUploadHandler || !deps.dragHandler)) {
@@ -1254,144 +1423,19 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
1254
1423
  if (isReadonly) {
1255
1424
  container.classList.add("cursor-pointer");
1256
1425
  }
1257
- const img = document.createElement("img");
1258
- img.className = "w-full h-full object-contain";
1259
- img.alt = fileName || "Preview";
1260
1426
  const meta = state.resourceIndex.get(resourceId);
1261
1427
  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
- }
1428
+ await renderLocalFilePreview(
1429
+ container,
1430
+ meta,
1431
+ fileName,
1432
+ resourceId,
1433
+ isReadonly,
1434
+ state,
1435
+ deps
1436
+ );
1393
1437
  } else {
1394
- setEmptyFileContainer(container, state);
1438
+ await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
1395
1439
  }
1396
1440
  }
1397
1441
  async function renderFilePreviewReadonly(resourceId, state, fileName) {
@@ -2306,6 +2350,464 @@ function updateFileField(element, fieldPath, value, context) {
2306
2350
  }
2307
2351
  }
2308
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
+
2309
2811
  // src/components/container.ts
2310
2812
  var renderElementFunc = null;
2311
2813
  function setRenderElement(fn) {
@@ -2749,35 +3251,34 @@ if (typeof document !== "undefined") {
2749
3251
  }
2750
3252
  });
2751
3253
  }
2752
- function renderElement2(element, ctx) {
3254
+ function checkDisplayCondition(element, ctx) {
2753
3255
  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
- );
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;
2774
3272
  }
3273
+ } catch (error) {
3274
+ console.error(
3275
+ `Error evaluating displayIf for field "${element.key}":`,
3276
+ error
3277
+ );
2775
3278
  }
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";
3279
+ return null;
3280
+ }
3281
+ function createFieldLabel(element) {
2781
3282
  const title = document.createElement("label");
2782
3283
  title.className = "text-sm font-medium text-gray-900";
2783
3284
  title.textContent = element.label || element.key;
@@ -2787,59 +3288,71 @@ function renderElement2(element, ctx) {
2787
3288
  req.textContent = "*";
2788
3289
  title.appendChild(req);
2789
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);
2790
3316
  label.appendChild(title);
2791
3317
  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
- };
3318
+ const infoBtn = createInfoButton(element);
2808
3319
  label.appendChild(infoBtn);
2809
3320
  }
2810
- wrapper.appendChild(label);
2811
- 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;
2812
3325
  switch (element.type) {
2813
3326
  case "text":
2814
- if ("multiple" in element && element.multiple) {
3327
+ if (isMultiple) {
2815
3328
  renderMultipleTextElement(element, ctx, wrapper, pathKey);
2816
3329
  } else {
2817
3330
  renderTextElement(element, ctx, wrapper, pathKey);
2818
3331
  }
2819
3332
  break;
2820
3333
  case "textarea":
2821
- if ("multiple" in element && element.multiple) {
3334
+ if (isMultiple) {
2822
3335
  renderMultipleTextareaElement(element, ctx, wrapper, pathKey);
2823
3336
  } else {
2824
3337
  renderTextareaElement(element, ctx, wrapper, pathKey);
2825
3338
  }
2826
3339
  break;
2827
3340
  case "number":
2828
- if ("multiple" in element && element.multiple) {
3341
+ if (isMultiple) {
2829
3342
  renderMultipleNumberElement(element, ctx, wrapper, pathKey);
2830
3343
  } else {
2831
3344
  renderNumberElement(element, ctx, wrapper, pathKey);
2832
3345
  }
2833
3346
  break;
2834
3347
  case "select":
2835
- if ("multiple" in element && element.multiple) {
3348
+ if (isMultiple) {
2836
3349
  renderMultipleSelectElement(element, ctx, wrapper, pathKey);
2837
3350
  } else {
2838
3351
  renderSelectElement(element, ctx, wrapper, pathKey);
2839
3352
  }
2840
3353
  break;
2841
3354
  case "file":
2842
- if ("multiple" in element && element.multiple) {
3355
+ if (isMultiple) {
2843
3356
  renderMultipleFileElement(element, ctx, wrapper, pathKey);
2844
3357
  } else {
2845
3358
  renderFileElement(element, ctx, wrapper, pathKey);
@@ -2848,11 +3361,18 @@ function renderElement2(element, ctx) {
2848
3361
  case "files":
2849
3362
  renderFilesElement(element, ctx, wrapper, pathKey);
2850
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;
2851
3371
  case "group":
2852
3372
  renderGroupElement(element, ctx, wrapper, pathKey);
2853
3373
  break;
2854
3374
  case "container":
2855
- if ("multiple" in element && element.multiple) {
3375
+ if (isMultiple) {
2856
3376
  renderMultipleContainerElement(element, ctx, wrapper);
2857
3377
  } else {
2858
3378
  renderSingleContainerElement(element, ctx, wrapper, pathKey);
@@ -2865,6 +3385,19 @@ function renderElement2(element, ctx) {
2865
3385
  wrapper.appendChild(unsupported);
2866
3386
  }
2867
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);
2868
3401
  return wrapper;
2869
3402
  }
2870
3403
  setRenderElement(renderElement2);
@@ -3148,6 +3681,10 @@ var componentRegistry = {
3148
3681
  validate: validateFileElement,
3149
3682
  update: updateFileField
3150
3683
  },
3684
+ colour: {
3685
+ validate: validateColourElement,
3686
+ update: updateColourField
3687
+ },
3151
3688
  container: {
3152
3689
  validate: validateContainerElement,
3153
3690
  update: updateContainerField