@dmitryvim/form-builder 0.2.0 → 0.2.2
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 +16 -0
- package/dist/browser/formbuilder.min.js +51 -46
- package/dist/browser/formbuilder.v0.2.2.min.js +184 -0
- package/dist/cjs/index.cjs +123 -53
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +122 -53
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +51 -46
- package/dist/types/types/config.d.ts +3 -0
- package/package.json +2 -1
- package/dist/browser/formbuilder.v0.2.0.min.js +0 -179
package/dist/esm/index.js
CHANGED
|
@@ -1496,6 +1496,14 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1496
1496
|
container.appendChild(slot);
|
|
1497
1497
|
}
|
|
1498
1498
|
}
|
|
1499
|
+
function renderThumbnailError(slot, iconSize = "w-12 h-12") {
|
|
1500
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1501
|
+
<svg class="${iconSize} text-red-400" fill="currentColor" viewBox="0 0 24 24">
|
|
1502
|
+
<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-2h2v2zm0-4h-2V7h2v6z"/>
|
|
1503
|
+
</svg>
|
|
1504
|
+
<div class="text-xs mt-1 text-red-600">Preview error</div>
|
|
1505
|
+
</div>`;
|
|
1506
|
+
}
|
|
1499
1507
|
async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
1500
1508
|
if (meta && meta.type?.startsWith("image/")) {
|
|
1501
1509
|
if (meta.file && meta.file instanceof File) {
|
|
@@ -1509,19 +1517,27 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1509
1517
|
reader.readAsDataURL(meta.file);
|
|
1510
1518
|
slot.appendChild(img);
|
|
1511
1519
|
} else if (state.config.getThumbnail) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
<
|
|
1523
|
-
|
|
1524
|
-
|
|
1520
|
+
try {
|
|
1521
|
+
const url = await state.config.getThumbnail(rid);
|
|
1522
|
+
if (url) {
|
|
1523
|
+
const img = document.createElement("img");
|
|
1524
|
+
img.className = "w-full h-full object-contain";
|
|
1525
|
+
img.alt = meta.name;
|
|
1526
|
+
img.src = url;
|
|
1527
|
+
slot.appendChild(img);
|
|
1528
|
+
} else {
|
|
1529
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1530
|
+
<svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
|
|
1531
|
+
<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"/>
|
|
1532
|
+
</svg>
|
|
1533
|
+
</div>`;
|
|
1534
|
+
}
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1537
|
+
if (state.config.onThumbnailError) {
|
|
1538
|
+
state.config.onThumbnailError(err, rid);
|
|
1539
|
+
}
|
|
1540
|
+
renderThumbnailError(slot);
|
|
1525
1541
|
}
|
|
1526
1542
|
} else {
|
|
1527
1543
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
@@ -1548,29 +1564,37 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1548
1564
|
</div>
|
|
1549
1565
|
`;
|
|
1550
1566
|
} else if (state.config.getThumbnail) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
<
|
|
1556
|
-
<
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
<div class="bg-
|
|
1560
|
-
<
|
|
1561
|
-
<
|
|
1562
|
-
|
|
1567
|
+
try {
|
|
1568
|
+
const videoUrl = await state.config.getThumbnail(rid);
|
|
1569
|
+
if (videoUrl) {
|
|
1570
|
+
slot.innerHTML = `
|
|
1571
|
+
<div class="relative group h-full w-full">
|
|
1572
|
+
<video class="w-full h-full object-contain" preload="metadata" muted>
|
|
1573
|
+
<source src="${videoUrl}" type="${meta.type}">
|
|
1574
|
+
</video>
|
|
1575
|
+
<div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center">
|
|
1576
|
+
<div class="bg-white bg-opacity-90 rounded-full p-1">
|
|
1577
|
+
<svg class="w-4 h-4 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
|
|
1578
|
+
<path d="M8 5v14l11-7z"/>
|
|
1579
|
+
</svg>
|
|
1580
|
+
</div>
|
|
1563
1581
|
</div>
|
|
1564
1582
|
</div>
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1583
|
+
`;
|
|
1584
|
+
} else {
|
|
1585
|
+
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1586
|
+
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
1587
|
+
<path d="M8 5v14l11-7z"/>
|
|
1588
|
+
</svg>
|
|
1589
|
+
<div class="text-xs mt-1">${meta?.name || "Video"}</div>
|
|
1590
|
+
</div>`;
|
|
1591
|
+
}
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1594
|
+
if (state.config.onThumbnailError) {
|
|
1595
|
+
state.config.onThumbnailError(err, rid);
|
|
1596
|
+
}
|
|
1597
|
+
renderThumbnailError(slot, "w-8 h-8");
|
|
1574
1598
|
}
|
|
1575
1599
|
} else {
|
|
1576
1600
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
@@ -1606,7 +1630,11 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
1606
1630
|
throw new Error("Upload handler must return a string resource ID");
|
|
1607
1631
|
}
|
|
1608
1632
|
} catch (error) {
|
|
1609
|
-
|
|
1633
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1634
|
+
if (state.config.onUploadError) {
|
|
1635
|
+
state.config.onUploadError(err, file);
|
|
1636
|
+
}
|
|
1637
|
+
throw new Error(`File upload failed: ${err.message}`);
|
|
1610
1638
|
}
|
|
1611
1639
|
} else {
|
|
1612
1640
|
throw new Error(
|
|
@@ -1683,7 +1711,11 @@ async function uploadSingleFile(file, state) {
|
|
|
1683
1711
|
}
|
|
1684
1712
|
return rid;
|
|
1685
1713
|
} catch (error) {
|
|
1686
|
-
|
|
1714
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1715
|
+
if (state.config.onUploadError) {
|
|
1716
|
+
state.config.onUploadError(err, file);
|
|
1717
|
+
}
|
|
1718
|
+
throw new Error(`File upload failed: ${err.message}`);
|
|
1687
1719
|
}
|
|
1688
1720
|
} else {
|
|
1689
1721
|
throw new Error(
|
|
@@ -1692,26 +1724,31 @@ async function uploadSingleFile(file, state) {
|
|
|
1692
1724
|
}
|
|
1693
1725
|
}
|
|
1694
1726
|
async function forceDownload(resourceId, fileName, state) {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1727
|
+
try {
|
|
1728
|
+
let fileUrl = null;
|
|
1729
|
+
if (state.config.getDownloadUrl) {
|
|
1730
|
+
fileUrl = state.config.getDownloadUrl(resourceId);
|
|
1731
|
+
} else if (state.config.getThumbnail) {
|
|
1732
|
+
fileUrl = await state.config.getThumbnail(resourceId);
|
|
1733
|
+
}
|
|
1734
|
+
if (fileUrl) {
|
|
1735
|
+
const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
|
|
1736
|
+
const response = await fetch(finalUrl);
|
|
1704
1737
|
if (!response.ok) {
|
|
1705
1738
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1706
1739
|
}
|
|
1707
|
-
|
|
1708
|
-
}).then((blob) => {
|
|
1740
|
+
const blob = await response.blob();
|
|
1709
1741
|
downloadBlob(blob, fileName);
|
|
1710
|
-
}
|
|
1711
|
-
throw new Error(
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1742
|
+
} else {
|
|
1743
|
+
throw new Error("No download URL available for resource");
|
|
1744
|
+
}
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1747
|
+
if (state.config.onDownloadError) {
|
|
1748
|
+
state.config.onDownloadError(err, resourceId, fileName);
|
|
1749
|
+
}
|
|
1750
|
+
console.error(`File download failed for ${fileName}:`, err);
|
|
1751
|
+
throw err;
|
|
1715
1752
|
}
|
|
1716
1753
|
}
|
|
1717
1754
|
function downloadBlob(blob, fileName) {
|
|
@@ -1737,7 +1774,14 @@ function addPrefillFilesToIndex(initialFiles, state) {
|
|
|
1737
1774
|
if (!state.resourceIndex.has(resourceId)) {
|
|
1738
1775
|
const filename = resourceId.split("/").pop() || "file";
|
|
1739
1776
|
const extension = filename.split(".").pop()?.toLowerCase();
|
|
1740
|
-
|
|
1777
|
+
let fileType = "application/octet-stream";
|
|
1778
|
+
if (extension) {
|
|
1779
|
+
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
1780
|
+
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
1781
|
+
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
1782
|
+
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1741
1785
|
state.resourceIndex.set(resourceId, {
|
|
1742
1786
|
name: filename,
|
|
1743
1787
|
type: fileType,
|
|
@@ -2670,6 +2714,9 @@ var defaultConfig = {
|
|
|
2670
2714
|
actionHandler: null,
|
|
2671
2715
|
onChange: null,
|
|
2672
2716
|
onFieldChange: null,
|
|
2717
|
+
onThumbnailError: null,
|
|
2718
|
+
onUploadError: null,
|
|
2719
|
+
onDownloadError: null,
|
|
2673
2720
|
debounceMs: 300,
|
|
2674
2721
|
enableFilePreview: true,
|
|
2675
2722
|
maxPreviewSize: "200px",
|
|
@@ -2970,6 +3017,25 @@ var FormBuilderInstance = class {
|
|
|
2970
3017
|
constructor(config) {
|
|
2971
3018
|
this.instanceId = generateInstanceId();
|
|
2972
3019
|
this.state = createInstanceState(config);
|
|
3020
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "development") {
|
|
3021
|
+
if (config?.getThumbnail) {
|
|
3022
|
+
const testResult = config.getThumbnail("test-id");
|
|
3023
|
+
if (!(testResult instanceof Promise)) {
|
|
3024
|
+
console.warn(
|
|
3025
|
+
"[form-builder] getThumbnail must be async (return Promise). Wrap your function: async (id) => { ... }"
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
if (!globalThis.__formBuilderInstances) {
|
|
3030
|
+
globalThis.__formBuilderInstances = /* @__PURE__ */ new Set();
|
|
3031
|
+
}
|
|
3032
|
+
globalThis.__formBuilderInstances.add(this.instanceId);
|
|
3033
|
+
if (globalThis.__formBuilderInstances.size > 10) {
|
|
3034
|
+
console.warn(
|
|
3035
|
+
`[form-builder] ${globalThis.__formBuilderInstances.size} instances active. Possible memory leak - ensure you call destroy() when done.`
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
2973
3039
|
}
|
|
2974
3040
|
/**
|
|
2975
3041
|
* Get instance ID (useful for debugging and resource prefixing)
|
|
@@ -3515,6 +3581,9 @@ var FormBuilderInstance = class {
|
|
|
3515
3581
|
this.state.formRoot = null;
|
|
3516
3582
|
this.state.schema = null;
|
|
3517
3583
|
this.state.externalActions = null;
|
|
3584
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "development") {
|
|
3585
|
+
globalThis.__formBuilderInstances?.delete(this.instanceId);
|
|
3586
|
+
}
|
|
3518
3587
|
}
|
|
3519
3588
|
};
|
|
3520
3589
|
|