@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/cjs/index.cjs
CHANGED
|
@@ -159,12 +159,14 @@ function validateSchema(schema) {
|
|
|
159
159
|
allOutputKeys.add(textKey);
|
|
160
160
|
allOutputKeys.add(filesKey);
|
|
161
161
|
} else {
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
if (el.key) {
|
|
163
|
+
if (allOutputKeys.has(el.key)) {
|
|
164
|
+
errors.push(
|
|
165
|
+
`${scopePath}: Element key "${el.key}" collides with a flatOutput richinput key`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
allOutputKeys.add(el.key);
|
|
166
169
|
}
|
|
167
|
-
allOutputKeys.add(el.key);
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
}
|
|
@@ -174,9 +176,17 @@ function validateSchema(schema) {
|
|
|
174
176
|
if (!element.type) {
|
|
175
177
|
errors.push(`${elementPath}: missing type`);
|
|
176
178
|
}
|
|
177
|
-
if (!element.key) {
|
|
179
|
+
if (!element.key && element.type !== "markdown") {
|
|
178
180
|
errors.push(`${elementPath}: missing key`);
|
|
179
181
|
}
|
|
182
|
+
if (element.type === "markdown") {
|
|
183
|
+
const content = element.content;
|
|
184
|
+
if (typeof content !== "string") {
|
|
185
|
+
errors.push(
|
|
186
|
+
`${elementPath}: markdown element requires "content" to be a string (got ${content === null ? "null" : typeof content})`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
180
190
|
if (element.enableIf) {
|
|
181
191
|
const enableIf = element.enableIf;
|
|
182
192
|
if (!enableIf.key || typeof enableIf.key !== "string") {
|
|
@@ -269,6 +279,18 @@ function escapeHtml(text) {
|
|
|
269
279
|
div.textContent = text;
|
|
270
280
|
return div.innerHTML;
|
|
271
281
|
}
|
|
282
|
+
function getElementLookupKey(element, state) {
|
|
283
|
+
if (element.key) {
|
|
284
|
+
return element.key;
|
|
285
|
+
}
|
|
286
|
+
const cached = state.syntheticElementIds.get(element);
|
|
287
|
+
if (cached !== void 0) {
|
|
288
|
+
return cached;
|
|
289
|
+
}
|
|
290
|
+
const id = `fb-synthetic-${state.syntheticElementIdCounter++}`;
|
|
291
|
+
state.syntheticElementIds.set(element, id);
|
|
292
|
+
return id;
|
|
293
|
+
}
|
|
272
294
|
function pathJoin(base, key) {
|
|
273
295
|
return base ? `${base}.${key}` : key;
|
|
274
296
|
}
|
|
@@ -2664,23 +2686,35 @@ function setEmptyFileContainer(fileContainer, state, hint) {
|
|
|
2664
2686
|
</div>
|
|
2665
2687
|
`;
|
|
2666
2688
|
}
|
|
2689
|
+
var dragDropHandlers = /* @__PURE__ */ new WeakMap();
|
|
2667
2690
|
function setupDragAndDrop(element, dropHandler) {
|
|
2668
|
-
|
|
2691
|
+
const prev = dragDropHandlers.get(element);
|
|
2692
|
+
if (prev) {
|
|
2693
|
+
element.removeEventListener("dragover", prev.dragover);
|
|
2694
|
+
element.removeEventListener("dragleave", prev.dragleave);
|
|
2695
|
+
element.removeEventListener("drop", prev.drop);
|
|
2696
|
+
}
|
|
2697
|
+
const dragover = (e) => {
|
|
2669
2698
|
e.preventDefault();
|
|
2670
2699
|
element.classList.add("border-blue-500", "bg-blue-50");
|
|
2671
|
-
}
|
|
2672
|
-
|
|
2700
|
+
};
|
|
2701
|
+
const dragleave = (e) => {
|
|
2673
2702
|
e.preventDefault();
|
|
2674
2703
|
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2704
|
+
};
|
|
2705
|
+
const drop = (e) => {
|
|
2677
2706
|
var _a;
|
|
2678
2707
|
e.preventDefault();
|
|
2679
2708
|
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2680
|
-
|
|
2681
|
-
|
|
2709
|
+
const files = (_a = e.dataTransfer) == null ? void 0 : _a.files;
|
|
2710
|
+
if (files) {
|
|
2711
|
+
dropHandler(files);
|
|
2682
2712
|
}
|
|
2683
|
-
}
|
|
2713
|
+
};
|
|
2714
|
+
element.addEventListener("dragover", dragover);
|
|
2715
|
+
element.addEventListener("dragleave", dragleave);
|
|
2716
|
+
element.addEventListener("drop", drop);
|
|
2717
|
+
dragDropHandlers.set(element, { dragover, dragleave, drop });
|
|
2684
2718
|
}
|
|
2685
2719
|
|
|
2686
2720
|
// src/components/file/preview.ts
|
|
@@ -3957,7 +3991,7 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3957
3991
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(currentRid)) == null ? void 0 : _a2.file);
|
|
3958
3992
|
}
|
|
3959
3993
|
if (hiddenInput) hiddenInput.value = "";
|
|
3960
|
-
|
|
3994
|
+
renderEmptySingleState();
|
|
3961
3995
|
}
|
|
3962
3996
|
};
|
|
3963
3997
|
const buildDeps = () => ({
|
|
@@ -3972,6 +4006,9 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3972
4006
|
fileContainer.className = "file-preview-container";
|
|
3973
4007
|
fileContainer.removeAttribute("style");
|
|
3974
4008
|
fileContainer.onclick = null;
|
|
4009
|
+
while (fileContainer.firstChild) {
|
|
4010
|
+
fileContainer.removeChild(fileContainer.firstChild);
|
|
4011
|
+
}
|
|
3975
4012
|
const row = document.createElement("div");
|
|
3976
4013
|
row.className = "fb-file-card-row";
|
|
3977
4014
|
row.style.cssText = "display:flex;gap:8px;align-items:stretch;";
|
|
@@ -5516,7 +5553,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
5516
5553
|
function extractChildDefaults(elements) {
|
|
5517
5554
|
const defaults = {};
|
|
5518
5555
|
for (const child of elements) {
|
|
5519
|
-
if ("default" in child && child.default !== void 0) {
|
|
5556
|
+
if (child.key && "default" in child && child.default !== void 0) {
|
|
5520
5557
|
defaults[child.key] = child.default;
|
|
5521
5558
|
}
|
|
5522
5559
|
}
|
|
@@ -5613,8 +5650,8 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
5613
5650
|
};
|
|
5614
5651
|
element.elements.forEach((child) => {
|
|
5615
5652
|
var _a2, _b2;
|
|
5616
|
-
if (child.hidden || child.type === "hidden") {
|
|
5617
|
-
const prefillVal = (_b2 = (_a2 = containerPrefill[child.key]) != null ? _a2 : child.default) != null ? _b2 : null;
|
|
5653
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5654
|
+
const prefillVal = (_b2 = (_a2 = containerPrefill[child.key]) != null ? _a2 : "default" in child ? child.default : null) != null ? _b2 : null;
|
|
5618
5655
|
itemsWrap.appendChild(
|
|
5619
5656
|
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
5620
5657
|
);
|
|
@@ -5690,11 +5727,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5690
5727
|
}
|
|
5691
5728
|
element.elements.forEach((child) => {
|
|
5692
5729
|
var _a2;
|
|
5693
|
-
if (child.hidden || child.type === "hidden") {
|
|
5730
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5694
5731
|
childWrapper.appendChild(
|
|
5695
5732
|
createHiddenInput(
|
|
5696
5733
|
pathJoin(subCtx.path, child.key),
|
|
5697
|
-
(_a2 = child.default) != null ? _a2 : null
|
|
5734
|
+
(_a2 = "default" in child ? child.default : null) != null ? _a2 : null
|
|
5698
5735
|
)
|
|
5699
5736
|
);
|
|
5700
5737
|
} else {
|
|
@@ -5769,8 +5806,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5769
5806
|
}
|
|
5770
5807
|
element.elements.forEach((child) => {
|
|
5771
5808
|
var _a3, _b2;
|
|
5772
|
-
if (child.hidden || child.type === "hidden") {
|
|
5773
|
-
const prefillVal = (_b2 = (_a3 = prefillObj == null ? void 0 : prefillObj[child.key]) != null ? _a3 : child.default) != null ? _b2 : null;
|
|
5809
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5810
|
+
const prefillVal = (_b2 = (_a3 = prefillObj == null ? void 0 : prefillObj[child.key]) != null ? _a3 : "default" in child ? child.default : null) != null ? _b2 : null;
|
|
5774
5811
|
childWrapper.appendChild(
|
|
5775
5812
|
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
5776
5813
|
);
|
|
@@ -5826,11 +5863,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5826
5863
|
}
|
|
5827
5864
|
element.elements.forEach((child) => {
|
|
5828
5865
|
var _a2;
|
|
5829
|
-
if (child.hidden || child.type === "hidden") {
|
|
5866
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5830
5867
|
childWrapper.appendChild(
|
|
5831
5868
|
createHiddenInput(
|
|
5832
5869
|
pathJoin(subCtx.path, child.key),
|
|
5833
|
-
(_a2 = child.default) != null ? _a2 : null
|
|
5870
|
+
(_a2 = "default" in child ? child.default : null) != null ? _a2 : null
|
|
5834
5871
|
)
|
|
5835
5872
|
);
|
|
5836
5873
|
} else {
|
|
@@ -6020,6 +6057,7 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
6020
6057
|
if (isPlainObject(itemValue)) {
|
|
6021
6058
|
element.elements.forEach((childElement) => {
|
|
6022
6059
|
var _a, _b;
|
|
6060
|
+
if (childElement.type === "markdown" || !childElement.key) return;
|
|
6023
6061
|
const childPath = `${fieldPath}[${index}].${childElement.key}`;
|
|
6024
6062
|
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
6025
6063
|
const richChild = childElement;
|
|
@@ -6060,6 +6098,7 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
6060
6098
|
}
|
|
6061
6099
|
element.elements.forEach((childElement) => {
|
|
6062
6100
|
var _a, _b;
|
|
6101
|
+
if (childElement.type === "markdown" || !childElement.key) return;
|
|
6063
6102
|
const childPath = `${fieldPath}.${childElement.key}`;
|
|
6064
6103
|
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
6065
6104
|
const richChild = childElement;
|
|
@@ -8827,6 +8866,250 @@ function updateRichInputField(element, fieldPath, value, context) {
|
|
|
8827
8866
|
}
|
|
8828
8867
|
}
|
|
8829
8868
|
|
|
8869
|
+
// src/components/markdown/snarkdown.ts
|
|
8870
|
+
var TAGS = {
|
|
8871
|
+
"": ["<em>", "</em>"],
|
|
8872
|
+
_: ["<strong>", "</strong>"],
|
|
8873
|
+
"*": ["<strong>", "</strong>"],
|
|
8874
|
+
"~": ["<s>", "</s>"],
|
|
8875
|
+
"\n": ["<br />"],
|
|
8876
|
+
" ": ["<br />"],
|
|
8877
|
+
"-": ["<hr />"]
|
|
8878
|
+
};
|
|
8879
|
+
function outdent(str) {
|
|
8880
|
+
return str.replace(
|
|
8881
|
+
RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"),
|
|
8882
|
+
""
|
|
8883
|
+
);
|
|
8884
|
+
}
|
|
8885
|
+
function encodeAttr(str) {
|
|
8886
|
+
return (str + "").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
8887
|
+
}
|
|
8888
|
+
function parse(md, prevLinks) {
|
|
8889
|
+
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;
|
|
8890
|
+
const context = [];
|
|
8891
|
+
let out = "";
|
|
8892
|
+
const links = prevLinks || {};
|
|
8893
|
+
let last = 0;
|
|
8894
|
+
let chunk;
|
|
8895
|
+
let prev;
|
|
8896
|
+
let token;
|
|
8897
|
+
let inner;
|
|
8898
|
+
let t2;
|
|
8899
|
+
function tag(token2) {
|
|
8900
|
+
const desc = TAGS[token2[1] || ""];
|
|
8901
|
+
const end = context[context.length - 1] === token2;
|
|
8902
|
+
if (!desc) return token2;
|
|
8903
|
+
if (!desc[1]) return desc[0];
|
|
8904
|
+
if (end) context.pop();
|
|
8905
|
+
else context.push(token2);
|
|
8906
|
+
return desc[end ? 1 : 0];
|
|
8907
|
+
}
|
|
8908
|
+
function flush() {
|
|
8909
|
+
let str = "";
|
|
8910
|
+
while (context.length) str += tag(context[context.length - 1]);
|
|
8911
|
+
return str;
|
|
8912
|
+
}
|
|
8913
|
+
md = md.replace(/^\[(.+?)\]:\s*(.+)$/gm, (_s, name, url) => {
|
|
8914
|
+
links[name.toLowerCase()] = url;
|
|
8915
|
+
return "";
|
|
8916
|
+
}).replace(/^\n+|\n+$/g, "");
|
|
8917
|
+
while (token = tokenizer.exec(md)) {
|
|
8918
|
+
prev = md.substring(last, token.index);
|
|
8919
|
+
last = tokenizer.lastIndex;
|
|
8920
|
+
chunk = token[0];
|
|
8921
|
+
if (prev.match(/[^\\](\\\\)*\\$/)) ; else if (t2 = token[3] || token[4]) {
|
|
8922
|
+
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>";
|
|
8923
|
+
} else if (t2 = token[6]) {
|
|
8924
|
+
if (t2.match(/\./)) {
|
|
8925
|
+
token[5] = token[5].replace(/^\d+/gm, "");
|
|
8926
|
+
}
|
|
8927
|
+
inner = parse(outdent(token[5].replace(/^\s*[>*+.-]/gm, "")));
|
|
8928
|
+
if (t2 === ">") t2 = "blockquote";
|
|
8929
|
+
else {
|
|
8930
|
+
t2 = t2.match(/\./) ? "ol" : "ul";
|
|
8931
|
+
inner = inner.replace(/^(.*)(\n|$)/gm, "<li>$1</li>");
|
|
8932
|
+
}
|
|
8933
|
+
chunk = "<" + t2 + ">" + inner + "</" + t2 + ">";
|
|
8934
|
+
} else if (token[8]) {
|
|
8935
|
+
chunk = `<img src="${encodeAttr(token[8])}" alt="${encodeAttr(token[7])}">`;
|
|
8936
|
+
} else if (token[10]) {
|
|
8937
|
+
out = out.replace(
|
|
8938
|
+
"<a>",
|
|
8939
|
+
`<a href="${encodeAttr(token[11] || links[prev.toLowerCase()])}">`
|
|
8940
|
+
);
|
|
8941
|
+
chunk = flush() + "</a>";
|
|
8942
|
+
} else if (token[9]) {
|
|
8943
|
+
chunk = "<a>";
|
|
8944
|
+
} else if (token[12] || token[14]) {
|
|
8945
|
+
t2 = "h" + (token[14] ? token[14].length : token[13] > "=" ? 1 : 2);
|
|
8946
|
+
chunk = "<" + t2 + ">" + parse(token[12] || token[15], links) + "</" + t2 + ">";
|
|
8947
|
+
} else if (token[16]) {
|
|
8948
|
+
chunk = "<code>" + encodeAttr(token[16]) + "</code>";
|
|
8949
|
+
} else if (token[17] || token[1]) {
|
|
8950
|
+
chunk = tag(token[17] || "--");
|
|
8951
|
+
}
|
|
8952
|
+
out += prev;
|
|
8953
|
+
out += chunk;
|
|
8954
|
+
}
|
|
8955
|
+
return (out + md.substring(last) + flush()).replace(/^\n+|\n+$/g, "");
|
|
8956
|
+
}
|
|
8957
|
+
|
|
8958
|
+
// src/components/markdown/render.ts
|
|
8959
|
+
var STYLE_ID2 = "fb-markdown-styles";
|
|
8960
|
+
function ensureMarkdownStyles() {
|
|
8961
|
+
if (typeof document === "undefined") return;
|
|
8962
|
+
if (document.getElementById(STYLE_ID2)) return;
|
|
8963
|
+
const style = document.createElement("style");
|
|
8964
|
+
style.id = STYLE_ID2;
|
|
8965
|
+
style.setAttribute("data-fb-markdown-styles", "true");
|
|
8966
|
+
style.textContent = `
|
|
8967
|
+
.fb-markdown {
|
|
8968
|
+
font-family: var(--fb-font-family, inherit);
|
|
8969
|
+
font-size: var(--fb-font-size, 1rem);
|
|
8970
|
+
color: var(--fb-text-color, #1f2937);
|
|
8971
|
+
line-height: 1.6;
|
|
8972
|
+
}
|
|
8973
|
+
.fb-markdown h1,
|
|
8974
|
+
.fb-markdown h2,
|
|
8975
|
+
.fb-markdown h3,
|
|
8976
|
+
.fb-markdown h4,
|
|
8977
|
+
.fb-markdown h5,
|
|
8978
|
+
.fb-markdown h6 {
|
|
8979
|
+
font-weight: var(--fb-font-weight-medium, 600);
|
|
8980
|
+
color: var(--fb-text-color, #1f2937);
|
|
8981
|
+
margin-top: 0.75em;
|
|
8982
|
+
margin-bottom: 0.25em;
|
|
8983
|
+
}
|
|
8984
|
+
.fb-markdown h1 { font-size: 1.5rem; }
|
|
8985
|
+
.fb-markdown h2 { font-size: 1.25rem; }
|
|
8986
|
+
.fb-markdown h3 { font-size: 1.1rem; }
|
|
8987
|
+
.fb-markdown h4,
|
|
8988
|
+
.fb-markdown h5,
|
|
8989
|
+
.fb-markdown h6 { font-size: 1rem; }
|
|
8990
|
+
.fb-markdown p {
|
|
8991
|
+
margin-top: 0;
|
|
8992
|
+
margin-bottom: 0.5em;
|
|
8993
|
+
}
|
|
8994
|
+
.fb-markdown ul,
|
|
8995
|
+
.fb-markdown ol {
|
|
8996
|
+
margin: 0.25em 0 0.5em 1.5em;
|
|
8997
|
+
padding: 0;
|
|
8998
|
+
}
|
|
8999
|
+
.fb-markdown li {
|
|
9000
|
+
margin-bottom: 0.15em;
|
|
9001
|
+
}
|
|
9002
|
+
.fb-markdown a {
|
|
9003
|
+
color: var(--fb-primary-color, #2563eb);
|
|
9004
|
+
text-decoration: underline;
|
|
9005
|
+
}
|
|
9006
|
+
.fb-markdown a:hover {
|
|
9007
|
+
opacity: 0.8;
|
|
9008
|
+
}
|
|
9009
|
+
.fb-markdown code {
|
|
9010
|
+
background: var(--fb-input-background-color, #f3f4f6);
|
|
9011
|
+
border-radius: 3px;
|
|
9012
|
+
padding: 0.1em 0.35em;
|
|
9013
|
+
font-size: 0.9em;
|
|
9014
|
+
font-family: monospace;
|
|
9015
|
+
}
|
|
9016
|
+
.fb-markdown pre {
|
|
9017
|
+
background: var(--fb-input-background-color, #f3f4f6);
|
|
9018
|
+
border-radius: var(--fb-border-radius, 0.375rem);
|
|
9019
|
+
padding: 0.75em 1em;
|
|
9020
|
+
overflow-x: auto;
|
|
9021
|
+
margin: 0.5em 0;
|
|
9022
|
+
}
|
|
9023
|
+
.fb-markdown pre code {
|
|
9024
|
+
background: none;
|
|
9025
|
+
padding: 0;
|
|
9026
|
+
border-radius: 0;
|
|
9027
|
+
font-size: inherit;
|
|
9028
|
+
}
|
|
9029
|
+
.fb-markdown blockquote {
|
|
9030
|
+
border-left: 3px solid var(--fb-border-color, #d1d5db);
|
|
9031
|
+
margin: 0.5em 0;
|
|
9032
|
+
padding-left: 1em;
|
|
9033
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
9034
|
+
}
|
|
9035
|
+
.fb-markdown hr {
|
|
9036
|
+
border: none;
|
|
9037
|
+
border-top: 1px solid var(--fb-border-color, #d1d5db);
|
|
9038
|
+
margin: 0.75em 0;
|
|
9039
|
+
}
|
|
9040
|
+
.fb-markdown strong { font-weight: var(--fb-font-weight-medium, 600); }
|
|
9041
|
+
.fb-markdown em { font-style: italic; }
|
|
9042
|
+
.fb-markdown s { text-decoration: line-through; }
|
|
9043
|
+
`;
|
|
9044
|
+
document.head.appendChild(style);
|
|
9045
|
+
}
|
|
9046
|
+
var ANCHOR_DANGEROUS_SCHEMES = [
|
|
9047
|
+
"javascript:",
|
|
9048
|
+
"data:",
|
|
9049
|
+
"vbscript:",
|
|
9050
|
+
"blob:"
|
|
9051
|
+
];
|
|
9052
|
+
var IMG_DANGEROUS_SCHEMES = ["javascript:", "vbscript:", "blob:"];
|
|
9053
|
+
function isImgSrcDangerous(normalized) {
|
|
9054
|
+
if (IMG_DANGEROUS_SCHEMES.some((scheme) => normalized.startsWith(scheme))) {
|
|
9055
|
+
return true;
|
|
9056
|
+
}
|
|
9057
|
+
if (normalized.startsWith("data:") && !normalized.startsWith("data:image/")) {
|
|
9058
|
+
return true;
|
|
9059
|
+
}
|
|
9060
|
+
return false;
|
|
9061
|
+
}
|
|
9062
|
+
function escapeRawHtml(content) {
|
|
9063
|
+
return content.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
9064
|
+
}
|
|
9065
|
+
function sanitizeElements(container) {
|
|
9066
|
+
const anchors = container.querySelectorAll("a[href]");
|
|
9067
|
+
anchors.forEach((a) => {
|
|
9068
|
+
var _a;
|
|
9069
|
+
const href = (_a = a.getAttribute("href")) != null ? _a : "";
|
|
9070
|
+
const normalized = href.trim().toLowerCase();
|
|
9071
|
+
const isDangerous = ANCHOR_DANGEROUS_SCHEMES.some(
|
|
9072
|
+
(scheme) => normalized.startsWith(scheme)
|
|
9073
|
+
);
|
|
9074
|
+
if (isDangerous) {
|
|
9075
|
+
a.setAttribute("href", "#");
|
|
9076
|
+
}
|
|
9077
|
+
a.setAttribute("target", "_blank");
|
|
9078
|
+
a.setAttribute("rel", "noopener noreferrer");
|
|
9079
|
+
});
|
|
9080
|
+
const images = container.querySelectorAll("img[src]");
|
|
9081
|
+
images.forEach((img) => {
|
|
9082
|
+
var _a;
|
|
9083
|
+
const src = (_a = img.getAttribute("src")) != null ? _a : "";
|
|
9084
|
+
const normalized = src.trim().toLowerCase();
|
|
9085
|
+
if (isImgSrcDangerous(normalized)) {
|
|
9086
|
+
img.setAttribute("src", "");
|
|
9087
|
+
}
|
|
9088
|
+
});
|
|
9089
|
+
}
|
|
9090
|
+
function renderMarkdown(element, _ctx, parent) {
|
|
9091
|
+
if (typeof element.content !== "string") {
|
|
9092
|
+
throw new Error(
|
|
9093
|
+
`renderMarkdown: markdown element${element.key ? ` "${element.key}"` : ""} requires "content" to be a string (got ${element.content === null ? "null" : typeof element.content})`
|
|
9094
|
+
);
|
|
9095
|
+
}
|
|
9096
|
+
ensureMarkdownStyles();
|
|
9097
|
+
const wrapper = document.createElement("div");
|
|
9098
|
+
wrapper.className = "fb-markdown";
|
|
9099
|
+
const escaped = escapeRawHtml(element.content);
|
|
9100
|
+
wrapper.innerHTML = parse(escaped);
|
|
9101
|
+
sanitizeElements(wrapper);
|
|
9102
|
+
parent.appendChild(wrapper);
|
|
9103
|
+
return wrapper;
|
|
9104
|
+
}
|
|
9105
|
+
|
|
9106
|
+
// src/components/markdown/index.ts
|
|
9107
|
+
function validateMarkdown(_element, _key, _context) {
|
|
9108
|
+
return { value: void 0, errors: [], skip: true };
|
|
9109
|
+
}
|
|
9110
|
+
function updateMarkdown(_element, _fieldPath, _value, _context) {
|
|
9111
|
+
}
|
|
9112
|
+
|
|
8830
9113
|
// src/components/index.ts
|
|
8831
9114
|
function showTooltip(tooltipId, button) {
|
|
8832
9115
|
const tooltip = document.getElementById(tooltipId);
|
|
@@ -9058,9 +9341,10 @@ function setupEnableIfListeners(wrapper, element, ctx) {
|
|
|
9058
9341
|
});
|
|
9059
9342
|
}
|
|
9060
9343
|
function createFieldLabel(element) {
|
|
9344
|
+
var _a, _b;
|
|
9061
9345
|
const title = document.createElement("label");
|
|
9062
9346
|
title.className = "text-sm font-medium text-gray-900";
|
|
9063
|
-
title.textContent = element.label
|
|
9347
|
+
title.textContent = (_b = (_a = element.label) != null ? _a : element.key) != null ? _b : "";
|
|
9064
9348
|
if (element.required) {
|
|
9065
9349
|
const req = document.createElement("span");
|
|
9066
9350
|
req.className = "text-red-500 ml-1";
|
|
@@ -9188,6 +9472,29 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
9188
9472
|
}
|
|
9189
9473
|
}
|
|
9190
9474
|
function renderElement2(element, ctx) {
|
|
9475
|
+
if (element.type === "markdown") {
|
|
9476
|
+
if (element.hidden === true) {
|
|
9477
|
+
const placeholder = document.createElement("div");
|
|
9478
|
+
placeholder.style.display = "none";
|
|
9479
|
+
placeholder.setAttribute("data-fb-hidden-markdown", "true");
|
|
9480
|
+
return placeholder;
|
|
9481
|
+
}
|
|
9482
|
+
const initiallyDisabled2 = shouldDisableElement(element, ctx);
|
|
9483
|
+
const outerWrapper = document.createElement("div");
|
|
9484
|
+
outerWrapper.className = "mb-6 fb-field-wrapper fb-markdown-wrapper";
|
|
9485
|
+
outerWrapper.setAttribute(
|
|
9486
|
+
"data-field-key",
|
|
9487
|
+
getElementLookupKey(element, ctx.state)
|
|
9488
|
+
);
|
|
9489
|
+
renderMarkdown(element, ctx, outerWrapper);
|
|
9490
|
+
if (initiallyDisabled2) {
|
|
9491
|
+
outerWrapper.style.display = "none";
|
|
9492
|
+
outerWrapper.classList.add("fb-field-wrapper-disabled");
|
|
9493
|
+
outerWrapper.setAttribute("data-conditionally-disabled", "true");
|
|
9494
|
+
}
|
|
9495
|
+
setupEnableIfListeners(outerWrapper, element, ctx);
|
|
9496
|
+
return outerWrapper;
|
|
9497
|
+
}
|
|
9191
9498
|
const initiallyDisabled = shouldDisableElement(element, ctx);
|
|
9192
9499
|
const wrapper = document.createElement("div");
|
|
9193
9500
|
wrapper.className = "mb-6 fb-field-wrapper";
|
|
@@ -9398,7 +9705,9 @@ function createInstanceState(config) {
|
|
|
9398
9705
|
translations: mergedTranslations
|
|
9399
9706
|
},
|
|
9400
9707
|
debounceTimer: null,
|
|
9401
|
-
prefill: {}
|
|
9708
|
+
prefill: {},
|
|
9709
|
+
syntheticElementIds: /* @__PURE__ */ new WeakMap(),
|
|
9710
|
+
syntheticElementIdCounter: 0
|
|
9402
9711
|
};
|
|
9403
9712
|
}
|
|
9404
9713
|
function generateInstanceId() {
|
|
@@ -9676,6 +9985,11 @@ var componentRegistry = {
|
|
|
9676
9985
|
// Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
|
|
9677
9986
|
validate: validateHiddenElement,
|
|
9678
9987
|
update: updateHiddenField
|
|
9988
|
+
},
|
|
9989
|
+
markdown: {
|
|
9990
|
+
// Display-only element — no value, no errors, skip from form data
|
|
9991
|
+
validate: validateMarkdown,
|
|
9992
|
+
update: updateMarkdown
|
|
9679
9993
|
}
|
|
9680
9994
|
};
|
|
9681
9995
|
function getComponentOperations(elementType) {
|
|
@@ -10104,7 +10418,7 @@ var FormBuilderInstance = class {
|
|
|
10104
10418
|
}
|
|
10105
10419
|
schema.elements.forEach((element) => {
|
|
10106
10420
|
var _a, _b;
|
|
10107
|
-
if (element.hidden || element.type === "hidden") {
|
|
10421
|
+
if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
|
|
10108
10422
|
const val = (_b = (_a = prefill == null ? void 0 : prefill[element.key]) != null ? _a : element.default) != null ? _b : null;
|
|
10109
10423
|
fieldsWrapper.appendChild(createHiddenInput(element.key, val));
|
|
10110
10424
|
return;
|
|
@@ -10139,7 +10453,8 @@ var FormBuilderInstance = class {
|
|
|
10139
10453
|
const errors = [];
|
|
10140
10454
|
const data = {};
|
|
10141
10455
|
const validateElement2 = (element, ctx, customScopeRoot = null) => {
|
|
10142
|
-
|
|
10456
|
+
var _a;
|
|
10457
|
+
const key = (_a = element.key) != null ? _a : "";
|
|
10143
10458
|
const scopeRoot = customScopeRoot || this.state.formRoot;
|
|
10144
10459
|
const componentContext = {
|
|
10145
10460
|
scopeRoot,
|
|
@@ -10157,7 +10472,8 @@ var FormBuilderInstance = class {
|
|
|
10157
10472
|
errors.push(...componentResult.errors);
|
|
10158
10473
|
return {
|
|
10159
10474
|
value: componentResult.value,
|
|
10160
|
-
spread: !!componentResult.spread
|
|
10475
|
+
spread: !!componentResult.spread,
|
|
10476
|
+
skip: !!componentResult.skip
|
|
10161
10477
|
};
|
|
10162
10478
|
}
|
|
10163
10479
|
console.warn(`Unknown field type "${element.type}" for key "${key}"`);
|
|
@@ -10179,6 +10495,9 @@ var FormBuilderInstance = class {
|
|
|
10179
10495
|
);
|
|
10180
10496
|
}
|
|
10181
10497
|
}
|
|
10498
|
+
if (element.type === "markdown") {
|
|
10499
|
+
return;
|
|
10500
|
+
}
|
|
10182
10501
|
if (element.hidden || element.type === "hidden") {
|
|
10183
10502
|
const hiddenInput = this.state.formRoot.querySelector(
|
|
10184
10503
|
`input[type="hidden"][data-hidden-field="true"][name="${element.key}"]`
|
|
@@ -10191,9 +10510,10 @@ var FormBuilderInstance = class {
|
|
|
10191
10510
|
}
|
|
10192
10511
|
} else {
|
|
10193
10512
|
const result = validateElement2(element, { path: "" });
|
|
10513
|
+
if (result.skip) return;
|
|
10194
10514
|
if (result.spread && result.value !== null && typeof result.value === "object") {
|
|
10195
10515
|
Object.assign(data, result.value);
|
|
10196
|
-
} else {
|
|
10516
|
+
} else if (element.key) {
|
|
10197
10517
|
data[element.key] = result.value;
|
|
10198
10518
|
}
|
|
10199
10519
|
}
|
|
@@ -10269,6 +10589,7 @@ var FormBuilderInstance = class {
|
|
|
10269
10589
|
buildHiddenFieldsData(elements) {
|
|
10270
10590
|
const data = {};
|
|
10271
10591
|
for (const element of elements) {
|
|
10592
|
+
if (element.type === "markdown" || !element.key) continue;
|
|
10272
10593
|
const key = element.key;
|
|
10273
10594
|
if (element.hidden && element.default !== void 0) {
|
|
10274
10595
|
data[key] = element.default;
|
|
@@ -10390,6 +10711,72 @@ var FormBuilderInstance = class {
|
|
|
10390
10711
|
);
|
|
10391
10712
|
}
|
|
10392
10713
|
}
|
|
10714
|
+
/**
|
|
10715
|
+
* Find the field wrapper DOM element for a given element+path combo.
|
|
10716
|
+
* Used by reevaluateConditionalFields.
|
|
10717
|
+
*/
|
|
10718
|
+
findFieldWrapper(element, currentPath) {
|
|
10719
|
+
const formRoot = this.state.formRoot;
|
|
10720
|
+
const lookupKey = getElementLookupKey(element, this.state);
|
|
10721
|
+
if (!currentPath) {
|
|
10722
|
+
return formRoot.querySelector(`[data-field-key="${lookupKey}"]`);
|
|
10723
|
+
}
|
|
10724
|
+
const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
|
|
10725
|
+
if (pathMatch) {
|
|
10726
|
+
const containerEl2 = formRoot.querySelector(
|
|
10727
|
+
`[data-container-item="${pathMatch[1]}[${pathMatch[2]}]"]`
|
|
10728
|
+
);
|
|
10729
|
+
return containerEl2 ? containerEl2.querySelector(`[data-field-key="${lookupKey}"]`) : null;
|
|
10730
|
+
}
|
|
10731
|
+
const containerEl = formRoot.querySelector(
|
|
10732
|
+
`[data-container="${currentPath}"]`
|
|
10733
|
+
);
|
|
10734
|
+
return containerEl ? containerEl.querySelector(`[data-field-key="${lookupKey}"]`) : null;
|
|
10735
|
+
}
|
|
10736
|
+
/**
|
|
10737
|
+
* Apply enableIf show/hide logic to a single field wrapper.
|
|
10738
|
+
* Extracted to reduce cyclomatic complexity of checkElements.
|
|
10739
|
+
*/
|
|
10740
|
+
applyEnableIfVisibility(element, wrapper, currentPath, fullPath, formData) {
|
|
10741
|
+
var _a, _b, _c, _d;
|
|
10742
|
+
try {
|
|
10743
|
+
const scope = (_a = element.enableIf.scope) != null ? _a : "relative";
|
|
10744
|
+
const containerData = scope === "relative" && currentPath ? getValueByPath(formData, currentPath) : void 0;
|
|
10745
|
+
const shouldEnable = evaluateEnableCondition(
|
|
10746
|
+
element.enableIf,
|
|
10747
|
+
formData,
|
|
10748
|
+
containerData
|
|
10749
|
+
);
|
|
10750
|
+
const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
|
|
10751
|
+
if (shouldEnable && isCurrentlyDisabled) {
|
|
10752
|
+
const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
|
|
10753
|
+
const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
|
|
10754
|
+
const newWrapper = renderElement2(element, {
|
|
10755
|
+
path: currentPath,
|
|
10756
|
+
prefill: prefillContext,
|
|
10757
|
+
formData,
|
|
10758
|
+
state: this.state,
|
|
10759
|
+
instance: this
|
|
10760
|
+
});
|
|
10761
|
+
(_b = wrapper.parentNode) == null ? void 0 : _b.replaceChild(newWrapper, wrapper);
|
|
10762
|
+
} else if (!shouldEnable && !isCurrentlyDisabled) {
|
|
10763
|
+
const disabledWrapper = document.createElement("div");
|
|
10764
|
+
disabledWrapper.className = "fb-field-wrapper-disabled";
|
|
10765
|
+
disabledWrapper.style.display = "none";
|
|
10766
|
+
disabledWrapper.setAttribute(
|
|
10767
|
+
"data-field-key",
|
|
10768
|
+
getElementLookupKey(element, this.state)
|
|
10769
|
+
);
|
|
10770
|
+
disabledWrapper.setAttribute("data-conditionally-disabled", "true");
|
|
10771
|
+
(_c = wrapper.parentNode) == null ? void 0 : _c.replaceChild(disabledWrapper, wrapper);
|
|
10772
|
+
}
|
|
10773
|
+
} catch (error) {
|
|
10774
|
+
console.error(
|
|
10775
|
+
`Error re-evaluating enableIf for field "${(_d = element.key) != null ? _d : "<no key>"}" at path "${fullPath}":`,
|
|
10776
|
+
error
|
|
10777
|
+
);
|
|
10778
|
+
}
|
|
10779
|
+
}
|
|
10393
10780
|
/**
|
|
10394
10781
|
* Re-evaluate all conditional fields (enableIf) based on current form data
|
|
10395
10782
|
* This is called automatically when form data changes (via onChange events)
|
|
@@ -10399,98 +10786,32 @@ var FormBuilderInstance = class {
|
|
|
10399
10786
|
const formData = this.validateForm(true).data;
|
|
10400
10787
|
const checkElements = (elements, currentPath) => {
|
|
10401
10788
|
elements.forEach((element) => {
|
|
10402
|
-
var _a, _b
|
|
10403
|
-
const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
|
|
10789
|
+
var _a, _b;
|
|
10790
|
+
const fullPath = currentPath ? `${currentPath}.${(_a = element.key) != null ? _a : ""}` : (_b = element.key) != null ? _b : "";
|
|
10404
10791
|
if (element.enableIf) {
|
|
10405
|
-
|
|
10406
|
-
if (currentPath) {
|
|
10407
|
-
const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
|
|
10408
|
-
if (pathMatch) {
|
|
10409
|
-
const containerKey = pathMatch[1];
|
|
10410
|
-
const containerIndex = pathMatch[2];
|
|
10411
|
-
const containerElement = this.state.formRoot.querySelector(
|
|
10412
|
-
`[data-container-item="${containerKey}[${containerIndex}]"]`
|
|
10413
|
-
);
|
|
10414
|
-
if (containerElement) {
|
|
10415
|
-
fieldWrapper = containerElement.querySelector(
|
|
10416
|
-
`[data-field-key="${element.key}"]`
|
|
10417
|
-
);
|
|
10418
|
-
}
|
|
10419
|
-
} else {
|
|
10420
|
-
const containerElement = this.state.formRoot.querySelector(
|
|
10421
|
-
`[data-container="${currentPath}"]`
|
|
10422
|
-
);
|
|
10423
|
-
if (containerElement) {
|
|
10424
|
-
fieldWrapper = containerElement.querySelector(
|
|
10425
|
-
`[data-field-key="${element.key}"]`
|
|
10426
|
-
);
|
|
10427
|
-
}
|
|
10428
|
-
}
|
|
10429
|
-
} else {
|
|
10430
|
-
fieldWrapper = this.state.formRoot.querySelector(
|
|
10431
|
-
`[data-field-key="${element.key}"]`
|
|
10432
|
-
);
|
|
10433
|
-
}
|
|
10792
|
+
const fieldWrapper = this.findFieldWrapper(element, currentPath);
|
|
10434
10793
|
if (fieldWrapper) {
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
const shouldEnable = evaluateEnableCondition(
|
|
10443
|
-
element.enableIf,
|
|
10444
|
-
formData,
|
|
10445
|
-
// Use complete formData for absolute scope
|
|
10446
|
-
containerData
|
|
10447
|
-
// Use container-specific data for relative scope
|
|
10448
|
-
);
|
|
10449
|
-
const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
|
|
10450
|
-
if (shouldEnable && isCurrentlyDisabled) {
|
|
10451
|
-
const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
|
|
10452
|
-
const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
|
|
10453
|
-
const newWrapper = renderElement2(element, {
|
|
10454
|
-
path: currentPath,
|
|
10455
|
-
// Use container path (empty string for root-level)
|
|
10456
|
-
prefill: prefillContext,
|
|
10457
|
-
formData,
|
|
10458
|
-
// Pass complete formData for enableIf evaluation
|
|
10459
|
-
state: this.state,
|
|
10460
|
-
instance: this
|
|
10461
|
-
});
|
|
10462
|
-
(_b = wrapper.parentNode) == null ? void 0 : _b.replaceChild(newWrapper, wrapper);
|
|
10463
|
-
} else if (!shouldEnable && !isCurrentlyDisabled) {
|
|
10464
|
-
const disabledWrapper = document.createElement("div");
|
|
10465
|
-
disabledWrapper.className = "fb-field-wrapper-disabled";
|
|
10466
|
-
disabledWrapper.style.display = "none";
|
|
10467
|
-
disabledWrapper.setAttribute("data-field-key", element.key);
|
|
10468
|
-
disabledWrapper.setAttribute(
|
|
10469
|
-
"data-conditionally-disabled",
|
|
10470
|
-
"true"
|
|
10471
|
-
);
|
|
10472
|
-
(_c = wrapper.parentNode) == null ? void 0 : _c.replaceChild(disabledWrapper, wrapper);
|
|
10473
|
-
}
|
|
10474
|
-
} catch (error) {
|
|
10475
|
-
console.error(
|
|
10476
|
-
`Error re-evaluating enableIf for field "${element.key}" at path "${fullPath}":`,
|
|
10477
|
-
error
|
|
10478
|
-
);
|
|
10479
|
-
}
|
|
10794
|
+
this.applyEnableIfVisibility(
|
|
10795
|
+
element,
|
|
10796
|
+
fieldWrapper,
|
|
10797
|
+
currentPath,
|
|
10798
|
+
fullPath,
|
|
10799
|
+
formData
|
|
10800
|
+
);
|
|
10480
10801
|
}
|
|
10481
10802
|
}
|
|
10482
10803
|
if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
|
|
10483
|
-
const containerData = formData == null ? void 0 : formData[element.key];
|
|
10804
|
+
const containerData = element.key ? formData == null ? void 0 : formData[element.key] : void 0;
|
|
10484
10805
|
if (Array.isArray(containerData)) {
|
|
10485
10806
|
const containerItems = this.state.formRoot.querySelectorAll(
|
|
10486
10807
|
`[data-container-item]`
|
|
10487
10808
|
);
|
|
10488
|
-
const directItems = Array.from(containerItems).filter((el) => {
|
|
10809
|
+
const directItems = fullPath ? Array.from(containerItems).filter((el) => {
|
|
10489
10810
|
const attr = el.getAttribute("data-container-item") || "";
|
|
10490
10811
|
if (!attr.startsWith(`${fullPath}[`)) return false;
|
|
10491
10812
|
const suffix = attr.slice(fullPath.length);
|
|
10492
10813
|
return /^\[\d+\]$/.test(suffix);
|
|
10493
|
-
});
|
|
10814
|
+
}) : [];
|
|
10494
10815
|
directItems.forEach((el) => {
|
|
10495
10816
|
const attr = el.getAttribute("data-container-item") || "";
|
|
10496
10817
|
checkElements(element.elements, attr);
|