@usefillo/dom 0.5.4 → 0.6.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/dist/index.js +70 -40
- package/dist/standalone.global.js +1 -1
- package/dist/styles.css +15 -11
- package/dist/styles.unlayered.css +700 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -18,9 +18,10 @@ import {
|
|
|
18
18
|
parsePhone,
|
|
19
19
|
pipeBlock,
|
|
20
20
|
provisionWorkspace,
|
|
21
|
+
needsExplicitSubmit,
|
|
22
|
+
shouldAutoSubmit,
|
|
21
23
|
syncCodeForm,
|
|
22
24
|
toE164,
|
|
23
|
-
validateResponse,
|
|
24
25
|
visibleFields
|
|
25
26
|
} from "@usefillo/core";
|
|
26
27
|
var FIELD_INPUT_PREFIX = "fillo-";
|
|
@@ -992,44 +993,12 @@ var DEFAULT_COMPONENTS = {
|
|
|
992
993
|
signature,
|
|
993
994
|
file_upload: fileUpload
|
|
994
995
|
};
|
|
995
|
-
function isAutoSubmitBlock(block) {
|
|
996
|
-
return isField(block) && (block.kind === "select" || block.kind === "dropdown" || block.kind === "checkbox" || block.kind === "rating" || block.kind === "linear_scale");
|
|
997
|
-
}
|
|
998
|
-
function needsExplicitSubmit(visible) {
|
|
999
|
-
const interactive = visible.filter((b) => !(isField(b) && b.kind === "hidden"));
|
|
1000
|
-
if (interactive.length === 0) return false;
|
|
1001
|
-
if (interactive.length > 1) return true;
|
|
1002
|
-
return !isAutoSubmitBlock(interactive[0]);
|
|
1003
|
-
}
|
|
1004
996
|
function defaultFieldRenderer(context) {
|
|
1005
997
|
if (context.field.kind === "hidden") return null;
|
|
1006
998
|
if (context.field.kind === "custom") return null;
|
|
1007
999
|
const renderer = DEFAULT_COMPONENTS[context.field.kind];
|
|
1008
1000
|
return renderer ? renderer(context) : null;
|
|
1009
1001
|
}
|
|
1010
|
-
function shouldAutoSubmit(field, value, api, uploading) {
|
|
1011
|
-
if (api.form.settings.submitMode !== "auto") return false;
|
|
1012
|
-
if (!api.isLastPage || api.status !== "idle" || uploading) return false;
|
|
1013
|
-
const hasDiscreteAnswer = (() => {
|
|
1014
|
-
switch (field.kind) {
|
|
1015
|
-
case "select":
|
|
1016
|
-
case "dropdown":
|
|
1017
|
-
return typeof value === "string" && field.options.some((option) => option.id === value);
|
|
1018
|
-
case "checkbox":
|
|
1019
|
-
return value === true;
|
|
1020
|
-
case "rating":
|
|
1021
|
-
case "linear_scale":
|
|
1022
|
-
return typeof value === "number";
|
|
1023
|
-
default:
|
|
1024
|
-
return false;
|
|
1025
|
-
}
|
|
1026
|
-
})();
|
|
1027
|
-
if (!hasDiscreteAnswer) return false;
|
|
1028
|
-
const nextData = { ...api.data, [field.id]: value };
|
|
1029
|
-
const interactive = visibleFields(api.form, nextData).filter((f) => f.kind !== "hidden");
|
|
1030
|
-
if (interactive.length !== 1 || interactive[0]?.id !== field.id) return false;
|
|
1031
|
-
return validateResponse(api.form, nextData).ok;
|
|
1032
|
-
}
|
|
1033
1002
|
var DomFormController = class {
|
|
1034
1003
|
constructor(target, options) {
|
|
1035
1004
|
this.target = target;
|
|
@@ -1104,22 +1073,43 @@ var DomFormController = class {
|
|
|
1104
1073
|
submit: () => this.submit()
|
|
1105
1074
|
};
|
|
1106
1075
|
}
|
|
1076
|
+
resolveFormIdFn;
|
|
1077
|
+
/** Prod-only: the code form is an unpublished draft — submissions would 404. */
|
|
1078
|
+
notLive = false;
|
|
1107
1079
|
async resolve() {
|
|
1108
1080
|
try {
|
|
1109
1081
|
const codeForm = isCodeForm(this.options.form) ? this.options.form : null;
|
|
1110
1082
|
if (codeForm) {
|
|
1083
|
+
const client = this.options.client;
|
|
1084
|
+
if (client?.key) {
|
|
1085
|
+
this.resolveFormIdFn = () => syncCodeForm(client, codeForm, { bypassCache: true }).then((r) => r.formId);
|
|
1086
|
+
}
|
|
1111
1087
|
this.setSchema(codeForm.schema, this.options.theme ?? codeForm.theme, this.options.formId);
|
|
1112
|
-
if (
|
|
1113
|
-
syncCodeForm(
|
|
1088
|
+
if (client?.key) {
|
|
1089
|
+
syncCodeForm(client, codeForm).then(({ formId, branding, staged, status, warning }) => {
|
|
1114
1090
|
this.formId = formId;
|
|
1115
1091
|
this.engine?.setContext({ formId });
|
|
1092
|
+
if (warning) console.warn(`[fillo] ${warning}`);
|
|
1093
|
+
if (staged) {
|
|
1094
|
+
console.info(`[fillo] "${codeForm.id}" changes staged as a draft \u2014 publish them in your Fillo dashboard`);
|
|
1095
|
+
} else if (status === "draft") {
|
|
1096
|
+
console.info(`[fillo] "${codeForm.id}" is a draft \u2014 it renders here but won't accept responses until published`);
|
|
1097
|
+
if (!isDevEnv()) {
|
|
1098
|
+
this.notLive = true;
|
|
1099
|
+
this.render();
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1116
1102
|
if (branding) {
|
|
1117
1103
|
this.fetchedPoweredBy = branding.poweredBy;
|
|
1118
1104
|
this.fetchedBrandLabel = branding.label;
|
|
1119
1105
|
this.fetchedBrandHref = branding.href;
|
|
1120
1106
|
this.render();
|
|
1121
1107
|
}
|
|
1122
|
-
}).catch((error) =>
|
|
1108
|
+
}).catch((error) => {
|
|
1109
|
+
const err = toFilloError(error);
|
|
1110
|
+
console.warn("[fillo] form sync failed:", err.message);
|
|
1111
|
+
this.options.onError?.(err);
|
|
1112
|
+
});
|
|
1123
1113
|
}
|
|
1124
1114
|
return;
|
|
1125
1115
|
}
|
|
@@ -1159,7 +1149,16 @@ var DomFormController = class {
|
|
|
1159
1149
|
onChange: this.options.onChange,
|
|
1160
1150
|
onSubmitted: this.options.onSubmitted,
|
|
1161
1151
|
getHoneypot: () => this.hpValue,
|
|
1162
|
-
surface: "default"
|
|
1152
|
+
surface: "default",
|
|
1153
|
+
resolveFormId: this.resolveFormIdFn
|
|
1154
|
+
});
|
|
1155
|
+
let lastStatus = this.engine.getState().status;
|
|
1156
|
+
this.engine.subscribe(() => {
|
|
1157
|
+
const status = this.engine?.getState().status;
|
|
1158
|
+
if (status !== void 0 && status !== lastStatus) {
|
|
1159
|
+
lastStatus = status;
|
|
1160
|
+
this.queueRender();
|
|
1161
|
+
}
|
|
1163
1162
|
});
|
|
1164
1163
|
this.phase = this.closed ? "closed" : "ready";
|
|
1165
1164
|
this.render();
|
|
@@ -1192,7 +1191,9 @@ var DomFormController = class {
|
|
|
1192
1191
|
try {
|
|
1193
1192
|
await pending;
|
|
1194
1193
|
} catch (error) {
|
|
1195
|
-
this.
|
|
1194
|
+
this.options.onError?.(toFilloError(error));
|
|
1195
|
+
this.focusIntent = "error";
|
|
1196
|
+
this.render();
|
|
1196
1197
|
return;
|
|
1197
1198
|
}
|
|
1198
1199
|
if (this.hasErrors()) this.focusIntent = "error";
|
|
@@ -1396,6 +1397,13 @@ var DomFormController = class {
|
|
|
1396
1397
|
this.element.appendChild(closed);
|
|
1397
1398
|
return;
|
|
1398
1399
|
}
|
|
1400
|
+
if (this.notLive) {
|
|
1401
|
+
const panel = el("div", { className: `fillo-form fillo-form--closed ${this.options.className ?? ""}` });
|
|
1402
|
+
themeStyle(panel, this.theme);
|
|
1403
|
+
panel.appendChild(el("p", { className: "fillo-closed", text: "This form isn't live yet." }));
|
|
1404
|
+
this.element.appendChild(panel);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1399
1407
|
if (!this.schema) return;
|
|
1400
1408
|
if (this.engine?.getState().status === "submitted") {
|
|
1401
1409
|
const api = this.api();
|
|
@@ -1439,7 +1447,7 @@ var DomFormController = class {
|
|
|
1439
1447
|
});
|
|
1440
1448
|
progress.appendChild(el("div", { className: "fillo-progress-fill" }));
|
|
1441
1449
|
progress.firstElementChild?.style.setProperty(
|
|
1442
|
-
"
|
|
1450
|
+
"--fillo-progress-value",
|
|
1443
1451
|
`${(api.pageIndex + 1) / api.pageCount * 100}%`
|
|
1444
1452
|
);
|
|
1445
1453
|
form.appendChild(progress);
|
|
@@ -1459,10 +1467,24 @@ var DomFormController = class {
|
|
|
1459
1467
|
}
|
|
1460
1468
|
form.appendChild(blocks);
|
|
1461
1469
|
const hp = el("input", { className: "fillo-hp", attrs: { type: "text", name: "fillo_hp_field", tabindex: "-1", autocomplete: "off", "aria-hidden": "true" } });
|
|
1470
|
+
hp.style.position = "absolute";
|
|
1471
|
+
hp.style.left = "-9999px";
|
|
1472
|
+
hp.style.top = "auto";
|
|
1473
|
+
hp.style.width = "1px";
|
|
1474
|
+
hp.style.height = "1px";
|
|
1475
|
+
hp.style.opacity = "0";
|
|
1476
|
+
hp.style.overflow = "hidden";
|
|
1477
|
+
hp.style.pointerEvents = "none";
|
|
1462
1478
|
hp.addEventListener("input", () => {
|
|
1463
1479
|
this.hpValue = hp.value;
|
|
1464
1480
|
});
|
|
1465
1481
|
form.appendChild(hp);
|
|
1482
|
+
const submitError = api.status === "error" ? "This form can't submit right now. Please try again in a moment." : this.engine?.getState().submitError;
|
|
1483
|
+
if (submitError) {
|
|
1484
|
+
form.appendChild(
|
|
1485
|
+
el("p", { className: "fillo-submit-error", attrs: { role: "alert" }, text: submitError })
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1466
1488
|
const autoSubmit = api.form.settings.submitMode === "auto";
|
|
1467
1489
|
const showPrimaryButton = !autoSubmit || !api.isLastPage || needsExplicitSubmit(visibleFields(api.form, api.data));
|
|
1468
1490
|
const showFooter = showPrimaryButton || api.pageCount > 1 && !api.isFirstPage;
|
|
@@ -1495,7 +1517,15 @@ var DomFormController = class {
|
|
|
1495
1517
|
const setValue = (value, options) => {
|
|
1496
1518
|
this.setValueInternal(block.id, value, options);
|
|
1497
1519
|
if (options?.render === false) return;
|
|
1498
|
-
|
|
1520
|
+
const autoCtx = {
|
|
1521
|
+
form: api.form,
|
|
1522
|
+
data: api.data,
|
|
1523
|
+
// dom's status union adds render phases; non-"idle" is a no anyway.
|
|
1524
|
+
status: api.status,
|
|
1525
|
+
isLastPage: api.isLastPage,
|
|
1526
|
+
uploading: this.uploadProgress.size > 0
|
|
1527
|
+
};
|
|
1528
|
+
if (shouldAutoSubmit(block, value, autoCtx)) void this.submit();
|
|
1499
1529
|
};
|
|
1500
1530
|
const context = {
|
|
1501
1531
|
field: block,
|