@dmitryvim/form-builder 0.2.29 → 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 +196 -119
- package/dist/browser/formbuilder.v0.2.30.min.js +1033 -0
- package/dist/cjs/index.cjs +406 -100
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +400 -99
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +196 -119
- 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.29.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
|
}
|
|
@@ -5454,7 +5476,7 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
5454
5476
|
function extractChildDefaults(elements) {
|
|
5455
5477
|
const defaults = {};
|
|
5456
5478
|
for (const child of elements) {
|
|
5457
|
-
if ("default" in child && child.default !== void 0) {
|
|
5479
|
+
if (child.key && "default" in child && child.default !== void 0) {
|
|
5458
5480
|
defaults[child.key] = child.default;
|
|
5459
5481
|
}
|
|
5460
5482
|
}
|
|
@@ -5549,8 +5571,8 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
5549
5571
|
inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
|
|
5550
5572
|
};
|
|
5551
5573
|
element.elements.forEach((child) => {
|
|
5552
|
-
if (child.hidden || child.type === "hidden") {
|
|
5553
|
-
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;
|
|
5554
5576
|
itemsWrap.appendChild(
|
|
5555
5577
|
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
5556
5578
|
);
|
|
@@ -5624,11 +5646,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5624
5646
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5625
5647
|
}
|
|
5626
5648
|
element.elements.forEach((child) => {
|
|
5627
|
-
if (child.hidden || child.type === "hidden") {
|
|
5649
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5628
5650
|
childWrapper.appendChild(
|
|
5629
5651
|
createHiddenInput(
|
|
5630
5652
|
pathJoin(subCtx.path, child.key),
|
|
5631
|
-
child.default ?? null
|
|
5653
|
+
("default" in child ? child.default : null) ?? null
|
|
5632
5654
|
)
|
|
5633
5655
|
);
|
|
5634
5656
|
} else {
|
|
@@ -5701,8 +5723,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5701
5723
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5702
5724
|
}
|
|
5703
5725
|
element.elements.forEach((child) => {
|
|
5704
|
-
if (child.hidden || child.type === "hidden") {
|
|
5705
|
-
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;
|
|
5706
5728
|
childWrapper.appendChild(
|
|
5707
5729
|
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
5708
5730
|
);
|
|
@@ -5757,11 +5779,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
5757
5779
|
childWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
5758
5780
|
}
|
|
5759
5781
|
element.elements.forEach((child) => {
|
|
5760
|
-
if (child.hidden || child.type === "hidden") {
|
|
5782
|
+
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
5761
5783
|
childWrapper.appendChild(
|
|
5762
5784
|
createHiddenInput(
|
|
5763
5785
|
pathJoin(subCtx.path, child.key),
|
|
5764
|
-
child.default ?? null
|
|
5786
|
+
("default" in child ? child.default : null) ?? null
|
|
5765
5787
|
)
|
|
5766
5788
|
);
|
|
5767
5789
|
} else {
|
|
@@ -5947,6 +5969,7 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
5947
5969
|
value.forEach((itemValue, index) => {
|
|
5948
5970
|
if (isPlainObject(itemValue)) {
|
|
5949
5971
|
element.elements.forEach((childElement) => {
|
|
5972
|
+
if (childElement.type === "markdown" || !childElement.key) return;
|
|
5950
5973
|
const childPath = `${fieldPath}[${index}].${childElement.key}`;
|
|
5951
5974
|
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
5952
5975
|
const richChild = childElement;
|
|
@@ -5986,6 +6009,7 @@ function updateContainerField(element, fieldPath, value, context) {
|
|
|
5986
6009
|
return;
|
|
5987
6010
|
}
|
|
5988
6011
|
element.elements.forEach((childElement) => {
|
|
6012
|
+
if (childElement.type === "markdown" || !childElement.key) return;
|
|
5989
6013
|
const childPath = `${fieldPath}.${childElement.key}`;
|
|
5990
6014
|
if (childElement.type === "richinput" && childElement.flatOutput) {
|
|
5991
6015
|
const richChild = childElement;
|
|
@@ -8685,6 +8709,248 @@ function updateRichInputField(element, fieldPath, value, context) {
|
|
|
8685
8709
|
}
|
|
8686
8710
|
}
|
|
8687
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
|
+
|
|
8688
8954
|
// src/components/index.ts
|
|
8689
8955
|
function showTooltip(tooltipId, button) {
|
|
8690
8956
|
const tooltip = document.getElementById(tooltipId);
|
|
@@ -8915,7 +9181,7 @@ function setupEnableIfListeners(wrapper, element, ctx) {
|
|
|
8915
9181
|
function createFieldLabel(element) {
|
|
8916
9182
|
const title = document.createElement("label");
|
|
8917
9183
|
title.className = "text-sm font-medium text-gray-900";
|
|
8918
|
-
title.textContent = element.label
|
|
9184
|
+
title.textContent = element.label ?? element.key ?? "";
|
|
8919
9185
|
if (element.required) {
|
|
8920
9186
|
const req = document.createElement("span");
|
|
8921
9187
|
req.className = "text-red-500 ml-1";
|
|
@@ -9043,6 +9309,29 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
9043
9309
|
}
|
|
9044
9310
|
}
|
|
9045
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
|
+
}
|
|
9046
9335
|
const initiallyDisabled = shouldDisableElement(element, ctx);
|
|
9047
9336
|
const wrapper = document.createElement("div");
|
|
9048
9337
|
wrapper.className = "mb-6 fb-field-wrapper";
|
|
@@ -9253,7 +9542,9 @@ function createInstanceState(config) {
|
|
|
9253
9542
|
translations: mergedTranslations
|
|
9254
9543
|
},
|
|
9255
9544
|
debounceTimer: null,
|
|
9256
|
-
prefill: {}
|
|
9545
|
+
prefill: {},
|
|
9546
|
+
syntheticElementIds: /* @__PURE__ */ new WeakMap(),
|
|
9547
|
+
syntheticElementIdCounter: 0
|
|
9257
9548
|
};
|
|
9258
9549
|
}
|
|
9259
9550
|
function generateInstanceId() {
|
|
@@ -9530,6 +9821,11 @@ var componentRegistry = {
|
|
|
9530
9821
|
// Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
|
|
9531
9822
|
validate: validateHiddenElement,
|
|
9532
9823
|
update: updateHiddenField
|
|
9824
|
+
},
|
|
9825
|
+
markdown: {
|
|
9826
|
+
// Display-only element — no value, no errors, skip from form data
|
|
9827
|
+
validate: validateMarkdown,
|
|
9828
|
+
update: updateMarkdown
|
|
9533
9829
|
}
|
|
9534
9830
|
};
|
|
9535
9831
|
function getComponentOperations(elementType) {
|
|
@@ -9957,7 +10253,7 @@ var FormBuilderInstance = class {
|
|
|
9957
10253
|
fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
|
|
9958
10254
|
}
|
|
9959
10255
|
schema.elements.forEach((element) => {
|
|
9960
|
-
if (element.hidden || element.type === "hidden") {
|
|
10256
|
+
if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
|
|
9961
10257
|
const val = prefill?.[element.key] ?? element.default ?? null;
|
|
9962
10258
|
fieldsWrapper.appendChild(createHiddenInput(element.key, val));
|
|
9963
10259
|
return;
|
|
@@ -9992,7 +10288,7 @@ var FormBuilderInstance = class {
|
|
|
9992
10288
|
const errors = [];
|
|
9993
10289
|
const data = {};
|
|
9994
10290
|
const validateElement2 = (element, ctx, customScopeRoot = null) => {
|
|
9995
|
-
const key = element.key;
|
|
10291
|
+
const key = element.key ?? "";
|
|
9996
10292
|
const scopeRoot = customScopeRoot || this.state.formRoot;
|
|
9997
10293
|
const componentContext = {
|
|
9998
10294
|
scopeRoot,
|
|
@@ -10010,7 +10306,8 @@ var FormBuilderInstance = class {
|
|
|
10010
10306
|
errors.push(...componentResult.errors);
|
|
10011
10307
|
return {
|
|
10012
10308
|
value: componentResult.value,
|
|
10013
|
-
spread: !!componentResult.spread
|
|
10309
|
+
spread: !!componentResult.spread,
|
|
10310
|
+
skip: !!componentResult.skip
|
|
10014
10311
|
};
|
|
10015
10312
|
}
|
|
10016
10313
|
console.warn(`Unknown field type "${element.type}" for key "${key}"`);
|
|
@@ -10031,6 +10328,9 @@ var FormBuilderInstance = class {
|
|
|
10031
10328
|
);
|
|
10032
10329
|
}
|
|
10033
10330
|
}
|
|
10331
|
+
if (element.type === "markdown") {
|
|
10332
|
+
return;
|
|
10333
|
+
}
|
|
10034
10334
|
if (element.hidden || element.type === "hidden") {
|
|
10035
10335
|
const hiddenInput = this.state.formRoot.querySelector(
|
|
10036
10336
|
`input[type="hidden"][data-hidden-field="true"][name="${element.key}"]`
|
|
@@ -10043,9 +10343,10 @@ var FormBuilderInstance = class {
|
|
|
10043
10343
|
}
|
|
10044
10344
|
} else {
|
|
10045
10345
|
const result = validateElement2(element, { path: "" });
|
|
10346
|
+
if (result.skip) return;
|
|
10046
10347
|
if (result.spread && result.value !== null && typeof result.value === "object") {
|
|
10047
10348
|
Object.assign(data, result.value);
|
|
10048
|
-
} else {
|
|
10349
|
+
} else if (element.key) {
|
|
10049
10350
|
data[element.key] = result.value;
|
|
10050
10351
|
}
|
|
10051
10352
|
}
|
|
@@ -10121,6 +10422,7 @@ var FormBuilderInstance = class {
|
|
|
10121
10422
|
buildHiddenFieldsData(elements) {
|
|
10122
10423
|
const data = {};
|
|
10123
10424
|
for (const element of elements) {
|
|
10425
|
+
if (element.type === "markdown" || !element.key) continue;
|
|
10124
10426
|
const key = element.key;
|
|
10125
10427
|
if (element.hidden && element.default !== void 0) {
|
|
10126
10428
|
data[key] = element.default;
|
|
@@ -10241,6 +10543,71 @@ var FormBuilderInstance = class {
|
|
|
10241
10543
|
);
|
|
10242
10544
|
}
|
|
10243
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
|
+
}
|
|
10244
10611
|
/**
|
|
10245
10612
|
* Re-evaluate all conditional fields (enableIf) based on current form data
|
|
10246
10613
|
* This is called automatically when form data changes (via onChange events)
|
|
@@ -10250,97 +10617,31 @@ var FormBuilderInstance = class {
|
|
|
10250
10617
|
const formData = this.validateForm(true).data;
|
|
10251
10618
|
const checkElements = (elements, currentPath) => {
|
|
10252
10619
|
elements.forEach((element) => {
|
|
10253
|
-
const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
|
|
10620
|
+
const fullPath = currentPath ? `${currentPath}.${element.key ?? ""}` : element.key ?? "";
|
|
10254
10621
|
if (element.enableIf) {
|
|
10255
|
-
|
|
10256
|
-
if (currentPath) {
|
|
10257
|
-
const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
|
|
10258
|
-
if (pathMatch) {
|
|
10259
|
-
const containerKey = pathMatch[1];
|
|
10260
|
-
const containerIndex = pathMatch[2];
|
|
10261
|
-
const containerElement = this.state.formRoot.querySelector(
|
|
10262
|
-
`[data-container-item="${containerKey}[${containerIndex}]"]`
|
|
10263
|
-
);
|
|
10264
|
-
if (containerElement) {
|
|
10265
|
-
fieldWrapper = containerElement.querySelector(
|
|
10266
|
-
`[data-field-key="${element.key}"]`
|
|
10267
|
-
);
|
|
10268
|
-
}
|
|
10269
|
-
} else {
|
|
10270
|
-
const containerElement = this.state.formRoot.querySelector(
|
|
10271
|
-
`[data-container="${currentPath}"]`
|
|
10272
|
-
);
|
|
10273
|
-
if (containerElement) {
|
|
10274
|
-
fieldWrapper = containerElement.querySelector(
|
|
10275
|
-
`[data-field-key="${element.key}"]`
|
|
10276
|
-
);
|
|
10277
|
-
}
|
|
10278
|
-
}
|
|
10279
|
-
} else {
|
|
10280
|
-
fieldWrapper = this.state.formRoot.querySelector(
|
|
10281
|
-
`[data-field-key="${element.key}"]`
|
|
10282
|
-
);
|
|
10283
|
-
}
|
|
10622
|
+
const fieldWrapper = this.findFieldWrapper(element, currentPath);
|
|
10284
10623
|
if (fieldWrapper) {
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
const shouldEnable = evaluateEnableCondition(
|
|
10293
|
-
element.enableIf,
|
|
10294
|
-
formData,
|
|
10295
|
-
// Use complete formData for absolute scope
|
|
10296
|
-
containerData
|
|
10297
|
-
// Use container-specific data for relative scope
|
|
10298
|
-
);
|
|
10299
|
-
const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
|
|
10300
|
-
if (shouldEnable && isCurrentlyDisabled) {
|
|
10301
|
-
const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
|
|
10302
|
-
const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
|
|
10303
|
-
const newWrapper = renderElement2(element, {
|
|
10304
|
-
path: currentPath,
|
|
10305
|
-
// Use container path (empty string for root-level)
|
|
10306
|
-
prefill: prefillContext,
|
|
10307
|
-
formData,
|
|
10308
|
-
// Pass complete formData for enableIf evaluation
|
|
10309
|
-
state: this.state,
|
|
10310
|
-
instance: this
|
|
10311
|
-
});
|
|
10312
|
-
wrapper.parentNode?.replaceChild(newWrapper, wrapper);
|
|
10313
|
-
} else if (!shouldEnable && !isCurrentlyDisabled) {
|
|
10314
|
-
const disabledWrapper = document.createElement("div");
|
|
10315
|
-
disabledWrapper.className = "fb-field-wrapper-disabled";
|
|
10316
|
-
disabledWrapper.style.display = "none";
|
|
10317
|
-
disabledWrapper.setAttribute("data-field-key", element.key);
|
|
10318
|
-
disabledWrapper.setAttribute(
|
|
10319
|
-
"data-conditionally-disabled",
|
|
10320
|
-
"true"
|
|
10321
|
-
);
|
|
10322
|
-
wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
|
|
10323
|
-
}
|
|
10324
|
-
} catch (error) {
|
|
10325
|
-
console.error(
|
|
10326
|
-
`Error re-evaluating enableIf for field "${element.key}" at path "${fullPath}":`,
|
|
10327
|
-
error
|
|
10328
|
-
);
|
|
10329
|
-
}
|
|
10624
|
+
this.applyEnableIfVisibility(
|
|
10625
|
+
element,
|
|
10626
|
+
fieldWrapper,
|
|
10627
|
+
currentPath,
|
|
10628
|
+
fullPath,
|
|
10629
|
+
formData
|
|
10630
|
+
);
|
|
10330
10631
|
}
|
|
10331
10632
|
}
|
|
10332
10633
|
if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
|
|
10333
|
-
const containerData = formData?.[element.key];
|
|
10634
|
+
const containerData = element.key ? formData?.[element.key] : void 0;
|
|
10334
10635
|
if (Array.isArray(containerData)) {
|
|
10335
10636
|
const containerItems = this.state.formRoot.querySelectorAll(
|
|
10336
10637
|
`[data-container-item]`
|
|
10337
10638
|
);
|
|
10338
|
-
const directItems = Array.from(containerItems).filter((el) => {
|
|
10639
|
+
const directItems = fullPath ? Array.from(containerItems).filter((el) => {
|
|
10339
10640
|
const attr = el.getAttribute("data-container-item") || "";
|
|
10340
10641
|
if (!attr.startsWith(`${fullPath}[`)) return false;
|
|
10341
10642
|
const suffix = attr.slice(fullPath.length);
|
|
10342
10643
|
return /^\[\d+\]$/.test(suffix);
|
|
10343
|
-
});
|
|
10644
|
+
}) : [];
|
|
10344
10645
|
directItems.forEach((el) => {
|
|
10345
10646
|
const attr = el.getAttribute("data-container-item") || "";
|
|
10346
10647
|
checkElements(element.elements, attr);
|