@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.
- package/dist/browser/formbuilder.min.js +118 -61
- package/dist/browser/formbuilder.v0.2.7.min.js +241 -0
- package/dist/cjs/index.cjs +722 -185
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +715 -182
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +118 -61
- package/dist/types/components/colour.d.ts +18 -0
- package/dist/types/components/index.d.ts +2 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +8 -1
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.6.min.js +0 -184
package/dist/esm/index.js
CHANGED
|
@@ -1222,6 +1222,172 @@ function t(key, state) {
|
|
|
1222
1222
|
}
|
|
1223
1223
|
|
|
1224
1224
|
// src/components/file.ts
|
|
1225
|
+
function renderLocalImagePreview(container, file, fileName) {
|
|
1226
|
+
const img = document.createElement("img");
|
|
1227
|
+
img.className = "w-full h-full object-contain";
|
|
1228
|
+
img.alt = fileName || "Preview";
|
|
1229
|
+
const reader = new FileReader();
|
|
1230
|
+
reader.onload = (e) => {
|
|
1231
|
+
img.src = e.target?.result || "";
|
|
1232
|
+
};
|
|
1233
|
+
reader.readAsDataURL(file);
|
|
1234
|
+
container.appendChild(img);
|
|
1235
|
+
}
|
|
1236
|
+
function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
|
|
1237
|
+
const videoUrl = URL.createObjectURL(file);
|
|
1238
|
+
container.onclick = null;
|
|
1239
|
+
const newContainer = container.cloneNode(false);
|
|
1240
|
+
if (container.parentNode) {
|
|
1241
|
+
container.parentNode.replaceChild(newContainer, container);
|
|
1242
|
+
}
|
|
1243
|
+
newContainer.innerHTML = `
|
|
1244
|
+
<div class="relative group h-full">
|
|
1245
|
+
<video class="w-full h-full object-contain" controls preload="auto" muted>
|
|
1246
|
+
<source src="${videoUrl}" type="${videoType}">
|
|
1247
|
+
Your browser does not support the video tag.
|
|
1248
|
+
</video>
|
|
1249
|
+
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
|
|
1250
|
+
<button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
|
|
1251
|
+
${t("removeElement", state)}
|
|
1252
|
+
</button>
|
|
1253
|
+
<button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
|
|
1254
|
+
Change
|
|
1255
|
+
</button>
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
`;
|
|
1259
|
+
attachVideoButtonHandlers(newContainer, resourceId, state, deps);
|
|
1260
|
+
return newContainer;
|
|
1261
|
+
}
|
|
1262
|
+
function attachVideoButtonHandlers(container, resourceId, state, deps) {
|
|
1263
|
+
const changeBtn = container.querySelector(".change-file-btn");
|
|
1264
|
+
if (changeBtn) {
|
|
1265
|
+
changeBtn.onclick = (e) => {
|
|
1266
|
+
e.stopPropagation();
|
|
1267
|
+
if (deps?.picker) {
|
|
1268
|
+
deps.picker.click();
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
const deleteBtn = container.querySelector(".delete-file-btn");
|
|
1273
|
+
if (deleteBtn) {
|
|
1274
|
+
deleteBtn.onclick = (e) => {
|
|
1275
|
+
e.stopPropagation();
|
|
1276
|
+
handleVideoDelete(container, resourceId, state, deps);
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function handleVideoDelete(container, resourceId, state, deps) {
|
|
1281
|
+
state.resourceIndex.delete(resourceId);
|
|
1282
|
+
const hiddenInput = container.parentElement?.querySelector(
|
|
1283
|
+
'input[type="hidden"]'
|
|
1284
|
+
);
|
|
1285
|
+
if (hiddenInput) {
|
|
1286
|
+
hiddenInput.value = "";
|
|
1287
|
+
}
|
|
1288
|
+
if (deps?.fileUploadHandler) {
|
|
1289
|
+
container.onclick = deps.fileUploadHandler;
|
|
1290
|
+
}
|
|
1291
|
+
if (deps?.dragHandler) {
|
|
1292
|
+
setupDragAndDrop(container, deps.dragHandler);
|
|
1293
|
+
}
|
|
1294
|
+
container.innerHTML = `
|
|
1295
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1296
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1297
|
+
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
|
1298
|
+
</svg>
|
|
1299
|
+
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1300
|
+
</div>
|
|
1301
|
+
`;
|
|
1302
|
+
}
|
|
1303
|
+
function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
|
|
1304
|
+
const video = document.createElement("video");
|
|
1305
|
+
video.className = "w-full h-full object-contain";
|
|
1306
|
+
video.controls = true;
|
|
1307
|
+
video.preload = "metadata";
|
|
1308
|
+
video.muted = true;
|
|
1309
|
+
const source = document.createElement("source");
|
|
1310
|
+
source.src = thumbnailUrl;
|
|
1311
|
+
source.type = videoType;
|
|
1312
|
+
video.appendChild(source);
|
|
1313
|
+
video.appendChild(document.createTextNode("Your browser does not support the video tag."));
|
|
1314
|
+
container.appendChild(video);
|
|
1315
|
+
}
|
|
1316
|
+
function renderDeleteButton(container, resourceId, state) {
|
|
1317
|
+
addDeleteButton(container, state, () => {
|
|
1318
|
+
state.resourceIndex.delete(resourceId);
|
|
1319
|
+
const hiddenInput = container.parentElement?.querySelector(
|
|
1320
|
+
'input[type="hidden"]'
|
|
1321
|
+
);
|
|
1322
|
+
if (hiddenInput) {
|
|
1323
|
+
hiddenInput.value = "";
|
|
1324
|
+
}
|
|
1325
|
+
container.innerHTML = `
|
|
1326
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1327
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1328
|
+
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
|
1329
|
+
</svg>
|
|
1330
|
+
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1331
|
+
</div>
|
|
1332
|
+
`;
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
async function renderLocalFilePreview(container, meta, fileName, resourceId, isReadonly, state, deps) {
|
|
1336
|
+
if (!meta.file || !(meta.file instanceof File)) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
if (meta.type && meta.type.startsWith("image/")) {
|
|
1340
|
+
renderLocalImagePreview(container, meta.file, fileName);
|
|
1341
|
+
} else if (meta.type && meta.type.startsWith("video/")) {
|
|
1342
|
+
const newContainer = renderLocalVideoPreview(
|
|
1343
|
+
container,
|
|
1344
|
+
meta.file,
|
|
1345
|
+
meta.type,
|
|
1346
|
+
resourceId,
|
|
1347
|
+
state,
|
|
1348
|
+
deps
|
|
1349
|
+
);
|
|
1350
|
+
container = newContainer;
|
|
1351
|
+
} else {
|
|
1352
|
+
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">\u{1F4C1}</div><div class="text-sm">${fileName}</div></div>`;
|
|
1353
|
+
}
|
|
1354
|
+
if (!isReadonly && !(meta.type && meta.type.startsWith("video/"))) {
|
|
1355
|
+
renderDeleteButton(container, resourceId, state);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
async function renderUploadedFilePreview(container, resourceId, fileName, meta, state) {
|
|
1359
|
+
if (!state.config.getThumbnail) {
|
|
1360
|
+
setEmptyFileContainer(container, state);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
try {
|
|
1364
|
+
const thumbnailUrl = await state.config.getThumbnail(resourceId);
|
|
1365
|
+
if (thumbnailUrl) {
|
|
1366
|
+
clear(container);
|
|
1367
|
+
if (meta && meta.type && meta.type.startsWith("video/")) {
|
|
1368
|
+
renderUploadedVideoPreview(container, thumbnailUrl, meta.type);
|
|
1369
|
+
} else {
|
|
1370
|
+
const img = document.createElement("img");
|
|
1371
|
+
img.className = "w-full h-full object-contain";
|
|
1372
|
+
img.alt = fileName || "Preview";
|
|
1373
|
+
img.src = thumbnailUrl;
|
|
1374
|
+
container.appendChild(img);
|
|
1375
|
+
}
|
|
1376
|
+
} else {
|
|
1377
|
+
setEmptyFileContainer(container, state);
|
|
1378
|
+
}
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
console.error("Failed to get thumbnail:", error);
|
|
1381
|
+
container.innerHTML = `
|
|
1382
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1383
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1384
|
+
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
|
1385
|
+
</svg>
|
|
1386
|
+
<div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
|
|
1387
|
+
</div>
|
|
1388
|
+
`;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1225
1391
|
async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
1226
1392
|
const { fileName = "", isReadonly = false, deps = null } = options;
|
|
1227
1393
|
if (!isReadonly && deps && (!deps.picker || !deps.fileUploadHandler || !deps.dragHandler)) {
|
|
@@ -1233,141 +1399,19 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
1233
1399
|
if (isReadonly) {
|
|
1234
1400
|
container.classList.add("cursor-pointer");
|
|
1235
1401
|
}
|
|
1236
|
-
const img = document.createElement("img");
|
|
1237
|
-
img.className = "w-full h-full object-contain";
|
|
1238
|
-
img.alt = fileName || "Preview";
|
|
1239
1402
|
const meta = state.resourceIndex.get(resourceId);
|
|
1240
1403
|
if (meta && meta.file && meta.file instanceof File) {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
container.onclick = null;
|
|
1251
|
-
const newContainer = container.cloneNode(false);
|
|
1252
|
-
if (container.parentNode) {
|
|
1253
|
-
container.parentNode.replaceChild(newContainer, container);
|
|
1254
|
-
}
|
|
1255
|
-
container = newContainer;
|
|
1256
|
-
container.innerHTML = `
|
|
1257
|
-
<div class="relative group h-full">
|
|
1258
|
-
<video class="w-full h-full object-contain" controls preload="auto" muted>
|
|
1259
|
-
<source src="${videoUrl}" type="${meta.type}">
|
|
1260
|
-
Your browser does not support the video tag.
|
|
1261
|
-
</video>
|
|
1262
|
-
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
|
|
1263
|
-
<button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
|
|
1264
|
-
${t("removeElement", state)}
|
|
1265
|
-
</button>
|
|
1266
|
-
<button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
|
|
1267
|
-
Change
|
|
1268
|
-
</button>
|
|
1269
|
-
</div>
|
|
1270
|
-
</div>
|
|
1271
|
-
`;
|
|
1272
|
-
const changeBtn = container.querySelector(
|
|
1273
|
-
".change-file-btn"
|
|
1274
|
-
);
|
|
1275
|
-
if (changeBtn) {
|
|
1276
|
-
changeBtn.onclick = (e) => {
|
|
1277
|
-
e.stopPropagation();
|
|
1278
|
-
if (deps?.picker) {
|
|
1279
|
-
deps.picker.click();
|
|
1280
|
-
}
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
const deleteBtn = container.querySelector(
|
|
1284
|
-
".delete-file-btn"
|
|
1285
|
-
);
|
|
1286
|
-
if (deleteBtn) {
|
|
1287
|
-
deleteBtn.onclick = (e) => {
|
|
1288
|
-
e.stopPropagation();
|
|
1289
|
-
state.resourceIndex.delete(resourceId);
|
|
1290
|
-
const hiddenInput = container.parentElement?.querySelector(
|
|
1291
|
-
'input[type="hidden"]'
|
|
1292
|
-
);
|
|
1293
|
-
if (hiddenInput) {
|
|
1294
|
-
hiddenInput.value = "";
|
|
1295
|
-
}
|
|
1296
|
-
if (deps?.fileUploadHandler) {
|
|
1297
|
-
container.onclick = deps.fileUploadHandler;
|
|
1298
|
-
}
|
|
1299
|
-
if (deps?.dragHandler) {
|
|
1300
|
-
setupDragAndDrop(container, deps.dragHandler);
|
|
1301
|
-
}
|
|
1302
|
-
container.innerHTML = `
|
|
1303
|
-
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1304
|
-
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1305
|
-
<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"/>
|
|
1306
|
-
</svg>
|
|
1307
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1308
|
-
</div>
|
|
1309
|
-
`;
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
} else {
|
|
1313
|
-
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>`;
|
|
1314
|
-
}
|
|
1315
|
-
if (!isReadonly && !(meta && meta.type && meta.type.startsWith("video/"))) {
|
|
1316
|
-
addDeleteButton(container, state, () => {
|
|
1317
|
-
state.resourceIndex.delete(resourceId);
|
|
1318
|
-
const hiddenInput = container.parentElement?.querySelector(
|
|
1319
|
-
'input[type="hidden"]'
|
|
1320
|
-
);
|
|
1321
|
-
if (hiddenInput) {
|
|
1322
|
-
hiddenInput.value = "";
|
|
1323
|
-
}
|
|
1324
|
-
container.innerHTML = `
|
|
1325
|
-
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1326
|
-
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1327
|
-
<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"/>
|
|
1328
|
-
</svg>
|
|
1329
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1330
|
-
</div>
|
|
1331
|
-
`;
|
|
1332
|
-
});
|
|
1333
|
-
}
|
|
1334
|
-
} else if (state.config.getThumbnail) {
|
|
1335
|
-
try {
|
|
1336
|
-
const thumbnailUrl = await state.config.getThumbnail(resourceId);
|
|
1337
|
-
if (thumbnailUrl) {
|
|
1338
|
-
clear(container);
|
|
1339
|
-
if (meta && meta.type && meta.type.startsWith("video/")) {
|
|
1340
|
-
const video = document.createElement("video");
|
|
1341
|
-
video.className = "w-full h-full object-contain";
|
|
1342
|
-
video.controls = true;
|
|
1343
|
-
video.preload = "metadata";
|
|
1344
|
-
video.muted = true;
|
|
1345
|
-
const source = document.createElement("source");
|
|
1346
|
-
source.src = thumbnailUrl;
|
|
1347
|
-
source.type = meta.type;
|
|
1348
|
-
video.appendChild(source);
|
|
1349
|
-
video.appendChild(document.createTextNode("Your browser does not support the video tag."));
|
|
1350
|
-
container.appendChild(video);
|
|
1351
|
-
} else {
|
|
1352
|
-
img.src = thumbnailUrl;
|
|
1353
|
-
container.appendChild(img);
|
|
1354
|
-
}
|
|
1355
|
-
} else {
|
|
1356
|
-
setEmptyFileContainer(container, state);
|
|
1357
|
-
}
|
|
1358
|
-
} catch (error) {
|
|
1359
|
-
console.error("Failed to get thumbnail:", error);
|
|
1360
|
-
container.innerHTML = `
|
|
1361
|
-
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1362
|
-
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1363
|
-
<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"/>
|
|
1364
|
-
</svg>
|
|
1365
|
-
<div class="text-sm text-center">${fileName || "Preview unavailable"}</div>
|
|
1366
|
-
</div>
|
|
1367
|
-
`;
|
|
1368
|
-
}
|
|
1404
|
+
await renderLocalFilePreview(
|
|
1405
|
+
container,
|
|
1406
|
+
meta,
|
|
1407
|
+
fileName,
|
|
1408
|
+
resourceId,
|
|
1409
|
+
isReadonly,
|
|
1410
|
+
state,
|
|
1411
|
+
deps
|
|
1412
|
+
);
|
|
1369
1413
|
} else {
|
|
1370
|
-
|
|
1414
|
+
await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
|
|
1371
1415
|
}
|
|
1372
1416
|
}
|
|
1373
1417
|
async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
@@ -2268,6 +2312,460 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
2268
2312
|
}
|
|
2269
2313
|
}
|
|
2270
2314
|
|
|
2315
|
+
// src/components/colour.ts
|
|
2316
|
+
function normalizeColourValue(value) {
|
|
2317
|
+
if (!value) return "#000000";
|
|
2318
|
+
return value.toUpperCase();
|
|
2319
|
+
}
|
|
2320
|
+
function isValidHexColour(value) {
|
|
2321
|
+
return /^#[0-9A-F]{6}$/i.test(value) || /^#[0-9A-F]{3}$/i.test(value);
|
|
2322
|
+
}
|
|
2323
|
+
function expandHexColour(value) {
|
|
2324
|
+
if (/^#[0-9A-F]{3}$/i.test(value)) {
|
|
2325
|
+
const r = value[1];
|
|
2326
|
+
const g = value[2];
|
|
2327
|
+
const b = value[3];
|
|
2328
|
+
return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
|
|
2329
|
+
}
|
|
2330
|
+
return value.toUpperCase();
|
|
2331
|
+
}
|
|
2332
|
+
function createReadonlyColourUI(value) {
|
|
2333
|
+
const container = document.createElement("div");
|
|
2334
|
+
container.className = "flex items-center gap-2";
|
|
2335
|
+
const normalizedValue = normalizeColourValue(value);
|
|
2336
|
+
const swatch = document.createElement("div");
|
|
2337
|
+
swatch.style.cssText = `
|
|
2338
|
+
width: 32px;
|
|
2339
|
+
height: 32px;
|
|
2340
|
+
border-radius: var(--fb-border-radius);
|
|
2341
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
2342
|
+
background-color: ${normalizedValue};
|
|
2343
|
+
`;
|
|
2344
|
+
const hexText = document.createElement("span");
|
|
2345
|
+
hexText.style.cssText = `
|
|
2346
|
+
font-size: var(--fb-font-size);
|
|
2347
|
+
color: var(--fb-text-color);
|
|
2348
|
+
font-family: var(--fb-font-family-mono, monospace);
|
|
2349
|
+
`;
|
|
2350
|
+
hexText.textContent = normalizedValue;
|
|
2351
|
+
container.appendChild(swatch);
|
|
2352
|
+
container.appendChild(hexText);
|
|
2353
|
+
return container;
|
|
2354
|
+
}
|
|
2355
|
+
function createEditColourUI(value, pathKey, ctx) {
|
|
2356
|
+
const normalizedValue = normalizeColourValue(value);
|
|
2357
|
+
const pickerWrapper = document.createElement("div");
|
|
2358
|
+
pickerWrapper.className = "colour-picker-wrapper";
|
|
2359
|
+
pickerWrapper.style.cssText = `
|
|
2360
|
+
display: flex;
|
|
2361
|
+
align-items: center;
|
|
2362
|
+
gap: 8px;
|
|
2363
|
+
`;
|
|
2364
|
+
const swatch = document.createElement("div");
|
|
2365
|
+
swatch.className = "colour-swatch";
|
|
2366
|
+
swatch.style.cssText = `
|
|
2367
|
+
width: 40px;
|
|
2368
|
+
height: 40px;
|
|
2369
|
+
border-radius: var(--fb-border-radius);
|
|
2370
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
2371
|
+
background-color: ${normalizedValue};
|
|
2372
|
+
cursor: pointer;
|
|
2373
|
+
transition: border-color var(--fb-transition-duration) ease-in-out;
|
|
2374
|
+
flex-shrink: 0;
|
|
2375
|
+
`;
|
|
2376
|
+
const hexInput = document.createElement("input");
|
|
2377
|
+
hexInput.type = "text";
|
|
2378
|
+
hexInput.className = "colour-hex-input";
|
|
2379
|
+
hexInput.name = pathKey;
|
|
2380
|
+
hexInput.value = normalizedValue;
|
|
2381
|
+
hexInput.placeholder = "#000000";
|
|
2382
|
+
hexInput.style.cssText = `
|
|
2383
|
+
width: 100px;
|
|
2384
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
2385
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
2386
|
+
border-radius: var(--fb-border-radius);
|
|
2387
|
+
background-color: var(--fb-background-color);
|
|
2388
|
+
color: var(--fb-text-color);
|
|
2389
|
+
font-size: var(--fb-font-size);
|
|
2390
|
+
font-family: var(--fb-font-family-mono, monospace);
|
|
2391
|
+
transition: all var(--fb-transition-duration) ease-in-out;
|
|
2392
|
+
`;
|
|
2393
|
+
const colourInput = document.createElement("input");
|
|
2394
|
+
colourInput.type = "color";
|
|
2395
|
+
colourInput.className = "colour-picker-hidden";
|
|
2396
|
+
colourInput.value = normalizedValue.toLowerCase();
|
|
2397
|
+
colourInput.style.cssText = `
|
|
2398
|
+
position: absolute;
|
|
2399
|
+
opacity: 0;
|
|
2400
|
+
pointer-events: none;
|
|
2401
|
+
`;
|
|
2402
|
+
hexInput.addEventListener("input", () => {
|
|
2403
|
+
const inputValue = hexInput.value.trim();
|
|
2404
|
+
if (isValidHexColour(inputValue)) {
|
|
2405
|
+
const expanded = expandHexColour(inputValue);
|
|
2406
|
+
swatch.style.backgroundColor = expanded;
|
|
2407
|
+
colourInput.value = expanded.toLowerCase();
|
|
2408
|
+
hexInput.classList.remove("invalid");
|
|
2409
|
+
if (ctx.instance) {
|
|
2410
|
+
ctx.instance.triggerOnChange(pathKey, expanded);
|
|
2411
|
+
}
|
|
2412
|
+
} else {
|
|
2413
|
+
hexInput.classList.add("invalid");
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
hexInput.addEventListener("blur", () => {
|
|
2417
|
+
const inputValue = hexInput.value.trim();
|
|
2418
|
+
if (isValidHexColour(inputValue)) {
|
|
2419
|
+
const expanded = expandHexColour(inputValue);
|
|
2420
|
+
hexInput.value = expanded;
|
|
2421
|
+
swatch.style.backgroundColor = expanded;
|
|
2422
|
+
colourInput.value = expanded.toLowerCase();
|
|
2423
|
+
hexInput.classList.remove("invalid");
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
colourInput.addEventListener("change", () => {
|
|
2427
|
+
const normalized = normalizeColourValue(colourInput.value);
|
|
2428
|
+
hexInput.value = normalized;
|
|
2429
|
+
swatch.style.backgroundColor = normalized;
|
|
2430
|
+
if (ctx.instance) {
|
|
2431
|
+
ctx.instance.triggerOnChange(pathKey, normalized);
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2434
|
+
swatch.addEventListener("click", () => {
|
|
2435
|
+
colourInput.click();
|
|
2436
|
+
});
|
|
2437
|
+
swatch.addEventListener("mouseenter", () => {
|
|
2438
|
+
swatch.style.borderColor = "var(--fb-border-hover-color)";
|
|
2439
|
+
});
|
|
2440
|
+
swatch.addEventListener("mouseleave", () => {
|
|
2441
|
+
swatch.style.borderColor = "var(--fb-border-color)";
|
|
2442
|
+
});
|
|
2443
|
+
hexInput.addEventListener("focus", () => {
|
|
2444
|
+
hexInput.style.borderColor = "var(--fb-border-focus-color)";
|
|
2445
|
+
hexInput.style.outline = `var(--fb-focus-ring-width) solid var(--fb-focus-ring-color)`;
|
|
2446
|
+
hexInput.style.outlineOffset = "0";
|
|
2447
|
+
});
|
|
2448
|
+
hexInput.addEventListener("blur", () => {
|
|
2449
|
+
hexInput.style.borderColor = "var(--fb-border-color)";
|
|
2450
|
+
hexInput.style.outline = "none";
|
|
2451
|
+
});
|
|
2452
|
+
hexInput.addEventListener("mouseenter", () => {
|
|
2453
|
+
if (document.activeElement !== hexInput) {
|
|
2454
|
+
hexInput.style.borderColor = "var(--fb-border-hover-color)";
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
hexInput.addEventListener("mouseleave", () => {
|
|
2458
|
+
if (document.activeElement !== hexInput) {
|
|
2459
|
+
hexInput.style.borderColor = "var(--fb-border-color)";
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
pickerWrapper.appendChild(swatch);
|
|
2463
|
+
pickerWrapper.appendChild(hexInput);
|
|
2464
|
+
pickerWrapper.appendChild(colourInput);
|
|
2465
|
+
return pickerWrapper;
|
|
2466
|
+
}
|
|
2467
|
+
function renderColourElement(element, ctx, wrapper, pathKey) {
|
|
2468
|
+
const state = ctx.state;
|
|
2469
|
+
const initialValue = ctx.prefill[element.key] || element.default || "#000000";
|
|
2470
|
+
if (state.config.readonly) {
|
|
2471
|
+
const readonlyUI = createReadonlyColourUI(initialValue);
|
|
2472
|
+
wrapper.appendChild(readonlyUI);
|
|
2473
|
+
} else {
|
|
2474
|
+
const editUI = createEditColourUI(initialValue, pathKey, ctx);
|
|
2475
|
+
wrapper.appendChild(editUI);
|
|
2476
|
+
}
|
|
2477
|
+
const colourHint = document.createElement("p");
|
|
2478
|
+
colourHint.className = "mt-1";
|
|
2479
|
+
colourHint.style.cssText = `
|
|
2480
|
+
font-size: var(--fb-font-size-small);
|
|
2481
|
+
color: var(--fb-text-secondary-color);
|
|
2482
|
+
`;
|
|
2483
|
+
colourHint.textContent = makeFieldHint(element);
|
|
2484
|
+
wrapper.appendChild(colourHint);
|
|
2485
|
+
}
|
|
2486
|
+
function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
2487
|
+
const state = ctx.state;
|
|
2488
|
+
const prefillValues = ctx.prefill[element.key] || [];
|
|
2489
|
+
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
2490
|
+
const minCount = element.minCount ?? 1;
|
|
2491
|
+
const maxCount = element.maxCount ?? Infinity;
|
|
2492
|
+
while (values.length < minCount) {
|
|
2493
|
+
values.push(element.default || "#000000");
|
|
2494
|
+
}
|
|
2495
|
+
const container = document.createElement("div");
|
|
2496
|
+
container.className = "space-y-2";
|
|
2497
|
+
wrapper.appendChild(container);
|
|
2498
|
+
function updateIndices() {
|
|
2499
|
+
const items = container.querySelectorAll(".multiple-colour-item");
|
|
2500
|
+
items.forEach((item, index) => {
|
|
2501
|
+
const input = item.querySelector("input");
|
|
2502
|
+
if (input) {
|
|
2503
|
+
input.name = `${pathKey}[${index}]`;
|
|
2504
|
+
}
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
function addColourItem(value = "#000000", index = -1) {
|
|
2508
|
+
const itemWrapper = document.createElement("div");
|
|
2509
|
+
itemWrapper.className = "multiple-colour-item flex items-center gap-2";
|
|
2510
|
+
if (state.config.readonly) {
|
|
2511
|
+
const readonlyUI = createReadonlyColourUI(value);
|
|
2512
|
+
while (readonlyUI.firstChild) {
|
|
2513
|
+
itemWrapper.appendChild(readonlyUI.firstChild);
|
|
2514
|
+
}
|
|
2515
|
+
} else {
|
|
2516
|
+
const tempPathKey = `${pathKey}[${container.children.length}]`;
|
|
2517
|
+
const editUI = createEditColourUI(value, tempPathKey, ctx);
|
|
2518
|
+
editUI.style.flex = "1";
|
|
2519
|
+
itemWrapper.appendChild(editUI);
|
|
2520
|
+
}
|
|
2521
|
+
if (index === -1) {
|
|
2522
|
+
container.appendChild(itemWrapper);
|
|
2523
|
+
} else {
|
|
2524
|
+
container.insertBefore(itemWrapper, container.children[index]);
|
|
2525
|
+
}
|
|
2526
|
+
updateIndices();
|
|
2527
|
+
return itemWrapper;
|
|
2528
|
+
}
|
|
2529
|
+
function updateRemoveButtons() {
|
|
2530
|
+
if (state.config.readonly) return;
|
|
2531
|
+
const items = container.querySelectorAll(".multiple-colour-item");
|
|
2532
|
+
const currentCount = items.length;
|
|
2533
|
+
items.forEach((item) => {
|
|
2534
|
+
let removeBtn = item.querySelector(
|
|
2535
|
+
".remove-item-btn"
|
|
2536
|
+
);
|
|
2537
|
+
if (!removeBtn) {
|
|
2538
|
+
removeBtn = document.createElement("button");
|
|
2539
|
+
removeBtn.type = "button";
|
|
2540
|
+
removeBtn.className = "remove-item-btn px-2 py-1 rounded";
|
|
2541
|
+
removeBtn.style.cssText = `
|
|
2542
|
+
color: var(--fb-error-color);
|
|
2543
|
+
background-color: transparent;
|
|
2544
|
+
transition: background-color var(--fb-transition-duration);
|
|
2545
|
+
`;
|
|
2546
|
+
removeBtn.innerHTML = "\u2715";
|
|
2547
|
+
removeBtn.addEventListener("mouseenter", () => {
|
|
2548
|
+
removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
2549
|
+
});
|
|
2550
|
+
removeBtn.addEventListener("mouseleave", () => {
|
|
2551
|
+
removeBtn.style.backgroundColor = "transparent";
|
|
2552
|
+
});
|
|
2553
|
+
removeBtn.onclick = () => {
|
|
2554
|
+
const currentIndex = Array.from(container.children).indexOf(
|
|
2555
|
+
item
|
|
2556
|
+
);
|
|
2557
|
+
if (container.children.length > minCount) {
|
|
2558
|
+
values.splice(currentIndex, 1);
|
|
2559
|
+
item.remove();
|
|
2560
|
+
updateIndices();
|
|
2561
|
+
updateAddButton();
|
|
2562
|
+
updateRemoveButtons();
|
|
2563
|
+
}
|
|
2564
|
+
};
|
|
2565
|
+
item.appendChild(removeBtn);
|
|
2566
|
+
}
|
|
2567
|
+
const disabled = currentCount <= minCount;
|
|
2568
|
+
removeBtn.disabled = disabled;
|
|
2569
|
+
removeBtn.style.opacity = disabled ? "0.5" : "1";
|
|
2570
|
+
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
function updateAddButton() {
|
|
2574
|
+
const existingAddBtn = wrapper.querySelector(".add-colour-btn");
|
|
2575
|
+
if (existingAddBtn) existingAddBtn.remove();
|
|
2576
|
+
if (!state.config.readonly && values.length < maxCount) {
|
|
2577
|
+
const addBtn = document.createElement("button");
|
|
2578
|
+
addBtn.type = "button";
|
|
2579
|
+
addBtn.className = "add-colour-btn mt-2 px-3 py-1 rounded";
|
|
2580
|
+
addBtn.style.cssText = `
|
|
2581
|
+
color: var(--fb-primary-color);
|
|
2582
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
2583
|
+
background-color: transparent;
|
|
2584
|
+
font-size: var(--fb-font-size);
|
|
2585
|
+
transition: all var(--fb-transition-duration);
|
|
2586
|
+
`;
|
|
2587
|
+
addBtn.textContent = `+ Add ${element.label || "Colour"}`;
|
|
2588
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
2589
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
2590
|
+
});
|
|
2591
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
2592
|
+
addBtn.style.backgroundColor = "transparent";
|
|
2593
|
+
});
|
|
2594
|
+
addBtn.onclick = () => {
|
|
2595
|
+
const defaultColour = element.default || "#000000";
|
|
2596
|
+
values.push(defaultColour);
|
|
2597
|
+
addColourItem(defaultColour);
|
|
2598
|
+
updateAddButton();
|
|
2599
|
+
updateRemoveButtons();
|
|
2600
|
+
};
|
|
2601
|
+
wrapper.appendChild(addBtn);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
values.forEach((value) => addColourItem(value));
|
|
2605
|
+
updateAddButton();
|
|
2606
|
+
updateRemoveButtons();
|
|
2607
|
+
const hint = document.createElement("p");
|
|
2608
|
+
hint.className = "mt-1";
|
|
2609
|
+
hint.style.cssText = `
|
|
2610
|
+
font-size: var(--fb-font-size-small);
|
|
2611
|
+
color: var(--fb-text-secondary-color);
|
|
2612
|
+
`;
|
|
2613
|
+
hint.textContent = makeFieldHint(element);
|
|
2614
|
+
wrapper.appendChild(hint);
|
|
2615
|
+
}
|
|
2616
|
+
function validateColourElement(element, key, context) {
|
|
2617
|
+
const errors = [];
|
|
2618
|
+
const { scopeRoot, skipValidation } = context;
|
|
2619
|
+
const markValidity = (input, errorMessage) => {
|
|
2620
|
+
if (!input) return;
|
|
2621
|
+
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
2622
|
+
let errorElement = document.getElementById(errorId);
|
|
2623
|
+
if (errorMessage) {
|
|
2624
|
+
input.classList.add("invalid");
|
|
2625
|
+
input.title = errorMessage;
|
|
2626
|
+
if (!errorElement) {
|
|
2627
|
+
errorElement = document.createElement("div");
|
|
2628
|
+
errorElement.id = errorId;
|
|
2629
|
+
errorElement.className = "error-message";
|
|
2630
|
+
errorElement.style.cssText = `
|
|
2631
|
+
color: var(--fb-error-color);
|
|
2632
|
+
font-size: var(--fb-font-size-small);
|
|
2633
|
+
margin-top: 0.25rem;
|
|
2634
|
+
`;
|
|
2635
|
+
if (input.nextSibling) {
|
|
2636
|
+
input.parentNode?.insertBefore(errorElement, input.nextSibling);
|
|
2637
|
+
} else {
|
|
2638
|
+
input.parentNode?.appendChild(errorElement);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
errorElement.textContent = errorMessage;
|
|
2642
|
+
errorElement.style.display = "block";
|
|
2643
|
+
} else {
|
|
2644
|
+
input.classList.remove("invalid");
|
|
2645
|
+
input.title = "";
|
|
2646
|
+
if (errorElement) {
|
|
2647
|
+
errorElement.remove();
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
const validateColourValue = (input, val, fieldKey) => {
|
|
2652
|
+
if (!val) {
|
|
2653
|
+
if (!skipValidation && element.required) {
|
|
2654
|
+
errors.push(`${fieldKey}: required`);
|
|
2655
|
+
markValidity(input, "required");
|
|
2656
|
+
return "";
|
|
2657
|
+
}
|
|
2658
|
+
markValidity(input, null);
|
|
2659
|
+
return "";
|
|
2660
|
+
}
|
|
2661
|
+
const normalized = normalizeColourValue(val);
|
|
2662
|
+
if (!skipValidation && !isValidHexColour(normalized)) {
|
|
2663
|
+
errors.push(`${fieldKey}: invalid hex colour format`);
|
|
2664
|
+
markValidity(input, "invalid hex colour format");
|
|
2665
|
+
return val;
|
|
2666
|
+
}
|
|
2667
|
+
markValidity(input, null);
|
|
2668
|
+
return normalized;
|
|
2669
|
+
};
|
|
2670
|
+
if (element.multiple) {
|
|
2671
|
+
const hexInputs = scopeRoot.querySelectorAll(
|
|
2672
|
+
`.colour-hex-input`
|
|
2673
|
+
);
|
|
2674
|
+
const values = [];
|
|
2675
|
+
hexInputs.forEach((input, index) => {
|
|
2676
|
+
const val = input?.value ?? "";
|
|
2677
|
+
const validated = validateColourValue(input, val, `${key}[${index}]`);
|
|
2678
|
+
values.push(validated);
|
|
2679
|
+
});
|
|
2680
|
+
if (!skipValidation) {
|
|
2681
|
+
const minCount = element.minCount ?? 1;
|
|
2682
|
+
const maxCount = element.maxCount ?? Infinity;
|
|
2683
|
+
const filteredValues = values.filter((v) => v !== "");
|
|
2684
|
+
if (element.required && filteredValues.length === 0) {
|
|
2685
|
+
errors.push(`${key}: required`);
|
|
2686
|
+
}
|
|
2687
|
+
if (filteredValues.length < minCount) {
|
|
2688
|
+
errors.push(`${key}: minimum ${minCount} items required`);
|
|
2689
|
+
}
|
|
2690
|
+
if (filteredValues.length > maxCount) {
|
|
2691
|
+
errors.push(`${key}: maximum ${maxCount} items allowed`);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
return { value: values, errors };
|
|
2695
|
+
} else {
|
|
2696
|
+
const hexInput = scopeRoot.querySelector(
|
|
2697
|
+
`[name="${key}"].colour-hex-input`
|
|
2698
|
+
);
|
|
2699
|
+
const val = hexInput?.value ?? "";
|
|
2700
|
+
if (!skipValidation && element.required && val === "") {
|
|
2701
|
+
errors.push(`${key}: required`);
|
|
2702
|
+
markValidity(hexInput, "required");
|
|
2703
|
+
return { value: "", errors };
|
|
2704
|
+
}
|
|
2705
|
+
const validated = validateColourValue(hexInput, val, key);
|
|
2706
|
+
return { value: validated, errors };
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
function updateColourField(element, fieldPath, value, context) {
|
|
2710
|
+
const { scopeRoot } = context;
|
|
2711
|
+
if (element.multiple) {
|
|
2712
|
+
if (!Array.isArray(value)) {
|
|
2713
|
+
console.warn(
|
|
2714
|
+
`updateColourField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
|
|
2715
|
+
);
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
const hexInputs = scopeRoot.querySelectorAll(
|
|
2719
|
+
`.colour-hex-input`
|
|
2720
|
+
);
|
|
2721
|
+
hexInputs.forEach((hexInput, index) => {
|
|
2722
|
+
if (index < value.length) {
|
|
2723
|
+
const normalized = normalizeColourValue(value[index]);
|
|
2724
|
+
hexInput.value = normalized;
|
|
2725
|
+
hexInput.classList.remove("invalid");
|
|
2726
|
+
hexInput.title = "";
|
|
2727
|
+
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
2728
|
+
if (wrapper) {
|
|
2729
|
+
const swatch = wrapper.querySelector(".colour-swatch");
|
|
2730
|
+
const colourInput = wrapper.querySelector(".colour-picker-hidden");
|
|
2731
|
+
if (swatch) {
|
|
2732
|
+
swatch.style.backgroundColor = normalized;
|
|
2733
|
+
}
|
|
2734
|
+
if (colourInput) {
|
|
2735
|
+
colourInput.value = normalized.toLowerCase();
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
});
|
|
2740
|
+
if (value.length !== hexInputs.length) {
|
|
2741
|
+
console.warn(
|
|
2742
|
+
`updateColourField: Multiple field "${fieldPath}" has ${hexInputs.length} inputs but received ${value.length} values. Consider re-rendering for add/remove.`
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
} else {
|
|
2746
|
+
const hexInput = scopeRoot.querySelector(
|
|
2747
|
+
`[name="${fieldPath}"].colour-hex-input`
|
|
2748
|
+
);
|
|
2749
|
+
if (hexInput) {
|
|
2750
|
+
const normalized = normalizeColourValue(value);
|
|
2751
|
+
hexInput.value = normalized;
|
|
2752
|
+
hexInput.classList.remove("invalid");
|
|
2753
|
+
hexInput.title = "";
|
|
2754
|
+
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
2755
|
+
if (wrapper) {
|
|
2756
|
+
const swatch = wrapper.querySelector(".colour-swatch");
|
|
2757
|
+
const colourInput = wrapper.querySelector(".colour-picker-hidden");
|
|
2758
|
+
if (swatch) {
|
|
2759
|
+
swatch.style.backgroundColor = normalized;
|
|
2760
|
+
}
|
|
2761
|
+
if (colourInput) {
|
|
2762
|
+
colourInput.value = normalized.toLowerCase();
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2271
2769
|
// src/components/container.ts
|
|
2272
2770
|
var renderElementFunc = null;
|
|
2273
2771
|
function setRenderElement(fn) {
|
|
@@ -2704,34 +3202,33 @@ if (typeof document !== "undefined") {
|
|
|
2704
3202
|
}
|
|
2705
3203
|
});
|
|
2706
3204
|
}
|
|
2707
|
-
function
|
|
2708
|
-
if (element.displayIf) {
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
console.error(
|
|
2725
|
-
`Error evaluating displayIf for field "${element.key}":`,
|
|
2726
|
-
error
|
|
2727
|
-
);
|
|
3205
|
+
function checkDisplayCondition(element, ctx) {
|
|
3206
|
+
if (!element.displayIf) {
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
try {
|
|
3210
|
+
const dataForCondition = ctx.formData ?? ctx.prefill;
|
|
3211
|
+
const shouldDisplay = evaluateDisplayCondition(
|
|
3212
|
+
element.displayIf,
|
|
3213
|
+
dataForCondition
|
|
3214
|
+
);
|
|
3215
|
+
if (!shouldDisplay) {
|
|
3216
|
+
const hiddenWrapper = document.createElement("div");
|
|
3217
|
+
hiddenWrapper.className = "fb-field-wrapper-hidden";
|
|
3218
|
+
hiddenWrapper.style.display = "none";
|
|
3219
|
+
hiddenWrapper.setAttribute("data-field-key", element.key);
|
|
3220
|
+
hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
|
|
3221
|
+
return hiddenWrapper;
|
|
2728
3222
|
}
|
|
3223
|
+
} catch (error) {
|
|
3224
|
+
console.error(
|
|
3225
|
+
`Error evaluating displayIf for field "${element.key}":`,
|
|
3226
|
+
error
|
|
3227
|
+
);
|
|
2729
3228
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
const label = document.createElement("div");
|
|
2734
|
-
label.className = "flex items-center mb-2";
|
|
3229
|
+
return null;
|
|
3230
|
+
}
|
|
3231
|
+
function createFieldLabel(element) {
|
|
2735
3232
|
const title = document.createElement("label");
|
|
2736
3233
|
title.className = "text-sm font-medium text-gray-900";
|
|
2737
3234
|
title.textContent = element.label || element.key;
|
|
@@ -2741,59 +3238,71 @@ function renderElement2(element, ctx) {
|
|
|
2741
3238
|
req.textContent = "*";
|
|
2742
3239
|
title.appendChild(req);
|
|
2743
3240
|
}
|
|
3241
|
+
return title;
|
|
3242
|
+
}
|
|
3243
|
+
function createInfoButton(element) {
|
|
3244
|
+
const infoBtn = document.createElement("button");
|
|
3245
|
+
infoBtn.type = "button";
|
|
3246
|
+
infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
|
|
3247
|
+
infoBtn.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>';
|
|
3248
|
+
const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3249
|
+
const tooltip = document.createElement("div");
|
|
3250
|
+
tooltip.id = tooltipId;
|
|
3251
|
+
tooltip.className = "hidden absolute z-50 bg-gray-200 text-gray-900 text-sm rounded-lg p-3 max-w-sm border border-gray-300 shadow-lg";
|
|
3252
|
+
tooltip.style.position = "fixed";
|
|
3253
|
+
tooltip.textContent = element.description || element.hint || "Field information";
|
|
3254
|
+
document.body.appendChild(tooltip);
|
|
3255
|
+
infoBtn.onclick = (e) => {
|
|
3256
|
+
e.preventDefault();
|
|
3257
|
+
e.stopPropagation();
|
|
3258
|
+
showTooltip(tooltipId, infoBtn);
|
|
3259
|
+
};
|
|
3260
|
+
return infoBtn;
|
|
3261
|
+
}
|
|
3262
|
+
function createLabelContainer(element) {
|
|
3263
|
+
const label = document.createElement("div");
|
|
3264
|
+
label.className = "flex items-center mb-2";
|
|
3265
|
+
const title = createFieldLabel(element);
|
|
2744
3266
|
label.appendChild(title);
|
|
2745
3267
|
if (element.description || element.hint) {
|
|
2746
|
-
const infoBtn =
|
|
2747
|
-
infoBtn.type = "button";
|
|
2748
|
-
infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
|
|
2749
|
-
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>';
|
|
2750
|
-
const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
|
|
2751
|
-
const tooltip = document.createElement("div");
|
|
2752
|
-
tooltip.id = tooltipId;
|
|
2753
|
-
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";
|
|
2754
|
-
tooltip.style.position = "fixed";
|
|
2755
|
-
tooltip.textContent = element.description || element.hint || "Field information";
|
|
2756
|
-
document.body.appendChild(tooltip);
|
|
2757
|
-
infoBtn.onclick = (e) => {
|
|
2758
|
-
e.preventDefault();
|
|
2759
|
-
e.stopPropagation();
|
|
2760
|
-
showTooltip(tooltipId, infoBtn);
|
|
2761
|
-
};
|
|
3268
|
+
const infoBtn = createInfoButton(element);
|
|
2762
3269
|
label.appendChild(infoBtn);
|
|
2763
3270
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
3271
|
+
return label;
|
|
3272
|
+
}
|
|
3273
|
+
function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
3274
|
+
const isMultiple = "multiple" in element && element.multiple;
|
|
2766
3275
|
switch (element.type) {
|
|
2767
3276
|
case "text":
|
|
2768
|
-
if (
|
|
3277
|
+
if (isMultiple) {
|
|
2769
3278
|
renderMultipleTextElement(element, ctx, wrapper, pathKey);
|
|
2770
3279
|
} else {
|
|
2771
3280
|
renderTextElement(element, ctx, wrapper, pathKey);
|
|
2772
3281
|
}
|
|
2773
3282
|
break;
|
|
2774
3283
|
case "textarea":
|
|
2775
|
-
if (
|
|
3284
|
+
if (isMultiple) {
|
|
2776
3285
|
renderMultipleTextareaElement(element, ctx, wrapper, pathKey);
|
|
2777
3286
|
} else {
|
|
2778
3287
|
renderTextareaElement(element, ctx, wrapper, pathKey);
|
|
2779
3288
|
}
|
|
2780
3289
|
break;
|
|
2781
3290
|
case "number":
|
|
2782
|
-
if (
|
|
3291
|
+
if (isMultiple) {
|
|
2783
3292
|
renderMultipleNumberElement(element, ctx, wrapper, pathKey);
|
|
2784
3293
|
} else {
|
|
2785
3294
|
renderNumberElement(element, ctx, wrapper, pathKey);
|
|
2786
3295
|
}
|
|
2787
3296
|
break;
|
|
2788
3297
|
case "select":
|
|
2789
|
-
if (
|
|
3298
|
+
if (isMultiple) {
|
|
2790
3299
|
renderMultipleSelectElement(element, ctx, wrapper, pathKey);
|
|
2791
3300
|
} else {
|
|
2792
3301
|
renderSelectElement(element, ctx, wrapper, pathKey);
|
|
2793
3302
|
}
|
|
2794
3303
|
break;
|
|
2795
3304
|
case "file":
|
|
2796
|
-
if (
|
|
3305
|
+
if (isMultiple) {
|
|
2797
3306
|
renderMultipleFileElement(element, ctx, wrapper, pathKey);
|
|
2798
3307
|
} else {
|
|
2799
3308
|
renderFileElement(element, ctx, wrapper, pathKey);
|
|
@@ -2802,11 +3311,18 @@ function renderElement2(element, ctx) {
|
|
|
2802
3311
|
case "files":
|
|
2803
3312
|
renderFilesElement(element, ctx, wrapper, pathKey);
|
|
2804
3313
|
break;
|
|
3314
|
+
case "colour":
|
|
3315
|
+
if (isMultiple) {
|
|
3316
|
+
renderMultipleColourElement(element, ctx, wrapper, pathKey);
|
|
3317
|
+
} else {
|
|
3318
|
+
renderColourElement(element, ctx, wrapper, pathKey);
|
|
3319
|
+
}
|
|
3320
|
+
break;
|
|
2805
3321
|
case "group":
|
|
2806
3322
|
renderGroupElement(element, ctx, wrapper, pathKey);
|
|
2807
3323
|
break;
|
|
2808
3324
|
case "container":
|
|
2809
|
-
if (
|
|
3325
|
+
if (isMultiple) {
|
|
2810
3326
|
renderMultipleContainerElement(element, ctx, wrapper);
|
|
2811
3327
|
} else {
|
|
2812
3328
|
renderSingleContainerElement(element, ctx, wrapper, pathKey);
|
|
@@ -2819,6 +3335,19 @@ function renderElement2(element, ctx) {
|
|
|
2819
3335
|
wrapper.appendChild(unsupported);
|
|
2820
3336
|
}
|
|
2821
3337
|
}
|
|
3338
|
+
}
|
|
3339
|
+
function renderElement2(element, ctx) {
|
|
3340
|
+
const hiddenElement = checkDisplayCondition(element, ctx);
|
|
3341
|
+
if (hiddenElement) {
|
|
3342
|
+
return hiddenElement;
|
|
3343
|
+
}
|
|
3344
|
+
const wrapper = document.createElement("div");
|
|
3345
|
+
wrapper.className = "mb-6 fb-field-wrapper";
|
|
3346
|
+
wrapper.setAttribute("data-field-key", element.key);
|
|
3347
|
+
const label = createLabelContainer(element);
|
|
3348
|
+
wrapper.appendChild(label);
|
|
3349
|
+
const pathKey = pathJoin(ctx.path, element.key);
|
|
3350
|
+
dispatchToRenderer(element, ctx, wrapper, pathKey);
|
|
2822
3351
|
return wrapper;
|
|
2823
3352
|
}
|
|
2824
3353
|
setRenderElement(renderElement2);
|
|
@@ -3102,6 +3631,10 @@ var componentRegistry = {
|
|
|
3102
3631
|
validate: validateFileElement,
|
|
3103
3632
|
update: updateFileField
|
|
3104
3633
|
},
|
|
3634
|
+
colour: {
|
|
3635
|
+
validate: validateColourElement,
|
|
3636
|
+
update: updateColourField
|
|
3637
|
+
},
|
|
3105
3638
|
container: {
|
|
3106
3639
|
validate: validateContainerElement,
|
|
3107
3640
|
update: updateContainerField
|