@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.
- package/README.md +3 -1
- package/dist/browser/formbuilder.min.js +183 -63
- package/dist/browser/formbuilder.v0.2.8.min.js +304 -0
- package/dist/cjs/index.cjs +1476 -286
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1464 -284
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +183 -63
- package/dist/types/components/colour.d.ts +18 -0
- package/dist/types/components/index.d.ts +3 -1
- package/dist/types/components/slider.d.ts +11 -0
- package/dist/types/instance/FormBuilderInstance.d.ts +4 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +27 -1
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.6.min.js +0 -184
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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
|
-
|
|
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/
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2390
|
+
// src/components/colour.ts
|
|
2391
|
+
function normalizeColourValue(value) {
|
|
2392
|
+
if (!value) return "#000000";
|
|
2393
|
+
return value.toUpperCase();
|
|
2313
2394
|
}
|
|
2314
|
-
function
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
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
|
|
2405
|
+
return value.toUpperCase();
|
|
2321
2406
|
}
|
|
2322
|
-
function
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
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
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
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
|
|
2353
|
-
var _a, _b, _c, _d;
|
|
2542
|
+
function renderColourElement(element, ctx, wrapper, pathKey) {
|
|
2354
2543
|
const state = ctx.state;
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
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
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
}
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3866
|
+
function checkDisplayCondition(element, ctx) {
|
|
2753
3867
|
var _a;
|
|
2754
|
-
if (element.displayIf) {
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
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
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
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 =
|
|
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
|
-
|
|
2811
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
}
|