@dmitryvim/form-builder 0.2.28 → 0.2.30
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 +51 -0
- package/dist/browser/formbuilder.min.js +193 -116
- package/dist/browser/formbuilder.v0.2.30.min.js +1033 -0
- package/dist/cjs/index.cjs +430 -109
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +424 -108
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +193 -116
- package/dist/types/components/file/dom.d.ts +2 -0
- package/dist/types/components/markdown/index.d.ts +15 -0
- package/dist/types/components/markdown/render.d.ts +6 -0
- package/dist/types/components/markdown/snarkdown.d.ts +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/instance/FormBuilderInstance.d.ts +10 -0
- package/dist/types/types/component-operations.d.ts +5 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +14 -1
- package/dist/types/types/state.d.ts +5 -1
- package/dist/types/utils/helpers.d.ts +14 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.28.min.js +0 -956
package/dist/esm/index.js
CHANGED
|
@@ -153,12 +153,14 @@ function validateSchema(schema) {
|
|
|
153
153
|
allOutputKeys.add(textKey);
|
|
154
154
|
allOutputKeys.add(filesKey);
|
|
155
155
|
} else {
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
if (el.key) {
|
|
157
|
+
if (allOutputKeys.has(el.key)) {
|
|
158
|
+
errors.push(
|
|
159
|
+
`${scopePath}: Element key "${el.key}" collides with a flatOutput richinput key`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
allOutputKeys.add(el.key);
|
|
160
163
|
}
|
|
161
|
-
allOutputKeys.add(el.key);
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
}
|
|
@@ -168,9 +170,17 @@ function validateSchema(schema) {
|
|
|
168
170
|
if (!element.type) {
|
|
169
171
|
errors.push(`${elementPath}: missing type`);
|
|
170
172
|
}
|
|
171
|
-
if (!element.key) {
|
|
173
|
+
if (!element.key && element.type !== "markdown") {
|
|
172
174
|
errors.push(`${elementPath}: missing key`);
|
|
173
175
|
}
|
|
176
|
+
if (element.type === "markdown") {
|
|
177
|
+
const content = element.content;
|
|
178
|
+
if (typeof content !== "string") {
|
|
179
|
+
errors.push(
|
|
180
|
+
`${elementPath}: markdown element requires "content" to be a string (got ${content === null ? "null" : typeof content})`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
174
184
|
if (element.enableIf) {
|
|
175
185
|
const enableIf = element.enableIf;
|
|
176
186
|
if (!enableIf.key || typeof enableIf.key !== "string") {
|
|
@@ -263,6 +273,18 @@ function escapeHtml(text) {
|
|
|
263
273
|
div.textContent = text;
|
|
264
274
|
return div.innerHTML;
|
|
265
275
|
}
|
|
276
|
+
function getElementLookupKey(element, state) {
|
|
277
|
+
if (element.key) {
|
|
278
|
+
return element.key;
|
|
279
|
+
}
|
|
280
|
+
const cached = state.syntheticElementIds.get(element);
|
|
281
|
+
if (cached !== void 0) {
|
|
282
|
+
return cached;
|
|
283
|
+
}
|
|
284
|
+
const id = `fb-synthetic-${state.syntheticElementIdCounter++}`;
|
|
285
|
+
state.syntheticElementIds.set(element, id);
|
|
286
|
+
return id;
|
|
287
|
+
}
|
|
266
288
|
function pathJoin(base, key) {
|
|
267
289
|
return base ? `${base}.${key}` : key;
|
|
268
290
|
}
|
|
@@ -2628,22 +2650,34 @@ function setEmptyFileContainer(fileContainer, state, hint) {
|
|
|
2628
2650
|
</div>
|
|
2629
2651
|
`;
|
|
2630
2652
|
}
|
|
2653
|
+
var dragDropHandlers = /* @__PURE__ */ new WeakMap();
|
|
2631
2654
|
function setupDragAndDrop(element, dropHandler) {
|
|
2632
|
-
|
|
2655
|
+
const prev = dragDropHandlers.get(element);
|
|
2656
|
+
if (prev) {
|
|
2657
|
+
element.removeEventListener("dragover", prev.dragover);
|
|
2658
|
+
element.removeEventListener("dragleave", prev.dragleave);
|
|
2659
|
+
element.removeEventListener("drop", prev.drop);
|
|
2660
|
+
}
|
|
2661
|
+
const dragover = (e) => {
|
|
2633
2662
|
e.preventDefault();
|
|
2634
2663
|
element.classList.add("border-blue-500", "bg-blue-50");
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2664
|
+
};
|
|
2665
|
+
const dragleave = (e) => {
|
|
2637
2666
|
e.preventDefault();
|
|
2638
2667
|
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2668
|
+
};
|
|
2669
|
+
const drop = (e) => {
|
|
2641
2670
|
e.preventDefault();
|
|
2642
2671
|
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2643
|
-
|
|
2644
|
-
|
|
2672
|
+
const files = e.dataTransfer?.files;
|
|
2673
|
+
if (files) {
|
|
2674
|
+
dropHandler(files);
|
|
2645
2675
|
}
|
|
2646
|
-
}
|
|
2676
|
+
};
|
|
2677
|
+
element.addEventListener("dragover", dragover);
|
|
2678
|
+
element.addEventListener("dragleave", dragleave);
|
|
2679
|
+
element.addEventListener("drop", drop);
|
|
2680
|
+
dragDropHandlers.set(element, { dragover, dragleave, drop });
|
|
2647
2681
|
}
|
|
2648
2682
|
|
|
2649
2683
|
// src/components/file/preview.ts
|
|
@@ -3898,7 +3932,7 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3898
3932
|
releaseLocalFileUrl(state.resourceIndex.get(currentRid)?.file);
|
|
3899
3933
|
}
|
|
3900
3934
|
if (hiddenInput) hiddenInput.value = "";
|
|
3901
|
-
|
|
3935
|
+
renderEmptySingleState();
|
|
3902
3936
|
}
|
|
3903
3937
|
};
|
|
3904
3938
|
const buildDeps = () => ({
|
|
@@ -3913,6 +3947,9 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3913
3947
|
fileContainer.className = "file-preview-container";
|
|
3914
3948
|
fileContainer.removeAttribute("style");
|
|
3915
3949
|
fileContainer.onclick = null;
|
|
3950
|
+
while (fileContainer.firstChild) {
|
|
3951
|
+
fileContainer.removeChild(fileContainer.firstChild);
|
|
3952
|
+
}
|
|
3916
3953
|
const row = document.createElement("div");
|
|
3917
3954
|
row.className = "fb-file-card-row";
|
|
3918
3955
|
row.style.cssText = "display:flex;gap:8px;align-items:stretch;";
|
|
@@ -5439,7 +5476,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
5439
5476
|
function extractChildDefaults(elements) {
|
|
5440
5477
|
const defaults = {};
|
|
5441
5478
|
for (const child of elements) {
|
|
5442
|
-
if ("default" in child && child.default !== void 0) {
|
|
5479
|
+
if (child.key && "default" in child && child.default !== void 0) {
|
|
5443
5480
|
defaults[child.key] = child.default;
|
|
5444
5481
|
}
|
|
5445
5482
|
}
|
|
@@ -5534,8 +5571,8 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
5534
5571
|
inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
|
|
5535
5572
|
};
|
|
5536
5573
|
element.elements.forEach((child) => {
|
|
5537
|
-
if (child.hidden || child.type === "hidden") {
|
|
5538
|
-
const prefillVal = containerPrefill[child.key] ?? child.default ?? null;
|
|
5574
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5575
|
+
const prefillVal = containerPrefill[child.key] ?? ("default" in child ? child.default : null) ?? null;
|
|
5539
5576
|
itemsWrap.appendChild(
|
|
5540
5577
|
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
5541
5578
|
);
|
|
@@ -5609,11 +5646,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5609
5646
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5610
5647
|
}
|
|
5611
5648
|
element.elements.forEach((child) => {
|
|
5612
|
-
if (child.hidden || child.type === "hidden") {
|
|
5649
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5613
5650
|
childWrapper.appendChild(
|
|
5614
5651
|
createHiddenInput(
|
|
5615
5652
|
pathJoin(subCtx.path, child.key),
|
|
5616
|
-
child.default ?? null
|
|
5653
|
+
("default" in child ? child.default : null) ?? null
|
|
5617
5654
|
)
|
|
5618
5655
|
);
|
|
5619
5656
|
} else {
|
|
@@ -5686,8 +5723,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5686
5723
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5687
5724
|
}
|
|
5688
5725
|
element.elements.forEach((child) => {
|
|
5689
|
-
if (child.hidden || child.type === "hidden") {
|
|
5690
|
-
const prefillVal = prefillObj?.[child.key] ?? child.default ?? null;
|
|
5726
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5727
|
+
const prefillVal = prefillObj?.[child.key] ?? ("default" in child ? child.default : null) ?? null;
|
|
5691
5728
|
childWrapper.appendChild(
|
|
5692
5729
|
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
5693
5730
|
);
|
|
@@ -5742,11 +5779,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5742
5779
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5743
5780
|
}
|
|
5744
5781
|
element.elements.forEach((child) => {
|
|
5745
|
-
if (child.hidden || child.type === "hidden") {
|
|
5782
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5746
5783
|
childWrapper.appendChild(
|
|
5747
5784
|
createHiddenInput(
|
|
5748
5785
|
pathJoin(subCtx.path, child.key),
|
|
5749
|
-
child.default ?? null
|
|
5786
|
+
("default" in child ? child.default : null) ?? null
|
|
5750
5787
|
)
|
|
5751
5788
|
);
|
|
5752
5789
|
} else {
|
|
@@ -5932,6 +5969,7 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
5932
5969
|
value.forEach((itemValue, index) => {
|
|
5933
5970
|
if (isPlainObject(itemValue)) {
|
|
5934
5971
|
element.elements.forEach((childElement) => {
|
|
5972
|
+
if (childElement.type === "markdown" || !childElement.key) return;
|
|
5935
5973
|
const childPath = `${fieldPath}[${index}].${childElement.key}`;
|
|
5936
5974
|
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
5937
5975
|
const richChild = childElement;
|
|
@@ -5971,6 +6009,7 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
5971
6009
|
return;
|
|
5972
6010
|
}
|
|
5973
6011
|
element.elements.forEach((childElement) => {
|
|
6012
|
+
if (childElement.type === "markdown" || !childElement.key) return;
|
|
5974
6013
|
const childPath = `${fieldPath}.${childElement.key}`;
|
|
5975
6014
|
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
5976
6015
|
const richChild = childElement;
|
|
@@ -8670,6 +8709,248 @@ function updateRichInputField(element, fieldPath, value, context) {
|
|
|
8670
8709
|
}
|
|
8671
8710
|
}
|
|
8672
8711
|
|
|
8712
|
+
// src/components/markdown/snarkdown.ts
|
|
8713
|
+
var TAGS = {
|
|
8714
|
+
"": ["<em>", "</em>"],
|
|
8715
|
+
_: ["<strong>", "</strong>"],
|
|
8716
|
+
"*": ["<strong>", "</strong>"],
|
|
8717
|
+
"~": ["<s>", "</s>"],
|
|
8718
|
+
"\n": ["<br />"],
|
|
8719
|
+
" ": ["<br />"],
|
|
8720
|
+
"-": ["<hr />"]
|
|
8721
|
+
};
|
|
8722
|
+
function outdent(str) {
|
|
8723
|
+
return str.replace(
|
|
8724
|
+
RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"),
|
|
8725
|
+
""
|
|
8726
|
+
);
|
|
8727
|
+
}
|
|
8728
|
+
function encodeAttr(str) {
|
|
8729
|
+
return (str + "").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
8730
|
+
}
|
|
8731
|
+
function parse(md, prevLinks) {
|
|
8732
|
+
const tokenizer = /((?:^|\n+)(?:\n---+|\*[ ]\*(?:[ ]\*)+)\n)|(?:^```[ ]*(\w*)\n([\s\S]*?)\n```$)|((?:(?:^|\n+)(?:\t|[ ]{2,}).+)+\n*)|((?:(?:^|\n)([>*+-]|\d+\.)\s+.*)+)|(?:!\[([^\]]*?)\]\(([^)]+?)\))|(\[)|(\](?:\(([^)]+?)\))?)|(?:(?:^|\n+)([^\s].*)\n(-{3,}|={3,})(?:\n+|$))|(?:(?:^|\n+)(#{1,6})\s*(.+)(?:\n+|$))|(?:`([^`].*?)`)|([ ]{2}\n\n*|\n{2,}|__|\*\*|[_*]|~~)/gm;
|
|
8733
|
+
const context = [];
|
|
8734
|
+
let out = "";
|
|
8735
|
+
const links = prevLinks || {};
|
|
8736
|
+
let last = 0;
|
|
8737
|
+
let chunk;
|
|
8738
|
+
let prev;
|
|
8739
|
+
let token;
|
|
8740
|
+
let inner;
|
|
8741
|
+
let t2;
|
|
8742
|
+
function tag(token2) {
|
|
8743
|
+
const desc = TAGS[token2[1] || ""];
|
|
8744
|
+
const end = context[context.length - 1] === token2;
|
|
8745
|
+
if (!desc) return token2;
|
|
8746
|
+
if (!desc[1]) return desc[0];
|
|
8747
|
+
if (end) context.pop();
|
|
8748
|
+
else context.push(token2);
|
|
8749
|
+
return desc[end ? 1 : 0];
|
|
8750
|
+
}
|
|
8751
|
+
function flush() {
|
|
8752
|
+
let str = "";
|
|
8753
|
+
while (context.length) str += tag(context[context.length - 1]);
|
|
8754
|
+
return str;
|
|
8755
|
+
}
|
|
8756
|
+
md = md.replace(/^\[(.+?)\]:\s*(.+)$/gm, (_s, name, url) => {
|
|
8757
|
+
links[name.toLowerCase()] = url;
|
|
8758
|
+
return "";
|
|
8759
|
+
}).replace(/^\n+|\n+$/g, "");
|
|
8760
|
+
while (token = tokenizer.exec(md)) {
|
|
8761
|
+
prev = md.substring(last, token.index);
|
|
8762
|
+
last = tokenizer.lastIndex;
|
|
8763
|
+
chunk = token[0];
|
|
8764
|
+
if (prev.match(/[^\\](\\\\)*\\$/)) ; else if (t2 = token[3] || token[4]) {
|
|
8765
|
+
chunk = '<pre class="code ' + (token[4] ? "poetry" : token[2].toLowerCase()) + '"><code' + (token[2] ? ` class="language-${token[2].toLowerCase()}"` : "") + ">" + outdent(encodeAttr(t2).replace(/^\n+|\n+$/g, "")) + "</code></pre>";
|
|
8766
|
+
} else if (t2 = token[6]) {
|
|
8767
|
+
if (t2.match(/\./)) {
|
|
8768
|
+
token[5] = token[5].replace(/^\d+/gm, "");
|
|
8769
|
+
}
|
|
8770
|
+
inner = parse(outdent(token[5].replace(/^\s*[>*+.-]/gm, "")));
|
|
8771
|
+
if (t2 === ">") t2 = "blockquote";
|
|
8772
|
+
else {
|
|
8773
|
+
t2 = t2.match(/\./) ? "ol" : "ul";
|
|
8774
|
+
inner = inner.replace(/^(.*)(\n|$)/gm, "<li>$1</li>");
|
|
8775
|
+
}
|
|
8776
|
+
chunk = "<" + t2 + ">" + inner + "</" + t2 + ">";
|
|
8777
|
+
} else if (token[8]) {
|
|
8778
|
+
chunk = `<img src="${encodeAttr(token[8])}" alt="${encodeAttr(token[7])}">`;
|
|
8779
|
+
} else if (token[10]) {
|
|
8780
|
+
out = out.replace(
|
|
8781
|
+
"<a>",
|
|
8782
|
+
`<a href="${encodeAttr(token[11] || links[prev.toLowerCase()])}">`
|
|
8783
|
+
);
|
|
8784
|
+
chunk = flush() + "</a>";
|
|
8785
|
+
} else if (token[9]) {
|
|
8786
|
+
chunk = "<a>";
|
|
8787
|
+
} else if (token[12] || token[14]) {
|
|
8788
|
+
t2 = "h" + (token[14] ? token[14].length : token[13] > "=" ? 1 : 2);
|
|
8789
|
+
chunk = "<" + t2 + ">" + parse(token[12] || token[15], links) + "</" + t2 + ">";
|
|
8790
|
+
} else if (token[16]) {
|
|
8791
|
+
chunk = "<code>" + encodeAttr(token[16]) + "</code>";
|
|
8792
|
+
} else if (token[17] || token[1]) {
|
|
8793
|
+
chunk = tag(token[17] || "--");
|
|
8794
|
+
}
|
|
8795
|
+
out += prev;
|
|
8796
|
+
out += chunk;
|
|
8797
|
+
}
|
|
8798
|
+
return (out + md.substring(last) + flush()).replace(/^\n+|\n+$/g, "");
|
|
8799
|
+
}
|
|
8800
|
+
|
|
8801
|
+
// src/components/markdown/render.ts
|
|
8802
|
+
var STYLE_ID2 = "fb-markdown-styles";
|
|
8803
|
+
function ensureMarkdownStyles() {
|
|
8804
|
+
if (typeof document === "undefined") return;
|
|
8805
|
+
if (document.getElementById(STYLE_ID2)) return;
|
|
8806
|
+
const style = document.createElement("style");
|
|
8807
|
+
style.id = STYLE_ID2;
|
|
8808
|
+
style.setAttribute("data-fb-markdown-styles", "true");
|
|
8809
|
+
style.textContent = `
|
|
8810
|
+
.fb-markdown {
|
|
8811
|
+
font-family: var(--fb-font-family, inherit);
|
|
8812
|
+
font-size: var(--fb-font-size, 1rem);
|
|
8813
|
+
color: var(--fb-text-color, #1f2937);
|
|
8814
|
+
line-height: 1.6;
|
|
8815
|
+
}
|
|
8816
|
+
.fb-markdown h1,
|
|
8817
|
+
.fb-markdown h2,
|
|
8818
|
+
.fb-markdown h3,
|
|
8819
|
+
.fb-markdown h4,
|
|
8820
|
+
.fb-markdown h5,
|
|
8821
|
+
.fb-markdown h6 {
|
|
8822
|
+
font-weight: var(--fb-font-weight-medium, 600);
|
|
8823
|
+
color: var(--fb-text-color, #1f2937);
|
|
8824
|
+
margin-top: 0.75em;
|
|
8825
|
+
margin-bottom: 0.25em;
|
|
8826
|
+
}
|
|
8827
|
+
.fb-markdown h1 { font-size: 1.5rem; }
|
|
8828
|
+
.fb-markdown h2 { font-size: 1.25rem; }
|
|
8829
|
+
.fb-markdown h3 { font-size: 1.1rem; }
|
|
8830
|
+
.fb-markdown h4,
|
|
8831
|
+
.fb-markdown h5,
|
|
8832
|
+
.fb-markdown h6 { font-size: 1rem; }
|
|
8833
|
+
.fb-markdown p {
|
|
8834
|
+
margin-top: 0;
|
|
8835
|
+
margin-bottom: 0.5em;
|
|
8836
|
+
}
|
|
8837
|
+
.fb-markdown ul,
|
|
8838
|
+
.fb-markdown ol {
|
|
8839
|
+
margin: 0.25em 0 0.5em 1.5em;
|
|
8840
|
+
padding: 0;
|
|
8841
|
+
}
|
|
8842
|
+
.fb-markdown li {
|
|
8843
|
+
margin-bottom: 0.15em;
|
|
8844
|
+
}
|
|
8845
|
+
.fb-markdown a {
|
|
8846
|
+
color: var(--fb-primary-color, #2563eb);
|
|
8847
|
+
text-decoration: underline;
|
|
8848
|
+
}
|
|
8849
|
+
.fb-markdown a:hover {
|
|
8850
|
+
opacity: 0.8;
|
|
8851
|
+
}
|
|
8852
|
+
.fb-markdown code {
|
|
8853
|
+
background: var(--fb-input-background-color, #f3f4f6);
|
|
8854
|
+
border-radius: 3px;
|
|
8855
|
+
padding: 0.1em 0.35em;
|
|
8856
|
+
font-size: 0.9em;
|
|
8857
|
+
font-family: monospace;
|
|
8858
|
+
}
|
|
8859
|
+
.fb-markdown pre {
|
|
8860
|
+
background: var(--fb-input-background-color, #f3f4f6);
|
|
8861
|
+
border-radius: var(--fb-border-radius, 0.375rem);
|
|
8862
|
+
padding: 0.75em 1em;
|
|
8863
|
+
overflow-x: auto;
|
|
8864
|
+
margin: 0.5em 0;
|
|
8865
|
+
}
|
|
8866
|
+
.fb-markdown pre code {
|
|
8867
|
+
background: none;
|
|
8868
|
+
padding: 0;
|
|
8869
|
+
border-radius: 0;
|
|
8870
|
+
font-size: inherit;
|
|
8871
|
+
}
|
|
8872
|
+
.fb-markdown blockquote {
|
|
8873
|
+
border-left: 3px solid var(--fb-border-color, #d1d5db);
|
|
8874
|
+
margin: 0.5em 0;
|
|
8875
|
+
padding-left: 1em;
|
|
8876
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
8877
|
+
}
|
|
8878
|
+
.fb-markdown hr {
|
|
8879
|
+
border: none;
|
|
8880
|
+
border-top: 1px solid var(--fb-border-color, #d1d5db);
|
|
8881
|
+
margin: 0.75em 0;
|
|
8882
|
+
}
|
|
8883
|
+
.fb-markdown strong { font-weight: var(--fb-font-weight-medium, 600); }
|
|
8884
|
+
.fb-markdown em { font-style: italic; }
|
|
8885
|
+
.fb-markdown s { text-decoration: line-through; }
|
|
8886
|
+
`;
|
|
8887
|
+
document.head.appendChild(style);
|
|
8888
|
+
}
|
|
8889
|
+
var ANCHOR_DANGEROUS_SCHEMES = [
|
|
8890
|
+
"javascript:",
|
|
8891
|
+
"data:",
|
|
8892
|
+
"vbscript:",
|
|
8893
|
+
"blob:"
|
|
8894
|
+
];
|
|
8895
|
+
var IMG_DANGEROUS_SCHEMES = ["javascript:", "vbscript:", "blob:"];
|
|
8896
|
+
function isImgSrcDangerous(normalized) {
|
|
8897
|
+
if (IMG_DANGEROUS_SCHEMES.some((scheme) => normalized.startsWith(scheme))) {
|
|
8898
|
+
return true;
|
|
8899
|
+
}
|
|
8900
|
+
if (normalized.startsWith("data:") && !normalized.startsWith("data:image/")) {
|
|
8901
|
+
return true;
|
|
8902
|
+
}
|
|
8903
|
+
return false;
|
|
8904
|
+
}
|
|
8905
|
+
function escapeRawHtml(content) {
|
|
8906
|
+
return content.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
8907
|
+
}
|
|
8908
|
+
function sanitizeElements(container) {
|
|
8909
|
+
const anchors = container.querySelectorAll("a[href]");
|
|
8910
|
+
anchors.forEach((a) => {
|
|
8911
|
+
const href = a.getAttribute("href") ?? "";
|
|
8912
|
+
const normalized = href.trim().toLowerCase();
|
|
8913
|
+
const isDangerous = ANCHOR_DANGEROUS_SCHEMES.some(
|
|
8914
|
+
(scheme) => normalized.startsWith(scheme)
|
|
8915
|
+
);
|
|
8916
|
+
if (isDangerous) {
|
|
8917
|
+
a.setAttribute("href", "#");
|
|
8918
|
+
}
|
|
8919
|
+
a.setAttribute("target", "_blank");
|
|
8920
|
+
a.setAttribute("rel", "noopener noreferrer");
|
|
8921
|
+
});
|
|
8922
|
+
const images = container.querySelectorAll("img[src]");
|
|
8923
|
+
images.forEach((img) => {
|
|
8924
|
+
const src = img.getAttribute("src") ?? "";
|
|
8925
|
+
const normalized = src.trim().toLowerCase();
|
|
8926
|
+
if (isImgSrcDangerous(normalized)) {
|
|
8927
|
+
img.setAttribute("src", "");
|
|
8928
|
+
}
|
|
8929
|
+
});
|
|
8930
|
+
}
|
|
8931
|
+
function renderMarkdown(element, _ctx, parent) {
|
|
8932
|
+
if (typeof element.content !== "string") {
|
|
8933
|
+
throw new Error(
|
|
8934
|
+
`renderMarkdown: markdown element${element.key ? ` "${element.key}"` : ""} requires "content" to be a string (got ${element.content === null ? "null" : typeof element.content})`
|
|
8935
|
+
);
|
|
8936
|
+
}
|
|
8937
|
+
ensureMarkdownStyles();
|
|
8938
|
+
const wrapper = document.createElement("div");
|
|
8939
|
+
wrapper.className = "fb-markdown";
|
|
8940
|
+
const escaped = escapeRawHtml(element.content);
|
|
8941
|
+
wrapper.innerHTML = parse(escaped);
|
|
8942
|
+
sanitizeElements(wrapper);
|
|
8943
|
+
parent.appendChild(wrapper);
|
|
8944
|
+
return wrapper;
|
|
8945
|
+
}
|
|
8946
|
+
|
|
8947
|
+
// src/components/markdown/index.ts
|
|
8948
|
+
function validateMarkdown(_element, _key, _context) {
|
|
8949
|
+
return { value: void 0, errors: [], skip: true };
|
|
8950
|
+
}
|
|
8951
|
+
function updateMarkdown(_element, _fieldPath, _value, _context) {
|
|
8952
|
+
}
|
|
8953
|
+
|
|
8673
8954
|
// src/components/index.ts
|
|
8674
8955
|
function showTooltip(tooltipId, button) {
|
|
8675
8956
|
const tooltip = document.getElementById(tooltipId);
|
|
@@ -8900,7 +9181,7 @@ function setupEnableIfListeners(wrapper, element, ctx) {
|
|
|
8900
9181
|
function createFieldLabel(element) {
|
|
8901
9182
|
const title = document.createElement("label");
|
|
8902
9183
|
title.className = "text-sm font-medium text-gray-900";
|
|
8903
|
-
title.textContent = element.label
|
|
9184
|
+
title.textContent = element.label ?? element.key ?? "";
|
|
8904
9185
|
if (element.required) {
|
|
8905
9186
|
const req = document.createElement("span");
|
|
8906
9187
|
req.className = "text-red-500 ml-1";
|
|
@@ -9028,6 +9309,29 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
9028
9309
|
}
|
|
9029
9310
|
}
|
|
9030
9311
|
function renderElement2(element, ctx) {
|
|
9312
|
+
if (element.type === "markdown") {
|
|
9313
|
+
if (element.hidden === true) {
|
|
9314
|
+
const placeholder = document.createElement("div");
|
|
9315
|
+
placeholder.style.display = "none";
|
|
9316
|
+
placeholder.setAttribute("data-fb-hidden-markdown", "true");
|
|
9317
|
+
return placeholder;
|
|
9318
|
+
}
|
|
9319
|
+
const initiallyDisabled2 = shouldDisableElement(element, ctx);
|
|
9320
|
+
const outerWrapper = document.createElement("div");
|
|
9321
|
+
outerWrapper.className = "mb-6 fb-field-wrapper fb-markdown-wrapper";
|
|
9322
|
+
outerWrapper.setAttribute(
|
|
9323
|
+
"data-field-key",
|
|
9324
|
+
getElementLookupKey(element, ctx.state)
|
|
9325
|
+
);
|
|
9326
|
+
renderMarkdown(element, ctx, outerWrapper);
|
|
9327
|
+
if (initiallyDisabled2) {
|
|
9328
|
+
outerWrapper.style.display = "none";
|
|
9329
|
+
outerWrapper.classList.add("fb-field-wrapper-disabled");
|
|
9330
|
+
outerWrapper.setAttribute("data-conditionally-disabled", "true");
|
|
9331
|
+
}
|
|
9332
|
+
setupEnableIfListeners(outerWrapper, element, ctx);
|
|
9333
|
+
return outerWrapper;
|
|
9334
|
+
}
|
|
9031
9335
|
const initiallyDisabled = shouldDisableElement(element, ctx);
|
|
9032
9336
|
const wrapper = document.createElement("div");
|
|
9033
9337
|
wrapper.className = "mb-6 fb-field-wrapper";
|
|
@@ -9238,7 +9542,9 @@ function createInstanceState(config) {
|
|
|
9238
9542
|
translations: mergedTranslations
|
|
9239
9543
|
},
|
|
9240
9544
|
debounceTimer: null,
|
|
9241
|
-
prefill: {}
|
|
9545
|
+
prefill: {},
|
|
9546
|
+
syntheticElementIds: /* @__PURE__ */ new WeakMap(),
|
|
9547
|
+
syntheticElementIdCounter: 0
|
|
9242
9548
|
};
|
|
9243
9549
|
}
|
|
9244
9550
|
function generateInstanceId() {
|
|
@@ -9515,6 +9821,11 @@ var componentRegistry = {
|
|
|
9515
9821
|
// Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
|
|
9516
9822
|
validate: validateHiddenElement,
|
|
9517
9823
|
update: updateHiddenField
|
|
9824
|
+
},
|
|
9825
|
+
markdown: {
|
|
9826
|
+
// Display-only element — no value, no errors, skip from form data
|
|
9827
|
+
validate: validateMarkdown,
|
|
9828
|
+
update: updateMarkdown
|
|
9518
9829
|
}
|
|
9519
9830
|
};
|
|
9520
9831
|
function getComponentOperations(elementType) {
|
|
@@ -9942,7 +10253,7 @@ var FormBuilderInstance = class {
|
|
|
9942
10253
|
fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
9943
10254
|
}
|
|
9944
10255
|
schema.elements.forEach((element) => {
|
|
9945
|
-
if (element.hidden || element.type === "hidden") {
|
|
10256
|
+
if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
|
|
9946
10257
|
const val = prefill?.[element.key] ?? element.default ?? null;
|
|
9947
10258
|
fieldsWrapper.appendChild(createHiddenInput(element.key, val));
|
|
9948
10259
|
return;
|
|
@@ -9977,7 +10288,7 @@ var FormBuilderInstance = class {
|
|
|
9977
10288
|
const errors = [];
|
|
9978
10289
|
const data = {};
|
|
9979
10290
|
const validateElement2 = (element, ctx, customScopeRoot = null) => {
|
|
9980
|
-
const key = element.key;
|
|
10291
|
+
const key = element.key ?? "";
|
|
9981
10292
|
const scopeRoot = customScopeRoot || this.state.formRoot;
|
|
9982
10293
|
const componentContext = {
|
|
9983
10294
|
scopeRoot,
|
|
@@ -9995,7 +10306,8 @@ var FormBuilderInstance = class {
|
|
|
9995
10306
|
errors.push(...componentResult.errors);
|
|
9996
10307
|
return {
|
|
9997
10308
|
value: componentResult.value,
|
|
9998
|
-
spread: !!componentResult.spread
|
|
10309
|
+
spread: !!componentResult.spread,
|
|
10310
|
+
skip: !!componentResult.skip
|
|
9999
10311
|
};
|
|
10000
10312
|
}
|
|
10001
10313
|
console.warn(`Unknown field type "${element.type}" for key "${key}"`);
|
|
@@ -10016,6 +10328,9 @@ var FormBuilderInstance = class {
|
|
|
10016
10328
|
);
|
|
10017
10329
|
}
|
|
10018
10330
|
}
|
|
10331
|
+
if (element.type === "markdown") {
|
|
10332
|
+
return;
|
|
10333
|
+
}
|
|
10019
10334
|
if (element.hidden || element.type === "hidden") {
|
|
10020
10335
|
const hiddenInput = this.state.formRoot.querySelector(
|
|
10021
10336
|
`input[type="hidden"][data-hidden-field="true"][name="${element.key}"]`
|
|
@@ -10028,9 +10343,10 @@ var FormBuilderInstance = class {
|
|
|
10028
10343
|
}
|
|
10029
10344
|
} else {
|
|
10030
10345
|
const result = validateElement2(element, { path: "" });
|
|
10346
|
+
if (result.skip) return;
|
|
10031
10347
|
if (result.spread && result.value !== null && typeof result.value === "object") {
|
|
10032
10348
|
Object.assign(data, result.value);
|
|
10033
|
-
} else {
|
|
10349
|
+
} else if (element.key) {
|
|
10034
10350
|
data[element.key] = result.value;
|
|
10035
10351
|
}
|
|
10036
10352
|
}
|
|
@@ -10106,6 +10422,7 @@ var FormBuilderInstance = class {
|
|
|
10106
10422
|
buildHiddenFieldsData(elements) {
|
|
10107
10423
|
const data = {};
|
|
10108
10424
|
for (const element of elements) {
|
|
10425
|
+
if (element.type === "markdown" || !element.key) continue;
|
|
10109
10426
|
const key = element.key;
|
|
10110
10427
|
if (element.hidden && element.default !== void 0) {
|
|
10111
10428
|
data[key] = element.default;
|
|
@@ -10226,6 +10543,71 @@ var FormBuilderInstance = class {
|
|
|
10226
10543
|
);
|
|
10227
10544
|
}
|
|
10228
10545
|
}
|
|
10546
|
+
/**
|
|
10547
|
+
* Find the field wrapper DOM element for a given element+path combo.
|
|
10548
|
+
* Used by reevaluateConditionalFields.
|
|
10549
|
+
*/
|
|
10550
|
+
findFieldWrapper(element, currentPath) {
|
|
10551
|
+
const formRoot = this.state.formRoot;
|
|
10552
|
+
const lookupKey = getElementLookupKey(element, this.state);
|
|
10553
|
+
if (!currentPath) {
|
|
10554
|
+
return formRoot.querySelector(`[data-field-key="${lookupKey}"]`);
|
|
10555
|
+
}
|
|
10556
|
+
const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
|
|
10557
|
+
if (pathMatch) {
|
|
10558
|
+
const containerEl2 = formRoot.querySelector(
|
|
10559
|
+
`[data-container-item="${pathMatch[1]}[${pathMatch[2]}]"]`
|
|
10560
|
+
);
|
|
10561
|
+
return containerEl2 ? containerEl2.querySelector(`[data-field-key="${lookupKey}"]`) : null;
|
|
10562
|
+
}
|
|
10563
|
+
const containerEl = formRoot.querySelector(
|
|
10564
|
+
`[data-container="${currentPath}"]`
|
|
10565
|
+
);
|
|
10566
|
+
return containerEl ? containerEl.querySelector(`[data-field-key="${lookupKey}"]`) : null;
|
|
10567
|
+
}
|
|
10568
|
+
/**
|
|
10569
|
+
* Apply enableIf show/hide logic to a single field wrapper.
|
|
10570
|
+
* Extracted to reduce cyclomatic complexity of checkElements.
|
|
10571
|
+
*/
|
|
10572
|
+
applyEnableIfVisibility(element, wrapper, currentPath, fullPath, formData) {
|
|
10573
|
+
try {
|
|
10574
|
+
const scope = element.enableIf.scope ?? "relative";
|
|
10575
|
+
const containerData = scope === "relative" && currentPath ? getValueByPath(formData, currentPath) : void 0;
|
|
10576
|
+
const shouldEnable = evaluateEnableCondition(
|
|
10577
|
+
element.enableIf,
|
|
10578
|
+
formData,
|
|
10579
|
+
containerData
|
|
10580
|
+
);
|
|
10581
|
+
const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
|
|
10582
|
+
if (shouldEnable && isCurrentlyDisabled) {
|
|
10583
|
+
const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
|
|
10584
|
+
const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
|
|
10585
|
+
const newWrapper = renderElement2(element, {
|
|
10586
|
+
path: currentPath,
|
|
10587
|
+
prefill: prefillContext,
|
|
10588
|
+
formData,
|
|
10589
|
+
state: this.state,
|
|
10590
|
+
instance: this
|
|
10591
|
+
});
|
|
10592
|
+
wrapper.parentNode?.replaceChild(newWrapper, wrapper);
|
|
10593
|
+
} else if (!shouldEnable && !isCurrentlyDisabled) {
|
|
10594
|
+
const disabledWrapper = document.createElement("div");
|
|
10595
|
+
disabledWrapper.className = "fb-field-wrapper-disabled";
|
|
10596
|
+
disabledWrapper.style.display = "none";
|
|
10597
|
+
disabledWrapper.setAttribute(
|
|
10598
|
+
"data-field-key",
|
|
10599
|
+
getElementLookupKey(element, this.state)
|
|
10600
|
+
);
|
|
10601
|
+
disabledWrapper.setAttribute("data-conditionally-disabled", "true");
|
|
10602
|
+
wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
|
|
10603
|
+
}
|
|
10604
|
+
} catch (error) {
|
|
10605
|
+
console.error(
|
|
10606
|
+
`Error re-evaluating enableIf for field "${element.key ?? "<no key>"}" at path "${fullPath}":`,
|
|
10607
|
+
error
|
|
10608
|
+
);
|
|
10609
|
+
}
|
|
10610
|
+
}
|
|
10229
10611
|
/**
|
|
10230
10612
|
* Re-evaluate all conditional fields (enableIf) based on current form data
|
|
10231
10613
|
* This is called automatically when form data changes (via onChange events)
|
|
@@ -10235,97 +10617,31 @@ var FormBuilderInstance = class {
|
|
|
10235
10617
|
const formData = this.validateForm(true).data;
|
|
10236
10618
|
const checkElements = (elements, currentPath) => {
|
|
10237
10619
|
elements.forEach((element) => {
|
|
10238
|
-
const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
|
|
10620
|
+
const fullPath = currentPath ? `${currentPath}.${element.key ?? ""}` : element.key ?? "";
|
|
10239
10621
|
if (element.enableIf) {
|
|
10240
|
-
|
|
10241
|
-
if (currentPath) {
|
|
10242
|
-
const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
|
|
10243
|
-
if (pathMatch) {
|
|
10244
|
-
const containerKey = pathMatch[1];
|
|
10245
|
-
const containerIndex = pathMatch[2];
|
|
10246
|
-
const containerElement = this.state.formRoot.querySelector(
|
|
10247
|
-
`[data-container-item="${containerKey}[${containerIndex}]"]`
|
|
10248
|
-
);
|
|
10249
|
-
if (containerElement) {
|
|
10250
|
-
fieldWrapper = containerElement.querySelector(
|
|
10251
|
-
`[data-field-key="${element.key}"]`
|
|
10252
|
-
);
|
|
10253
|
-
}
|
|
10254
|
-
} else {
|
|
10255
|
-
const containerElement = this.state.formRoot.querySelector(
|
|
10256
|
-
`[data-container="${currentPath}"]`
|
|
10257
|
-
);
|
|
10258
|
-
if (containerElement) {
|
|
10259
|
-
fieldWrapper = containerElement.querySelector(
|
|
10260
|
-
`[data-field-key="${element.key}"]`
|
|
10261
|
-
);
|
|
10262
|
-
}
|
|
10263
|
-
}
|
|
10264
|
-
} else {
|
|
10265
|
-
fieldWrapper = this.state.formRoot.querySelector(
|
|
10266
|
-
`[data-field-key="${element.key}"]`
|
|
10267
|
-
);
|
|
10268
|
-
}
|
|
10622
|
+
const fieldWrapper = this.findFieldWrapper(element, currentPath);
|
|
10269
10623
|
if (fieldWrapper) {
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
const shouldEnable = evaluateEnableCondition(
|
|
10278
|
-
element.enableIf,
|
|
10279
|
-
formData,
|
|
10280
|
-
// Use complete formData for absolute scope
|
|
10281
|
-
containerData
|
|
10282
|
-
// Use container-specific data for relative scope
|
|
10283
|
-
);
|
|
10284
|
-
const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
|
|
10285
|
-
if (shouldEnable && isCurrentlyDisabled) {
|
|
10286
|
-
const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
|
|
10287
|
-
const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
|
|
10288
|
-
const newWrapper = renderElement2(element, {
|
|
10289
|
-
path: currentPath,
|
|
10290
|
-
// Use container path (empty string for root-level)
|
|
10291
|
-
prefill: prefillContext,
|
|
10292
|
-
formData,
|
|
10293
|
-
// Pass complete formData for enableIf evaluation
|
|
10294
|
-
state: this.state,
|
|
10295
|
-
instance: this
|
|
10296
|
-
});
|
|
10297
|
-
wrapper.parentNode?.replaceChild(newWrapper, wrapper);
|
|
10298
|
-
} else if (!shouldEnable && !isCurrentlyDisabled) {
|
|
10299
|
-
const disabledWrapper = document.createElement("div");
|
|
10300
|
-
disabledWrapper.className = "fb-field-wrapper-disabled";
|
|
10301
|
-
disabledWrapper.style.display = "none";
|
|
10302
|
-
disabledWrapper.setAttribute("data-field-key", element.key);
|
|
10303
|
-
disabledWrapper.setAttribute(
|
|
10304
|
-
"data-conditionally-disabled",
|
|
10305
|
-
"true"
|
|
10306
|
-
);
|
|
10307
|
-
wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
|
|
10308
|
-
}
|
|
10309
|
-
} catch (error) {
|
|
10310
|
-
console.error(
|
|
10311
|
-
`Error re-evaluating enableIf for field "${element.key}" at path "${fullPath}":`,
|
|
10312
|
-
error
|
|
10313
|
-
);
|
|
10314
|
-
}
|
|
10624
|
+
this.applyEnableIfVisibility(
|
|
10625
|
+
element,
|
|
10626
|
+
fieldWrapper,
|
|
10627
|
+
currentPath,
|
|
10628
|
+
fullPath,
|
|
10629
|
+
formData
|
|
10630
|
+
);
|
|
10315
10631
|
}
|
|
10316
10632
|
}
|
|
10317
10633
|
if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
|
|
10318
|
-
const containerData = formData?.[element.key];
|
|
10634
|
+
const containerData = element.key ? formData?.[element.key] : void 0;
|
|
10319
10635
|
if (Array.isArray(containerData)) {
|
|
10320
10636
|
const containerItems = this.state.formRoot.querySelectorAll(
|
|
10321
10637
|
`[data-container-item]`
|
|
10322
10638
|
);
|
|
10323
|
-
const directItems = Array.from(containerItems).filter((el) => {
|
|
10639
|
+
const directItems = fullPath ? Array.from(containerItems).filter((el) => {
|
|
10324
10640
|
const attr = el.getAttribute("data-container-item") || "";
|
|
10325
10641
|
if (!attr.startsWith(`${fullPath}[`)) return false;
|
|
10326
10642
|
const suffix = attr.slice(fullPath.length);
|
|
10327
10643
|
return /^\[\d+\]$/.test(suffix);
|
|
10328
|
-
});
|
|
10644
|
+
}) : [];
|
|
10329
10645
|
directItems.forEach((el) => {
|
|
10330
10646
|
const attr = el.getAttribute("data-container-item") || "";
|
|
10331
10647
|
checkElements(element.elements, attr);
|