@dmitryvim/form-builder 0.5.4 → 0.7.0
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 +13 -2
- package/dist/browser/formbuilder.min.js +145 -153
- package/dist/browser/formbuilder.v0.7.0.min.js +1482 -0
- package/dist/cjs/index.cjs +1416 -1296
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1377 -1260
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +145 -153
- package/dist/types/components/container.d.ts +0 -7
- package/dist/types/components/file/dom.d.ts +16 -4
- package/dist/types/components/file/render-edit.d.ts +6 -5
- package/dist/types/components/index.d.ts +1 -2
- package/dist/types/instance/FormBuilderInstance.d.ts +49 -8
- package/dist/types/instance/state.d.ts +8 -0
- package/dist/types/types/component-operations.d.ts +28 -2
- package/dist/types/types/config.d.ts +2 -0
- package/dist/types/types/state.d.ts +25 -0
- package/dist/types/utils/helpers.d.ts +18 -1
- package/dist/types/utils/styles.d.ts +75 -7
- package/dist/types/utils/validation.d.ts +14 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.5.4.min.js +0 -1490
package/dist/cjs/index.cjs
CHANGED
|
@@ -4,15 +4,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
// src/utils/translation.ts
|
|
6
6
|
function t(key, state, params) {
|
|
7
|
+
var _a, _b;
|
|
7
8
|
const locale = state.config.locale || "en";
|
|
8
9
|
const localeTranslations = state.config.translations[locale];
|
|
9
10
|
const fallbackTranslations = state.config.translations.en;
|
|
10
|
-
let text = (localeTranslations == null ? void 0 : localeTranslations[key])
|
|
11
|
+
let text = (_b = (_a = localeTranslations == null ? void 0 : localeTranslations[key]) != null ? _a : fallbackTranslations == null ? void 0 : fallbackTranslations[key]) != null ? _b : key;
|
|
11
12
|
if (params) {
|
|
12
13
|
for (const [paramKey, paramValue] of Object.entries(params)) {
|
|
13
14
|
text = text.replace(
|
|
14
15
|
new RegExp(`\\{${paramKey}\\}`, "g"),
|
|
15
|
-
String(paramValue)
|
|
16
|
+
() => String(paramValue)
|
|
16
17
|
);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
@@ -62,7 +63,7 @@ function formatFileSize(bytes) {
|
|
|
62
63
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
63
64
|
}
|
|
64
65
|
function serializeHiddenValue(value) {
|
|
65
|
-
if (value ===
|
|
66
|
+
if (value === void 0) return "";
|
|
66
67
|
return JSON.stringify(value);
|
|
67
68
|
}
|
|
68
69
|
function deserializeHiddenValue(raw) {
|
|
@@ -73,6 +74,23 @@ function deserializeHiddenValue(raw) {
|
|
|
73
74
|
return raw;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
function readTypedInputValue(input) {
|
|
78
|
+
if (input instanceof HTMLInputElement) {
|
|
79
|
+
if (input.type === "checkbox") return input.checked;
|
|
80
|
+
if (input.dataset.hiddenField) return deserializeHiddenValue(input.value);
|
|
81
|
+
if (input.dataset.booleanField) return input.value === "true";
|
|
82
|
+
if (input.type === "number" || input.type === "range") {
|
|
83
|
+
if (input.value === "") return null;
|
|
84
|
+
const parsed = parseFloat(input.value);
|
|
85
|
+
const decimals = input.dataset.decimals;
|
|
86
|
+
return decimals !== void 0 ? Number(parsed.toFixed(parseInt(decimals, 10))) : parsed;
|
|
87
|
+
}
|
|
88
|
+
if (input.dataset.colourField) {
|
|
89
|
+
return input.value.toUpperCase();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return input.value === "" ? null : input.value;
|
|
93
|
+
}
|
|
76
94
|
function createHiddenInput(name, value) {
|
|
77
95
|
const input = document.createElement("input");
|
|
78
96
|
input.type = "hidden";
|
|
@@ -82,413 +100,201 @@ function createHiddenInput(name, value) {
|
|
|
82
100
|
return input;
|
|
83
101
|
}
|
|
84
102
|
|
|
85
|
-
// src/utils/
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
// src/utils/styles.ts
|
|
104
|
+
function findErrorAnchor(input) {
|
|
105
|
+
var _a, _b, _c, _d;
|
|
106
|
+
return (_d = (_c = (_a = input.closest) == null ? void 0 : _a.call(input, ".fb-chip")) != null ? _c : (_b = input.closest) == null ? void 0 : _b.call(input, ".slider-container")) != null ? _d : input;
|
|
107
|
+
}
|
|
108
|
+
function findErrorNode(input) {
|
|
109
|
+
const anchor = findErrorAnchor(input);
|
|
110
|
+
const name = input.getAttribute("name");
|
|
111
|
+
const parent = anchor.parentElement;
|
|
112
|
+
if (name && parent) {
|
|
113
|
+
for (const child of Array.from(parent.children)) {
|
|
114
|
+
if (isInputErrorNode(child) && child.getAttribute("data-error-for") === name) {
|
|
115
|
+
return child;
|
|
116
|
+
}
|
|
99
117
|
}
|
|
100
118
|
}
|
|
119
|
+
const sibling = anchor.nextElementSibling;
|
|
120
|
+
return sibling && isInputErrorNode(sibling) ? sibling : null;
|
|
101
121
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} else if (element.min != null) {
|
|
111
|
-
parts.push(t("hintMinValue", state, { min: element.min }));
|
|
112
|
-
}
|
|
122
|
+
function isInputErrorNode(node) {
|
|
123
|
+
return node.classList.contains("error-message") && !node.classList.contains("fb-field-error");
|
|
124
|
+
}
|
|
125
|
+
function resolveMark(target, message, scope) {
|
|
126
|
+
const reported = scope.state.reportedInvalid;
|
|
127
|
+
if (message === null || scope.readonly) {
|
|
128
|
+
reported.delete(target);
|
|
129
|
+
return null;
|
|
113
130
|
}
|
|
131
|
+
if (scope.draftMarks && !reported.has(target)) return void 0;
|
|
132
|
+
reported.add(target);
|
|
133
|
+
return message;
|
|
114
134
|
}
|
|
115
|
-
function
|
|
135
|
+
function joinErrorMessages(messages) {
|
|
136
|
+
return messages.length > 0 ? messages.join(" \u2022 ") : null;
|
|
137
|
+
}
|
|
138
|
+
function createErrorNode(state, className) {
|
|
139
|
+
const node = document.createElement("div");
|
|
140
|
+
node.className = className;
|
|
141
|
+
node.id = nextDomId(state, "error");
|
|
142
|
+
node.style.cssText = `
|
|
143
|
+
display: block;
|
|
144
|
+
color: var(--fb-error-color);
|
|
145
|
+
font-size: var(--fb-font-size-small);
|
|
146
|
+
margin-top: 0.25rem;
|
|
147
|
+
`;
|
|
148
|
+
return node;
|
|
149
|
+
}
|
|
150
|
+
function setAttr(el, name, value) {
|
|
151
|
+
if (el.getAttribute(name) !== value) el.setAttribute(name, value);
|
|
152
|
+
}
|
|
153
|
+
function nextDomId(state, kind) {
|
|
154
|
+
return `${state.instanceId}-${kind}-${++state.domIdCounter}`;
|
|
155
|
+
}
|
|
156
|
+
function describedByTokens(target) {
|
|
116
157
|
var _a;
|
|
117
|
-
|
|
118
|
-
if (sizeMB && sizeMB !== Infinity) {
|
|
119
|
-
parts.push(t("hintMaxSize", state, { size: sizeMB }));
|
|
120
|
-
}
|
|
158
|
+
return ((_a = target.getAttribute("aria-describedby")) != null ? _a : "").split(/\s+/).filter(Boolean);
|
|
121
159
|
}
|
|
122
|
-
|
|
160
|
+
var FORM_CONTROL = "input, select, textarea, button";
|
|
161
|
+
var ADDED_ATTRS = "data-fb-mark-added";
|
|
162
|
+
function fieldLabelOf(target) {
|
|
123
163
|
var _a;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
164
|
+
const field = target.closest(".fb-field-wrapper");
|
|
165
|
+
const labelRow = field ? Array.from(field.children).find(
|
|
166
|
+
(child) => child.hasAttribute("data-fb-label-row")
|
|
167
|
+
) : void 0;
|
|
168
|
+
return (_a = labelRow == null ? void 0 : labelRow.querySelector("label")) != null ? _a : null;
|
|
169
|
+
}
|
|
170
|
+
function exposeAsGroup(target, state) {
|
|
171
|
+
if (target.matches(FORM_CONTROL) || target.hasAttribute(ADDED_ATTRS)) return;
|
|
172
|
+
const added = [];
|
|
173
|
+
if (!target.hasAttribute("tabindex")) {
|
|
174
|
+
target.tabIndex = -1;
|
|
175
|
+
added.push("tabindex");
|
|
176
|
+
}
|
|
177
|
+
if (!target.hasAttribute("role")) {
|
|
178
|
+
target.setAttribute("role", "group");
|
|
179
|
+
added.push("role");
|
|
180
|
+
}
|
|
181
|
+
const label = fieldLabelOf(target);
|
|
182
|
+
if (label && !target.hasAttribute("aria-labelledby")) {
|
|
183
|
+
if (!label.id) label.id = nextDomId(state, "label");
|
|
184
|
+
target.setAttribute("aria-labelledby", label.id);
|
|
185
|
+
added.push("aria-labelledby");
|
|
186
|
+
}
|
|
187
|
+
target.setAttribute(ADDED_ATTRS, added.join(" "));
|
|
188
|
+
}
|
|
189
|
+
function linkDescription(target, node) {
|
|
190
|
+
if (!describedByTokens(target).includes(node.id)) {
|
|
191
|
+
target.setAttribute(
|
|
192
|
+
"aria-describedby",
|
|
193
|
+
[...describedByTokens(target), node.id].join(" ")
|
|
129
194
|
);
|
|
130
195
|
}
|
|
131
196
|
}
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
function makeFieldHint(element, state) {
|
|
138
|
-
const parts = [];
|
|
139
|
-
addLengthHint(element, parts, state);
|
|
140
|
-
if (element.type !== "slider") {
|
|
141
|
-
addRangeHint(element, parts, state);
|
|
142
|
-
}
|
|
143
|
-
addFileSizeHint(element, parts, state);
|
|
144
|
-
addFormatHint(element, parts, state);
|
|
145
|
-
addPatternHint(element, parts, state);
|
|
146
|
-
return parts.join(" \u2022 ");
|
|
197
|
+
function setInvalidMark(target, node, state) {
|
|
198
|
+
setAttr(target, "aria-invalid", "true");
|
|
199
|
+
exposeAsGroup(target, state);
|
|
200
|
+
if (node) linkDescription(target, node);
|
|
147
201
|
}
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (!Array.isArray(schema.elements)) {
|
|
155
|
-
errors.push("Schema missing elements array");
|
|
156
|
-
return errors;
|
|
157
|
-
}
|
|
158
|
-
if ("columns" in schema && schema.columns !== void 0) {
|
|
159
|
-
const columns = schema.columns;
|
|
160
|
-
const validColumns = [1, 2, 3, 4];
|
|
161
|
-
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
162
|
-
errors.push(`schema.columns must be 1, 2, 3, or 4 (got ${columns})`);
|
|
202
|
+
function unsetInvalidState(target) {
|
|
203
|
+
target.removeAttribute("aria-invalid");
|
|
204
|
+
const added = target.getAttribute(ADDED_ATTRS);
|
|
205
|
+
if (added !== null) {
|
|
206
|
+
for (const attr of added.split(" ").filter(Boolean)) {
|
|
207
|
+
target.removeAttribute(attr);
|
|
163
208
|
}
|
|
209
|
+
target.removeAttribute(ADDED_ATTRS);
|
|
164
210
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
if (!hint.values || typeof hint.values !== "object") {
|
|
175
|
-
errors.push(
|
|
176
|
-
`schema.prefillHints[${hintIndex}] must have a 'values' property of type object`
|
|
177
|
-
);
|
|
178
|
-
} else {
|
|
179
|
-
for (const fieldKey in hint.values) {
|
|
180
|
-
const fieldExists = schema.elements.some(
|
|
181
|
-
(element) => element.key === fieldKey
|
|
182
|
-
);
|
|
183
|
-
if (!fieldExists) {
|
|
184
|
-
errors.push(
|
|
185
|
-
`schema.prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
}
|
|
211
|
+
}
|
|
212
|
+
function clearInvalidMark(target, node) {
|
|
213
|
+
unsetInvalidState(target);
|
|
214
|
+
if (node) {
|
|
215
|
+
const rest = describedByTokens(target).filter((id) => id !== node.id);
|
|
216
|
+
if (rest.length > 0) setAttr(target, "aria-describedby", rest.join(" "));
|
|
217
|
+
else target.removeAttribute("aria-describedby");
|
|
218
|
+
node.remove();
|
|
192
219
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
`${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if ("displayMode" in element && element.displayMode !== void 0) {
|
|
204
|
-
const displayMode = element.displayMode;
|
|
205
|
-
if (displayMode !== "stack" && displayMode !== "slides") {
|
|
206
|
-
errors2.push(
|
|
207
|
-
`${elementPath}: displayMode must be "stack" or "slides" (got ${JSON.stringify(displayMode)})`
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
220
|
+
}
|
|
221
|
+
function drawMark(target, message, existing, createNode, state) {
|
|
222
|
+
if (message === "") {
|
|
223
|
+
if (existing) clearInvalidMark(target, existing);
|
|
224
|
+
setInvalidMark(target, null, state);
|
|
225
|
+
return;
|
|
211
226
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`${scopePath}: RichInput "${el.key}" flatOutput textKey "${textKey}" collides with element key "${otherEl.key}"`
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
if (otherEl.key === filesKey) {
|
|
228
|
-
errors.push(
|
|
229
|
-
`${scopePath}: RichInput "${el.key}" flatOutput filesKey "${filesKey}" collides with element key "${otherEl.key}"`
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (allOutputKeys.has(textKey)) {
|
|
234
|
-
errors.push(
|
|
235
|
-
`${scopePath}: RichInput "${el.key}" flatOutput textKey "${textKey}" collides with another flatOutput key`
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
if (allOutputKeys.has(filesKey)) {
|
|
239
|
-
errors.push(
|
|
240
|
-
`${scopePath}: RichInput "${el.key}" flatOutput filesKey "${filesKey}" collides with another flatOutput key`
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
allOutputKeys.add(textKey);
|
|
244
|
-
allOutputKeys.add(filesKey);
|
|
245
|
-
} else {
|
|
246
|
-
if (el.key) {
|
|
247
|
-
if (allOutputKeys.has(el.key)) {
|
|
248
|
-
errors.push(
|
|
249
|
-
`${scopePath}: Element key "${el.key}" collides with a flatOutput richinput key`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
allOutputKeys.add(el.key);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
227
|
+
const node = existing != null ? existing : createNode();
|
|
228
|
+
if (node.textContent !== message) node.textContent = message;
|
|
229
|
+
setInvalidMark(target, node, state);
|
|
230
|
+
}
|
|
231
|
+
function markFieldValidity(input, errorMessage, scope) {
|
|
232
|
+
var _a;
|
|
233
|
+
if (!input) return;
|
|
234
|
+
const mark = resolveMark(input, errorMessage, scope);
|
|
235
|
+
if (mark === void 0) return;
|
|
236
|
+
if (mark === null) {
|
|
237
|
+
clearFieldError(input);
|
|
238
|
+
return;
|
|
256
239
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
240
|
+
if (!input.classList.contains("invalid")) input.classList.add("invalid");
|
|
241
|
+
if (input.title !== mark) input.title = mark;
|
|
242
|
+
const errorFor = (_a = input.getAttribute("name")) != null ? _a : "";
|
|
243
|
+
const existing = findErrorNode(input);
|
|
244
|
+
if (existing) setAttr(existing, "data-error-for", errorFor);
|
|
245
|
+
drawMark(
|
|
246
|
+
input,
|
|
247
|
+
mark,
|
|
248
|
+
existing,
|
|
249
|
+
() => {
|
|
250
|
+
var _a2;
|
|
251
|
+
const node = createErrorNode(scope.state, "error-message");
|
|
252
|
+
node.setAttribute("data-error-for", errorFor);
|
|
253
|
+
const anchor = findErrorAnchor(input);
|
|
254
|
+
(_a2 = anchor.parentNode) == null ? void 0 : _a2.insertBefore(node, anchor.nextSibling);
|
|
255
|
+
return node;
|
|
256
|
+
},
|
|
257
|
+
scope.state
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
function clearFieldError(input) {
|
|
261
|
+
if (input.classList.contains("invalid")) input.classList.remove("invalid");
|
|
262
|
+
if (input.title !== "") input.title = "";
|
|
263
|
+
clearInvalidMark(input, findErrorNode(input));
|
|
264
|
+
}
|
|
265
|
+
function markFieldGroupValidity(scopeRoot, fieldPath, errorMessage, scope) {
|
|
266
|
+
var _a;
|
|
267
|
+
const wrapper = scopeRoot.querySelector(
|
|
268
|
+
`[data-field-path="${fieldPath}"]`
|
|
269
|
+
);
|
|
270
|
+
if (!wrapper) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`markFieldGroupValidity: no [data-field-path="${fieldPath}"] in scope`
|
|
283
273
|
);
|
|
284
274
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
const min = typeof minCount === "number" ? minCount : void 0;
|
|
297
|
-
const max = typeof maxCount === "number" ? maxCount : void 0;
|
|
298
|
-
if (max !== void 0 && (max < 0 || Number.isNaN(max))) {
|
|
299
|
-
errors2.push(
|
|
300
|
-
`${elementPath}: ${maxName} must be a non-negative number or Infinity (got ${max})`
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
if (min !== void 0 && (min < 0 || !Number.isFinite(min))) {
|
|
304
|
-
errors2.push(
|
|
305
|
-
`${elementPath}: ${minName} must be a finite non-negative number (got ${min})`
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
const effectiveMin = min != null ? min : requiredImpliesFloor ? 1 : void 0;
|
|
309
|
-
if (effectiveMin !== void 0 && max !== void 0 && effectiveMin > max) {
|
|
310
|
-
const shown = min !== void 0 ? `${minName} (${min})` : `required: true (implies ${minName} 1)`;
|
|
311
|
-
errors2.push(
|
|
312
|
-
`${elementPath}: ${shown} cannot be greater than ${maxName} (${max})`
|
|
313
|
-
);
|
|
314
|
-
}
|
|
275
|
+
if (wrapper.getAttribute("data-conditionally-disabled") === "true") return;
|
|
276
|
+
const mark = resolveMark(wrapper, errorMessage, scope);
|
|
277
|
+
if (mark === void 0) return;
|
|
278
|
+
const existing = (_a = Array.from(wrapper.children).find(
|
|
279
|
+
(child) => child instanceof HTMLElement && child.classList.contains("fb-field-error")
|
|
280
|
+
)) != null ? _a : null;
|
|
281
|
+
if (mark === null) {
|
|
282
|
+
clearInvalidMark(wrapper, existing);
|
|
283
|
+
return;
|
|
315
284
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const decimals = element.decimals;
|
|
328
|
-
if (decimals !== void 0 && (!Number.isInteger(decimals) || decimals < 0)) {
|
|
329
|
-
errors.push(
|
|
330
|
-
`${elementPath}: decimals must be a non-negative integer (got ${JSON.stringify(decimals)})`
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if (element.type === "markdown") {
|
|
335
|
-
const content = element.content;
|
|
336
|
-
if (typeof content !== "string") {
|
|
337
|
-
errors.push(
|
|
338
|
-
`${elementPath}: markdown element requires "content" to be a string (got ${content === null ? "null" : typeof content})`
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
if (element.enableIf) {
|
|
343
|
-
const enableIf = element.enableIf;
|
|
344
|
-
if (!enableIf.key || typeof enableIf.key !== "string") {
|
|
345
|
-
errors.push(
|
|
346
|
-
`${elementPath}: enableIf must have a 'key' property of type string`
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
const hasOperator = "equals" in enableIf;
|
|
350
|
-
if (!hasOperator) {
|
|
351
|
-
errors.push(
|
|
352
|
-
`${elementPath}: enableIf must have at least one operator (equals, etc.)`
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (element.type === "group" && "elements" in element && element.elements) {
|
|
357
|
-
validateElements(element.elements, `${elementPath}.elements`);
|
|
358
|
-
}
|
|
359
|
-
if (element.type === "container" && element.elements) {
|
|
360
|
-
validateContainerProps(element, elementPath, errors);
|
|
361
|
-
if ("prefillHints" in element && element.prefillHints) {
|
|
362
|
-
const prefillHints = element.prefillHints;
|
|
363
|
-
if (Array.isArray(prefillHints)) {
|
|
364
|
-
prefillHints.forEach((hint, hintIndex) => {
|
|
365
|
-
if (!hint.label || typeof hint.label !== "string") {
|
|
366
|
-
errors.push(
|
|
367
|
-
`${elementPath}: prefillHints[${hintIndex}] must have a 'label' property of type string`
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
if (!hint.values || typeof hint.values !== "object") {
|
|
371
|
-
errors.push(
|
|
372
|
-
`${elementPath}: prefillHints[${hintIndex}] must have a 'values' property of type object`
|
|
373
|
-
);
|
|
374
|
-
} else {
|
|
375
|
-
for (const fieldKey in hint.values) {
|
|
376
|
-
const fieldExists = element.elements.some(
|
|
377
|
-
(childElement) => childElement.key === fieldKey
|
|
378
|
-
);
|
|
379
|
-
if (!fieldExists) {
|
|
380
|
-
errors.push(
|
|
381
|
-
`container "${element.key}": prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
validateElements(element.elements, `${elementPath}.elements`);
|
|
390
|
-
checkFlatOutputCollisions(element.elements, `${elementPath}.elements`);
|
|
391
|
-
}
|
|
392
|
-
if (element.type === "select" && element.options) {
|
|
393
|
-
const defaultValue = element.default;
|
|
394
|
-
if (defaultValue !== void 0 && defaultValue !== null && defaultValue !== "") {
|
|
395
|
-
const hasMatchingOption = element.options.some(
|
|
396
|
-
(opt) => opt.value === defaultValue
|
|
397
|
-
);
|
|
398
|
-
if (!hasMatchingOption) {
|
|
399
|
-
errors.push(
|
|
400
|
-
`${elementPath}: default "${defaultValue}" not in options`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
if (Array.isArray(schema.elements)) {
|
|
408
|
-
validateElements(schema.elements, "elements");
|
|
409
|
-
checkFlatOutputCollisions(schema.elements, "elements");
|
|
410
|
-
}
|
|
411
|
-
return errors;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// src/utils/enable-conditions.ts
|
|
415
|
-
function getValueByPath(data, path) {
|
|
416
|
-
if (!data || typeof data !== "object") {
|
|
417
|
-
return void 0;
|
|
418
|
-
}
|
|
419
|
-
const segments = path.match(/[^.[\]]+|\[\d+\]/g);
|
|
420
|
-
if (!segments || segments.length === 0) {
|
|
421
|
-
return void 0;
|
|
422
|
-
}
|
|
423
|
-
let current = data;
|
|
424
|
-
for (const segment of segments) {
|
|
425
|
-
if (current === void 0 || current === null) {
|
|
426
|
-
return void 0;
|
|
427
|
-
}
|
|
428
|
-
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
429
|
-
const index = parseInt(segment.slice(1, -1), 10);
|
|
430
|
-
if (!Array.isArray(current) || isNaN(index)) {
|
|
431
|
-
return void 0;
|
|
432
|
-
}
|
|
433
|
-
current = current[index];
|
|
434
|
-
} else {
|
|
435
|
-
current = current[segment];
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return current;
|
|
439
|
-
}
|
|
440
|
-
function evaluateEnableCondition(condition, formData, containerData) {
|
|
441
|
-
var _a;
|
|
442
|
-
if (!condition || !condition.key) {
|
|
443
|
-
throw new Error("Invalid enableIf condition: must have a 'key' property");
|
|
444
|
-
}
|
|
445
|
-
const scope = (_a = condition.scope) != null ? _a : "relative";
|
|
446
|
-
let dataSource;
|
|
447
|
-
if (scope === "relative") {
|
|
448
|
-
dataSource = containerData != null ? containerData : formData;
|
|
449
|
-
} else if (scope === "absolute") {
|
|
450
|
-
dataSource = formData;
|
|
451
|
-
} else {
|
|
452
|
-
throw new Error(
|
|
453
|
-
`Invalid enableIf scope: must be "relative" or "absolute" (got "${scope}")`
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
const actualValue = getValueByPath(dataSource, condition.key);
|
|
457
|
-
if ("equals" in condition) {
|
|
458
|
-
return deepEqual(actualValue, condition.equals);
|
|
459
|
-
}
|
|
460
|
-
throw new Error(
|
|
461
|
-
`Invalid enableIf condition: no recognized operator (equals, etc.)`
|
|
285
|
+
drawMark(
|
|
286
|
+
wrapper,
|
|
287
|
+
mark,
|
|
288
|
+
existing,
|
|
289
|
+
() => {
|
|
290
|
+
const node = createErrorNode(scope.state, "error-message fb-field-error");
|
|
291
|
+
node.setAttribute("data-error-for", fieldPath);
|
|
292
|
+
wrapper.appendChild(node);
|
|
293
|
+
return node;
|
|
294
|
+
},
|
|
295
|
+
scope.state
|
|
462
296
|
);
|
|
463
297
|
}
|
|
464
|
-
function deepEqual(a, b) {
|
|
465
|
-
if (a === b) return true;
|
|
466
|
-
if (a == null || b == null) return a === b;
|
|
467
|
-
if (typeof a !== typeof b) return false;
|
|
468
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
469
|
-
try {
|
|
470
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
471
|
-
} catch (e) {
|
|
472
|
-
if (e instanceof TypeError && (e.message.includes("circular") || e.message.includes("cyclic"))) {
|
|
473
|
-
console.warn(
|
|
474
|
-
"deepEqual: Circular reference detected in enableIf comparison, using reference equality"
|
|
475
|
-
);
|
|
476
|
-
return a === b;
|
|
477
|
-
}
|
|
478
|
-
throw e;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return a === b;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// src/utils/styles.ts
|
|
485
|
-
function clearFieldError(input) {
|
|
486
|
-
const name = input.getAttribute("name");
|
|
487
|
-
if (!name) return;
|
|
488
|
-
const doc = input.ownerDocument || document;
|
|
489
|
-
const errorNode = doc.getElementById(`error-${name}`);
|
|
490
|
-
if (errorNode) errorNode.remove();
|
|
491
|
-
}
|
|
492
298
|
var BIN_ICON_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
|
493
299
|
function ensureThemingHooks(doc) {
|
|
494
300
|
if (doc.head.querySelector("[data-fb-theming-hooks]")) return;
|
|
@@ -606,11 +412,18 @@ function ensureThemingHooks(doc) {
|
|
|
606
412
|
/* .fb-size-md uses defaults \u2014 no override needed */
|
|
607
413
|
.fb-size-lg { --fb-input-padding-y: 2px; --fb-input-padding-x: 8px; --fb-font-size: 16px; --fb-font-size-small: 13px; --fb-file-wide-min: 128px; --fb-file-zone-min: 96px; --fb-file-zone-padding: 12px; }
|
|
608
414
|
.fb-size-xl { --fb-input-padding-y: 3px; --fb-input-padding-x: 8px; --fb-font-size: 18px; --fb-font-size-small: 14px; --fb-file-wide-min: 160px; --fb-file-zone-min: 120px; --fb-file-zone-padding: 14px; }
|
|
415
|
+
/* !important: controls carry their border inline and JS focus/hover
|
|
416
|
+
handlers rewrite it, so no weaker rule would ever show the mark. */
|
|
417
|
+
[data-fb-root] input[aria-invalid="true"],
|
|
418
|
+
[data-fb-root] select[aria-invalid="true"],
|
|
419
|
+
[data-fb-root] textarea[aria-invalid="true"] {
|
|
420
|
+
border-color: var(--fb-error-color) !important;
|
|
421
|
+
}
|
|
609
422
|
`;
|
|
610
423
|
doc.head.appendChild(style);
|
|
611
424
|
}
|
|
612
|
-
function applyAutoExpand(textarea, options
|
|
613
|
-
var _a;
|
|
425
|
+
function applyAutoExpand(textarea, options) {
|
|
426
|
+
var _a, _b;
|
|
614
427
|
textarea.style.overflow = "hidden";
|
|
615
428
|
textarea.style.resize = "none";
|
|
616
429
|
const minRows = Math.max(1, (_a = options.minRows) != null ? _a : 1);
|
|
@@ -638,18 +451,20 @@ function applyAutoExpand(textarea, options = {}) {
|
|
|
638
451
|
if (typeof ResizeObserver === "undefined") return;
|
|
639
452
|
let lastWidth = -1;
|
|
640
453
|
const ro = new ResizeObserver((entries) => {
|
|
641
|
-
var _a2,
|
|
454
|
+
var _a2, _b2, _c, _d, _e, _f;
|
|
642
455
|
if (!textarea.isConnected) {
|
|
643
456
|
ro.disconnect();
|
|
457
|
+
(_a2 = options.observers) == null ? void 0 : _a2.delete(ro);
|
|
644
458
|
return;
|
|
645
459
|
}
|
|
646
460
|
const entry = entries[0];
|
|
647
|
-
const w = (
|
|
461
|
+
const w = (_f = (_e = (_c = (_b2 = entry == null ? void 0 : entry.contentBoxSize) == null ? void 0 : _b2[0]) == null ? void 0 : _c.inlineSize) != null ? _e : (_d = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _d.width) != null ? _f : 0;
|
|
648
462
|
if (w === lastWidth) return;
|
|
649
463
|
lastWidth = w;
|
|
650
464
|
resize();
|
|
651
465
|
});
|
|
652
466
|
ro.observe(textarea);
|
|
467
|
+
(_b = options.observers) == null ? void 0 : _b.add(ro);
|
|
653
468
|
}
|
|
654
469
|
function applySingleLineMode(textarea) {
|
|
655
470
|
textarea.addEventListener("keydown", (e) => {
|
|
@@ -813,26 +628,453 @@ function createSlideAddTile(onClick, options = {}) {
|
|
|
813
628
|
};
|
|
814
629
|
return { tile, counter, update };
|
|
815
630
|
}
|
|
816
|
-
function applyActionButtonStyles(button, isFormLevel = false) {
|
|
817
|
-
button.style.cssText = `
|
|
818
|
-
background-color: var(--fb-action-bg-color);
|
|
819
|
-
color: var(--fb-action-text-color);
|
|
820
|
-
border: var(--fb-border-width) solid var(--fb-action-border-color);
|
|
821
|
-
padding: ${isFormLevel ? "0.5rem 1rem" : "0.5rem 0.75rem"};
|
|
822
|
-
font-size: var(--fb-font-size);
|
|
823
|
-
font-weight: var(--fb-font-weight-medium);
|
|
824
|
-
border-radius: var(--fb-border-radius);
|
|
825
|
-
transition: all var(--fb-transition-duration);
|
|
826
|
-
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
827
|
-
`;
|
|
828
|
-
button.addEventListener("mouseenter", () => {
|
|
829
|
-
button.style.backgroundColor = "var(--fb-action-hover-bg-color)";
|
|
830
|
-
button.style.borderColor = "var(--fb-action-hover-border-color)";
|
|
831
|
-
});
|
|
832
|
-
button.addEventListener("mouseleave", () => {
|
|
833
|
-
button.style.backgroundColor = "var(--fb-action-bg-color)";
|
|
834
|
-
button.style.borderColor = "var(--fb-action-border-color)";
|
|
835
|
-
});
|
|
631
|
+
function applyActionButtonStyles(button, isFormLevel = false) {
|
|
632
|
+
button.style.cssText = `
|
|
633
|
+
background-color: var(--fb-action-bg-color);
|
|
634
|
+
color: var(--fb-action-text-color);
|
|
635
|
+
border: var(--fb-border-width) solid var(--fb-action-border-color);
|
|
636
|
+
padding: ${isFormLevel ? "0.5rem 1rem" : "0.5rem 0.75rem"};
|
|
637
|
+
font-size: var(--fb-font-size);
|
|
638
|
+
font-weight: var(--fb-font-weight-medium);
|
|
639
|
+
border-radius: var(--fb-border-radius);
|
|
640
|
+
transition: all var(--fb-transition-duration);
|
|
641
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
642
|
+
`;
|
|
643
|
+
button.addEventListener("mouseenter", () => {
|
|
644
|
+
button.style.backgroundColor = "var(--fb-action-hover-bg-color)";
|
|
645
|
+
button.style.borderColor = "var(--fb-action-hover-border-color)";
|
|
646
|
+
});
|
|
647
|
+
button.addEventListener("mouseleave", () => {
|
|
648
|
+
button.style.backgroundColor = "var(--fb-action-bg-color)";
|
|
649
|
+
button.style.borderColor = "var(--fb-action-border-color)";
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/utils/validation.ts
|
|
654
|
+
function countRuleMessages(element, count, state, keys = { min: "minItems", max: "maxItems" }) {
|
|
655
|
+
var _a, _b;
|
|
656
|
+
const minCount = "minCount" in element ? (_a = element.minCount) != null ? _a : 0 : 0;
|
|
657
|
+
const maxCount = "maxCount" in element ? (_b = element.maxCount) != null ? _b : Infinity : Infinity;
|
|
658
|
+
const messages = [];
|
|
659
|
+
if (element.required && count === 0) messages.push(t("required", state));
|
|
660
|
+
if (count < minCount) messages.push(t(keys.min, state, { min: minCount }));
|
|
661
|
+
if (count > maxCount) messages.push(t(keys.max, state, { max: maxCount }));
|
|
662
|
+
return messages;
|
|
663
|
+
}
|
|
664
|
+
function validateItemCount(element, key, filledCount, context, errors) {
|
|
665
|
+
const messages = countRuleMessages(element, filledCount, context.state);
|
|
666
|
+
errors.push(...messages.map((message) => `${key}: ${message}`));
|
|
667
|
+
markFieldGroupValidity(
|
|
668
|
+
context.scopeRoot,
|
|
669
|
+
key,
|
|
670
|
+
joinErrorMessages(messages),
|
|
671
|
+
context
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
function addLengthHint(element, parts, state) {
|
|
675
|
+
if (element.minLength != null || element.maxLength != null) {
|
|
676
|
+
if (element.minLength != null && element.maxLength != null) {
|
|
677
|
+
parts.push(
|
|
678
|
+
t("hintLengthRange", state, {
|
|
679
|
+
min: element.minLength,
|
|
680
|
+
max: element.maxLength
|
|
681
|
+
})
|
|
682
|
+
);
|
|
683
|
+
} else if (element.maxLength != null) {
|
|
684
|
+
parts.push(t("hintMaxLength", state, { max: element.maxLength }));
|
|
685
|
+
} else if (element.minLength != null) {
|
|
686
|
+
parts.push(t("hintMinLength", state, { min: element.minLength }));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function addRangeHint(element, parts, state) {
|
|
691
|
+
if (element.min != null || element.max != null) {
|
|
692
|
+
if (element.min != null && element.max != null) {
|
|
693
|
+
parts.push(
|
|
694
|
+
t("hintValueRange", state, { min: element.min, max: element.max })
|
|
695
|
+
);
|
|
696
|
+
} else if (element.max != null) {
|
|
697
|
+
parts.push(t("hintMaxValue", state, { max: element.max }));
|
|
698
|
+
} else if (element.min != null) {
|
|
699
|
+
parts.push(t("hintMinValue", state, { min: element.min }));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function addFileSizeHint(element, parts, state) {
|
|
704
|
+
var _a;
|
|
705
|
+
const sizeMB = (_a = element.maxSize) != null ? _a : element.maxSizeMB;
|
|
706
|
+
if (sizeMB && sizeMB !== Infinity) {
|
|
707
|
+
parts.push(t("hintMaxSize", state, { size: sizeMB }));
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function addFormatHint(element, parts, state) {
|
|
711
|
+
var _a;
|
|
712
|
+
if ((_a = element.accept) == null ? void 0 : _a.extensions) {
|
|
713
|
+
parts.push(
|
|
714
|
+
t("hintFormats", state, {
|
|
715
|
+
formats: element.accept.extensions.map((ext) => ext.toUpperCase()).join(",")
|
|
716
|
+
})
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function addPatternHint(element, parts, state) {
|
|
721
|
+
if (element.pattern) {
|
|
722
|
+
parts.push(t("hintPattern", state, { pattern: element.pattern }));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function makeFieldHint(element, state) {
|
|
726
|
+
const parts = [];
|
|
727
|
+
addLengthHint(element, parts, state);
|
|
728
|
+
if (element.type !== "slider") {
|
|
729
|
+
addRangeHint(element, parts, state);
|
|
730
|
+
}
|
|
731
|
+
addFileSizeHint(element, parts, state);
|
|
732
|
+
addFormatHint(element, parts, state);
|
|
733
|
+
addPatternHint(element, parts, state);
|
|
734
|
+
return parts.join(" \u2022 ");
|
|
735
|
+
}
|
|
736
|
+
function validateSchema(schema) {
|
|
737
|
+
const errors = [];
|
|
738
|
+
if (!schema || typeof schema !== "object") {
|
|
739
|
+
errors.push("Schema must be an object");
|
|
740
|
+
return errors;
|
|
741
|
+
}
|
|
742
|
+
if (!Array.isArray(schema.elements)) {
|
|
743
|
+
errors.push("Schema missing elements array");
|
|
744
|
+
return errors;
|
|
745
|
+
}
|
|
746
|
+
if ("columns" in schema && schema.columns !== void 0) {
|
|
747
|
+
const columns = schema.columns;
|
|
748
|
+
const validColumns = [1, 2, 3, 4];
|
|
749
|
+
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
750
|
+
errors.push(`schema.columns must be 1, 2, 3, or 4 (got ${columns})`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if ("prefillHints" in schema && schema.prefillHints) {
|
|
754
|
+
const prefillHints = schema.prefillHints;
|
|
755
|
+
if (Array.isArray(prefillHints)) {
|
|
756
|
+
prefillHints.forEach((hint, hintIndex) => {
|
|
757
|
+
if (!hint.label || typeof hint.label !== "string") {
|
|
758
|
+
errors.push(
|
|
759
|
+
`schema.prefillHints[${hintIndex}] must have a 'label' property of type string`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
if (!hint.values || typeof hint.values !== "object") {
|
|
763
|
+
errors.push(
|
|
764
|
+
`schema.prefillHints[${hintIndex}] must have a 'values' property of type object`
|
|
765
|
+
);
|
|
766
|
+
} else {
|
|
767
|
+
for (const fieldKey in hint.values) {
|
|
768
|
+
const fieldExists = schema.elements.some(
|
|
769
|
+
(element) => element.key === fieldKey
|
|
770
|
+
);
|
|
771
|
+
if (!fieldExists) {
|
|
772
|
+
errors.push(
|
|
773
|
+
`schema.prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function validateContainerProps(element, elementPath, errors2) {
|
|
782
|
+
if ("columns" in element && element.columns !== void 0) {
|
|
783
|
+
const columns = element.columns;
|
|
784
|
+
const validColumns = [1, 2, 3, 4];
|
|
785
|
+
if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
|
|
786
|
+
errors2.push(
|
|
787
|
+
`${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if ("displayMode" in element && element.displayMode !== void 0) {
|
|
792
|
+
const displayMode = element.displayMode;
|
|
793
|
+
if (displayMode !== "stack" && displayMode !== "slides") {
|
|
794
|
+
errors2.push(
|
|
795
|
+
`${elementPath}: displayMode must be "stack" or "slides" (got ${JSON.stringify(displayMode)})`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function checkFlatOutputCollisions(elements, scopePath) {
|
|
801
|
+
var _a, _b;
|
|
802
|
+
const allOutputKeys = /* @__PURE__ */ new Set();
|
|
803
|
+
for (const el of elements) {
|
|
804
|
+
if (el.type === "richinput" && el.flatOutput) {
|
|
805
|
+
const richEl = el;
|
|
806
|
+
const textKey = (_a = richEl.textKey) != null ? _a : "text";
|
|
807
|
+
const filesKey = (_b = richEl.filesKey) != null ? _b : "files";
|
|
808
|
+
for (const otherEl of elements) {
|
|
809
|
+
if (otherEl === el) continue;
|
|
810
|
+
if (otherEl.key === textKey) {
|
|
811
|
+
errors.push(
|
|
812
|
+
`${scopePath}: RichInput "${el.key}" flatOutput textKey "${textKey}" collides with element key "${otherEl.key}"`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
if (otherEl.key === filesKey) {
|
|
816
|
+
errors.push(
|
|
817
|
+
`${scopePath}: RichInput "${el.key}" flatOutput filesKey "${filesKey}" collides with element key "${otherEl.key}"`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (allOutputKeys.has(textKey)) {
|
|
822
|
+
errors.push(
|
|
823
|
+
`${scopePath}: RichInput "${el.key}" flatOutput textKey "${textKey}" collides with another flatOutput key`
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
if (allOutputKeys.has(filesKey)) {
|
|
827
|
+
errors.push(
|
|
828
|
+
`${scopePath}: RichInput "${el.key}" flatOutput filesKey "${filesKey}" collides with another flatOutput key`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
allOutputKeys.add(textKey);
|
|
832
|
+
allOutputKeys.add(filesKey);
|
|
833
|
+
} else {
|
|
834
|
+
if (el.key) {
|
|
835
|
+
if (allOutputKeys.has(el.key)) {
|
|
836
|
+
errors.push(
|
|
837
|
+
`${scopePath}: Element key "${el.key}" collides with a flatOutput richinput key`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
allOutputKeys.add(el.key);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function validateCountBounds(element, elementPath, errors2) {
|
|
846
|
+
var _a, _b;
|
|
847
|
+
const el = element;
|
|
848
|
+
if (el.type === "group") {
|
|
849
|
+
if (!isPlainObject(el.repeat)) return;
|
|
850
|
+
checkBounds(
|
|
851
|
+
elementPath,
|
|
852
|
+
(_a = el.repeat) == null ? void 0 : _a.min,
|
|
853
|
+
(_b = el.repeat) == null ? void 0 : _b.max,
|
|
854
|
+
"repeat.min",
|
|
855
|
+
"repeat.max",
|
|
856
|
+
el.required === true,
|
|
857
|
+
errors2
|
|
858
|
+
);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const isMultiple = el.multiple === true || el.type === "files";
|
|
862
|
+
if (!isMultiple) return;
|
|
863
|
+
checkBounds(
|
|
864
|
+
elementPath,
|
|
865
|
+
el.minCount,
|
|
866
|
+
el.maxCount,
|
|
867
|
+
"minCount",
|
|
868
|
+
"maxCount",
|
|
869
|
+
el.required === true,
|
|
870
|
+
errors2
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function checkBounds(elementPath, minCount, maxCount, minName, maxName, requiredImpliesFloor, errors2) {
|
|
874
|
+
for (const [name, bound] of [
|
|
875
|
+
[minName, minCount],
|
|
876
|
+
[maxName, maxCount]
|
|
877
|
+
]) {
|
|
878
|
+
if (bound !== void 0 && typeof bound !== "number") {
|
|
879
|
+
errors2.push(
|
|
880
|
+
`${elementPath}: ${name} must be a number (got ${typeof bound})`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const min = typeof minCount === "number" ? minCount : void 0;
|
|
885
|
+
const max = typeof maxCount === "number" ? maxCount : void 0;
|
|
886
|
+
if (max !== void 0 && (max < 0 || Number.isNaN(max))) {
|
|
887
|
+
errors2.push(
|
|
888
|
+
`${elementPath}: ${maxName} must be a non-negative number or Infinity (got ${max})`
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
if (min !== void 0 && (min < 0 || !Number.isFinite(min))) {
|
|
892
|
+
errors2.push(
|
|
893
|
+
`${elementPath}: ${minName} must be a finite non-negative number (got ${min})`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const effectiveMin = min != null ? min : requiredImpliesFloor ? 1 : void 0;
|
|
897
|
+
if (effectiveMin !== void 0 && max !== void 0 && effectiveMin > max) {
|
|
898
|
+
const shown = min !== void 0 ? `${minName} (${min})` : `required: true (implies ${minName} 1)`;
|
|
899
|
+
errors2.push(
|
|
900
|
+
`${elementPath}: ${shown} cannot be greater than ${maxName} (${max})`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function validateElements(elements, path) {
|
|
905
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
906
|
+
elements.forEach((element, index) => {
|
|
907
|
+
if (!element.key) return;
|
|
908
|
+
if (seenKeys.has(element.key)) {
|
|
909
|
+
errors.push(`${path}[${index}]: duplicate key "${element.key}"`);
|
|
910
|
+
}
|
|
911
|
+
seenKeys.add(element.key);
|
|
912
|
+
});
|
|
913
|
+
elements.forEach((element, index) => {
|
|
914
|
+
const elementPath = `${path}[${index}]`;
|
|
915
|
+
if (!element.type) {
|
|
916
|
+
errors.push(`${elementPath}: missing type`);
|
|
917
|
+
}
|
|
918
|
+
if (!element.key && element.type !== "markdown") {
|
|
919
|
+
errors.push(`${elementPath}: missing key`);
|
|
920
|
+
}
|
|
921
|
+
validateCountBounds(element, elementPath, errors);
|
|
922
|
+
if (element.type === "number" && "decimals" in element) {
|
|
923
|
+
const decimals = element.decimals;
|
|
924
|
+
if (decimals !== void 0 && (!Number.isInteger(decimals) || decimals < 0)) {
|
|
925
|
+
errors.push(
|
|
926
|
+
`${elementPath}: decimals must be a non-negative integer (got ${JSON.stringify(decimals)})`
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (element.type === "markdown") {
|
|
931
|
+
const content = element.content;
|
|
932
|
+
if (typeof content !== "string") {
|
|
933
|
+
errors.push(
|
|
934
|
+
`${elementPath}: markdown element requires "content" to be a string (got ${content === null ? "null" : typeof content})`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (element.enableIf) {
|
|
939
|
+
const enableIf = element.enableIf;
|
|
940
|
+
if (!enableIf.key || typeof enableIf.key !== "string") {
|
|
941
|
+
errors.push(
|
|
942
|
+
`${elementPath}: enableIf must have a 'key' property of type string`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
const hasOperator = "equals" in enableIf;
|
|
946
|
+
if (!hasOperator) {
|
|
947
|
+
errors.push(
|
|
948
|
+
`${elementPath}: enableIf must have at least one operator (equals, etc.)`
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (element.type === "group" && "elements" in element && element.elements) {
|
|
953
|
+
validateElements(element.elements, `${elementPath}.elements`);
|
|
954
|
+
}
|
|
955
|
+
if (element.type === "container" && element.elements) {
|
|
956
|
+
validateContainerProps(element, elementPath, errors);
|
|
957
|
+
if ("prefillHints" in element && element.prefillHints) {
|
|
958
|
+
const prefillHints = element.prefillHints;
|
|
959
|
+
if (Array.isArray(prefillHints)) {
|
|
960
|
+
prefillHints.forEach((hint, hintIndex) => {
|
|
961
|
+
if (!hint.label || typeof hint.label !== "string") {
|
|
962
|
+
errors.push(
|
|
963
|
+
`${elementPath}: prefillHints[${hintIndex}] must have a 'label' property of type string`
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
if (!hint.values || typeof hint.values !== "object") {
|
|
967
|
+
errors.push(
|
|
968
|
+
`${elementPath}: prefillHints[${hintIndex}] must have a 'values' property of type object`
|
|
969
|
+
);
|
|
970
|
+
} else {
|
|
971
|
+
for (const fieldKey in hint.values) {
|
|
972
|
+
const fieldExists = element.elements.some(
|
|
973
|
+
(childElement) => childElement.key === fieldKey
|
|
974
|
+
);
|
|
975
|
+
if (!fieldExists) {
|
|
976
|
+
errors.push(
|
|
977
|
+
`container "${element.key}": prefillHints[${hintIndex}] references non-existent field "${fieldKey}"`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
validateElements(element.elements, `${elementPath}.elements`);
|
|
986
|
+
checkFlatOutputCollisions(element.elements, `${elementPath}.elements`);
|
|
987
|
+
}
|
|
988
|
+
if (element.type === "select" && element.options) {
|
|
989
|
+
const defaultValue = element.default;
|
|
990
|
+
if (defaultValue !== void 0 && defaultValue !== null && defaultValue !== "") {
|
|
991
|
+
const hasMatchingOption = element.options.some(
|
|
992
|
+
(opt) => opt.value === defaultValue
|
|
993
|
+
);
|
|
994
|
+
if (!hasMatchingOption) {
|
|
995
|
+
errors.push(
|
|
996
|
+
`${elementPath}: default "${defaultValue}" not in options`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
if (Array.isArray(schema.elements)) {
|
|
1004
|
+
validateElements(schema.elements, "elements");
|
|
1005
|
+
checkFlatOutputCollisions(schema.elements, "elements");
|
|
1006
|
+
}
|
|
1007
|
+
return errors;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/utils/enable-conditions.ts
|
|
1011
|
+
function getValueByPath(data, path) {
|
|
1012
|
+
if (!data || typeof data !== "object") {
|
|
1013
|
+
return void 0;
|
|
1014
|
+
}
|
|
1015
|
+
const segments = path.match(/[^.[\]]+|\[\d+\]/g);
|
|
1016
|
+
if (!segments || segments.length === 0) {
|
|
1017
|
+
return void 0;
|
|
1018
|
+
}
|
|
1019
|
+
let current = data;
|
|
1020
|
+
for (const segment of segments) {
|
|
1021
|
+
if (current === void 0 || current === null) {
|
|
1022
|
+
return void 0;
|
|
1023
|
+
}
|
|
1024
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
1025
|
+
const index = parseInt(segment.slice(1, -1), 10);
|
|
1026
|
+
if (!Array.isArray(current) || isNaN(index)) {
|
|
1027
|
+
return void 0;
|
|
1028
|
+
}
|
|
1029
|
+
current = current[index];
|
|
1030
|
+
} else {
|
|
1031
|
+
current = current[segment];
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return current;
|
|
1035
|
+
}
|
|
1036
|
+
function evaluateEnableCondition(condition, formData, containerData) {
|
|
1037
|
+
var _a;
|
|
1038
|
+
if (!condition || !condition.key) {
|
|
1039
|
+
throw new Error("Invalid enableIf condition: must have a 'key' property");
|
|
1040
|
+
}
|
|
1041
|
+
const scope = (_a = condition.scope) != null ? _a : "relative";
|
|
1042
|
+
let dataSource;
|
|
1043
|
+
if (scope === "relative") {
|
|
1044
|
+
dataSource = containerData != null ? containerData : formData;
|
|
1045
|
+
} else if (scope === "absolute") {
|
|
1046
|
+
dataSource = formData;
|
|
1047
|
+
} else {
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`Invalid enableIf scope: must be "relative" or "absolute" (got "${scope}")`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
const actualValue = getValueByPath(dataSource, condition.key);
|
|
1053
|
+
if ("equals" in condition) {
|
|
1054
|
+
return deepEqual(actualValue, condition.equals);
|
|
1055
|
+
}
|
|
1056
|
+
throw new Error(
|
|
1057
|
+
`Invalid enableIf condition: no recognized operator (equals, etc.)`
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
function deepEqual(a, b) {
|
|
1061
|
+
if (a === b) return true;
|
|
1062
|
+
if (a == null || b == null) return a === b;
|
|
1063
|
+
if (typeof a !== typeof b) return false;
|
|
1064
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1065
|
+
try {
|
|
1066
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
1067
|
+
} catch (e) {
|
|
1068
|
+
if (e instanceof TypeError && (e.message.includes("circular") || e.message.includes("cyclic"))) {
|
|
1069
|
+
console.warn(
|
|
1070
|
+
"deepEqual: Circular reference detected in enableIf comparison, using reference equality"
|
|
1071
|
+
);
|
|
1072
|
+
return a === b;
|
|
1073
|
+
}
|
|
1074
|
+
throw e;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return a === b;
|
|
836
1078
|
}
|
|
837
1079
|
|
|
838
1080
|
// src/components/text.ts
|
|
@@ -937,7 +1179,7 @@ function createCharCounter(element, input) {
|
|
|
937
1179
|
return counter;
|
|
938
1180
|
}
|
|
939
1181
|
function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
940
|
-
var _a;
|
|
1182
|
+
var _a, _b, _c;
|
|
941
1183
|
const state = ctx.state;
|
|
942
1184
|
const readonly = isElementReadonly(element, state, ctx);
|
|
943
1185
|
const inputWrapper = document.createElement("div");
|
|
@@ -965,10 +1207,10 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
965
1207
|
`;
|
|
966
1208
|
textInput.name = pathKey;
|
|
967
1209
|
textInput.placeholder = (_a = element.placeholder) != null ? _a : t("placeholderText", state);
|
|
968
|
-
textInput.value = ctx.prefill[element.key]
|
|
1210
|
+
textInput.value = (_c = (_b = ctx.prefill[element.key]) != null ? _b : element.default) != null ? _c : "";
|
|
969
1211
|
textInput.readOnly = readonly;
|
|
970
1212
|
applySingleLineMode(textInput);
|
|
971
|
-
applyAutoExpand(textInput);
|
|
1213
|
+
applyAutoExpand(textInput, { observers: state.autoExpandObservers });
|
|
972
1214
|
if (!readonly) {
|
|
973
1215
|
textInput.addEventListener("focus", () => {
|
|
974
1216
|
textInput.style.borderColor = "var(--fb-border-focus-color)";
|
|
@@ -1027,11 +1269,12 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1027
1269
|
const chip = input.closest(".fb-chip");
|
|
1028
1270
|
const sib = chip == null ? void 0 : chip.nextElementSibling;
|
|
1029
1271
|
if (sib && sib.classList.contains("error-message")) {
|
|
1030
|
-
sib.
|
|
1272
|
+
sib.setAttribute("data-error-for", input.name);
|
|
1031
1273
|
}
|
|
1032
1274
|
});
|
|
1033
1275
|
}
|
|
1034
1276
|
function addChip(value = "") {
|
|
1277
|
+
var _a2;
|
|
1035
1278
|
const chip = document.createElement("div");
|
|
1036
1279
|
chip.className = "fb-chip";
|
|
1037
1280
|
const dot = document.createElement("span");
|
|
@@ -1042,7 +1285,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1042
1285
|
input.rows = 1;
|
|
1043
1286
|
input.className = "fb-chip-input";
|
|
1044
1287
|
input.value = value;
|
|
1045
|
-
input.placeholder = element.placeholder
|
|
1288
|
+
input.placeholder = (_a2 = element.placeholder) != null ? _a2 : t("placeholderText", state);
|
|
1046
1289
|
input.readOnly = readonly;
|
|
1047
1290
|
chip.appendChild(input);
|
|
1048
1291
|
if (!readonly && ctx.instance) {
|
|
@@ -1056,7 +1299,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1056
1299
|
input.addEventListener("input", handleChange);
|
|
1057
1300
|
}
|
|
1058
1301
|
applySingleLineMode(input);
|
|
1059
|
-
applyAutoExpand(input);
|
|
1302
|
+
applyAutoExpand(input, { observers: state.autoExpandObservers });
|
|
1060
1303
|
if (!readonly) {
|
|
1061
1304
|
const rem = document.createElement("button");
|
|
1062
1305
|
rem.type = "button";
|
|
@@ -1064,7 +1307,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1064
1307
|
rem.setAttribute("aria-label", t("removeElement", state));
|
|
1065
1308
|
rem.innerHTML = BIN_ICON_SVG;
|
|
1066
1309
|
rem.onclick = () => {
|
|
1067
|
-
var
|
|
1310
|
+
var _a3;
|
|
1068
1311
|
const chips = list.querySelectorAll(".fb-chip");
|
|
1069
1312
|
const idx = Array.prototype.indexOf.call(chips, chip);
|
|
1070
1313
|
if (idx < 0) return;
|
|
@@ -1078,7 +1321,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1078
1321
|
updateIndices();
|
|
1079
1322
|
updateAddButton();
|
|
1080
1323
|
updateRemoveButtons();
|
|
1081
|
-
(
|
|
1324
|
+
(_a3 = ctx.instance) == null ? void 0 : _a3.triggerOnChange(pathKey);
|
|
1082
1325
|
};
|
|
1083
1326
|
chip.appendChild(rem);
|
|
1084
1327
|
}
|
|
@@ -1120,113 +1363,53 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
1120
1363
|
updateRemoveButtons();
|
|
1121
1364
|
}
|
|
1122
1365
|
function validateTextElement(element, key, context) {
|
|
1123
|
-
var _a
|
|
1366
|
+
var _a;
|
|
1124
1367
|
const errors = [];
|
|
1125
|
-
const { scopeRoot,
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
if (
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
errorElement.style.cssText = `
|
|
1139
|
-
color: var(--fb-error-color);
|
|
1140
|
-
font-size: var(--fb-font-size-small);
|
|
1141
|
-
margin-top: 0.25rem;
|
|
1142
|
-
`;
|
|
1143
|
-
const chipAncestor = (_a2 = input.closest) == null ? void 0 : _a2.call(input, ".fb-chip");
|
|
1144
|
-
const anchor = chipAncestor || input;
|
|
1145
|
-
if (anchor.nextSibling) {
|
|
1146
|
-
(_b2 = anchor.parentNode) == null ? void 0 : _b2.insertBefore(errorElement, anchor.nextSibling);
|
|
1147
|
-
} else {
|
|
1148
|
-
(_c2 = anchor.parentNode) == null ? void 0 : _c2.appendChild(errorElement);
|
|
1368
|
+
const { scopeRoot, state } = context;
|
|
1369
|
+
const lengthOrPatternError = (val) => {
|
|
1370
|
+
if (!val) return null;
|
|
1371
|
+
if (element.minLength != null && val.length < element.minLength) {
|
|
1372
|
+
return t("minLength", state, { min: element.minLength });
|
|
1373
|
+
}
|
|
1374
|
+
if (element.maxLength != null && val.length > element.maxLength) {
|
|
1375
|
+
return t("maxLength", state, { max: element.maxLength });
|
|
1376
|
+
}
|
|
1377
|
+
if (element.pattern) {
|
|
1378
|
+
try {
|
|
1379
|
+
if (!new RegExp(element.pattern).test(val)) {
|
|
1380
|
+
return t("patternMismatch", state);
|
|
1149
1381
|
}
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
errorElement.style.display = "block";
|
|
1153
|
-
} else {
|
|
1154
|
-
input.classList.remove("invalid");
|
|
1155
|
-
input.title = "";
|
|
1156
|
-
if (errorElement) {
|
|
1157
|
-
errorElement.remove();
|
|
1382
|
+
} catch {
|
|
1383
|
+
return t("invalidPattern", state);
|
|
1158
1384
|
}
|
|
1159
1385
|
}
|
|
1386
|
+
return null;
|
|
1160
1387
|
};
|
|
1161
1388
|
const validateTextInput = (input, val, fieldKey) => {
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
if (element.minLength !== void 0 && element.minLength !== null && val.length < element.minLength) {
|
|
1166
|
-
const msg = t("minLength", state, { min: element.minLength });
|
|
1167
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
1168
|
-
markValidity(input, msg);
|
|
1169
|
-
hasError = true;
|
|
1170
|
-
} else if (element.maxLength !== void 0 && element.maxLength !== null && val.length > element.maxLength) {
|
|
1171
|
-
const msg = t("maxLength", state, { max: element.maxLength });
|
|
1172
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
1173
|
-
markValidity(input, msg);
|
|
1174
|
-
hasError = true;
|
|
1175
|
-
} else if (element.pattern) {
|
|
1176
|
-
try {
|
|
1177
|
-
const re = new RegExp(element.pattern);
|
|
1178
|
-
if (!re.test(val)) {
|
|
1179
|
-
const msg = t("patternMismatch", state);
|
|
1180
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
1181
|
-
markValidity(input, msg);
|
|
1182
|
-
hasError = true;
|
|
1183
|
-
}
|
|
1184
|
-
} catch {
|
|
1185
|
-
const msg = t("invalidPattern", state);
|
|
1186
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
1187
|
-
markValidity(input, msg);
|
|
1188
|
-
hasError = true;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
if (!hasError) {
|
|
1193
|
-
markValidity(input, null);
|
|
1194
|
-
}
|
|
1389
|
+
const msg = lengthOrPatternError(val);
|
|
1390
|
+
if (msg !== null) errors.push(`${fieldKey}: ${msg}`);
|
|
1391
|
+
markFieldValidity(input, msg, context);
|
|
1195
1392
|
};
|
|
1196
1393
|
if (element.multiple) {
|
|
1197
1394
|
const inputs = scopeRoot.querySelectorAll(`[name^="${key}\\["]`);
|
|
1198
1395
|
const values = [];
|
|
1199
|
-
|
|
1396
|
+
let filledCount = 0;
|
|
1200
1397
|
inputs.forEach((input, index) => {
|
|
1201
1398
|
var _a2;
|
|
1202
1399
|
const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
|
|
1203
|
-
rawValues.push(val);
|
|
1204
1400
|
values.push(val === "" ? null : val);
|
|
1401
|
+
if (val.trim() !== "") filledCount++;
|
|
1205
1402
|
validateTextInput(input, val, `${key}[${index}]`);
|
|
1206
1403
|
});
|
|
1207
|
-
|
|
1208
|
-
const { state } = context;
|
|
1209
|
-
const minCount = (_a = element.minCount) != null ? _a : 0;
|
|
1210
|
-
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
1211
|
-
const filteredValues = rawValues.filter((v) => v.trim() !== "");
|
|
1212
|
-
if (element.required && filteredValues.length === 0) {
|
|
1213
|
-
errors.push(`${key}: ${t("required", state)}`);
|
|
1214
|
-
}
|
|
1215
|
-
if (filteredValues.length < minCount) {
|
|
1216
|
-
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
1217
|
-
}
|
|
1218
|
-
if (filteredValues.length > maxCount) {
|
|
1219
|
-
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1404
|
+
validateItemCount(element, key, filledCount, context, errors);
|
|
1222
1405
|
return { value: values, errors };
|
|
1223
1406
|
} else {
|
|
1224
1407
|
const input = scopeRoot.querySelector(`[name="${key}"]`);
|
|
1225
|
-
const val = (
|
|
1226
|
-
if (
|
|
1227
|
-
const msg = t("required",
|
|
1408
|
+
const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
1409
|
+
if (element.required && val === "") {
|
|
1410
|
+
const msg = t("required", state);
|
|
1228
1411
|
errors.push(`${key}: ${msg}`);
|
|
1229
|
-
|
|
1412
|
+
markFieldValidity(input, msg, context);
|
|
1230
1413
|
return { value: null, errors };
|
|
1231
1414
|
}
|
|
1232
1415
|
if (input) {
|
|
@@ -1248,8 +1431,6 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
1248
1431
|
inputs.forEach((input, index) => {
|
|
1249
1432
|
if (index < value.length) {
|
|
1250
1433
|
input.value = value[index] != null ? String(value[index]) : "";
|
|
1251
|
-
input.classList.remove("invalid");
|
|
1252
|
-
input.title = "";
|
|
1253
1434
|
clearFieldError(input);
|
|
1254
1435
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1255
1436
|
}
|
|
@@ -1263,8 +1444,6 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
1263
1444
|
const input = scopeRoot.querySelector(`[name="${fieldPath}"]`);
|
|
1264
1445
|
if (input) {
|
|
1265
1446
|
input.value = value != null ? String(value) : "";
|
|
1266
|
-
input.classList.remove("invalid");
|
|
1267
|
-
input.title = "";
|
|
1268
1447
|
clearFieldError(input);
|
|
1269
1448
|
if (input instanceof HTMLTextAreaElement) {
|
|
1270
1449
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -1275,7 +1454,7 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
1275
1454
|
|
|
1276
1455
|
// src/components/textarea.ts
|
|
1277
1456
|
function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
1278
|
-
var _a, _b;
|
|
1457
|
+
var _a, _b, _c, _d;
|
|
1279
1458
|
const state = ctx.state;
|
|
1280
1459
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1281
1460
|
const textareaWrapper = document.createElement("div");
|
|
@@ -1289,8 +1468,8 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1289
1468
|
line-height: var(--fb-line-height, 1.5);
|
|
1290
1469
|
`;
|
|
1291
1470
|
textareaInput.name = pathKey;
|
|
1292
|
-
textareaInput.placeholder = (_a = element.placeholder) != null ? _a : "
|
|
1293
|
-
textareaInput.value = ctx.prefill[element.key]
|
|
1471
|
+
textareaInput.placeholder = (_a = element.placeholder) != null ? _a : t("placeholderText", state);
|
|
1472
|
+
textareaInput.value = (_c = (_b = ctx.prefill[element.key]) != null ? _b : element.default) != null ? _c : "";
|
|
1294
1473
|
textareaInput.readOnly = readonly;
|
|
1295
1474
|
if (!readonly && ctx.instance) {
|
|
1296
1475
|
const handleChange = () => {
|
|
@@ -1300,7 +1479,10 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1300
1479
|
textareaInput.addEventListener("blur", handleChange);
|
|
1301
1480
|
textareaInput.addEventListener("input", handleChange);
|
|
1302
1481
|
}
|
|
1303
|
-
applyAutoExpand(textareaInput, {
|
|
1482
|
+
applyAutoExpand(textareaInput, {
|
|
1483
|
+
minRows: (_d = element.rows) != null ? _d : 1,
|
|
1484
|
+
observers: state.autoExpandObservers
|
|
1485
|
+
});
|
|
1304
1486
|
textareaWrapper.appendChild(textareaInput);
|
|
1305
1487
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1306
1488
|
const counter = createCharCounter(element, textareaInput);
|
|
@@ -1332,7 +1514,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1332
1514
|
});
|
|
1333
1515
|
}
|
|
1334
1516
|
function addTextareaItem(value = "", index = -1) {
|
|
1335
|
-
var _a2;
|
|
1517
|
+
var _a2, _b2;
|
|
1336
1518
|
const itemWrapper = document.createElement("div");
|
|
1337
1519
|
itemWrapper.className = "multiple-textarea-item";
|
|
1338
1520
|
const textareaContainer = document.createElement("div");
|
|
@@ -1345,7 +1527,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1345
1527
|
font-family: var(--fb-font-family);
|
|
1346
1528
|
line-height: var(--fb-line-height, 1.5);
|
|
1347
1529
|
`;
|
|
1348
|
-
textareaInput.placeholder = element.placeholder
|
|
1530
|
+
textareaInput.placeholder = (_a2 = element.placeholder) != null ? _a2 : t("placeholderText", state);
|
|
1349
1531
|
textareaInput.value = value;
|
|
1350
1532
|
textareaInput.readOnly = readonly;
|
|
1351
1533
|
if (!readonly && ctx.instance) {
|
|
@@ -1356,7 +1538,10 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1356
1538
|
textareaInput.addEventListener("blur", handleChange);
|
|
1357
1539
|
textareaInput.addEventListener("input", handleChange);
|
|
1358
1540
|
}
|
|
1359
|
-
applyAutoExpand(textareaInput, {
|
|
1541
|
+
applyAutoExpand(textareaInput, {
|
|
1542
|
+
minRows: (_b2 = element.rows) != null ? _b2 : 1,
|
|
1543
|
+
observers: state.autoExpandObservers
|
|
1544
|
+
});
|
|
1360
1545
|
textareaContainer.appendChild(textareaInput);
|
|
1361
1546
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1362
1547
|
const counter = createCharCounter(element, textareaInput);
|
|
@@ -1579,7 +1764,19 @@ function createNumberRangeHint(element, input) {
|
|
|
1579
1764
|
updateColor();
|
|
1580
1765
|
return hint;
|
|
1581
1766
|
}
|
|
1767
|
+
function numberStepAttr(element) {
|
|
1768
|
+
if (element.step !== void 0) return element.step.toString();
|
|
1769
|
+
if (element.decimals !== void 0)
|
|
1770
|
+
return (10 ** -element.decimals).toString();
|
|
1771
|
+
return "any";
|
|
1772
|
+
}
|
|
1773
|
+
function applyDecimalsMarker(input, element) {
|
|
1774
|
+
if (element.decimals !== void 0) {
|
|
1775
|
+
input.setAttribute("data-decimals", String(element.decimals));
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1582
1778
|
function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
1779
|
+
var _a, _b, _c;
|
|
1583
1780
|
const state = ctx.state;
|
|
1584
1781
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1585
1782
|
const inputWrapper = document.createElement("div");
|
|
@@ -1587,11 +1784,12 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1587
1784
|
const numberInput = document.createElement("input");
|
|
1588
1785
|
numberInput.type = "number";
|
|
1589
1786
|
numberInput.name = pathKey;
|
|
1590
|
-
numberInput.placeholder = element.placeholder
|
|
1787
|
+
numberInput.placeholder = (_a = element.placeholder) != null ? _a : "0";
|
|
1591
1788
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
1592
1789
|
if (element.max !== void 0) numberInput.max = element.max.toString();
|
|
1593
|
-
|
|
1594
|
-
numberInput
|
|
1790
|
+
numberInput.step = numberStepAttr(element);
|
|
1791
|
+
applyDecimalsMarker(numberInput, element);
|
|
1792
|
+
numberInput.value = (_c = (_b = ctx.prefill[element.key]) != null ? _b : element.default) != null ? _c : "";
|
|
1595
1793
|
numberInput.readOnly = readonly;
|
|
1596
1794
|
if (!element.stepper) {
|
|
1597
1795
|
numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
@@ -1623,7 +1821,7 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1623
1821
|
wrapper.appendChild(inputWrapper);
|
|
1624
1822
|
}
|
|
1625
1823
|
function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
1626
|
-
var _a, _b;
|
|
1824
|
+
var _a, _b, _c;
|
|
1627
1825
|
const state = ctx.state;
|
|
1628
1826
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1629
1827
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
@@ -1631,7 +1829,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1631
1829
|
const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
|
|
1632
1830
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
1633
1831
|
while (values.length < minCount) {
|
|
1634
|
-
values.push(element.default
|
|
1832
|
+
values.push((_c = element.default) != null ? _c : "");
|
|
1635
1833
|
}
|
|
1636
1834
|
const container = document.createElement("div");
|
|
1637
1835
|
container.className = "fb-row";
|
|
@@ -1646,6 +1844,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1646
1844
|
});
|
|
1647
1845
|
}
|
|
1648
1846
|
function addNumberItem(value = "", index = -1) {
|
|
1847
|
+
var _a2;
|
|
1649
1848
|
const itemWrapper = document.createElement("div");
|
|
1650
1849
|
itemWrapper.className = "multiple-number-item flex items-center gap-2";
|
|
1651
1850
|
const inputContainer = document.createElement("div");
|
|
@@ -1660,10 +1859,11 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1660
1859
|
width: 100%;
|
|
1661
1860
|
box-sizing: border-box;
|
|
1662
1861
|
`;
|
|
1663
|
-
numberInput.placeholder = element.placeholder
|
|
1862
|
+
numberInput.placeholder = (_a2 = element.placeholder) != null ? _a2 : "0";
|
|
1664
1863
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
1665
1864
|
if (element.max !== void 0) numberInput.max = element.max.toString();
|
|
1666
|
-
|
|
1865
|
+
numberInput.step = numberStepAttr(element);
|
|
1866
|
+
applyDecimalsMarker(numberInput, element);
|
|
1667
1867
|
numberInput.value = value.toString();
|
|
1668
1868
|
numberInput.readOnly = readonly;
|
|
1669
1869
|
if (!readonly && ctx.instance) {
|
|
@@ -1728,12 +1928,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1728
1928
|
const handle = createAddItemRow(
|
|
1729
1929
|
"number",
|
|
1730
1930
|
() => {
|
|
1731
|
-
var _a2;
|
|
1732
|
-
values.push(element.default
|
|
1733
|
-
addNumberItem(element.default
|
|
1931
|
+
var _a2, _b2, _c2;
|
|
1932
|
+
values.push((_a2 = element.default) != null ? _a2 : "");
|
|
1933
|
+
addNumberItem((_b2 = element.default) != null ? _b2 : "");
|
|
1734
1934
|
updateAddButton();
|
|
1735
1935
|
updateRemoveButtons();
|
|
1736
|
-
(
|
|
1936
|
+
(_c2 = ctx.instance) == null ? void 0 : _c2.triggerOnChange(pathKey);
|
|
1737
1937
|
},
|
|
1738
1938
|
{ label: element.addLabel }
|
|
1739
1939
|
);
|
|
@@ -1749,59 +1949,29 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1749
1949
|
updateRemoveButtons();
|
|
1750
1950
|
}
|
|
1751
1951
|
function validateNumberElement(element, key, context) {
|
|
1752
|
-
var _a
|
|
1952
|
+
var _a;
|
|
1753
1953
|
const errors = [];
|
|
1754
|
-
const { scopeRoot,
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
input.classList.add("invalid");
|
|
1762
|
-
input.title = errorMessage;
|
|
1763
|
-
if (!errorElement) {
|
|
1764
|
-
errorElement = document.createElement("div");
|
|
1765
|
-
errorElement.id = errorId;
|
|
1766
|
-
errorElement.className = "error-message";
|
|
1767
|
-
errorElement.style.cssText = `
|
|
1768
|
-
color: var(--fb-error-color);
|
|
1769
|
-
font-size: var(--fb-font-size-small);
|
|
1770
|
-
margin-top: 0.25rem;
|
|
1771
|
-
`;
|
|
1772
|
-
if (input.nextSibling) {
|
|
1773
|
-
(_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
|
|
1774
|
-
} else {
|
|
1775
|
-
(_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
errorElement.textContent = errorMessage;
|
|
1779
|
-
errorElement.style.display = "block";
|
|
1780
|
-
} else {
|
|
1781
|
-
input.classList.remove("invalid");
|
|
1782
|
-
input.title = "";
|
|
1783
|
-
if (errorElement) {
|
|
1784
|
-
errorElement.remove();
|
|
1785
|
-
}
|
|
1954
|
+
const { scopeRoot, state } = context;
|
|
1955
|
+
const rangeError = (v) => {
|
|
1956
|
+
if (element.min != null && v < element.min) {
|
|
1957
|
+
return t("minValue", state, { min: element.min });
|
|
1958
|
+
}
|
|
1959
|
+
if (element.max != null && v > element.max) {
|
|
1960
|
+
return t("maxValue", state, { max: element.max });
|
|
1786
1961
|
}
|
|
1962
|
+
return null;
|
|
1787
1963
|
};
|
|
1788
|
-
const validateNumberInput = (input,
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
1794
|
-
markValidity(input, msg);
|
|
1795
|
-
hasError = true;
|
|
1796
|
-
} else if (!skipValidation && element.max !== void 0 && element.max !== null && v > element.max) {
|
|
1797
|
-
const msg = t("maxValue", state, { max: element.max });
|
|
1798
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
1799
|
-
markValidity(input, msg);
|
|
1800
|
-
hasError = true;
|
|
1801
|
-
}
|
|
1802
|
-
if (!hasError) {
|
|
1803
|
-
markValidity(input, null);
|
|
1964
|
+
const validateNumberInput = (input, fieldKey) => {
|
|
1965
|
+
const raw = input.value;
|
|
1966
|
+
if (raw === "") {
|
|
1967
|
+
markFieldValidity(input, null, context);
|
|
1968
|
+
return null;
|
|
1804
1969
|
}
|
|
1970
|
+
const v = parseFloat(raw);
|
|
1971
|
+
const msg = Number.isFinite(v) ? rangeError(v) : t("notANumber", state);
|
|
1972
|
+
if (msg !== null) errors.push(`${fieldKey}: ${msg}`);
|
|
1973
|
+
markFieldValidity(input, msg, context);
|
|
1974
|
+
return Number.isFinite(v) ? applyDecimals(v, element.decimals) : null;
|
|
1805
1975
|
};
|
|
1806
1976
|
if (element.multiple) {
|
|
1807
1977
|
const inputs = scopeRoot.querySelectorAll(
|
|
@@ -1809,63 +1979,21 @@ function validateNumberElement(element, key, context) {
|
|
|
1809
1979
|
);
|
|
1810
1980
|
const values = [];
|
|
1811
1981
|
inputs.forEach((input, index) => {
|
|
1812
|
-
|
|
1813
|
-
const raw = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
|
|
1814
|
-
if (raw === "") {
|
|
1815
|
-
values.push(null);
|
|
1816
|
-
markValidity(input, null);
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
const v = parseFloat(raw);
|
|
1820
|
-
if (!skipValidation && !Number.isFinite(v)) {
|
|
1821
|
-
const msg = t("notANumber", context.state);
|
|
1822
|
-
errors.push(`${key}[${index}]: ${msg}`);
|
|
1823
|
-
markValidity(input, msg);
|
|
1824
|
-
values.push(null);
|
|
1825
|
-
return;
|
|
1826
|
-
}
|
|
1827
|
-
validateNumberInput(input, v, `${key}[${index}]`);
|
|
1828
|
-
values.push(applyDecimals(v, element.decimals));
|
|
1982
|
+
values.push(validateNumberInput(input, `${key}[${index}]`));
|
|
1829
1983
|
});
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
const minCount = (_a = element.minCount) != null ? _a : 0;
|
|
1833
|
-
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
1834
|
-
const filteredValues = values.filter((v) => v !== null);
|
|
1835
|
-
if (element.required && filteredValues.length === 0) {
|
|
1836
|
-
errors.push(`${key}: ${t("required", state)}`);
|
|
1837
|
-
}
|
|
1838
|
-
if (filteredValues.length < minCount) {
|
|
1839
|
-
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
1840
|
-
}
|
|
1841
|
-
if (filteredValues.length > maxCount) {
|
|
1842
|
-
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1984
|
+
const filledCount = values.filter((v) => v !== null).length;
|
|
1985
|
+
validateItemCount(element, key, filledCount, context, errors);
|
|
1845
1986
|
return { value: values, errors };
|
|
1846
1987
|
} else {
|
|
1847
1988
|
const input = scopeRoot.querySelector(`[name="${key}"]`);
|
|
1848
|
-
|
|
1849
|
-
const { state } = context;
|
|
1850
|
-
if (!skipValidation && element.required && raw === "") {
|
|
1989
|
+
if (element.required && ((_a = input == null ? void 0 : input.value) != null ? _a : "") === "") {
|
|
1851
1990
|
const msg = t("required", state);
|
|
1852
1991
|
errors.push(`${key}: ${msg}`);
|
|
1853
|
-
|
|
1854
|
-
return { value: null, errors };
|
|
1855
|
-
}
|
|
1856
|
-
if (raw === "") {
|
|
1857
|
-
markValidity(input, null);
|
|
1858
|
-
return { value: null, errors };
|
|
1859
|
-
}
|
|
1860
|
-
const v = parseFloat(raw);
|
|
1861
|
-
if (!skipValidation && !Number.isFinite(v)) {
|
|
1862
|
-
const msg = t("notANumber", state);
|
|
1863
|
-
errors.push(`${key}: ${msg}`);
|
|
1864
|
-
markValidity(input, msg);
|
|
1992
|
+
markFieldValidity(input, msg, context);
|
|
1865
1993
|
return { value: null, errors };
|
|
1866
1994
|
}
|
|
1867
|
-
|
|
1868
|
-
return { value:
|
|
1995
|
+
if (!input) return { value: null, errors };
|
|
1996
|
+
return { value: validateNumberInput(input, key), errors };
|
|
1869
1997
|
}
|
|
1870
1998
|
}
|
|
1871
1999
|
function applyDecimals(v, decimals) {
|
|
@@ -1887,8 +2015,6 @@ function updateNumberField(element, fieldPath, value, context) {
|
|
|
1887
2015
|
inputs.forEach((input, index) => {
|
|
1888
2016
|
if (index < value.length) {
|
|
1889
2017
|
input.value = value[index] != null ? String(value[index]) : "";
|
|
1890
|
-
input.classList.remove("invalid");
|
|
1891
|
-
input.title = "";
|
|
1892
2018
|
clearFieldError(input);
|
|
1893
2019
|
}
|
|
1894
2020
|
});
|
|
@@ -1903,15 +2029,47 @@ function updateNumberField(element, fieldPath, value, context) {
|
|
|
1903
2029
|
);
|
|
1904
2030
|
if (input) {
|
|
1905
2031
|
input.value = value != null ? String(value) : "";
|
|
1906
|
-
input.classList.remove("invalid");
|
|
1907
|
-
input.title = "";
|
|
1908
2032
|
clearFieldError(input);
|
|
1909
2033
|
}
|
|
1910
2034
|
}
|
|
1911
2035
|
}
|
|
1912
2036
|
|
|
1913
2037
|
// src/components/select.ts
|
|
2038
|
+
function appendSelectOptions(select, element, selectedValue, state) {
|
|
2039
|
+
var _a;
|
|
2040
|
+
const options = element.options || [];
|
|
2041
|
+
if (!options.some((option) => option.value === "")) {
|
|
2042
|
+
const emptyOption = document.createElement("option");
|
|
2043
|
+
emptyOption.value = "";
|
|
2044
|
+
emptyOption.textContent = (_a = element.placeholder) != null ? _a : t("selectPlaceholder", state);
|
|
2045
|
+
select.appendChild(emptyOption);
|
|
2046
|
+
}
|
|
2047
|
+
const strSelected = selectedValue == null ? null : String(selectedValue);
|
|
2048
|
+
let anySelected = false;
|
|
2049
|
+
options.forEach((option) => {
|
|
2050
|
+
const optionEl = document.createElement("option");
|
|
2051
|
+
optionEl.value = option.value;
|
|
2052
|
+
optionEl.textContent = option.label;
|
|
2053
|
+
if (strSelected === option.value) {
|
|
2054
|
+
optionEl.selected = true;
|
|
2055
|
+
anySelected = true;
|
|
2056
|
+
}
|
|
2057
|
+
select.appendChild(optionEl);
|
|
2058
|
+
});
|
|
2059
|
+
if (!anySelected && strSelected !== null && strSelected !== "") {
|
|
2060
|
+
console.warn(
|
|
2061
|
+
`select "${element.key}": prefill value "${strSelected}" is not among the options; leaving the field unselected`
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
if (!anySelected) {
|
|
2065
|
+
const empty = Array.from(select.options).find(
|
|
2066
|
+
(option) => option.value === ""
|
|
2067
|
+
);
|
|
2068
|
+
if (empty) empty.selected = true;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
1914
2071
|
function renderSelectElement(element, ctx, wrapper, pathKey) {
|
|
2072
|
+
var _a;
|
|
1915
2073
|
const state = ctx.state;
|
|
1916
2074
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1917
2075
|
const selectInput = document.createElement("select");
|
|
@@ -1923,18 +2081,18 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1923
2081
|
`;
|
|
1924
2082
|
selectInput.name = pathKey;
|
|
1925
2083
|
selectInput.disabled = readonly;
|
|
1926
|
-
(
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
}
|
|
1933
|
-
selectInput.appendChild(optionEl);
|
|
1934
|
-
});
|
|
2084
|
+
appendSelectOptions(
|
|
2085
|
+
selectInput,
|
|
2086
|
+
element,
|
|
2087
|
+
(_a = ctx.prefill[element.key]) != null ? _a : element.default,
|
|
2088
|
+
state
|
|
2089
|
+
);
|
|
1935
2090
|
if (!readonly && ctx.instance) {
|
|
1936
2091
|
const handleChange = () => {
|
|
1937
|
-
ctx.instance.triggerOnChange(
|
|
2092
|
+
ctx.instance.triggerOnChange(
|
|
2093
|
+
pathKey,
|
|
2094
|
+
selectInput.value === "" ? null : selectInput.value
|
|
2095
|
+
);
|
|
1938
2096
|
};
|
|
1939
2097
|
selectInput.addEventListener("change", handleChange);
|
|
1940
2098
|
}
|
|
@@ -1947,7 +2105,7 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1947
2105
|
}
|
|
1948
2106
|
}
|
|
1949
2107
|
function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
1950
|
-
var _a, _b, _c
|
|
2108
|
+
var _a, _b, _c;
|
|
1951
2109
|
const state = ctx.state;
|
|
1952
2110
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1953
2111
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
@@ -1955,7 +2113,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1955
2113
|
const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
|
|
1956
2114
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
1957
2115
|
while (values.length < minCount) {
|
|
1958
|
-
values.push(
|
|
2116
|
+
values.push((_c = element.default) != null ? _c : "");
|
|
1959
2117
|
}
|
|
1960
2118
|
const container = document.createElement("div");
|
|
1961
2119
|
container.className = "fb-row";
|
|
@@ -1980,15 +2138,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1980
2138
|
font-family: var(--fb-font-family);
|
|
1981
2139
|
`;
|
|
1982
2140
|
selectInput.disabled = readonly;
|
|
1983
|
-
(element
|
|
1984
|
-
const optionElement = document.createElement("option");
|
|
1985
|
-
optionElement.value = option.value;
|
|
1986
|
-
optionElement.textContent = option.label;
|
|
1987
|
-
if (value === option.value) {
|
|
1988
|
-
optionElement.selected = true;
|
|
1989
|
-
}
|
|
1990
|
-
selectInput.appendChild(optionElement);
|
|
1991
|
-
});
|
|
2141
|
+
appendSelectOptions(selectInput, element, value, state);
|
|
1992
2142
|
if (!readonly && ctx.instance) {
|
|
1993
2143
|
const handleChange = () => {
|
|
1994
2144
|
ctx.instance.triggerOnChange(selectInput.name, selectInput.value);
|
|
@@ -2042,13 +2192,13 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2042
2192
|
const handle = createAddItemRow(
|
|
2043
2193
|
"select",
|
|
2044
2194
|
() => {
|
|
2045
|
-
var _a2, _b2
|
|
2046
|
-
const defaultValue =
|
|
2195
|
+
var _a2, _b2;
|
|
2196
|
+
const defaultValue = (_a2 = element.default) != null ? _a2 : "";
|
|
2047
2197
|
values.push(defaultValue);
|
|
2048
2198
|
addSelectItem(defaultValue);
|
|
2049
2199
|
updateAddButton();
|
|
2050
2200
|
updateRemoveButtons();
|
|
2051
|
-
(
|
|
2201
|
+
(_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
|
|
2052
2202
|
},
|
|
2053
2203
|
{ label: element.addLabel }
|
|
2054
2204
|
);
|
|
@@ -2072,57 +2222,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
2072
2222
|
function validateSelectElement(element, key, context) {
|
|
2073
2223
|
var _a;
|
|
2074
2224
|
const errors = [];
|
|
2075
|
-
const { scopeRoot
|
|
2076
|
-
const markValidity = (input, errorMessage) => {
|
|
2077
|
-
var _a2, _b;
|
|
2078
|
-
if (!input) return;
|
|
2079
|
-
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
2080
|
-
let errorElement = document.getElementById(errorId);
|
|
2081
|
-
if (errorMessage) {
|
|
2082
|
-
input.classList.add("invalid");
|
|
2083
|
-
input.title = errorMessage;
|
|
2084
|
-
if (!errorElement) {
|
|
2085
|
-
errorElement = document.createElement("div");
|
|
2086
|
-
errorElement.id = errorId;
|
|
2087
|
-
errorElement.className = "error-message";
|
|
2088
|
-
errorElement.style.cssText = `
|
|
2089
|
-
color: var(--fb-error-color);
|
|
2090
|
-
font-size: var(--fb-font-size-small);
|
|
2091
|
-
margin-top: 0.25rem;
|
|
2092
|
-
`;
|
|
2093
|
-
if (input.nextSibling) {
|
|
2094
|
-
(_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
|
|
2095
|
-
} else {
|
|
2096
|
-
(_b = input.parentNode) == null ? void 0 : _b.appendChild(errorElement);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
errorElement.textContent = errorMessage;
|
|
2100
|
-
errorElement.style.display = "block";
|
|
2101
|
-
} else {
|
|
2102
|
-
input.classList.remove("invalid");
|
|
2103
|
-
input.title = "";
|
|
2104
|
-
if (errorElement) {
|
|
2105
|
-
errorElement.remove();
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
};
|
|
2109
|
-
const validateMultipleCount = (key2, values, element2, filterFn) => {
|
|
2110
|
-
var _a2, _b;
|
|
2111
|
-
if (skipValidation) return;
|
|
2112
|
-
const { state } = context;
|
|
2113
|
-
const filteredValues = values.filter(filterFn);
|
|
2114
|
-
const minCount = "minCount" in element2 ? (_a2 = element2.minCount) != null ? _a2 : 0 : 0;
|
|
2115
|
-
const maxCount = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
2116
|
-
if (element2.required && filteredValues.length === 0) {
|
|
2117
|
-
errors.push(`${key2}: ${t("required", state)}`);
|
|
2118
|
-
}
|
|
2119
|
-
if (filteredValues.length < minCount) {
|
|
2120
|
-
errors.push(`${key2}: ${t("minItems", state, { min: minCount })}`);
|
|
2121
|
-
}
|
|
2122
|
-
if (filteredValues.length > maxCount) {
|
|
2123
|
-
errors.push(`${key2}: ${t("maxItems", state, { max: maxCount })}`);
|
|
2124
|
-
}
|
|
2125
|
-
};
|
|
2225
|
+
const { scopeRoot } = context;
|
|
2126
2226
|
if ("multiple" in element && element.multiple) {
|
|
2127
2227
|
const inputs = scopeRoot.querySelectorAll(
|
|
2128
2228
|
`[name^="${key}\\["]`
|
|
@@ -2131,27 +2231,36 @@ function validateSelectElement(element, key, context) {
|
|
|
2131
2231
|
inputs.forEach((input) => {
|
|
2132
2232
|
var _a2;
|
|
2133
2233
|
const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
|
|
2134
|
-
values.push(val);
|
|
2135
|
-
|
|
2234
|
+
values.push(val === "" ? null : val);
|
|
2235
|
+
markFieldValidity(input, null, context);
|
|
2136
2236
|
});
|
|
2137
|
-
|
|
2237
|
+
const filledCount = values.filter((v) => v != null).length;
|
|
2238
|
+
validateItemCount(element, key, filledCount, context, errors);
|
|
2138
2239
|
return { value: values, errors };
|
|
2139
2240
|
} else {
|
|
2140
|
-
const input = scopeRoot.querySelector(
|
|
2141
|
-
`[name="${key}"]`
|
|
2142
|
-
);
|
|
2241
|
+
const input = scopeRoot.querySelector(`[name="${key}"]`);
|
|
2143
2242
|
const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
2144
|
-
if (
|
|
2243
|
+
if (element.required && val === "") {
|
|
2145
2244
|
const msg = t("required", context.state);
|
|
2146
2245
|
errors.push(`${key}: ${msg}`);
|
|
2147
|
-
|
|
2246
|
+
markFieldValidity(input, msg, context);
|
|
2148
2247
|
return { value: null, errors };
|
|
2149
|
-
} else {
|
|
2150
|
-
markValidity(input, null);
|
|
2151
2248
|
}
|
|
2249
|
+
markFieldValidity(input, null, context);
|
|
2152
2250
|
return { value: val === "" ? null : val, errors };
|
|
2153
2251
|
}
|
|
2154
2252
|
}
|
|
2253
|
+
function assertValueInOptions(select, strValue, fieldPath) {
|
|
2254
|
+
if (strValue === "") return;
|
|
2255
|
+
const match = Array.from(select.options).some(
|
|
2256
|
+
(option) => option.value === strValue
|
|
2257
|
+
);
|
|
2258
|
+
if (!match) {
|
|
2259
|
+
throw new Error(
|
|
2260
|
+
`updateSelectField: value "${strValue}" is not among the options of "${fieldPath}"`
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2155
2264
|
function updateSelectField(element, fieldPath, value, context) {
|
|
2156
2265
|
const { scopeRoot } = context;
|
|
2157
2266
|
if ("multiple" in element && element.multiple) {
|
|
@@ -2166,13 +2275,18 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
2166
2275
|
);
|
|
2167
2276
|
selects.forEach((select, index) => {
|
|
2168
2277
|
if (index < value.length) {
|
|
2169
|
-
|
|
2278
|
+
const strValue = value[index] != null ? String(value[index]) : "";
|
|
2279
|
+
assertValueInOptions(select, strValue, `${fieldPath}[${index}]`);
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
selects.forEach((select, index) => {
|
|
2283
|
+
if (index < value.length) {
|
|
2284
|
+
const strValue = value[index] != null ? String(value[index]) : "";
|
|
2285
|
+
select.value = strValue;
|
|
2170
2286
|
const options = select.querySelectorAll("option");
|
|
2171
2287
|
options.forEach((option) => {
|
|
2172
|
-
option.selected = option.value ===
|
|
2288
|
+
option.selected = option.value === strValue;
|
|
2173
2289
|
});
|
|
2174
|
-
select.classList.remove("invalid");
|
|
2175
|
-
select.title = "";
|
|
2176
2290
|
clearFieldError(select);
|
|
2177
2291
|
}
|
|
2178
2292
|
});
|
|
@@ -2186,13 +2300,13 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
2186
2300
|
`[name="${fieldPath}"]`
|
|
2187
2301
|
);
|
|
2188
2302
|
if (select) {
|
|
2189
|
-
|
|
2303
|
+
const strValue = value != null ? String(value) : "";
|
|
2304
|
+
assertValueInOptions(select, strValue, fieldPath);
|
|
2305
|
+
select.value = strValue;
|
|
2190
2306
|
const options = select.querySelectorAll("option");
|
|
2191
2307
|
options.forEach((option) => {
|
|
2192
|
-
option.selected = option.value ===
|
|
2308
|
+
option.selected = option.value === strValue;
|
|
2193
2309
|
});
|
|
2194
|
-
select.classList.remove("invalid");
|
|
2195
|
-
select.title = "";
|
|
2196
2310
|
clearFieldError(select);
|
|
2197
2311
|
}
|
|
2198
2312
|
}
|
|
@@ -2387,14 +2501,14 @@ function renderSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
|
2387
2501
|
}
|
|
2388
2502
|
}
|
|
2389
2503
|
function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
2390
|
-
var _a, _b, _c
|
|
2504
|
+
var _a, _b, _c;
|
|
2391
2505
|
const state = ctx.state;
|
|
2392
2506
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
2393
2507
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
2394
2508
|
const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
|
|
2395
2509
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
2396
2510
|
while (values.length < minCount) {
|
|
2397
|
-
values.push(
|
|
2511
|
+
values.push((_c = element.default) != null ? _c : "");
|
|
2398
2512
|
}
|
|
2399
2513
|
const readonly = isElementReadonly(element, state, ctx);
|
|
2400
2514
|
const container = document.createElement("div");
|
|
@@ -2489,13 +2603,13 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
|
2489
2603
|
const handle = createAddItemRow(
|
|
2490
2604
|
"switcher",
|
|
2491
2605
|
() => {
|
|
2492
|
-
var _a2, _b2
|
|
2493
|
-
const defaultValue =
|
|
2606
|
+
var _a2, _b2;
|
|
2607
|
+
const defaultValue = (_a2 = element.default) != null ? _a2 : "";
|
|
2494
2608
|
values.push(defaultValue);
|
|
2495
2609
|
addSwitcherItem(defaultValue);
|
|
2496
2610
|
updateAddButton();
|
|
2497
2611
|
updateRemoveButtons();
|
|
2498
|
-
(
|
|
2612
|
+
(_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
|
|
2499
2613
|
},
|
|
2500
2614
|
{ label: element.addLabel }
|
|
2501
2615
|
);
|
|
@@ -2519,60 +2633,11 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
|
2519
2633
|
function validateSwitcherElement(element, key, context) {
|
|
2520
2634
|
var _a;
|
|
2521
2635
|
const errors = [];
|
|
2522
|
-
const { scopeRoot,
|
|
2523
|
-
const markValidity = (input, errorMessage) => {
|
|
2524
|
-
var _a2, _b;
|
|
2525
|
-
if (!input) return;
|
|
2526
|
-
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
2527
|
-
let errorElement = document.getElementById(errorId);
|
|
2528
|
-
if (errorMessage) {
|
|
2529
|
-
input.classList.add("invalid");
|
|
2530
|
-
input.title = errorMessage;
|
|
2531
|
-
if (!errorElement) {
|
|
2532
|
-
errorElement = document.createElement("div");
|
|
2533
|
-
errorElement.id = errorId;
|
|
2534
|
-
errorElement.className = "error-message";
|
|
2535
|
-
errorElement.style.cssText = `
|
|
2536
|
-
color: var(--fb-error-color);
|
|
2537
|
-
font-size: var(--fb-font-size-small);
|
|
2538
|
-
margin-top: 0.25rem;
|
|
2539
|
-
`;
|
|
2540
|
-
if (input.nextSibling) {
|
|
2541
|
-
(_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
|
|
2542
|
-
} else {
|
|
2543
|
-
(_b = input.parentNode) == null ? void 0 : _b.appendChild(errorElement);
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
errorElement.textContent = errorMessage;
|
|
2547
|
-
errorElement.style.display = "block";
|
|
2548
|
-
} else {
|
|
2549
|
-
input.classList.remove("invalid");
|
|
2550
|
-
input.title = "";
|
|
2551
|
-
if (errorElement) {
|
|
2552
|
-
errorElement.remove();
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
};
|
|
2556
|
-
const validateMultipleCount = (fieldKey, values, el, filterFn) => {
|
|
2557
|
-
var _a2, _b;
|
|
2558
|
-
if (skipValidation) return;
|
|
2559
|
-
const { state } = context;
|
|
2560
|
-
const filteredValues = values.filter(filterFn);
|
|
2561
|
-
const minCount = "minCount" in el ? (_a2 = el.minCount) != null ? _a2 : 0 : 0;
|
|
2562
|
-
const maxCount = "maxCount" in el ? (_b = el.maxCount) != null ? _b : Infinity : Infinity;
|
|
2563
|
-
if (el.required && filteredValues.length === 0) {
|
|
2564
|
-
errors.push(`${fieldKey}: ${t("required", state)}`);
|
|
2565
|
-
}
|
|
2566
|
-
if (filteredValues.length < minCount) {
|
|
2567
|
-
errors.push(`${fieldKey}: ${t("minItems", state, { min: minCount })}`);
|
|
2568
|
-
}
|
|
2569
|
-
if (filteredValues.length > maxCount) {
|
|
2570
|
-
errors.push(`${fieldKey}: ${t("maxItems", state, { max: maxCount })}`);
|
|
2571
|
-
}
|
|
2572
|
-
};
|
|
2636
|
+
const { scopeRoot, state } = context;
|
|
2573
2637
|
const validOptionValues = new Set(
|
|
2574
2638
|
"options" in element ? element.options.map((o) => o.value) : []
|
|
2575
2639
|
);
|
|
2640
|
+
const optionError = (val) => val !== "" && !validOptionValues.has(val) ? t("invalidOption", state) : null;
|
|
2576
2641
|
if ("multiple" in element && element.multiple) {
|
|
2577
2642
|
const inputs = scopeRoot.querySelectorAll(
|
|
2578
2643
|
`input[type="hidden"][name^="${key}\\["]`
|
|
@@ -2581,38 +2646,38 @@ function validateSwitcherElement(element, key, context) {
|
|
|
2581
2646
|
inputs.forEach((input) => {
|
|
2582
2647
|
var _a2;
|
|
2583
2648
|
const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
|
|
2584
|
-
values.push(val);
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
errors.push(`${key}: ${msg}`);
|
|
2589
|
-
} else {
|
|
2590
|
-
markValidity(input, null);
|
|
2591
|
-
}
|
|
2649
|
+
values.push(val === "" ? null : val);
|
|
2650
|
+
const msg = optionError(val);
|
|
2651
|
+
if (msg !== null) errors.push(`${key}: ${msg}`);
|
|
2652
|
+
markFieldValidity(switcherGroupOf(input), msg, context);
|
|
2592
2653
|
});
|
|
2593
|
-
|
|
2654
|
+
const filledCount = values.filter((v) => v != null).length;
|
|
2655
|
+
validateItemCount(element, key, filledCount, context, errors);
|
|
2594
2656
|
return { value: values, errors };
|
|
2595
2657
|
} else {
|
|
2596
2658
|
const input = scopeRoot.querySelector(
|
|
2597
2659
|
`input[type="hidden"][name="${key}"]`
|
|
2598
2660
|
);
|
|
2599
2661
|
const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
2600
|
-
|
|
2601
|
-
|
|
2662
|
+
const msg = element.required && val === "" ? t("required", state) : optionError(val);
|
|
2663
|
+
if (input) markFieldValidity(switcherGroupOf(input), msg, context);
|
|
2664
|
+
if (msg !== null) {
|
|
2602
2665
|
errors.push(`${key}: ${msg}`);
|
|
2603
|
-
markValidity(input, msg);
|
|
2604
2666
|
return { value: null, errors };
|
|
2605
2667
|
}
|
|
2606
|
-
if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
|
|
2607
|
-
const msg = t("invalidOption", context.state);
|
|
2608
|
-
errors.push(`${key}: ${msg}`);
|
|
2609
|
-
markValidity(input, msg);
|
|
2610
|
-
return { value: null, errors };
|
|
2611
|
-
}
|
|
2612
|
-
markValidity(input, null);
|
|
2613
2668
|
return { value: val === "" ? null : val, errors };
|
|
2614
2669
|
}
|
|
2615
2670
|
}
|
|
2671
|
+
function switcherGroupOf(input) {
|
|
2672
|
+
var _a;
|
|
2673
|
+
const group = (_a = input.parentElement) == null ? void 0 : _a.querySelector(".fb-switcher-group");
|
|
2674
|
+
if (!group) {
|
|
2675
|
+
throw new Error(
|
|
2676
|
+
`switcher "${input.name}": no .fb-switcher-group next to its hidden input`
|
|
2677
|
+
);
|
|
2678
|
+
}
|
|
2679
|
+
return group;
|
|
2680
|
+
}
|
|
2616
2681
|
function updateSwitcherField(element, fieldPath, value, context) {
|
|
2617
2682
|
var _a;
|
|
2618
2683
|
const { scopeRoot } = context;
|
|
@@ -2642,9 +2707,7 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2642
2707
|
}
|
|
2643
2708
|
});
|
|
2644
2709
|
}
|
|
2645
|
-
input
|
|
2646
|
-
input.title = "";
|
|
2647
|
-
clearFieldError(input);
|
|
2710
|
+
clearFieldError(switcherGroupOf(input));
|
|
2648
2711
|
}
|
|
2649
2712
|
});
|
|
2650
2713
|
if (value.length !== inputs.length) {
|
|
@@ -2670,9 +2733,7 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2670
2733
|
}
|
|
2671
2734
|
});
|
|
2672
2735
|
}
|
|
2673
|
-
input
|
|
2674
|
-
input.title = "";
|
|
2675
|
-
clearFieldError(input);
|
|
2736
|
+
clearFieldError(switcherGroupOf(input));
|
|
2676
2737
|
}
|
|
2677
2738
|
}
|
|
2678
2739
|
}
|
|
@@ -2784,6 +2845,7 @@ function renderBooleanElement(element, ctx, wrapper, pathKey) {
|
|
|
2784
2845
|
const hiddenInput = document.createElement("input");
|
|
2785
2846
|
hiddenInput.type = "hidden";
|
|
2786
2847
|
hiddenInput.name = pathKey;
|
|
2848
|
+
hiddenInput.setAttribute("data-boolean-field", "true");
|
|
2787
2849
|
hiddenInput.value = initial ? "true" : "false";
|
|
2788
2850
|
const row = document.createElement("div");
|
|
2789
2851
|
row.className = "fb-toggle-row";
|
|
@@ -3199,14 +3261,20 @@ function ensureFileStyles() {
|
|
|
3199
3261
|
padding: 6px;
|
|
3200
3262
|
}
|
|
3201
3263
|
|
|
3202
|
-
/* \u2500\u2500\u2500
|
|
3203
|
-
.fb-
|
|
3264
|
+
/* \u2500\u2500\u2500 Footer row below multi grid: N/max counter + clear-all \u2500\u2500\u2500 */
|
|
3265
|
+
.fb-multi-footer {
|
|
3204
3266
|
margin-top: 10px;
|
|
3205
3267
|
display: flex;
|
|
3206
3268
|
align-items: center;
|
|
3207
|
-
|
|
3269
|
+
gap: 8px;
|
|
3270
|
+
}
|
|
3271
|
+
.fb-files-counter {
|
|
3272
|
+
font-size: var(--fb-font-size-small, 12px);
|
|
3273
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
3274
|
+
font-variant-numeric: tabular-nums;
|
|
3208
3275
|
}
|
|
3209
3276
|
.fb-clear-all-btn {
|
|
3277
|
+
margin-left: auto;
|
|
3210
3278
|
font-size: 12px;
|
|
3211
3279
|
color: #94a3b8;
|
|
3212
3280
|
background: none;
|
|
@@ -3456,24 +3524,55 @@ function createFileTile() {
|
|
|
3456
3524
|
tile.className = "fb-tile";
|
|
3457
3525
|
return tile;
|
|
3458
3526
|
}
|
|
3459
|
-
function
|
|
3460
|
-
var _a
|
|
3461
|
-
const
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3527
|
+
function fileErrorSlot(container) {
|
|
3528
|
+
var _a;
|
|
3529
|
+
const wrapper = container.closest("[data-files-wrapper]");
|
|
3530
|
+
const node = wrapper ? (_a = Array.from(wrapper.children).find(
|
|
3531
|
+
(child) => child instanceof HTMLElement && child.classList.contains("file-error-message")
|
|
3532
|
+
)) != null ? _a : null : null;
|
|
3533
|
+
return { wrapper, node };
|
|
3534
|
+
}
|
|
3535
|
+
function showFileError(container, message, state, kind = "action") {
|
|
3536
|
+
const { wrapper, node: existing } = fileErrorSlot(container);
|
|
3537
|
+
if (!wrapper) return;
|
|
3538
|
+
let node = existing;
|
|
3539
|
+
if (!node) {
|
|
3540
|
+
node = createErrorNode(state, "file-error-message error-message");
|
|
3541
|
+
wrapper.appendChild(node);
|
|
3542
|
+
}
|
|
3543
|
+
setAttr(node, "data-error-kind", kind);
|
|
3544
|
+
if (node.textContent !== message) node.textContent = message;
|
|
3545
|
+
if (kind === "action") {
|
|
3546
|
+
setAttr(node, "role", "alert");
|
|
3547
|
+
unsetInvalidState(wrapper);
|
|
3548
|
+
linkDescription(wrapper, node);
|
|
3549
|
+
} else {
|
|
3550
|
+
node.removeAttribute("role");
|
|
3551
|
+
setInvalidMark(wrapper, node, state);
|
|
3552
|
+
}
|
|
3472
3553
|
}
|
|
3473
|
-
function clearFileError(container) {
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3554
|
+
function clearFileError(container, kind = "action") {
|
|
3555
|
+
const { wrapper, node } = fileErrorSlot(container);
|
|
3556
|
+
if (wrapper && (node == null ? void 0 : node.dataset.errorKind) === kind) {
|
|
3557
|
+
clearInvalidMark(wrapper, node);
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
function markFileValidity(wrapper, message, scope) {
|
|
3561
|
+
const mark = resolveMark(wrapper, message, scope);
|
|
3562
|
+
if (mark === void 0) return;
|
|
3563
|
+
const { node } = fileErrorSlot(wrapper);
|
|
3564
|
+
if (scope.readonly) {
|
|
3565
|
+
clearInvalidMark(wrapper, node);
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
if (mark === null) {
|
|
3569
|
+
clearFileError(wrapper, "validation");
|
|
3570
|
+
return;
|
|
3571
|
+
}
|
|
3572
|
+
if (scope.draftMarks && node && node.dataset.errorKind !== "validation") {
|
|
3573
|
+
return;
|
|
3574
|
+
}
|
|
3575
|
+
showFileError(wrapper, mark, scope.state, "validation");
|
|
3477
3576
|
}
|
|
3478
3577
|
function addDeleteButton(container, state, onDelete) {
|
|
3479
3578
|
const existingOverlay = container.querySelector(".delete-overlay");
|
|
@@ -4343,7 +4442,8 @@ async function handleFileSelect(opts) {
|
|
|
4343
4442
|
const formats = allowedExtensions.join(", ");
|
|
4344
4443
|
showFileError(
|
|
4345
4444
|
container,
|
|
4346
|
-
t("invalidFileExtension", state, { name: file.name, formats })
|
|
4445
|
+
t("invalidFileExtension", state, { name: file.name, formats }),
|
|
4446
|
+
state
|
|
4347
4447
|
);
|
|
4348
4448
|
return;
|
|
4349
4449
|
}
|
|
@@ -4351,14 +4451,16 @@ async function handleFileSelect(opts) {
|
|
|
4351
4451
|
const mimes = allowedMimes.join(", ");
|
|
4352
4452
|
showFileError(
|
|
4353
4453
|
container,
|
|
4354
|
-
t("invalidFileMime", state, { name: file.name, type: file.type, mimes })
|
|
4454
|
+
t("invalidFileMime", state, { name: file.name, type: file.type, mimes }),
|
|
4455
|
+
state
|
|
4355
4456
|
);
|
|
4356
4457
|
return;
|
|
4357
4458
|
}
|
|
4358
4459
|
if (!isFileSizeAllowed(file, maxSizeMB)) {
|
|
4359
4460
|
showFileError(
|
|
4360
4461
|
container,
|
|
4361
|
-
t("fileTooLarge", state, { name: file.name, maxSize: maxSizeMB })
|
|
4462
|
+
t("fileTooLarge", state, { name: file.name, maxSize: maxSizeMB }),
|
|
4463
|
+
state
|
|
4362
4464
|
);
|
|
4363
4465
|
return;
|
|
4364
4466
|
}
|
|
@@ -4555,7 +4657,7 @@ async function runMultiFileBatch(opts, files, listEl, errorTarget) {
|
|
|
4555
4657
|
state
|
|
4556
4658
|
);
|
|
4557
4659
|
if (errorTarget) {
|
|
4558
|
-
if (errorMessage) showFileError(errorTarget, errorMessage);
|
|
4660
|
+
if (errorMessage) showFileError(errorTarget, errorMessage, state);
|
|
4559
4661
|
else clearFileError(errorTarget);
|
|
4560
4662
|
}
|
|
4561
4663
|
const handle = coordinator.beginBatch(accepted.length);
|
|
@@ -4574,11 +4676,8 @@ async function runMultiFileBatch(opts, files, listEl, errorTarget) {
|
|
|
4574
4676
|
}
|
|
4575
4677
|
const { wasLast } = handle.end();
|
|
4576
4678
|
if (wasLast) updateCallback();
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
if (combined) showFileError(errorTarget, combined);
|
|
4580
|
-
else clearFileError(errorTarget);
|
|
4581
|
-
}
|
|
4679
|
+
const combined = buildBatchErrorMessage(errorMessage, failures, state);
|
|
4680
|
+
if (errorTarget && combined) showFileError(errorTarget, combined, state);
|
|
4582
4681
|
}
|
|
4583
4682
|
function setupFilesDropHandler(opts) {
|
|
4584
4683
|
const { filesContainer } = opts;
|
|
@@ -4693,7 +4792,7 @@ async function handleLibraryPickMulti(opts) {
|
|
|
4693
4792
|
selectedResourceIds: knownRids
|
|
4694
4793
|
});
|
|
4695
4794
|
} catch (error) {
|
|
4696
|
-
showFileError(wrapper, extractPickerError(error, state));
|
|
4795
|
+
showFileError(wrapper, extractPickerError(error, state), state);
|
|
4697
4796
|
return;
|
|
4698
4797
|
}
|
|
4699
4798
|
if (picked.length === 0) return;
|
|
@@ -4721,7 +4820,8 @@ async function handleLibraryPickMulti(opts) {
|
|
|
4721
4820
|
if (skipped > 0) {
|
|
4722
4821
|
showFileError(
|
|
4723
4822
|
wrapper,
|
|
4724
|
-
t("filesLimitExceeded", state, { skipped, max: maxCount })
|
|
4823
|
+
t("filesLimitExceeded", state, { skipped, max: maxCount }),
|
|
4824
|
+
state
|
|
4725
4825
|
);
|
|
4726
4826
|
}
|
|
4727
4827
|
return;
|
|
@@ -4730,7 +4830,8 @@ async function handleLibraryPickMulti(opts) {
|
|
|
4730
4830
|
if (skipped > 0) {
|
|
4731
4831
|
showFileError(
|
|
4732
4832
|
wrapper,
|
|
4733
|
-
t("filesLimitExceeded", state, { skipped, max: maxCount })
|
|
4833
|
+
t("filesLimitExceeded", state, { skipped, max: maxCount }),
|
|
4834
|
+
state
|
|
4734
4835
|
);
|
|
4735
4836
|
}
|
|
4736
4837
|
for (const resource of accepted) {
|
|
@@ -4768,7 +4869,7 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
|
|
|
4768
4869
|
selectedResourceIds: []
|
|
4769
4870
|
});
|
|
4770
4871
|
} catch (error) {
|
|
4771
|
-
showFileError(container, extractPickerError(error, state));
|
|
4872
|
+
showFileError(container, extractPickerError(error, state), state);
|
|
4772
4873
|
return;
|
|
4773
4874
|
}
|
|
4774
4875
|
if (picked.length === 0) return;
|
|
@@ -4781,7 +4882,7 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
|
|
|
4781
4882
|
state
|
|
4782
4883
|
);
|
|
4783
4884
|
if (validationError !== null) {
|
|
4784
|
-
showFileError(container, validationError);
|
|
4885
|
+
showFileError(container, validationError, state);
|
|
4785
4886
|
return;
|
|
4786
4887
|
}
|
|
4787
4888
|
clearFileError(container);
|
|
@@ -5024,10 +5125,7 @@ function buildPlaceholderTile(isDragOver = false) {
|
|
|
5024
5125
|
div.className = `fb-multi-placeholder fb-checker${isDragOver ? " fb-drag-over" : ""}`;
|
|
5025
5126
|
return div;
|
|
5026
5127
|
}
|
|
5027
|
-
function
|
|
5028
|
-
if (ridCount <= 1) return null;
|
|
5029
|
-
const row = document.createElement("div");
|
|
5030
|
-
row.className = "fb-clear-all-row";
|
|
5128
|
+
function buildClearAllButton(state, onClearAll) {
|
|
5031
5129
|
const clearBtn = document.createElement("button");
|
|
5032
5130
|
clearBtn.type = "button";
|
|
5033
5131
|
clearBtn.className = "fb-clear-all-btn";
|
|
@@ -5038,9 +5136,38 @@ function buildClearAllRow(state, ridCount, onClearAll) {
|
|
|
5038
5136
|
onClearAll();
|
|
5039
5137
|
}
|
|
5040
5138
|
};
|
|
5041
|
-
|
|
5139
|
+
return clearBtn;
|
|
5140
|
+
}
|
|
5141
|
+
function buildFooterRow(state, ridCount, maxCount, onClearAll) {
|
|
5142
|
+
const showCounter = maxCount !== Infinity;
|
|
5143
|
+
const showClearAll = onClearAll !== void 0 && ridCount > 1;
|
|
5144
|
+
if (!showCounter && !showClearAll) return null;
|
|
5145
|
+
const row = document.createElement("div");
|
|
5146
|
+
row.className = "fb-multi-footer";
|
|
5147
|
+
if (showCounter) {
|
|
5148
|
+
const counter = document.createElement("span");
|
|
5149
|
+
counter.className = "fb-files-counter";
|
|
5150
|
+
counter.textContent = t("filesCounter", state, {
|
|
5151
|
+
count: ridCount,
|
|
5152
|
+
max: maxCount
|
|
5153
|
+
});
|
|
5154
|
+
row.appendChild(counter);
|
|
5155
|
+
}
|
|
5156
|
+
if (showClearAll) row.appendChild(buildClearAllButton(state, onClearAll));
|
|
5042
5157
|
return row;
|
|
5043
5158
|
}
|
|
5159
|
+
function syncOverLimitError(container, ridCount, maxCount, state) {
|
|
5160
|
+
if (ridCount > maxCount) {
|
|
5161
|
+
showFileError(
|
|
5162
|
+
container,
|
|
5163
|
+
t("maxFiles", state, { max: maxCount }),
|
|
5164
|
+
state,
|
|
5165
|
+
"limit"
|
|
5166
|
+
);
|
|
5167
|
+
} else {
|
|
5168
|
+
clearFileError(container, "limit");
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5044
5171
|
var gridResizeObservers = /* @__PURE__ */ new WeakMap();
|
|
5045
5172
|
var gridMeasureFrames = /* @__PURE__ */ new WeakMap();
|
|
5046
5173
|
function cancelPendingMeasure(container) {
|
|
@@ -5136,6 +5263,7 @@ function renderResourcePills(opts) {
|
|
|
5136
5263
|
grid2.appendChild(tile);
|
|
5137
5264
|
}
|
|
5138
5265
|
}
|
|
5266
|
+
clearFileError(container, "limit");
|
|
5139
5267
|
return;
|
|
5140
5268
|
}
|
|
5141
5269
|
const outerDiv = document.createElement("div");
|
|
@@ -5215,10 +5343,14 @@ function renderResourcePills(opts) {
|
|
|
5215
5343
|
}
|
|
5216
5344
|
}
|
|
5217
5345
|
});
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5346
|
+
const footer = buildFooterRow(
|
|
5347
|
+
state,
|
|
5348
|
+
ridList.length,
|
|
5349
|
+
effectiveMax,
|
|
5350
|
+
onClearAll
|
|
5351
|
+
);
|
|
5352
|
+
if (footer) container.appendChild(footer);
|
|
5353
|
+
syncOverLimitError(container, ridList.length, effectiveMax, state);
|
|
5222
5354
|
}
|
|
5223
5355
|
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
5224
5356
|
var _a, _b;
|
|
@@ -5371,9 +5503,10 @@ function buildAcceptAttribute(accept) {
|
|
|
5371
5503
|
...(_c = accept.mime) != null ? _c : []
|
|
5372
5504
|
].join(",");
|
|
5373
5505
|
}
|
|
5374
|
-
function
|
|
5375
|
-
var _a, _b;
|
|
5506
|
+
function renderMultiFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
5507
|
+
var _a, _b, _c;
|
|
5376
5508
|
const state = ctx.state;
|
|
5509
|
+
const maxFiles = (_a = element.maxCount) != null ? _a : Infinity;
|
|
5377
5510
|
const filesWrapper = document.createElement("div");
|
|
5378
5511
|
filesWrapper.className = "fb-row";
|
|
5379
5512
|
filesWrapper.dataset.filesWrapper = pathKey;
|
|
@@ -5399,7 +5532,7 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
5399
5532
|
allowedMimes: getAllowedMimes(element.accept),
|
|
5400
5533
|
// Prefer schema's `maxSize`; fall back to legacy `maxSizeMB` for
|
|
5401
5534
|
// backward compatibility (matches addFileSizeHint in validation.ts).
|
|
5402
|
-
maxSize: (
|
|
5535
|
+
maxSize: (_c = (_b = element.maxSize) != null ? _b : element.maxSizeMB) != null ? _c : Infinity
|
|
5403
5536
|
};
|
|
5404
5537
|
const openPicker = () => {
|
|
5405
5538
|
filesPicker.click();
|
|
@@ -5542,7 +5675,7 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
5542
5675
|
state,
|
|
5543
5676
|
!currentlyReadonly,
|
|
5544
5677
|
currentlyReadonly ? null : () => {
|
|
5545
|
-
var _a2, _b2,
|
|
5678
|
+
var _a2, _b2, _c2;
|
|
5546
5679
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.file);
|
|
5547
5680
|
const idx = initialFiles.indexOf(rid);
|
|
5548
5681
|
if (idx > -1) {
|
|
@@ -5560,7 +5693,7 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
5560
5693
|
return;
|
|
5561
5694
|
}
|
|
5562
5695
|
pendingRemovals.add(rid);
|
|
5563
|
-
(
|
|
5696
|
+
(_c2 = list.querySelector(`[data-resource-id="${rid}"]`)) == null ? void 0 : _c2.remove();
|
|
5564
5697
|
if (ctx.instance && pathKey && !state.config.readonly) {
|
|
5565
5698
|
ctx.instance.triggerOnChange(pathKey, initialFiles);
|
|
5566
5699
|
}
|
|
@@ -5581,22 +5714,24 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
|
5581
5714
|
};
|
|
5582
5715
|
setupFilesDropHandler({ ...sharedHandlerOpts, filesContainer });
|
|
5583
5716
|
setupFilesPickerHandler({ ...sharedHandlerOpts, filesPicker });
|
|
5717
|
+
state.multiFileSetters.set(filesWrapper, (resourceIds) => {
|
|
5718
|
+
var _a2;
|
|
5719
|
+
if (coordinator.hasInFlightBatches()) {
|
|
5720
|
+
throw new Error(
|
|
5721
|
+
`setFormData/updateField: file field "${pathKey}" has uploads in flight; set its value after they settle`
|
|
5722
|
+
);
|
|
5723
|
+
}
|
|
5724
|
+
for (const rid of initialFiles) {
|
|
5725
|
+
if (!resourceIds.includes(rid)) {
|
|
5726
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.file);
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
initialFiles.splice(0, initialFiles.length, ...resourceIds);
|
|
5730
|
+
updateFilesDisplay();
|
|
5731
|
+
});
|
|
5584
5732
|
updateFilesDisplay();
|
|
5585
5733
|
wrapper.appendChild(filesWrapper);
|
|
5586
5734
|
}
|
|
5587
|
-
function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
5588
|
-
setupMultiFileEditMode(element, ctx, wrapper, pathKey, Infinity);
|
|
5589
|
-
}
|
|
5590
|
-
function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
5591
|
-
var _a;
|
|
5592
|
-
setupMultiFileEditMode(
|
|
5593
|
-
element,
|
|
5594
|
-
ctx,
|
|
5595
|
-
wrapper,
|
|
5596
|
-
pathKey,
|
|
5597
|
-
(_a = element.maxCount) != null ? _a : Infinity
|
|
5598
|
-
);
|
|
5599
|
-
}
|
|
5600
5735
|
|
|
5601
5736
|
// src/components/file/validate.ts
|
|
5602
5737
|
function readMultiFileResourceIds(scopeRoot, fullKey) {
|
|
@@ -5618,90 +5753,103 @@ function readMultiFileResourceIds(scopeRoot, fullKey) {
|
|
|
5618
5753
|
}
|
|
5619
5754
|
return parsed;
|
|
5620
5755
|
}
|
|
5621
|
-
function
|
|
5622
|
-
var _a, _b;
|
|
5623
|
-
const minFiles = "minCount" in element ? (_a = element.minCount) != null ? _a : 0 : 0;
|
|
5624
|
-
const maxFiles = "maxCount" in element ? (_b = element.maxCount) != null ? _b : Infinity : Infinity;
|
|
5625
|
-
if (element.required && resourceIds.length === 0) {
|
|
5626
|
-
errors.push(`${key}: ${t("required", state)}`);
|
|
5627
|
-
}
|
|
5628
|
-
if (resourceIds.length < minFiles) {
|
|
5629
|
-
errors.push(`${key}: ${t("minFiles", state, { min: minFiles })}`);
|
|
5630
|
-
}
|
|
5631
|
-
if (resourceIds.length > maxFiles) {
|
|
5632
|
-
errors.push(`${key}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
5633
|
-
}
|
|
5634
|
-
}
|
|
5635
|
-
function validateFileTypes(key, resourceIds, element, state, errors) {
|
|
5756
|
+
function validateFileTypes(resourceIds, element, state) {
|
|
5636
5757
|
var _a, _b;
|
|
5758
|
+
const messages = [];
|
|
5637
5759
|
const acceptField = "accept" in element ? element.accept : void 0;
|
|
5638
5760
|
const allowedExtensions = getAllowedExtensions(acceptField);
|
|
5639
5761
|
const allowedMimes = getAllowedMimes(acceptField);
|
|
5640
|
-
if (allowedExtensions.length === 0 && allowedMimes.length === 0)
|
|
5762
|
+
if (allowedExtensions.length === 0 && allowedMimes.length === 0) {
|
|
5763
|
+
return messages;
|
|
5764
|
+
}
|
|
5641
5765
|
const formats = allowedExtensions.join(", ");
|
|
5642
5766
|
const mimes = allowedMimes.join(", ");
|
|
5643
5767
|
for (const rid of resourceIds) {
|
|
5644
5768
|
const meta = state.resourceIndex.get(rid);
|
|
5645
5769
|
const fileName = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid;
|
|
5646
5770
|
if (allowedExtensions.length > 0 && !isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
5647
|
-
|
|
5648
|
-
|
|
5771
|
+
messages.push(
|
|
5772
|
+
t("invalidFileExtension", state, { name: fileName, formats })
|
|
5649
5773
|
);
|
|
5650
5774
|
continue;
|
|
5651
5775
|
}
|
|
5652
5776
|
if (allowedMimes.length > 0 && !(meta == null ? void 0 : meta.inferredFromExtension)) {
|
|
5653
5777
|
const mimeType = (_b = meta == null ? void 0 : meta.type) != null ? _b : "";
|
|
5654
5778
|
if (!isMimeAllowed(mimeType, allowedMimes)) {
|
|
5655
|
-
|
|
5656
|
-
|
|
5779
|
+
messages.push(
|
|
5780
|
+
t("invalidFileMime", state, {
|
|
5781
|
+
name: fileName,
|
|
5782
|
+
type: mimeType,
|
|
5783
|
+
mimes
|
|
5784
|
+
})
|
|
5657
5785
|
);
|
|
5658
5786
|
}
|
|
5659
5787
|
}
|
|
5660
5788
|
}
|
|
5789
|
+
return messages;
|
|
5661
5790
|
}
|
|
5662
|
-
function validateFileSizes(
|
|
5791
|
+
function validateFileSizes(resourceIds, element, state) {
|
|
5663
5792
|
var _a;
|
|
5793
|
+
const messages = [];
|
|
5664
5794
|
const maxSizeMB = "maxSize" in element ? (_a = element.maxSize) != null ? _a : Infinity : Infinity;
|
|
5665
|
-
if (maxSizeMB === Infinity) return;
|
|
5795
|
+
if (maxSizeMB === Infinity) return messages;
|
|
5666
5796
|
for (const rid of resourceIds) {
|
|
5667
5797
|
const meta = state.resourceIndex.get(rid);
|
|
5668
5798
|
if (!meta) continue;
|
|
5669
5799
|
if (meta.size > maxSizeMB * 1024 * 1024) {
|
|
5670
|
-
|
|
5671
|
-
|
|
5800
|
+
messages.push(
|
|
5801
|
+
t("fileTooLarge", state, { name: meta.name, maxSize: maxSizeMB })
|
|
5672
5802
|
);
|
|
5673
5803
|
}
|
|
5674
5804
|
}
|
|
5805
|
+
return messages;
|
|
5806
|
+
}
|
|
5807
|
+
function reportFileMessages(scopeRoot, wrapperKey, key, messages, context) {
|
|
5808
|
+
const wrapper = scopeRoot.querySelector(
|
|
5809
|
+
`[data-files-wrapper="${wrapperKey}"]`
|
|
5810
|
+
);
|
|
5811
|
+
if (wrapper) {
|
|
5812
|
+
markFileValidity(wrapper, joinErrorMessages(messages), context);
|
|
5813
|
+
}
|
|
5814
|
+
return messages.map((message) => `${key}: ${message}`);
|
|
5675
5815
|
}
|
|
5676
5816
|
function validateMultiFile(element, key, context) {
|
|
5677
|
-
const { scopeRoot,
|
|
5678
|
-
const errors = [];
|
|
5817
|
+
const { scopeRoot, path, state } = context;
|
|
5679
5818
|
const fullKey = pathJoin(path, key);
|
|
5680
5819
|
const resourceIds = readMultiFileResourceIds(scopeRoot, fullKey);
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5820
|
+
const messages = [
|
|
5821
|
+
...countRuleMessages(element, resourceIds.length, state, {
|
|
5822
|
+
min: "minFiles",
|
|
5823
|
+
max: "maxFiles"
|
|
5824
|
+
}),
|
|
5825
|
+
...validateFileTypes(resourceIds, element, state),
|
|
5826
|
+
...validateFileSizes(resourceIds, element, state)
|
|
5827
|
+
];
|
|
5828
|
+
return {
|
|
5829
|
+
value: resourceIds,
|
|
5830
|
+
errors: reportFileMessages(scopeRoot, fullKey, key, messages, context)
|
|
5831
|
+
};
|
|
5687
5832
|
}
|
|
5688
5833
|
function validateSingleFile(element, key, context) {
|
|
5689
5834
|
var _a;
|
|
5690
|
-
const { scopeRoot,
|
|
5691
|
-
const errors = [];
|
|
5835
|
+
const { scopeRoot, state } = context;
|
|
5692
5836
|
const input = scopeRoot.querySelector(
|
|
5693
5837
|
`input[name="${key}"][type="hidden"]`
|
|
5694
5838
|
);
|
|
5695
5839
|
const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
}
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5840
|
+
let messages = [];
|
|
5841
|
+
if (element.required && rid === "") {
|
|
5842
|
+
messages = [t("required", state)];
|
|
5843
|
+
} else if (rid !== "") {
|
|
5844
|
+
messages = [
|
|
5845
|
+
...validateFileTypes([rid], element, state),
|
|
5846
|
+
...validateFileSizes([rid], element, state)
|
|
5847
|
+
];
|
|
5703
5848
|
}
|
|
5704
|
-
return {
|
|
5849
|
+
return {
|
|
5850
|
+
value: rid || null,
|
|
5851
|
+
errors: reportFileMessages(scopeRoot, key, key, messages, context)
|
|
5852
|
+
};
|
|
5705
5853
|
}
|
|
5706
5854
|
function validateFileElement(element, key, context) {
|
|
5707
5855
|
const isMultipleField = element.type === "files" || "multiple" in element && Boolean(element.multiple);
|
|
@@ -5751,12 +5899,20 @@ function buildEmptyReadonlyTile(state) {
|
|
|
5751
5899
|
return emptyState;
|
|
5752
5900
|
}
|
|
5753
5901
|
function renderMultiFileReadonly(rids, state, wrapper, pathKey, _marginTop) {
|
|
5754
|
-
addPrefillFilesToIndex(rids, state.resourceIndex);
|
|
5755
|
-
ensureFileStyles();
|
|
5756
5902
|
const filesWrapper = document.createElement("div");
|
|
5757
5903
|
filesWrapper.dataset.filesWrapper = pathKey;
|
|
5758
|
-
filesWrapper.dataset.resourceIds = JSON.stringify(rids);
|
|
5759
5904
|
wrapper.appendChild(filesWrapper);
|
|
5905
|
+
state.multiFileSetters.set(
|
|
5906
|
+
filesWrapper,
|
|
5907
|
+
(resourceIds) => fillReadonlyGrid(resourceIds, state, filesWrapper)
|
|
5908
|
+
);
|
|
5909
|
+
fillReadonlyGrid(rids, state, filesWrapper);
|
|
5910
|
+
}
|
|
5911
|
+
function fillReadonlyGrid(rids, state, filesWrapper) {
|
|
5912
|
+
addPrefillFilesToIndex(rids, state.resourceIndex);
|
|
5913
|
+
ensureFileStyles();
|
|
5914
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(rids);
|
|
5915
|
+
filesWrapper.replaceChildren();
|
|
5760
5916
|
if (rids.length === 0) {
|
|
5761
5917
|
const emptyEl = document.createElement("div");
|
|
5762
5918
|
emptyEl.className = "fb-tile-empty-text";
|
|
@@ -5816,14 +5972,14 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
5816
5972
|
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
5817
5973
|
renderFilesElementReadonly(element, ctx, wrapper, pathKey);
|
|
5818
5974
|
} else {
|
|
5819
|
-
|
|
5975
|
+
renderMultiFileElementEdit(element, ctx, wrapper, pathKey);
|
|
5820
5976
|
}
|
|
5821
5977
|
}
|
|
5822
5978
|
function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
5823
5979
|
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
5824
5980
|
renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey);
|
|
5825
5981
|
} else {
|
|
5826
|
-
|
|
5982
|
+
renderMultiFileElementEdit(element, ctx, wrapper, pathKey);
|
|
5827
5983
|
}
|
|
5828
5984
|
}
|
|
5829
5985
|
function updateFileField(element, fieldPath, value, context) {
|
|
@@ -5843,13 +5999,19 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
5843
5999
|
const filesWrapper = scopeRoot.querySelector(
|
|
5844
6000
|
`[data-files-wrapper="${fieldPath}"]`
|
|
5845
6001
|
);
|
|
5846
|
-
if (filesWrapper) {
|
|
5847
|
-
filesWrapper.dataset.resourceIds = JSON.stringify(value);
|
|
5848
|
-
} else {
|
|
6002
|
+
if (!filesWrapper) {
|
|
5849
6003
|
console.warn(
|
|
5850
6004
|
`updateFileField: [data-files-wrapper="${fieldPath}"] not found in DOM; data-resource-ids not updated`
|
|
5851
6005
|
);
|
|
6006
|
+
return;
|
|
6007
|
+
}
|
|
6008
|
+
const setFiles = state.multiFileSetters.get(filesWrapper);
|
|
6009
|
+
if (!setFiles) {
|
|
6010
|
+
throw new Error(
|
|
6011
|
+
`updateFileField: [data-files-wrapper="${fieldPath}"] has no registered setter; this is a render bug`
|
|
6012
|
+
);
|
|
5852
6013
|
}
|
|
6014
|
+
setFiles(value);
|
|
5853
6015
|
} else {
|
|
5854
6016
|
const hiddenInput = scopeRoot.querySelector(
|
|
5855
6017
|
`input[name="${fieldPath}"][type="hidden"]`
|
|
@@ -5910,7 +6072,7 @@ function createReadonlyColourUI(value) {
|
|
|
5910
6072
|
container.appendChild(hexText);
|
|
5911
6073
|
return container;
|
|
5912
6074
|
}
|
|
5913
|
-
function createEditColourUI(value, pathKey, ctx) {
|
|
6075
|
+
function createEditColourUI(value, pathKey, ctx, placeholder) {
|
|
5914
6076
|
const normalizedValue = normalizeColourValue(value);
|
|
5915
6077
|
const pickerWrapper = document.createElement("div");
|
|
5916
6078
|
pickerWrapper.className = "colour-picker-wrapper";
|
|
@@ -5934,9 +6096,10 @@ function createEditColourUI(value, pathKey, ctx) {
|
|
|
5934
6096
|
const hexInput = document.createElement("input");
|
|
5935
6097
|
hexInput.type = "text";
|
|
5936
6098
|
hexInput.className = "colour-hex-input";
|
|
6099
|
+
hexInput.setAttribute("data-colour-field", "true");
|
|
5937
6100
|
hexInput.name = pathKey;
|
|
5938
6101
|
hexInput.value = normalizedValue;
|
|
5939
|
-
hexInput.placeholder = "#000000";
|
|
6102
|
+
hexInput.placeholder = placeholder != null ? placeholder : "#000000";
|
|
5940
6103
|
hexInput.style.cssText = `
|
|
5941
6104
|
width: 100px;
|
|
5942
6105
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
@@ -6023,14 +6186,20 @@ function createEditColourUI(value, pathKey, ctx) {
|
|
|
6023
6186
|
return pickerWrapper;
|
|
6024
6187
|
}
|
|
6025
6188
|
function renderColourElement(element, ctx, wrapper, pathKey) {
|
|
6189
|
+
var _a, _b;
|
|
6026
6190
|
const state = ctx.state;
|
|
6027
6191
|
const readonly = isElementReadonly(element, state, ctx);
|
|
6028
|
-
const initialValue = ctx.prefill[element.key]
|
|
6192
|
+
const initialValue = (_b = (_a = ctx.prefill[element.key]) != null ? _a : element.default) != null ? _b : "#000000";
|
|
6029
6193
|
if (readonly) {
|
|
6030
6194
|
const readonlyUI = createReadonlyColourUI(initialValue);
|
|
6031
6195
|
wrapper.appendChild(readonlyUI);
|
|
6032
6196
|
} else {
|
|
6033
|
-
const editUI = createEditColourUI(
|
|
6197
|
+
const editUI = createEditColourUI(
|
|
6198
|
+
initialValue,
|
|
6199
|
+
pathKey,
|
|
6200
|
+
ctx,
|
|
6201
|
+
element.placeholder
|
|
6202
|
+
);
|
|
6034
6203
|
wrapper.appendChild(editUI);
|
|
6035
6204
|
}
|
|
6036
6205
|
if (!readonly) {
|
|
@@ -6045,7 +6214,7 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
|
|
|
6045
6214
|
}
|
|
6046
6215
|
}
|
|
6047
6216
|
function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
6048
|
-
var _a, _b;
|
|
6217
|
+
var _a, _b, _c;
|
|
6049
6218
|
const state = ctx.state;
|
|
6050
6219
|
const readonly = isElementReadonly(element, state, ctx);
|
|
6051
6220
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
@@ -6053,7 +6222,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
6053
6222
|
const minCount = (_a = element.minCount) != null ? _a : element.required ? 1 : 0;
|
|
6054
6223
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
6055
6224
|
while (values.length < minCount) {
|
|
6056
|
-
values.push(element.default
|
|
6225
|
+
values.push((_c = element.default) != null ? _c : "#000000");
|
|
6057
6226
|
}
|
|
6058
6227
|
const container = document.createElement("div");
|
|
6059
6228
|
container.className = "fb-row";
|
|
@@ -6077,7 +6246,12 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
6077
6246
|
}
|
|
6078
6247
|
} else {
|
|
6079
6248
|
const tempPathKey = `${pathKey}[${container.children.length}]`;
|
|
6080
|
-
const editUI = createEditColourUI(
|
|
6249
|
+
const editUI = createEditColourUI(
|
|
6250
|
+
value,
|
|
6251
|
+
tempPathKey,
|
|
6252
|
+
ctx,
|
|
6253
|
+
element.placeholder
|
|
6254
|
+
);
|
|
6081
6255
|
editUI.style.flex = "1";
|
|
6082
6256
|
itemWrapper.appendChild(editUI);
|
|
6083
6257
|
}
|
|
@@ -6140,13 +6314,13 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
6140
6314
|
const handle = createAddItemRow(
|
|
6141
6315
|
"colour",
|
|
6142
6316
|
() => {
|
|
6143
|
-
var _a2;
|
|
6144
|
-
const defaultColour = element.default
|
|
6317
|
+
var _a2, _b2;
|
|
6318
|
+
const defaultColour = (_a2 = element.default) != null ? _a2 : "#000000";
|
|
6145
6319
|
values.push(defaultColour);
|
|
6146
6320
|
addColourItem(defaultColour);
|
|
6147
6321
|
updateAddButton();
|
|
6148
6322
|
updateRemoveButtons();
|
|
6149
|
-
(
|
|
6323
|
+
(_b2 = ctx.instance) == null ? void 0 : _b2.triggerOnChange(pathKey);
|
|
6150
6324
|
},
|
|
6151
6325
|
{ label: element.addLabel }
|
|
6152
6326
|
);
|
|
@@ -6172,63 +6346,20 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
6172
6346
|
}
|
|
6173
6347
|
}
|
|
6174
6348
|
function validateColourElement(element, key, context) {
|
|
6175
|
-
var _a
|
|
6349
|
+
var _a;
|
|
6176
6350
|
const errors = [];
|
|
6177
|
-
const { scopeRoot,
|
|
6178
|
-
const markValidity = (input, errorMessage) => {
|
|
6179
|
-
var _a2, _b2;
|
|
6180
|
-
if (!input) return;
|
|
6181
|
-
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
6182
|
-
let errorElement = document.getElementById(errorId);
|
|
6183
|
-
if (errorMessage) {
|
|
6184
|
-
input.classList.add("invalid");
|
|
6185
|
-
input.title = errorMessage;
|
|
6186
|
-
if (!errorElement) {
|
|
6187
|
-
errorElement = document.createElement("div");
|
|
6188
|
-
errorElement.id = errorId;
|
|
6189
|
-
errorElement.className = "error-message";
|
|
6190
|
-
errorElement.style.cssText = `
|
|
6191
|
-
color: var(--fb-error-color);
|
|
6192
|
-
font-size: var(--fb-font-size-small);
|
|
6193
|
-
margin-top: 0.25rem;
|
|
6194
|
-
`;
|
|
6195
|
-
if (input.nextSibling) {
|
|
6196
|
-
(_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
|
|
6197
|
-
} else {
|
|
6198
|
-
(_b2 = input.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
|
|
6199
|
-
}
|
|
6200
|
-
}
|
|
6201
|
-
errorElement.textContent = errorMessage;
|
|
6202
|
-
errorElement.style.display = "block";
|
|
6203
|
-
} else {
|
|
6204
|
-
input.classList.remove("invalid");
|
|
6205
|
-
input.title = "";
|
|
6206
|
-
if (errorElement) {
|
|
6207
|
-
errorElement.remove();
|
|
6208
|
-
}
|
|
6209
|
-
}
|
|
6210
|
-
};
|
|
6351
|
+
const { scopeRoot, state } = context;
|
|
6211
6352
|
const validateColourValue = (input, val, fieldKey) => {
|
|
6212
|
-
const
|
|
6353
|
+
const normalized = val ? normalizeColourValue(val) : "";
|
|
6354
|
+
let msg = null;
|
|
6213
6355
|
if (!val) {
|
|
6214
|
-
if (
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
return "";
|
|
6222
|
-
}
|
|
6223
|
-
const normalized = normalizeColourValue(val);
|
|
6224
|
-
if (!skipValidation && !isValidHexColour(normalized)) {
|
|
6225
|
-
const msg = t("invalidHexColour", state);
|
|
6226
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
6227
|
-
markValidity(input, msg);
|
|
6228
|
-
return val;
|
|
6229
|
-
}
|
|
6230
|
-
markValidity(input, null);
|
|
6231
|
-
return normalized;
|
|
6356
|
+
if (element.required) msg = t("required", state);
|
|
6357
|
+
} else if (!isValidHexColour(normalized)) {
|
|
6358
|
+
msg = t("invalidHexColour", state);
|
|
6359
|
+
}
|
|
6360
|
+
if (msg !== null) errors.push(`${fieldKey}: ${msg}`);
|
|
6361
|
+
markFieldValidity(input, msg, context);
|
|
6362
|
+
return val && msg !== null ? val : normalized;
|
|
6232
6363
|
};
|
|
6233
6364
|
if (element.multiple) {
|
|
6234
6365
|
const hexInputs = scopeRoot.querySelectorAll(
|
|
@@ -6238,38 +6369,17 @@ function validateColourElement(element, key, context) {
|
|
|
6238
6369
|
hexInputs.forEach((input, index) => {
|
|
6239
6370
|
var _a2;
|
|
6240
6371
|
const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
|
|
6241
|
-
|
|
6242
|
-
values.push(validated);
|
|
6372
|
+
values.push(validateColourValue(input, val, `${key}[${index}]`));
|
|
6243
6373
|
});
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
const minCount = (_a = element.minCount) != null ? _a : 0;
|
|
6247
|
-
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
6248
|
-
const filteredValues = values.filter((v) => v !== "");
|
|
6249
|
-
if (element.required && filteredValues.length === 0) {
|
|
6250
|
-
errors.push(`${key}: ${t("required", state)}`);
|
|
6251
|
-
}
|
|
6252
|
-
if (filteredValues.length < minCount) {
|
|
6253
|
-
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
6254
|
-
}
|
|
6255
|
-
if (filteredValues.length > maxCount) {
|
|
6256
|
-
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
6257
|
-
}
|
|
6258
|
-
}
|
|
6374
|
+
const filledCount = values.filter((v) => v !== "").length;
|
|
6375
|
+
validateItemCount(element, key, filledCount, context, errors);
|
|
6259
6376
|
return { value: values, errors };
|
|
6260
6377
|
} else {
|
|
6261
6378
|
const hexInput = scopeRoot.querySelector(
|
|
6262
6379
|
`[name="${key}"].colour-hex-input`
|
|
6263
6380
|
);
|
|
6264
|
-
const val = (
|
|
6265
|
-
|
|
6266
|
-
const msg = t("required", context.state);
|
|
6267
|
-
errors.push(`${key}: ${msg}`);
|
|
6268
|
-
markValidity(hexInput, msg);
|
|
6269
|
-
return { value: "", errors };
|
|
6270
|
-
}
|
|
6271
|
-
const validated = validateColourValue(hexInput, val, key);
|
|
6272
|
-
return { value: validated, errors };
|
|
6381
|
+
const val = (_a = hexInput == null ? void 0 : hexInput.value) != null ? _a : "";
|
|
6382
|
+
return { value: validateColourValue(hexInput, val, key), errors };
|
|
6273
6383
|
}
|
|
6274
6384
|
}
|
|
6275
6385
|
function updateColourField(element, fieldPath, value, context) {
|
|
@@ -6288,8 +6398,6 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
6288
6398
|
if (index < value.length) {
|
|
6289
6399
|
const normalized = normalizeColourValue(value[index]);
|
|
6290
6400
|
hexInput.value = normalized;
|
|
6291
|
-
hexInput.classList.remove("invalid");
|
|
6292
|
-
hexInput.title = "";
|
|
6293
6401
|
clearFieldError(hexInput);
|
|
6294
6402
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
6295
6403
|
if (wrapper) {
|
|
@@ -6318,8 +6426,6 @@ function updateColourField(element, fieldPath, value, context) {
|
|
|
6318
6426
|
if (hexInput) {
|
|
6319
6427
|
const normalized = normalizeColourValue(value);
|
|
6320
6428
|
hexInput.value = normalized;
|
|
6321
|
-
hexInput.classList.remove("invalid");
|
|
6322
|
-
hexInput.title = "";
|
|
6323
6429
|
clearFieldError(hexInput);
|
|
6324
6430
|
const wrapper = hexInput.closest(".colour-picker-wrapper");
|
|
6325
6431
|
if (wrapper) {
|
|
@@ -6637,9 +6743,9 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
6637
6743
|
}
|
|
6638
6744
|
}
|
|
6639
6745
|
function validateSliderElement(element, key, context) {
|
|
6640
|
-
var _a
|
|
6746
|
+
var _a;
|
|
6641
6747
|
const errors = [];
|
|
6642
|
-
const { scopeRoot
|
|
6748
|
+
const { scopeRoot } = context;
|
|
6643
6749
|
if (element.min === void 0 || element.min === null) {
|
|
6644
6750
|
throw new Error(
|
|
6645
6751
|
`Slider validation: field "${key}" requires "min" property`
|
|
@@ -6654,80 +6760,24 @@ function validateSliderElement(element, key, context) {
|
|
|
6654
6760
|
const max = element.max;
|
|
6655
6761
|
const step = (_a = element.step) != null ? _a : 1;
|
|
6656
6762
|
const scale = element.scale || "linear";
|
|
6657
|
-
const markValidity = (input, errorMessage) => {
|
|
6658
|
-
var _a2, _b2;
|
|
6659
|
-
if (!input) return;
|
|
6660
|
-
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
6661
|
-
let errorElement = document.getElementById(errorId);
|
|
6662
|
-
if (errorMessage) {
|
|
6663
|
-
input.classList.add("invalid");
|
|
6664
|
-
input.title = errorMessage;
|
|
6665
|
-
if (!errorElement) {
|
|
6666
|
-
errorElement = document.createElement("div");
|
|
6667
|
-
errorElement.id = errorId;
|
|
6668
|
-
errorElement.className = "error-message";
|
|
6669
|
-
errorElement.style.cssText = `
|
|
6670
|
-
color: var(--fb-error-color);
|
|
6671
|
-
font-size: var(--fb-font-size-small);
|
|
6672
|
-
margin-top: 0.25rem;
|
|
6673
|
-
`;
|
|
6674
|
-
const sliderContainer = input.closest(".slider-container");
|
|
6675
|
-
if (sliderContainer && sliderContainer.nextSibling) {
|
|
6676
|
-
(_a2 = sliderContainer.parentNode) == null ? void 0 : _a2.insertBefore(
|
|
6677
|
-
errorElement,
|
|
6678
|
-
sliderContainer.nextSibling
|
|
6679
|
-
);
|
|
6680
|
-
} else if (sliderContainer) {
|
|
6681
|
-
(_b2 = sliderContainer.parentNode) == null ? void 0 : _b2.appendChild(errorElement);
|
|
6682
|
-
}
|
|
6683
|
-
}
|
|
6684
|
-
errorElement.textContent = errorMessage;
|
|
6685
|
-
errorElement.style.display = "block";
|
|
6686
|
-
} else {
|
|
6687
|
-
input.classList.remove("invalid");
|
|
6688
|
-
input.title = "";
|
|
6689
|
-
if (errorElement) {
|
|
6690
|
-
errorElement.remove();
|
|
6691
|
-
}
|
|
6692
|
-
}
|
|
6693
|
-
};
|
|
6694
6763
|
const validateSliderValue = (slider, fieldKey) => {
|
|
6695
6764
|
const { state } = context;
|
|
6696
6765
|
const rawValue = slider.value;
|
|
6697
6766
|
if (!rawValue) {
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
markValidity(slider, msg);
|
|
6702
|
-
return null;
|
|
6703
|
-
}
|
|
6704
|
-
markValidity(slider, null);
|
|
6767
|
+
const msg2 = element.required ? t("required", state) : null;
|
|
6768
|
+
if (msg2 !== null) errors.push(`${fieldKey}: ${msg2}`);
|
|
6769
|
+
markFieldValidity(slider, msg2, context);
|
|
6705
6770
|
return null;
|
|
6706
6771
|
}
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
if (!skipValidation) {
|
|
6717
|
-
if (value < min) {
|
|
6718
|
-
const msg = t("minValue", state, { min });
|
|
6719
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
6720
|
-
markValidity(slider, msg);
|
|
6721
|
-
return value;
|
|
6722
|
-
}
|
|
6723
|
-
if (value > max) {
|
|
6724
|
-
const msg = t("maxValue", state, { max });
|
|
6725
|
-
errors.push(`${fieldKey}: ${msg}`);
|
|
6726
|
-
markValidity(slider, msg);
|
|
6727
|
-
return value;
|
|
6728
|
-
}
|
|
6729
|
-
}
|
|
6730
|
-
markValidity(slider, null);
|
|
6772
|
+
const value = scale === "exponential" ? alignToStep(
|
|
6773
|
+
positionToExponential(parseFloat(rawValue) / 1e3, min, max),
|
|
6774
|
+
step
|
|
6775
|
+
) : alignToStep(parseFloat(rawValue), step);
|
|
6776
|
+
let msg = null;
|
|
6777
|
+
if (value < min) msg = t("minValue", state, { min });
|
|
6778
|
+
else if (value > max) msg = t("maxValue", state, { max });
|
|
6779
|
+
if (msg !== null) errors.push(`${fieldKey}: ${msg}`);
|
|
6780
|
+
markFieldValidity(slider, msg, context);
|
|
6731
6781
|
return value;
|
|
6732
6782
|
};
|
|
6733
6783
|
if (element.multiple) {
|
|
@@ -6736,31 +6786,17 @@ function validateSliderElement(element, key, context) {
|
|
|
6736
6786
|
);
|
|
6737
6787
|
const values = [];
|
|
6738
6788
|
sliders.forEach((slider, index) => {
|
|
6739
|
-
|
|
6740
|
-
values.push(value);
|
|
6789
|
+
values.push(validateSliderValue(slider, `${key}[${index}]`));
|
|
6741
6790
|
});
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
const minCount = (_b = element.minCount) != null ? _b : 0;
|
|
6745
|
-
const maxCount = (_c = element.maxCount) != null ? _c : Infinity;
|
|
6746
|
-
const filteredValues = values.filter((v) => v !== null);
|
|
6747
|
-
if (element.required && filteredValues.length === 0) {
|
|
6748
|
-
errors.push(`${key}: ${t("required", state)}`);
|
|
6749
|
-
}
|
|
6750
|
-
if (filteredValues.length < minCount) {
|
|
6751
|
-
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
6752
|
-
}
|
|
6753
|
-
if (filteredValues.length > maxCount) {
|
|
6754
|
-
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
6755
|
-
}
|
|
6756
|
-
}
|
|
6791
|
+
const filledCount = values.filter((v) => v !== null).length;
|
|
6792
|
+
validateItemCount(element, key, filledCount, context, errors);
|
|
6757
6793
|
return { value: values, errors };
|
|
6758
6794
|
} else {
|
|
6759
6795
|
const slider = scopeRoot.querySelector(
|
|
6760
6796
|
`input[type="range"][name="${key}"]`
|
|
6761
6797
|
);
|
|
6762
6798
|
if (!slider) {
|
|
6763
|
-
if (
|
|
6799
|
+
if (element.required) {
|
|
6764
6800
|
errors.push(`${key}: ${t("required", context.state)}`);
|
|
6765
6801
|
}
|
|
6766
6802
|
return { value: null, errors };
|
|
@@ -6810,8 +6846,6 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
6810
6846
|
var(--fb-border-color) 100%
|
|
6811
6847
|
)`;
|
|
6812
6848
|
}
|
|
6813
|
-
slider.classList.remove("invalid");
|
|
6814
|
-
slider.title = "";
|
|
6815
6849
|
clearFieldError(slider);
|
|
6816
6850
|
}
|
|
6817
6851
|
});
|
|
@@ -6847,8 +6881,6 @@ function updateSliderField(element, fieldPath, value, context) {
|
|
|
6847
6881
|
var(--fb-border-color) 100%
|
|
6848
6882
|
)`;
|
|
6849
6883
|
}
|
|
6850
|
-
slider.classList.remove("invalid");
|
|
6851
|
-
slider.title = "";
|
|
6852
6884
|
clearFieldError(slider);
|
|
6853
6885
|
}
|
|
6854
6886
|
}
|
|
@@ -6873,22 +6905,12 @@ function extractRootFormData(formRoot) {
|
|
|
6873
6905
|
inputs.forEach((input) => {
|
|
6874
6906
|
const fieldName = input.getAttribute("name");
|
|
6875
6907
|
if (fieldName && !fieldName.includes("[") && !fieldName.includes(".")) {
|
|
6876
|
-
if (input instanceof
|
|
6877
|
-
|
|
6878
|
-
} else if (input instanceof HTMLInputElement) {
|
|
6879
|
-
if (input.type === "checkbox") {
|
|
6880
|
-
data[fieldName] = input.checked;
|
|
6881
|
-
} else if (input.type === "radio") {
|
|
6882
|
-
if (input.checked) {
|
|
6883
|
-
data[fieldName] = input.value;
|
|
6884
|
-
}
|
|
6885
|
-
} else if (input.dataset.hiddenField) {
|
|
6886
|
-
data[fieldName] = deserializeHiddenValue(input.value);
|
|
6887
|
-
} else {
|
|
6908
|
+
if (input instanceof HTMLInputElement && input.type === "radio") {
|
|
6909
|
+
if (input.checked) {
|
|
6888
6910
|
data[fieldName] = input.value;
|
|
6889
6911
|
}
|
|
6890
|
-
} else
|
|
6891
|
-
data[fieldName] = input
|
|
6912
|
+
} else {
|
|
6913
|
+
data[fieldName] = readTypedInputValue(input);
|
|
6892
6914
|
}
|
|
6893
6915
|
}
|
|
6894
6916
|
});
|
|
@@ -6957,9 +6979,9 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
6957
6979
|
inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
|
|
6958
6980
|
};
|
|
6959
6981
|
element.elements.forEach((child) => {
|
|
6960
|
-
var _a2
|
|
6982
|
+
var _a2;
|
|
6961
6983
|
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
6962
|
-
const prefillVal =
|
|
6984
|
+
const prefillVal = child.key in containerPrefill ? containerPrefill[child.key] : (_a2 = "default" in child ? child.default : null) != null ? _a2 : null;
|
|
6963
6985
|
itemsWrap.appendChild(
|
|
6964
6986
|
createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
|
|
6965
6987
|
);
|
|
@@ -7061,9 +7083,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
7061
7083
|
isSlides ? void 0 : element.columns
|
|
7062
7084
|
);
|
|
7063
7085
|
element.elements.forEach((child) => {
|
|
7064
|
-
var _a2
|
|
7086
|
+
var _a2;
|
|
7065
7087
|
if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
|
|
7066
|
-
const hiddenValue =
|
|
7088
|
+
const hiddenValue = rowPrefill && child.key in rowPrefill ? rowPrefill[child.key] : (_a2 = "default" in child ? child.default : null) != null ? _a2 : null;
|
|
7067
7089
|
childWrapper.appendChild(
|
|
7068
7090
|
createHiddenInput(pathJoin(subCtx.path, child.key), hiddenValue)
|
|
7069
7091
|
);
|
|
@@ -7174,40 +7196,21 @@ function renderMultipleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
7174
7196
|
}
|
|
7175
7197
|
}
|
|
7176
7198
|
}
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
validateElementFunc = fn;
|
|
7180
|
-
}
|
|
7181
|
-
function validateElement(element, ctx, customScopeRoot) {
|
|
7182
|
-
if (!validateElementFunc) {
|
|
7199
|
+
function requireValidateElement(context) {
|
|
7200
|
+
if (!context.validateElement) {
|
|
7183
7201
|
throw new Error(
|
|
7184
|
-
"validateElement
|
|
7202
|
+
"validateContainerElement: context.validateElement missing \u2014 container validation requires the instance validator"
|
|
7185
7203
|
);
|
|
7186
7204
|
}
|
|
7187
|
-
return
|
|
7205
|
+
return context.validateElement;
|
|
7188
7206
|
}
|
|
7189
7207
|
function validateContainerElement(element, key, context) {
|
|
7208
|
+
const validateChild = requireValidateElement(context);
|
|
7190
7209
|
const errors = [];
|
|
7191
|
-
const { scopeRoot,
|
|
7210
|
+
const { scopeRoot, path } = context;
|
|
7192
7211
|
if (!("elements" in element)) {
|
|
7193
7212
|
return { value: null, errors };
|
|
7194
7213
|
}
|
|
7195
|
-
const validateContainerCount = (key2, items, element2) => {
|
|
7196
|
-
var _a, _b;
|
|
7197
|
-
if (skipValidation) return;
|
|
7198
|
-
const { state } = context;
|
|
7199
|
-
const minItems = "minCount" in element2 ? (_a = element2.minCount) != null ? _a : 0 : 0;
|
|
7200
|
-
const maxItems = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
7201
|
-
if (element2.required && items.length === 0) {
|
|
7202
|
-
errors.push(`${key2}: ${t("required", state)}`);
|
|
7203
|
-
}
|
|
7204
|
-
if (items.length < minItems) {
|
|
7205
|
-
errors.push(`${key2}: ${t("minItems", state, { min: minItems })}`);
|
|
7206
|
-
}
|
|
7207
|
-
if (items.length > maxItems) {
|
|
7208
|
-
errors.push(`${key2}: ${t("maxItems", state, { max: maxItems })}`);
|
|
7209
|
-
}
|
|
7210
|
-
};
|
|
7211
7214
|
if ("multiple" in element && element.multiple) {
|
|
7212
7215
|
const items = [];
|
|
7213
7216
|
const containerWrappers = findDirectContainerRows(scopeRoot, key);
|
|
@@ -7239,9 +7242,9 @@ function validateContainerElement(element, key, context) {
|
|
|
7239
7242
|
}
|
|
7240
7243
|
}
|
|
7241
7244
|
const childKey = `${key}[${domIndex}].${child.key}`;
|
|
7242
|
-
const childResult =
|
|
7245
|
+
const childResult = validateChild(
|
|
7243
7246
|
{ ...child, key: childKey },
|
|
7244
|
-
{ path },
|
|
7247
|
+
{ path, inheritedReadonly: context.readonly },
|
|
7245
7248
|
itemContainer
|
|
7246
7249
|
);
|
|
7247
7250
|
if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
|
|
@@ -7252,7 +7255,7 @@ function validateContainerElement(element, key, context) {
|
|
|
7252
7255
|
});
|
|
7253
7256
|
items.push(itemData);
|
|
7254
7257
|
});
|
|
7255
|
-
|
|
7258
|
+
validateItemCount(element, key, items.length, context, errors);
|
|
7256
7259
|
return { value: items, errors };
|
|
7257
7260
|
} else {
|
|
7258
7261
|
const containerData = {};
|
|
@@ -7281,9 +7284,9 @@ function validateContainerElement(element, key, context) {
|
|
|
7281
7284
|
}
|
|
7282
7285
|
{
|
|
7283
7286
|
const childKey = `${key}.${child.key}`;
|
|
7284
|
-
const childResult =
|
|
7287
|
+
const childResult = validateChild(
|
|
7285
7288
|
{ ...child, key: childKey },
|
|
7286
|
-
{ path },
|
|
7289
|
+
{ path, inheritedReadonly: context.readonly },
|
|
7287
7290
|
containerContainer
|
|
7288
7291
|
);
|
|
7289
7292
|
if (childResult.spread && childResult.value !== null && typeof childResult.value === "object") {
|
|
@@ -7784,7 +7787,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
7784
7787
|
rebuild();
|
|
7785
7788
|
} catch (e) {
|
|
7786
7789
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
7787
|
-
console.error(t("tableImportError", state
|
|
7790
|
+
console.error(t("tableImportError", state, { error: errMsg }));
|
|
7788
7791
|
} finally {
|
|
7789
7792
|
overlay.remove();
|
|
7790
7793
|
}
|
|
@@ -8680,9 +8683,25 @@ function renderTableElement(element, ctx, wrapper, pathKey) {
|
|
|
8680
8683
|
renderEditTable(element, initialData, pathKey, ctx, wrapper);
|
|
8681
8684
|
}
|
|
8682
8685
|
}
|
|
8686
|
+
function parseTableValue(raw, cellsKey) {
|
|
8687
|
+
let parsed;
|
|
8688
|
+
try {
|
|
8689
|
+
parsed = JSON.parse(raw);
|
|
8690
|
+
} catch {
|
|
8691
|
+
return null;
|
|
8692
|
+
}
|
|
8693
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8694
|
+
return null;
|
|
8695
|
+
}
|
|
8696
|
+
const cells = parsed[cellsKey];
|
|
8697
|
+
const isGrid = Array.isArray(cells) && cells.every(
|
|
8698
|
+
(row) => Array.isArray(row) && row.every((cell) => typeof cell === "string")
|
|
8699
|
+
);
|
|
8700
|
+
return isGrid ? parsed : null;
|
|
8701
|
+
}
|
|
8683
8702
|
function validateTableElement(element, key, context) {
|
|
8684
8703
|
var _a, _b;
|
|
8685
|
-
const { scopeRoot
|
|
8704
|
+
const { scopeRoot } = context;
|
|
8686
8705
|
const errors = [];
|
|
8687
8706
|
const cellsKey = (_b = (_a = element.fieldNames) == null ? void 0 : _a.cells) != null ? _b : "cells";
|
|
8688
8707
|
const hiddenInput = scopeRoot.querySelector(
|
|
@@ -8691,22 +8710,20 @@ function validateTableElement(element, key, context) {
|
|
|
8691
8710
|
if (!hiddenInput) {
|
|
8692
8711
|
return { value: null, errors };
|
|
8693
8712
|
}
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8713
|
+
const value = parseTableValue(hiddenInput.value, cellsKey);
|
|
8714
|
+
if (value === null) {
|
|
8715
|
+
const msg2 = "invalid table data";
|
|
8716
|
+
errors.push(`${key}: ${msg2}`);
|
|
8717
|
+
markFieldGroupValidity(scopeRoot, key, msg2, context);
|
|
8699
8718
|
return { value: null, errors };
|
|
8700
8719
|
}
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
}
|
|
8709
|
-
}
|
|
8720
|
+
const cells = value[cellsKey];
|
|
8721
|
+
const hasContent = cells.some(
|
|
8722
|
+
(row) => row.some((cell) => cell.trim() !== "")
|
|
8723
|
+
);
|
|
8724
|
+
const msg = element.required && !hasContent ? t("required", context.state) : null;
|
|
8725
|
+
if (msg !== null) errors.push(`${key}: ${msg}`);
|
|
8726
|
+
markFieldGroupValidity(scopeRoot, key, msg, context);
|
|
8710
8727
|
return { value, errors };
|
|
8711
8728
|
}
|
|
8712
8729
|
function updateTableField(element, fieldPath, value, context) {
|
|
@@ -8744,7 +8761,7 @@ function updateTableField(element, fieldPath, value, context) {
|
|
|
8744
8761
|
}
|
|
8745
8762
|
|
|
8746
8763
|
// src/components/richinput.ts
|
|
8747
|
-
function applyAutoExpand2(textarea, backdrop) {
|
|
8764
|
+
function applyAutoExpand2(textarea, backdrop, observers) {
|
|
8748
8765
|
textarea.style.overflow = "hidden";
|
|
8749
8766
|
textarea.style.resize = "none";
|
|
8750
8767
|
const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
|
|
@@ -8767,6 +8784,7 @@ function applyAutoExpand2(textarea, backdrop) {
|
|
|
8767
8784
|
var _a, _b, _c, _d, _e;
|
|
8768
8785
|
if (!textarea.isConnected) {
|
|
8769
8786
|
ro.disconnect();
|
|
8787
|
+
observers.delete(ro);
|
|
8770
8788
|
return;
|
|
8771
8789
|
}
|
|
8772
8790
|
const entry = entries[0];
|
|
@@ -8776,6 +8794,7 @@ function applyAutoExpand2(textarea, backdrop) {
|
|
|
8776
8794
|
resize();
|
|
8777
8795
|
});
|
|
8778
8796
|
ro.observe(textarea);
|
|
8797
|
+
observers.add(ro);
|
|
8779
8798
|
}
|
|
8780
8799
|
function buildFileLabels(files, state) {
|
|
8781
8800
|
var _a, _b, _c, _d;
|
|
@@ -9204,7 +9223,7 @@ function filterFilesForDropdown(query, files, labels) {
|
|
|
9204
9223
|
var TEXTAREA_FONT = "font-size: var(--fb-font-size, 14px); font-family: var(--fb-font-family, inherit); line-height: 1.6;";
|
|
9205
9224
|
var TEXTAREA_PADDING = "padding: 8px 40px 8px 10px;";
|
|
9206
9225
|
function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
9207
|
-
var _a;
|
|
9226
|
+
var _a, _b;
|
|
9208
9227
|
const state = ctx.state;
|
|
9209
9228
|
const files = [...initialValue.files];
|
|
9210
9229
|
const dropdownState = {
|
|
@@ -9218,12 +9237,12 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9218
9237
|
hiddenInput.type = "hidden";
|
|
9219
9238
|
hiddenInput.name = pathKey;
|
|
9220
9239
|
function getCurrentValue() {
|
|
9221
|
-
var _a2,
|
|
9240
|
+
var _a2, _b2;
|
|
9222
9241
|
const rawText = textarea.value;
|
|
9223
9242
|
const nameToRid = buildNameToRid(files, state);
|
|
9224
9243
|
const submissionText = rawText ? replaceFilenamesWithRids(rawText, nameToRid) : null;
|
|
9225
9244
|
const textKey = (_a2 = element.textKey) != null ? _a2 : "text";
|
|
9226
|
-
const filesKey = (
|
|
9245
|
+
const filesKey = (_b2 = element.filesKey) != null ? _b2 : "files";
|
|
9227
9246
|
return {
|
|
9228
9247
|
[textKey]: rawText === "" ? null : submissionText,
|
|
9229
9248
|
[filesKey]: [...files]
|
|
@@ -9305,14 +9324,14 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9305
9324
|
}
|
|
9306
9325
|
});
|
|
9307
9326
|
outerDiv.addEventListener("drop", (e) => {
|
|
9308
|
-
var _a2,
|
|
9327
|
+
var _a2, _b2;
|
|
9309
9328
|
e.preventDefault();
|
|
9310
9329
|
dragCounter = 0;
|
|
9311
9330
|
outerDiv.style.borderColor = "var(--fb-border-color, #d1d5db)";
|
|
9312
9331
|
outerDiv.style.boxShadow = "none";
|
|
9313
9332
|
const droppedFiles = (_a2 = e.dataTransfer) == null ? void 0 : _a2.files;
|
|
9314
9333
|
if (!droppedFiles || !state.config.uploadFile) return;
|
|
9315
|
-
const maxFiles = (
|
|
9334
|
+
const maxFiles = (_b2 = element.maxFiles) != null ? _b2 : Infinity;
|
|
9316
9335
|
for (let i = 0; i < droppedFiles.length; i++) {
|
|
9317
9336
|
if (files.length >= maxFiles) {
|
|
9318
9337
|
showUploadError(
|
|
@@ -9361,8 +9380,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9361
9380
|
`;
|
|
9362
9381
|
const textarea = document.createElement("textarea");
|
|
9363
9382
|
textarea.name = `${pathKey}__text`;
|
|
9364
|
-
textarea.placeholder = element.placeholder
|
|
9365
|
-
const rawInitialText = (
|
|
9383
|
+
textarea.placeholder = (_a = element.placeholder) != null ? _a : t("richinputPlaceholder", state);
|
|
9384
|
+
const rawInitialText = (_b = initialValue.text) != null ? _b : "";
|
|
9366
9385
|
textarea.value = rawInitialText ? replaceRidsWithFilenames(rawInitialText, files, state) : "";
|
|
9367
9386
|
textarea.style.cssText = `
|
|
9368
9387
|
width: 100%;
|
|
@@ -9378,14 +9397,14 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9378
9397
|
z-index: 1;
|
|
9379
9398
|
caret-color: var(--fb-text-color, #111827);
|
|
9380
9399
|
`;
|
|
9381
|
-
applyAutoExpand2(textarea, backdrop);
|
|
9400
|
+
applyAutoExpand2(textarea, backdrop, ctx.state.autoExpandObservers);
|
|
9382
9401
|
textarea.addEventListener("scroll", () => {
|
|
9383
9402
|
backdrop.scrollTop = textarea.scrollTop;
|
|
9384
9403
|
});
|
|
9385
9404
|
let mentionTooltip = null;
|
|
9386
9405
|
backdrop.addEventListener("mouseover", (e) => {
|
|
9387
|
-
var _a2,
|
|
9388
|
-
const mark = (
|
|
9406
|
+
var _a2, _b2;
|
|
9407
|
+
const mark = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
|
|
9389
9408
|
_a2,
|
|
9390
9409
|
"mark"
|
|
9391
9410
|
);
|
|
@@ -9394,8 +9413,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9394
9413
|
mentionTooltip = showMentionTooltip(mark, mark.dataset.rid, state);
|
|
9395
9414
|
});
|
|
9396
9415
|
backdrop.addEventListener("mouseout", (e) => {
|
|
9397
|
-
var _a2,
|
|
9398
|
-
const mark = (
|
|
9416
|
+
var _a2, _b2, _c;
|
|
9417
|
+
const mark = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
|
|
9399
9418
|
_a2,
|
|
9400
9419
|
"mark"
|
|
9401
9420
|
);
|
|
@@ -9405,8 +9424,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9405
9424
|
mentionTooltip = removePortalTooltip(mentionTooltip);
|
|
9406
9425
|
});
|
|
9407
9426
|
backdrop.addEventListener("mousedown", (e) => {
|
|
9408
|
-
var _a2,
|
|
9409
|
-
const mark = (
|
|
9427
|
+
var _a2, _b2;
|
|
9428
|
+
const mark = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
|
|
9410
9429
|
_a2,
|
|
9411
9430
|
"mark"
|
|
9412
9431
|
);
|
|
@@ -9579,8 +9598,8 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9579
9598
|
dropdown.appendChild(item);
|
|
9580
9599
|
});
|
|
9581
9600
|
dropdown.onmousemove = (e) => {
|
|
9582
|
-
var _a2,
|
|
9583
|
-
const target = (
|
|
9601
|
+
var _a2, _b2, _c;
|
|
9602
|
+
const target = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
|
|
9584
9603
|
_a2,
|
|
9585
9604
|
".fb-richinput-dropdown-item"
|
|
9586
9605
|
);
|
|
@@ -9596,10 +9615,10 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9596
9615
|
dropdownState.selectedIndex = newIdx;
|
|
9597
9616
|
};
|
|
9598
9617
|
dropdown.onmousedown = (e) => {
|
|
9599
|
-
var _a2,
|
|
9618
|
+
var _a2, _b2;
|
|
9600
9619
|
e.preventDefault();
|
|
9601
9620
|
e.stopPropagation();
|
|
9602
|
-
const target = (
|
|
9621
|
+
const target = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(
|
|
9603
9622
|
_a2,
|
|
9604
9623
|
".fb-richinput-dropdown-item"
|
|
9605
9624
|
);
|
|
@@ -9627,9 +9646,9 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9627
9646
|
dropdownState.open = false;
|
|
9628
9647
|
}
|
|
9629
9648
|
function insertMention(rid) {
|
|
9630
|
-
var _a2,
|
|
9649
|
+
var _a2, _b2, _c, _d;
|
|
9631
9650
|
const labels = buildFileLabelsFromClosure();
|
|
9632
|
-
const label = (_c = (
|
|
9651
|
+
const label = (_c = (_b2 = labels.get(rid)) != null ? _b2 : (_a2 = state.resourceIndex.get(rid)) == null ? void 0 : _a2.name) != null ? _c : rid;
|
|
9633
9652
|
const cursorPos = (_d = textarea.selectionStart) != null ? _d : 0;
|
|
9634
9653
|
const before = textarea.value.slice(0, dropdownState.triggerPos);
|
|
9635
9654
|
const after = textarea.value.slice(cursorPos);
|
|
@@ -9721,10 +9740,10 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9721
9740
|
thumbWrapper.appendChild(thumbInner);
|
|
9722
9741
|
const tooltipHandle = createTooltipHandle();
|
|
9723
9742
|
const doMention = () => {
|
|
9724
|
-
var _a2,
|
|
9743
|
+
var _a2, _b2, _c;
|
|
9725
9744
|
const cursorPos = (_a2 = textarea.selectionStart) != null ? _a2 : textarea.value.length;
|
|
9726
9745
|
const labels = buildFileLabelsFromClosure();
|
|
9727
|
-
const label = (_c = (
|
|
9746
|
+
const label = (_c = (_b2 = labels.get(rid)) != null ? _b2 : meta == null ? void 0 : meta.name) != null ? _c : rid;
|
|
9728
9747
|
const before = textarea.value.slice(0, cursorPos);
|
|
9729
9748
|
const after = textarea.value.slice(cursorPos);
|
|
9730
9749
|
const prefix = before.length > 0 && !/[\s\n]$/.test(before) ? "\n" : "";
|
|
@@ -9802,12 +9821,12 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
|
|
|
9802
9821
|
writeHidden();
|
|
9803
9822
|
(_a2 = ctx.instance) == null ? void 0 : _a2.triggerOnChange(pathKey, getCurrentValue());
|
|
9804
9823
|
}).catch((err) => {
|
|
9805
|
-
var _a2,
|
|
9824
|
+
var _a2, _b2;
|
|
9806
9825
|
const idx = files.indexOf(tempId);
|
|
9807
9826
|
if (idx !== -1) files.splice(idx, 1);
|
|
9808
9827
|
state.resourceIndex.delete(tempId);
|
|
9809
9828
|
renderFilesRow();
|
|
9810
|
-
(
|
|
9829
|
+
(_b2 = (_a2 = state.config).onUploadError) == null ? void 0 : _b2.call(_a2, err, file);
|
|
9811
9830
|
});
|
|
9812
9831
|
}
|
|
9813
9832
|
fileInput.addEventListener("change", () => {
|
|
@@ -10046,9 +10065,17 @@ function renderRichInputElement(element, ctx, wrapper, pathKey) {
|
|
|
10046
10065
|
renderEditMode(element, ctx, wrapper, pathKey, initialValue);
|
|
10047
10066
|
}
|
|
10048
10067
|
}
|
|
10068
|
+
function parseRichInputValue(raw) {
|
|
10069
|
+
try {
|
|
10070
|
+
const parsed = JSON.parse(raw);
|
|
10071
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
10072
|
+
} catch {
|
|
10073
|
+
return null;
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10049
10076
|
function validateRichInputElement(element, key, context) {
|
|
10050
10077
|
var _a, _b;
|
|
10051
|
-
const { scopeRoot, state
|
|
10078
|
+
const { scopeRoot, state } = context;
|
|
10052
10079
|
const errors = [];
|
|
10053
10080
|
const textKey = (_a = element.textKey) != null ? _a : "text";
|
|
10054
10081
|
const filesKey = (_b = element.filesKey) != null ? _b : "files";
|
|
@@ -10058,17 +10085,11 @@ function validateRichInputElement(element, key, context) {
|
|
|
10058
10085
|
if (!hiddenInput) {
|
|
10059
10086
|
return { value: null, errors };
|
|
10060
10087
|
}
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
const
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
} else {
|
|
10067
|
-
errors.push(`${key}: invalid richinput data`);
|
|
10068
|
-
return { value: null, errors };
|
|
10069
|
-
}
|
|
10070
|
-
} catch {
|
|
10071
|
-
errors.push(`${key}: invalid richinput data`);
|
|
10088
|
+
const rawValue = parseRichInputValue(hiddenInput.value);
|
|
10089
|
+
if (rawValue === null) {
|
|
10090
|
+
const msg = "invalid richinput data";
|
|
10091
|
+
errors.push(`${key}: ${msg}`);
|
|
10092
|
+
markFieldGroupValidity(scopeRoot, key, msg, context);
|
|
10072
10093
|
return { value: null, errors };
|
|
10073
10094
|
}
|
|
10074
10095
|
const textVal = rawValue[textKey];
|
|
@@ -10079,28 +10100,24 @@ function validateRichInputElement(element, key, context) {
|
|
|
10079
10100
|
[textKey]: text != null ? text : null,
|
|
10080
10101
|
[filesKey]: files
|
|
10081
10102
|
};
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
if (
|
|
10089
|
-
|
|
10090
|
-
errors.push(
|
|
10091
|
-
`${key}: ${t("minLength", state, { min: element.minLength })}`
|
|
10092
|
-
);
|
|
10093
|
-
}
|
|
10094
|
-
if (element.maxLength != null && text.length > element.maxLength) {
|
|
10095
|
-
errors.push(
|
|
10096
|
-
`${key}: ${t("maxLength", state, { max: element.maxLength })}`
|
|
10097
|
-
);
|
|
10098
|
-
}
|
|
10103
|
+
const textEmpty = !text || text.trim() === "";
|
|
10104
|
+
const messages = [];
|
|
10105
|
+
if (element.required && textEmpty && files.length === 0) {
|
|
10106
|
+
messages.push(t("required", state));
|
|
10107
|
+
}
|
|
10108
|
+
if (!textEmpty && text) {
|
|
10109
|
+
if (element.minLength != null && text.length < element.minLength) {
|
|
10110
|
+
messages.push(t("minLength", state, { min: element.minLength }));
|
|
10099
10111
|
}
|
|
10100
|
-
if (element.
|
|
10101
|
-
|
|
10112
|
+
if (element.maxLength != null && text.length > element.maxLength) {
|
|
10113
|
+
messages.push(t("maxLength", state, { max: element.maxLength }));
|
|
10102
10114
|
}
|
|
10103
10115
|
}
|
|
10116
|
+
if (element.maxFiles != null && files.length > element.maxFiles) {
|
|
10117
|
+
messages.push(t("maxFiles", state, { max: element.maxFiles }));
|
|
10118
|
+
}
|
|
10119
|
+
errors.push(...messages.map((message) => `${key}: ${message}`));
|
|
10120
|
+
markFieldGroupValidity(scopeRoot, key, joinErrorMessages(messages), context);
|
|
10104
10121
|
return { value, errors, spread: !!element.flatOutput };
|
|
10105
10122
|
}
|
|
10106
10123
|
function updateRichInputField(element, fieldPath, value, context) {
|
|
@@ -10379,12 +10396,7 @@ function validateHiddenElement(element, key, context) {
|
|
|
10379
10396
|
const input = scopeRoot.querySelector(
|
|
10380
10397
|
`input[type="hidden"][data-hidden-field="true"][name="${key}"]`
|
|
10381
10398
|
);
|
|
10382
|
-
|
|
10383
|
-
if (raw === "") {
|
|
10384
|
-
const defaultVal = "default" in element ? element.default : null;
|
|
10385
|
-
return { value: defaultVal !== void 0 ? defaultVal : null, errors: [] };
|
|
10386
|
-
}
|
|
10387
|
-
return { value: deserializeHiddenValue(raw), errors: [] };
|
|
10399
|
+
return { value: deserializeHiddenValue((_a = input == null ? void 0 : input.value) != null ? _a : ""), errors: [] };
|
|
10388
10400
|
}
|
|
10389
10401
|
function updateHiddenField(_element, fieldPath, value, context) {
|
|
10390
10402
|
const { scopeRoot } = context;
|
|
@@ -10576,25 +10588,13 @@ function extractDOMValue(fieldPath, formRoot) {
|
|
|
10576
10588
|
if (!input) {
|
|
10577
10589
|
return void 0;
|
|
10578
10590
|
}
|
|
10579
|
-
if (input instanceof
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
} else if (input.type === "radio") {
|
|
10585
|
-
const checked = formRoot.querySelector(
|
|
10586
|
-
`[name="${fieldPath}"]:checked`
|
|
10587
|
-
);
|
|
10588
|
-
return checked ? checked.value : void 0;
|
|
10589
|
-
} else if (input.dataset.hiddenField) {
|
|
10590
|
-
return deserializeHiddenValue(input.value);
|
|
10591
|
-
} else {
|
|
10592
|
-
return input.value;
|
|
10593
|
-
}
|
|
10594
|
-
} else if (input instanceof HTMLTextAreaElement) {
|
|
10595
|
-
return input.value;
|
|
10591
|
+
if (input instanceof HTMLInputElement && input.type === "radio") {
|
|
10592
|
+
const checked = formRoot.querySelector(
|
|
10593
|
+
`[name="${fieldPath}"]:checked`
|
|
10594
|
+
);
|
|
10595
|
+
return checked ? checked.value : void 0;
|
|
10596
10596
|
}
|
|
10597
|
-
return
|
|
10597
|
+
return readTypedInputValue(input);
|
|
10598
10598
|
}
|
|
10599
10599
|
function buildScopedDataAtPath(path, value) {
|
|
10600
10600
|
const segments = path.match(/[^.[\]]+|\[\d+\]/g);
|
|
@@ -10726,11 +10726,19 @@ function createFieldLabel(element) {
|
|
|
10726
10726
|
}
|
|
10727
10727
|
return title;
|
|
10728
10728
|
}
|
|
10729
|
+
function ensureTooltipStyles(doc) {
|
|
10730
|
+
if (doc.head.querySelector("[data-fb-tooltip-styles]")) return;
|
|
10731
|
+
const style = doc.createElement("style");
|
|
10732
|
+
style.setAttribute("data-fb-tooltip-styles", "");
|
|
10733
|
+
style.textContent = `[id^="tooltip-"].hidden { display: none; }`;
|
|
10734
|
+
doc.head.appendChild(style);
|
|
10735
|
+
}
|
|
10729
10736
|
function createInfoButton(element, state) {
|
|
10730
10737
|
const infoBtn = document.createElement("button");
|
|
10731
10738
|
infoBtn.type = "button";
|
|
10732
10739
|
infoBtn.className = "ml-2 text-gray-400 hover:text-gray-600";
|
|
10733
10740
|
infoBtn.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>';
|
|
10741
|
+
ensureTooltipStyles(document);
|
|
10734
10742
|
const tooltipId = `tooltip-${element.key}-${Math.random().toString(36).substr(2, 9)}`;
|
|
10735
10743
|
const tooltip = document.createElement("div");
|
|
10736
10744
|
tooltip.id = tooltipId;
|
|
@@ -10880,12 +10888,13 @@ function renderElement2(element, ctx) {
|
|
|
10880
10888
|
wrapper.className = `fb-field-wrapper fb-size-${element.size || "md"}`;
|
|
10881
10889
|
wrapper.setAttribute("data-field-key", element.key);
|
|
10882
10890
|
wrapper.setAttribute("data-fb-width", element.width || "full");
|
|
10891
|
+
const pathKey = pathJoin(ctx.path, element.key);
|
|
10892
|
+
wrapper.setAttribute("data-field-path", pathKey);
|
|
10883
10893
|
const ops = getComponentOperations(element.type);
|
|
10884
10894
|
if (!(ops == null ? void 0 : ops.ownsLabel)) {
|
|
10885
10895
|
const label = createLabelContainer(element, ctx.state);
|
|
10886
10896
|
wrapper.appendChild(label);
|
|
10887
10897
|
}
|
|
10888
|
-
const pathKey = pathJoin(ctx.path, element.key);
|
|
10889
10898
|
dispatchToRenderer(element, ctx, wrapper, pathKey);
|
|
10890
10899
|
if (initiallyDisabled) {
|
|
10891
10900
|
wrapper.style.display = "none";
|
|
@@ -10911,6 +10920,7 @@ var defaultConfig = {
|
|
|
10911
10920
|
onDownloadError: null,
|
|
10912
10921
|
debounceMs: 300,
|
|
10913
10922
|
verboseErrors: false,
|
|
10923
|
+
postMessageTarget: null,
|
|
10914
10924
|
enableFilePreview: true,
|
|
10915
10925
|
maxPreviewSize: "200px",
|
|
10916
10926
|
readonly: false,
|
|
@@ -10932,6 +10942,7 @@ var defaultConfig = {
|
|
|
10932
10942
|
openInNewTab: "Open in new tab",
|
|
10933
10943
|
changeButton: "Change",
|
|
10934
10944
|
placeholderText: "Enter text",
|
|
10945
|
+
selectPlaceholder: "Select\u2026",
|
|
10935
10946
|
previewAlt: "Preview",
|
|
10936
10947
|
previewUnavailable: "Preview unavailable",
|
|
10937
10948
|
previewError: "Preview error",
|
|
@@ -11007,6 +11018,7 @@ var defaultConfig = {
|
|
|
11007
11018
|
openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435",
|
|
11008
11019
|
changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
|
|
11009
11020
|
placeholderText: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442",
|
|
11021
|
+
selectPlaceholder: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435\u2026",
|
|
11010
11022
|
previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
|
|
11011
11023
|
previewUnavailable: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
|
|
11012
11024
|
previewError: "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430",
|
|
@@ -11071,21 +11083,27 @@ var defaultConfig = {
|
|
|
11071
11083
|
},
|
|
11072
11084
|
theme: {}
|
|
11073
11085
|
};
|
|
11074
|
-
function
|
|
11075
|
-
const
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
config.translations
|
|
11081
|
-
)) {
|
|
11082
|
-
mergedTranslations[locale] = {
|
|
11083
|
-
...defaultConfig.translations[locale] || {},
|
|
11086
|
+
function mergeTranslations(base, overrides) {
|
|
11087
|
+
const merged = { ...base };
|
|
11088
|
+
if (overrides) {
|
|
11089
|
+
for (const [locale, userTranslations] of Object.entries(overrides)) {
|
|
11090
|
+
merged[locale] = {
|
|
11091
|
+
...base[locale] || {},
|
|
11084
11092
|
...userTranslations
|
|
11085
11093
|
};
|
|
11086
11094
|
}
|
|
11087
11095
|
}
|
|
11096
|
+
return merged;
|
|
11097
|
+
}
|
|
11098
|
+
function createInstanceState(config) {
|
|
11099
|
+
const mergedTranslations = mergeTranslations(
|
|
11100
|
+
defaultConfig.translations,
|
|
11101
|
+
config == null ? void 0 : config.translations
|
|
11102
|
+
);
|
|
11088
11103
|
return {
|
|
11104
|
+
instanceId: generateInstanceId(),
|
|
11105
|
+
domIdCounter: 0,
|
|
11106
|
+
reportedInvalid: /* @__PURE__ */ new WeakSet(),
|
|
11089
11107
|
schema: null,
|
|
11090
11108
|
formRoot: null,
|
|
11091
11109
|
resourceIndex: /* @__PURE__ */ new Map(),
|
|
@@ -11100,7 +11118,9 @@ function createInstanceState(config) {
|
|
|
11100
11118
|
prefill: {},
|
|
11101
11119
|
syntheticElementIds: /* @__PURE__ */ new WeakMap(),
|
|
11102
11120
|
syntheticElementIdCounter: 0,
|
|
11121
|
+
multiFileSetters: /* @__PURE__ */ new WeakMap(),
|
|
11103
11122
|
enableIfObservers: /* @__PURE__ */ new Set(),
|
|
11123
|
+
autoExpandObservers: /* @__PURE__ */ new Set(),
|
|
11104
11124
|
tooltipElements: /* @__PURE__ */ new Set()
|
|
11105
11125
|
};
|
|
11106
11126
|
}
|
|
@@ -11402,8 +11422,13 @@ function findOwnField(scope, lookupKey, ownBoundary) {
|
|
|
11402
11422
|
}
|
|
11403
11423
|
var FormBuilderInstance = class {
|
|
11404
11424
|
constructor(config) {
|
|
11405
|
-
|
|
11425
|
+
// The bound prefill-hint click handler currently attached to the form root.
|
|
11426
|
+
// Kept so renderForm()/destroy() can remove it — re-binding on every render
|
|
11427
|
+
// stacked listeners (hint clicks applied values N times) and destroy()
|
|
11428
|
+
// left the last one on the host-owned root, retaining the instance.
|
|
11429
|
+
this.prefillHintHandler = null;
|
|
11406
11430
|
this.state = createInstanceState(config);
|
|
11431
|
+
this.instanceId = this.state.instanceId;
|
|
11407
11432
|
if (this.state.config.verboseErrors) {
|
|
11408
11433
|
if (!globalThis.__formBuilderInstances) {
|
|
11409
11434
|
globalThis.__formBuilderInstances = /* @__PURE__ */ new Set();
|
|
@@ -11435,10 +11460,21 @@ var FormBuilderInstance = class {
|
|
|
11435
11460
|
this.state.formRoot = element;
|
|
11436
11461
|
}
|
|
11437
11462
|
/**
|
|
11438
|
-
* Configure the form builder
|
|
11463
|
+
* Configure the form builder. Translations deep-merge per locale (same as
|
|
11464
|
+
* the constructor); a locale without translations — configured or default —
|
|
11465
|
+
* is rejected, matching setLocale.
|
|
11439
11466
|
*/
|
|
11440
11467
|
configure(config) {
|
|
11441
|
-
|
|
11468
|
+
const translations = mergeTranslations(
|
|
11469
|
+
this.state.config.translations,
|
|
11470
|
+
config.translations
|
|
11471
|
+
);
|
|
11472
|
+
if (config.locale !== void 0 && !translations[config.locale]) {
|
|
11473
|
+
throw new Error(
|
|
11474
|
+
`configure: no translations configured for locale "${config.locale}"`
|
|
11475
|
+
);
|
|
11476
|
+
}
|
|
11477
|
+
Object.assign(this.state.config, config, { translations });
|
|
11442
11478
|
}
|
|
11443
11479
|
/**
|
|
11444
11480
|
* Set file upload handler
|
|
@@ -11471,12 +11507,16 @@ var FormBuilderInstance = class {
|
|
|
11471
11507
|
this.state.config.readonly = mode === "readonly";
|
|
11472
11508
|
}
|
|
11473
11509
|
/**
|
|
11474
|
-
* Set locale
|
|
11510
|
+
* Set locale. Custom locales are allowed — their translations must have
|
|
11511
|
+
* been provided via the constructor or configure() first.
|
|
11475
11512
|
*/
|
|
11476
11513
|
setLocale(locale) {
|
|
11477
|
-
if (this.state.config.translations[locale]) {
|
|
11478
|
-
|
|
11514
|
+
if (!this.state.config.translations[locale]) {
|
|
11515
|
+
throw new Error(
|
|
11516
|
+
`setLocale: no translations configured for locale "${locale}"`
|
|
11517
|
+
);
|
|
11479
11518
|
}
|
|
11519
|
+
this.state.config.locale = locale;
|
|
11480
11520
|
}
|
|
11481
11521
|
/**
|
|
11482
11522
|
* Trigger onChange callbacks with debouncing
|
|
@@ -11829,11 +11869,13 @@ var FormBuilderInstance = class {
|
|
|
11829
11869
|
renderForm(root, schema, prefill, actions) {
|
|
11830
11870
|
const errors = validateSchema(schema);
|
|
11831
11871
|
if (errors.length > 0) {
|
|
11832
|
-
|
|
11833
|
-
|
|
11872
|
+
throw new Error(`renderForm: invalid schema:
|
|
11873
|
+
- ${errors.join("\n- ")}`);
|
|
11834
11874
|
}
|
|
11835
11875
|
this.disconnectEnableIfObservers();
|
|
11876
|
+
this.disconnectAutoExpandObservers();
|
|
11836
11877
|
this.removeTooltipElements();
|
|
11878
|
+
this.removePrefillHintListener();
|
|
11837
11879
|
this.state.formRoot = root;
|
|
11838
11880
|
this.state.schema = schema;
|
|
11839
11881
|
this.state.externalActions = actions || null;
|
|
@@ -11855,9 +11897,9 @@ var FormBuilderInstance = class {
|
|
|
11855
11897
|
fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
|
|
11856
11898
|
}
|
|
11857
11899
|
schema.elements.forEach((element) => {
|
|
11858
|
-
var _a
|
|
11900
|
+
var _a;
|
|
11859
11901
|
if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
|
|
11860
|
-
const val =
|
|
11902
|
+
const val = prefill && element.key in prefill ? prefill[element.key] : (_a = element.default) != null ? _a : null;
|
|
11861
11903
|
fieldsWrapper.appendChild(createHiddenInput(element.key, val));
|
|
11862
11904
|
return;
|
|
11863
11905
|
}
|
|
@@ -11874,23 +11916,36 @@ var FormBuilderInstance = class {
|
|
|
11874
11916
|
rootContainer.appendChild(fieldsWrapper);
|
|
11875
11917
|
root.appendChild(rootContainer);
|
|
11876
11918
|
if (!this.state.config.readonly) {
|
|
11877
|
-
|
|
11919
|
+
this.prefillHintHandler = this.handlePrefillHintClick.bind(
|
|
11920
|
+
this
|
|
11921
|
+
);
|
|
11922
|
+
root.addEventListener("click", this.prefillHintHandler);
|
|
11878
11923
|
}
|
|
11879
11924
|
if (this.state.config.readonly && this.state.externalActions && Array.isArray(this.state.externalActions)) {
|
|
11880
11925
|
this.renderExternalActions();
|
|
11881
11926
|
}
|
|
11882
11927
|
}
|
|
11883
11928
|
/**
|
|
11884
|
-
* Validate form and extract data
|
|
11885
|
-
*
|
|
11886
|
-
*
|
|
11929
|
+
* Validate the form and extract its data. `skipValidation` is the draft
|
|
11930
|
+
* contract of saveDraft() and the onChange payload: marks are only
|
|
11931
|
+
* refreshed or cleared, and the result reports `valid: true, errors: []`.
|
|
11887
11932
|
*/
|
|
11888
11933
|
validateForm(skipValidation = false) {
|
|
11934
|
+
if (!skipValidation) return this.runValidation("full");
|
|
11935
|
+
return { ...this.runValidation("draft"), valid: true, errors: [] };
|
|
11936
|
+
}
|
|
11937
|
+
/**
|
|
11938
|
+
* Run every rule and return the real result. `marks` decides only what is
|
|
11939
|
+
* painted: "full" raises and clears marks and records reported fields;
|
|
11940
|
+
* "draft" refreshes or clears marks of reported fields and raises none
|
|
11941
|
+
* (see ValidityScope in utils/styles.ts).
|
|
11942
|
+
*/
|
|
11943
|
+
runValidation(marks) {
|
|
11889
11944
|
if (!this.state.schema || !this.state.formRoot)
|
|
11890
11945
|
return { valid: true, errors: [], data: {} };
|
|
11891
11946
|
const errors = [];
|
|
11892
11947
|
const data = {};
|
|
11893
|
-
const
|
|
11948
|
+
const validateElement = (element, ctx, customScopeRoot = null) => {
|
|
11894
11949
|
var _a;
|
|
11895
11950
|
const key = (_a = element.key) != null ? _a : "";
|
|
11896
11951
|
const scopeRoot = customScopeRoot || this.state.formRoot;
|
|
@@ -11899,7 +11954,11 @@ var FormBuilderInstance = class {
|
|
|
11899
11954
|
state: this.state,
|
|
11900
11955
|
instance: this,
|
|
11901
11956
|
path: ctx.path,
|
|
11902
|
-
|
|
11957
|
+
draftMarks: marks === "draft",
|
|
11958
|
+
readonly: isElementReadonly(element, this.state, ctx),
|
|
11959
|
+
// Containers recurse into their children through this — threaded per
|
|
11960
|
+
// pass, never module state (see ComponentContext.validateElement).
|
|
11961
|
+
validateElement
|
|
11903
11962
|
};
|
|
11904
11963
|
const componentResult = validateElementWithComponent(
|
|
11905
11964
|
element,
|
|
@@ -11917,7 +11976,6 @@ var FormBuilderInstance = class {
|
|
|
11917
11976
|
console.warn(`Unknown field type "${element.type}" for key "${key}"`);
|
|
11918
11977
|
return { value: null, spread: false };
|
|
11919
11978
|
};
|
|
11920
|
-
setValidateElement(validateElement2);
|
|
11921
11979
|
this.state.schema.elements.forEach((element) => {
|
|
11922
11980
|
if (element.enableIf) {
|
|
11923
11981
|
try {
|
|
@@ -11935,7 +11993,7 @@ var FormBuilderInstance = class {
|
|
|
11935
11993
|
if (element.type === "markdown") {
|
|
11936
11994
|
return;
|
|
11937
11995
|
}
|
|
11938
|
-
const result =
|
|
11996
|
+
const result = validateElement(element, { path: "" });
|
|
11939
11997
|
if (result.skip) return;
|
|
11940
11998
|
if (result.spread && result.value !== null && typeof result.value === "object") {
|
|
11941
11999
|
Object.assign(data, result.value);
|
|
@@ -11950,10 +12008,56 @@ var FormBuilderInstance = class {
|
|
|
11950
12008
|
};
|
|
11951
12009
|
}
|
|
11952
12010
|
/**
|
|
11953
|
-
*
|
|
12011
|
+
* Read the form: every rule runs and the result is the real
|
|
12012
|
+
* `{valid, errors, data}`. Safe to poll — it never paints a new error
|
|
12013
|
+
* mark (so a pristine form never turns red), it only refreshes or clears
|
|
12014
|
+
* marks that showErrors()/submitForm() drew, and a repeated call on
|
|
12015
|
+
* unchanged state touches no DOM at all.
|
|
11954
12016
|
*/
|
|
11955
12017
|
getFormData() {
|
|
11956
|
-
return this.
|
|
12018
|
+
return this.runValidation("draft");
|
|
12019
|
+
}
|
|
12020
|
+
/**
|
|
12021
|
+
* Paint every validation error next to its field (and clear marks of
|
|
12022
|
+
* fields that are now valid), then return the same result as
|
|
12023
|
+
* getFormData(). Call it when the user asks to submit, followed by
|
|
12024
|
+
* focusFirstError() to take them to the first problem.
|
|
12025
|
+
*/
|
|
12026
|
+
showErrors() {
|
|
12027
|
+
return this.runValidation("full");
|
|
12028
|
+
}
|
|
12029
|
+
/**
|
|
12030
|
+
* Focus the first field marked invalid, in DOM order, and scroll it into
|
|
12031
|
+
* view. A marked group (container, multi-value field, file field) gets
|
|
12032
|
+
* focus on its first focusable control, else on the group itself.
|
|
12033
|
+
* Does not validate: marks are drawn by showErrors() (or submitForm()),
|
|
12034
|
+
* so call showErrors() first. Fields hidden by enableIf are skipped, as is
|
|
12035
|
+
* any field that cannot take focus (e.g. inside a hidden slide).
|
|
12036
|
+
* @returns true only when focus actually landed on an invalid field
|
|
12037
|
+
*/
|
|
12038
|
+
focusFirstError() {
|
|
12039
|
+
const root = this.state.formRoot;
|
|
12040
|
+
if (!root) return false;
|
|
12041
|
+
const marked = root.querySelectorAll('[aria-invalid="true"]');
|
|
12042
|
+
for (const target of Array.from(marked)) {
|
|
12043
|
+
if (target.closest('[data-conditionally-disabled="true"]')) continue;
|
|
12044
|
+
const candidates = target.matches("input, select, textarea, button") ? [target] : [
|
|
12045
|
+
...Array.from(
|
|
12046
|
+
target.querySelectorAll(
|
|
12047
|
+
"input, select, textarea, button, [tabindex]"
|
|
12048
|
+
)
|
|
12049
|
+
),
|
|
12050
|
+
target
|
|
12051
|
+
];
|
|
12052
|
+
for (const candidate of candidates) {
|
|
12053
|
+
candidate.focus({ preventScroll: true });
|
|
12054
|
+
if (document.activeElement === candidate) {
|
|
12055
|
+
candidate.scrollIntoView({ block: "center" });
|
|
12056
|
+
return true;
|
|
12057
|
+
}
|
|
12058
|
+
}
|
|
12059
|
+
}
|
|
12060
|
+
return false;
|
|
11957
12061
|
}
|
|
11958
12062
|
/**
|
|
11959
12063
|
* Submit form with validation
|
|
@@ -11961,16 +12065,7 @@ var FormBuilderInstance = class {
|
|
|
11961
12065
|
submitForm() {
|
|
11962
12066
|
const result = this.validateForm(false);
|
|
11963
12067
|
if (result.valid) {
|
|
11964
|
-
|
|
11965
|
-
window.parent.postMessage(
|
|
11966
|
-
{
|
|
11967
|
-
type: "formSubmit",
|
|
11968
|
-
data: result.data,
|
|
11969
|
-
schema: this.state.schema
|
|
11970
|
-
},
|
|
11971
|
-
"*"
|
|
11972
|
-
);
|
|
11973
|
-
}
|
|
12068
|
+
this.postToParent("formSubmit", result.data);
|
|
11974
12069
|
}
|
|
11975
12070
|
return result;
|
|
11976
12071
|
}
|
|
@@ -11979,17 +12074,27 @@ var FormBuilderInstance = class {
|
|
|
11979
12074
|
*/
|
|
11980
12075
|
saveDraft() {
|
|
11981
12076
|
const result = this.validateForm(true);
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
12077
|
+
this.postToParent("formDraft", result.data);
|
|
12078
|
+
return result;
|
|
12079
|
+
}
|
|
12080
|
+
/**
|
|
12081
|
+
* Post form data to the parent frame — only when the host opted in via
|
|
12082
|
+
* `postMessageTarget`. Outside an iframe `window.parent === window`, so an
|
|
12083
|
+
* unconditional post broadcast form data and the full schema to any
|
|
12084
|
+
* embedding page (targetOrigin "*") on every submit. See CHANGELOG 0.6.0.
|
|
12085
|
+
*/
|
|
12086
|
+
postToParent(type, data) {
|
|
12087
|
+
const target = this.state.config.postMessageTarget;
|
|
12088
|
+
if (target === "") {
|
|
12089
|
+
throw new Error(
|
|
12090
|
+
'postMessageTarget: "" is not a valid target origin \u2014 use null to disable posting or "*" to knowingly broadcast'
|
|
11990
12091
|
);
|
|
11991
12092
|
}
|
|
11992
|
-
return
|
|
12093
|
+
if (!target || typeof window === "undefined" || !window.parent) return;
|
|
12094
|
+
window.parent.postMessage(
|
|
12095
|
+
{ type, data, schema: this.state.schema },
|
|
12096
|
+
target
|
|
12097
|
+
);
|
|
11993
12098
|
}
|
|
11994
12099
|
/**
|
|
11995
12100
|
* Clear the form - reset all field values to empty while preserving form structure
|
|
@@ -12195,6 +12300,7 @@ var FormBuilderInstance = class {
|
|
|
12195
12300
|
getElementLookupKey(element, this.state)
|
|
12196
12301
|
);
|
|
12197
12302
|
disabledWrapper.setAttribute("data-conditionally-disabled", "true");
|
|
12303
|
+
disabledWrapper.setAttribute("data-field-path", fullDomPath);
|
|
12198
12304
|
(_c = wrapper.parentNode) == null ? void 0 : _c.replaceChild(disabledWrapper, wrapper);
|
|
12199
12305
|
}
|
|
12200
12306
|
} catch (error) {
|
|
@@ -12263,7 +12369,9 @@ var FormBuilderInstance = class {
|
|
|
12263
12369
|
this.state.debounceTimer = null;
|
|
12264
12370
|
}
|
|
12265
12371
|
this.disconnectEnableIfObservers();
|
|
12372
|
+
this.disconnectAutoExpandObservers();
|
|
12266
12373
|
this.removeTooltipElements();
|
|
12374
|
+
this.removePrefillHintListener();
|
|
12267
12375
|
this.state.resourceIndex.clear();
|
|
12268
12376
|
if (this.state.formRoot) {
|
|
12269
12377
|
clear(this.state.formRoot);
|
|
@@ -12281,6 +12389,18 @@ var FormBuilderInstance = class {
|
|
|
12281
12389
|
}
|
|
12282
12390
|
this.state.enableIfObservers.clear();
|
|
12283
12391
|
}
|
|
12392
|
+
disconnectAutoExpandObservers() {
|
|
12393
|
+
for (const observer of this.state.autoExpandObservers) {
|
|
12394
|
+
observer.disconnect();
|
|
12395
|
+
}
|
|
12396
|
+
this.state.autoExpandObservers.clear();
|
|
12397
|
+
}
|
|
12398
|
+
removePrefillHintListener() {
|
|
12399
|
+
if (this.prefillHintHandler && this.state.formRoot) {
|
|
12400
|
+
this.state.formRoot.removeEventListener("click", this.prefillHintHandler);
|
|
12401
|
+
}
|
|
12402
|
+
this.prefillHintHandler = null;
|
|
12403
|
+
}
|
|
12284
12404
|
removeTooltipElements() {
|
|
12285
12405
|
for (const tooltip of this.state.tooltipElements) {
|
|
12286
12406
|
tooltip.remove();
|