gg-wf-scripts 2.5.0 → 3.0.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 +33 -0
- package/dist/action-engine.d.ts +2 -0
- package/dist/action-engine.js +97 -0
- package/dist/action-engine.js.map +1 -0
- package/dist/actions.d.ts +5 -0
- package/dist/actions.js +2 -0
- package/dist/actions.js.map +1 -0
- package/dist/attrs.d.ts +59 -0
- package/dist/attrs.js +67 -0
- package/dist/attrs.js.map +1 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +28 -0
- package/dist/auth.js.map +1 -0
- package/dist/bridges.d.ts +1 -0
- package/dist/bridges.js +43 -0
- package/dist/bridges.js.map +1 -0
- package/dist/data-engine.d.ts +2 -0
- package/dist/data-engine.js +153 -0
- package/dist/data-engine.js.map +1 -0
- package/dist/dialog.d.ts +1 -0
- package/dist/dialog.js +93 -0
- package/dist/dialog.js.map +1 -0
- package/dist/dom-observer.d.ts +4 -0
- package/dist/dom-observer.js +47 -0
- package/dist/dom-observer.js.map +1 -0
- package/dist/engine-deps.d.ts +23 -0
- package/dist/engine-deps.js +2 -0
- package/dist/engine-deps.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +30 -0
- package/dist/errors.js.map +1 -0
- package/dist/form-action-engine.d.ts +2 -0
- package/dist/form-action-engine.js +173 -0
- package/dist/form-action-engine.js.map +1 -0
- package/dist/form-actions.d.ts +10 -0
- package/dist/form-actions.js +2 -0
- package/dist/form-actions.js.map +1 -0
- package/dist/form-visibility.d.ts +1 -0
- package/dist/form-visibility.js +109 -0
- package/dist/form-visibility.js.map +1 -0
- package/dist/helpers/dom.d.ts +16 -0
- package/dist/helpers/dom.js +39 -0
- package/dist/helpers/dom.js.map +1 -0
- package/dist/helpers/form-field.d.ts +19 -0
- package/dist/helpers/form-field.js +58 -0
- package/dist/helpers/form-field.js.map +1 -0
- package/dist/helpers/path.d.ts +2 -0
- package/dist/helpers/path.js +9 -0
- package/dist/helpers/path.js.map +1 -0
- package/dist/helpers/run-handler.d.ts +26 -0
- package/dist/helpers/run-handler.js +36 -0
- package/dist/helpers/run-handler.js.map +1 -0
- package/dist/helpers/run-with-loading.d.ts +6 -0
- package/dist/helpers/run-with-loading.js +28 -0
- package/dist/helpers/run-with-loading.js.map +1 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/queries.d.ts +1 -0
- package/dist/queries.js +2 -0
- package/dist/queries.js.map +1 -0
- package/dist/query-params.d.ts +19 -0
- package/dist/query-params.js +160 -0
- package/dist/query-params.js.map +1 -0
- package/dist/switch-engine.d.ts +1 -0
- package/dist/switch-engine.js +28 -0
- package/dist/switch-engine.js.map +1 -0
- package/package.json +25 -2
- package/src/action-engine.js +0 -64
- package/src/actions.js +0 -14
- package/src/auth.js +0 -28
- package/src/bridges.js +0 -31
- package/src/data-engine.js +0 -165
- package/src/dialog.js +0 -86
- package/src/form-action-engine.js +0 -149
- package/src/form-actions.js +0 -21
- package/src/form-visibility.js +0 -122
- package/src/helpers/dom.js +0 -35
- package/src/helpers/log.js +0 -34
- package/src/helpers/path.js +0 -4
- package/src/index.js +0 -60
- package/src/queries.js +0 -14
- package/src/query-params.js +0 -144
- package/src/switch-engine.js +0 -22
package/src/bridges.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { setSwitchState } from "./helpers/dom.js";
|
|
2
|
-
import { onQueryChanged } from "./query-params.js";
|
|
3
|
-
|
|
4
|
-
export function initBridges() {
|
|
5
|
-
// ---- gg-switch-query: URL params → gg-switch-state ----
|
|
6
|
-
// On any URL param change, mirror its value onto matching
|
|
7
|
-
// [gg-switch-query="<key>"] elements' gg-switch-state.
|
|
8
|
-
onQueryChanged((key, value) => {
|
|
9
|
-
document
|
|
10
|
-
.querySelectorAll(`[gg-switch-query="${CSS.escape(key)}"]`)
|
|
11
|
-
.forEach((el) => setSwitchState(el, value));
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// Initial-load pass: read current URL params and set state for every
|
|
15
|
-
// [gg-switch-query] on the page, so we don't flash before the first change.
|
|
16
|
-
const params = new URLSearchParams(window.location.search);
|
|
17
|
-
document.querySelectorAll("[gg-switch-query]").forEach((el) => {
|
|
18
|
-
const key = el.getAttribute("gg-switch-query");
|
|
19
|
-
setSwitchState(el, params.get(key));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// ---- webflow:emit → Webflow IX ----
|
|
23
|
-
window.addEventListener("load", () => {
|
|
24
|
-
Webflow.push(() => {
|
|
25
|
-
const wfIx = Webflow.require("ix3");
|
|
26
|
-
document.addEventListener("webflow:emit", (e) => {
|
|
27
|
-
wfIx.emit(e.detail.event);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
package/src/data-engine.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { getPath } from "./helpers/path.js";
|
|
2
|
-
import {
|
|
3
|
-
populateFields,
|
|
4
|
-
setSwitchState,
|
|
5
|
-
applySwitchState,
|
|
6
|
-
} from "./helpers/dom.js";
|
|
7
|
-
import { withDebugLog } from "./helpers/log.js";
|
|
8
|
-
import { queryRegistry } from "./queries.js";
|
|
9
|
-
import { onQueryChanged, getParams } from "./query-params.js";
|
|
10
|
-
|
|
11
|
-
function applySwitchFields(root, record) {
|
|
12
|
-
root.querySelectorAll("[gg-switch-field]").forEach((el) => {
|
|
13
|
-
const path = el.getAttribute("gg-switch-field");
|
|
14
|
-
const value = getPath(record, path);
|
|
15
|
-
setSwitchState(el, value);
|
|
16
|
-
applySwitchState(el);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function queryDeep(root, selector) {
|
|
21
|
-
const results = [...root.querySelectorAll(selector)];
|
|
22
|
-
root.querySelectorAll("*").forEach((el) => {
|
|
23
|
-
if (el.shadowRoot) results.push(...queryDeep(el.shadowRoot, selector));
|
|
24
|
-
});
|
|
25
|
-
return results;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function applyDataValues(root, record) {
|
|
29
|
-
queryDeep(root, "[gg-data-key]").forEach((el) => {
|
|
30
|
-
const path = el.getAttribute("gg-data-key");
|
|
31
|
-
const value = path ? getPath(record, path) : record;
|
|
32
|
-
if (value === undefined) return;
|
|
33
|
-
el.setAttribute("gg-data-value", JSON.stringify(value));
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function populateFormFields(root, record) {
|
|
38
|
-
root.querySelectorAll(
|
|
39
|
-
"input[name], select[name], textarea[name]",
|
|
40
|
-
).forEach((el) => {
|
|
41
|
-
const name = el.getAttribute("name");
|
|
42
|
-
const value = getPath(record, name);
|
|
43
|
-
if (value == null) return;
|
|
44
|
-
|
|
45
|
-
if (el instanceof HTMLInputElement) {
|
|
46
|
-
if (el.type === "checkbox") {
|
|
47
|
-
el.checked = Boolean(value);
|
|
48
|
-
} else if (el.type === "radio") {
|
|
49
|
-
el.checked = String(el.value) === String(value);
|
|
50
|
-
} else {
|
|
51
|
-
el.value = String(value);
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
el.value = String(value);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
58
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function initDataEngine(context, { debug = false } = {}) {
|
|
63
|
-
async function runQuery(container) {
|
|
64
|
-
const isList = container.hasAttribute("gg-data-list");
|
|
65
|
-
const isForm = container.hasAttribute("gg-data-form");
|
|
66
|
-
const id = container.getAttribute(
|
|
67
|
-
isList ? "gg-data-list" : isForm ? "gg-data-form" : "gg-data",
|
|
68
|
-
);
|
|
69
|
-
const query = queryRegistry[id];
|
|
70
|
-
if (!query) {
|
|
71
|
-
console.warn(`[gg-data] no query registered for "${id}"`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const params = getParams();
|
|
76
|
-
const result = await withDebugLog(
|
|
77
|
-
"[gg-data]",
|
|
78
|
-
id,
|
|
79
|
-
{ container, params: Object.fromEntries(params) },
|
|
80
|
-
debug,
|
|
81
|
-
() => query(context, params),
|
|
82
|
-
);
|
|
83
|
-
if (result === undefined) return;
|
|
84
|
-
|
|
85
|
-
if (isList) {
|
|
86
|
-
if (!Array.isArray(result)) {
|
|
87
|
-
console.warn(
|
|
88
|
-
`[gg-data-list] query "${id}" did not return an array`,
|
|
89
|
-
);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const template = container.querySelector("[gg-list-template]");
|
|
94
|
-
if (!template) {
|
|
95
|
-
console.warn(
|
|
96
|
-
`[gg-data-list] no [gg-list-template] inside "${id}"`,
|
|
97
|
-
);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
Array.from(container.children).forEach((child) => {
|
|
102
|
-
if (child !== template) child.remove();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
result.forEach((record) => {
|
|
106
|
-
const clone = template.cloneNode(true);
|
|
107
|
-
clone.removeAttribute("gg-list-template");
|
|
108
|
-
clone.setAttribute(
|
|
109
|
-
"gg-query-set",
|
|
110
|
-
`modal:view,id:${record.id}`,
|
|
111
|
-
);
|
|
112
|
-
clone.style.display = "flex";
|
|
113
|
-
if (record?.id != null) clone.id = String(record.id);
|
|
114
|
-
clone.__ggRecord = record;
|
|
115
|
-
populateFields(clone, record);
|
|
116
|
-
applySwitchFields(clone, record);
|
|
117
|
-
applyDataValues(clone, record);
|
|
118
|
-
container.appendChild(clone);
|
|
119
|
-
});
|
|
120
|
-
} else if (isForm) {
|
|
121
|
-
if (Array.isArray(result)) {
|
|
122
|
-
console.warn(
|
|
123
|
-
`[gg-data-form] query "${id}" returned an array; expected a single record`,
|
|
124
|
-
);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (!result) return;
|
|
128
|
-
container.__ggRecord = result;
|
|
129
|
-
populateFormFields(container, result);
|
|
130
|
-
applyDataValues(container, result);
|
|
131
|
-
} else {
|
|
132
|
-
if (Array.isArray(result)) {
|
|
133
|
-
console.warn(
|
|
134
|
-
`[gg-data] query "${id}" returned an array; use gg-data-list instead`,
|
|
135
|
-
);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (!result) return;
|
|
139
|
-
container.__ggRecord = result;
|
|
140
|
-
populateFields(container, result);
|
|
141
|
-
applySwitchFields(container, result);
|
|
142
|
-
applyDataValues(container, result);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Run all data containers on load
|
|
147
|
-
document
|
|
148
|
-
.querySelectorAll("[gg-data], [gg-data-list], [gg-data-form]")
|
|
149
|
-
.forEach(runQuery);
|
|
150
|
-
|
|
151
|
-
// Re-run queries when matching URL params change
|
|
152
|
-
onQueryChanged((key) => {
|
|
153
|
-
document
|
|
154
|
-
.querySelectorAll(
|
|
155
|
-
"[gg-data][gg-data-on], [gg-data-list][gg-data-on], [gg-data-form][gg-data-on]",
|
|
156
|
-
)
|
|
157
|
-
.forEach((c) => {
|
|
158
|
-
const keys = c
|
|
159
|
-
.getAttribute("gg-data-on")
|
|
160
|
-
.split(",")
|
|
161
|
-
.map((s) => s.trim());
|
|
162
|
-
if (keys.includes(key)) runQuery(c);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
}
|
package/src/dialog.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { removeQueryParams, onQueryChanged } from "./query-params.js";
|
|
2
|
-
|
|
3
|
-
function stopLenis() {
|
|
4
|
-
if (typeof lenis !== "undefined") lenis.stop();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function startLenis() {
|
|
8
|
-
if (typeof lenis !== "undefined") lenis.start();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function openDialog() {
|
|
12
|
-
const dialog = document.querySelector("dialog");
|
|
13
|
-
if (!dialog) return;
|
|
14
|
-
dialog.removeAttribute("aria-hidden");
|
|
15
|
-
dialog.removeAttribute("inert");
|
|
16
|
-
dialog.showModal();
|
|
17
|
-
stopLenis();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function closeDialog() {
|
|
21
|
-
const dialog = document.querySelector("dialog");
|
|
22
|
-
if (!dialog) return;
|
|
23
|
-
dialog.setAttribute("aria-hidden", "true");
|
|
24
|
-
dialog.setAttribute("inert", "");
|
|
25
|
-
dialog.close();
|
|
26
|
-
startLenis();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function dismissViaUrlOrDirect() {
|
|
30
|
-
const modalParam = new URLSearchParams(window.location.search).get("modal");
|
|
31
|
-
if (modalParam) {
|
|
32
|
-
removeQueryParams(["modal", "id"]);
|
|
33
|
-
} else {
|
|
34
|
-
closeDialog();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function syncDialogToUrl() {
|
|
39
|
-
const modalParam = new URLSearchParams(window.location.search).get("modal");
|
|
40
|
-
if (modalParam) {
|
|
41
|
-
openDialog();
|
|
42
|
-
} else {
|
|
43
|
-
closeDialog();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function initDialog() {
|
|
48
|
-
// Subscribe to query param changes — open/close when "modal" changes
|
|
49
|
-
onQueryChanged((key, value) => {
|
|
50
|
-
if (key !== "modal") return;
|
|
51
|
-
if (value) {
|
|
52
|
-
openDialog();
|
|
53
|
-
} else {
|
|
54
|
-
closeDialog();
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Inbound events from external code
|
|
59
|
-
document.addEventListener("gg:dialog:open", openDialog);
|
|
60
|
-
document.addEventListener("gg:dialog:close", closeDialog);
|
|
61
|
-
|
|
62
|
-
// Backdrop click
|
|
63
|
-
document.addEventListener("click", (e) => {
|
|
64
|
-
if (!e.target.matches("dialog[open]")) return;
|
|
65
|
-
const rect = e.target.getBoundingClientRect();
|
|
66
|
-
const outside =
|
|
67
|
-
e.clientX < rect.left ||
|
|
68
|
-
e.clientX > rect.right ||
|
|
69
|
-
e.clientY < rect.top ||
|
|
70
|
-
e.clientY > rect.bottom;
|
|
71
|
-
if (outside) dismissViaUrlOrDirect();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Escape key — preempt the default close so the URL stays source of truth
|
|
75
|
-
document.addEventListener("cancel", (e) => {
|
|
76
|
-
if (!e.target.matches("dialog")) return;
|
|
77
|
-
e.preventDefault();
|
|
78
|
-
dismissViaUrlOrDirect();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Back button
|
|
82
|
-
window.addEventListener("popstate", syncDialogToUrl);
|
|
83
|
-
|
|
84
|
-
// Initial load
|
|
85
|
-
syncDialogToUrl();
|
|
86
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { formActionRegistry } from "./form-actions.js";
|
|
2
|
-
import { populateFields } from "./helpers/dom.js";
|
|
3
|
-
import { withDebugLog } from "./helpers/log.js";
|
|
4
|
-
import { getParams } from "./query-params.js";
|
|
5
|
-
|
|
6
|
-
function findInputs(form, name) {
|
|
7
|
-
const escaped = CSS.escape(name);
|
|
8
|
-
return form.querySelectorAll(
|
|
9
|
-
`input[name="${escaped}"], select[name="${escaped}"], textarea[name="${escaped}"]`,
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function clearFieldError(form, name) {
|
|
14
|
-
findInputs(form, name).forEach((el) => {
|
|
15
|
-
el.removeAttribute("gg-form-field-invalid");
|
|
16
|
-
});
|
|
17
|
-
const escaped = CSS.escape(name);
|
|
18
|
-
form
|
|
19
|
-
.querySelectorAll(`[gg-form-field-error="${escaped}"]`)
|
|
20
|
-
.forEach((el) => {
|
|
21
|
-
el.textContent = "";
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function clearFormErrors(form) {
|
|
26
|
-
form.querySelectorAll("[gg-form-field-invalid]").forEach((el) => {
|
|
27
|
-
el.removeAttribute("gg-form-field-invalid");
|
|
28
|
-
});
|
|
29
|
-
form.querySelectorAll("[gg-form-field-error]").forEach((el) => {
|
|
30
|
-
el.textContent = "";
|
|
31
|
-
});
|
|
32
|
-
form.querySelectorAll("[gg-form-error]").forEach((el) => {
|
|
33
|
-
el.textContent = "";
|
|
34
|
-
});
|
|
35
|
-
form.querySelectorAll("[gg-form-error-list]").forEach((list) => {
|
|
36
|
-
const template = list.querySelector("[gg-list-template]");
|
|
37
|
-
Array.from(list.children).forEach((child) => {
|
|
38
|
-
if (child !== template) child.remove();
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function applyFieldErrors(form, fieldErrors) {
|
|
44
|
-
fieldErrors.forEach(({ name, message }) => {
|
|
45
|
-
if (!name) return;
|
|
46
|
-
findInputs(form, name).forEach((el) => {
|
|
47
|
-
el.setAttribute("gg-form-field-invalid", "true");
|
|
48
|
-
});
|
|
49
|
-
const escaped = CSS.escape(name);
|
|
50
|
-
form
|
|
51
|
-
.querySelectorAll(`[gg-form-field-error="${escaped}"]`)
|
|
52
|
-
.forEach((el) => {
|
|
53
|
-
el.textContent = message ?? "";
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function applyErrorList(form, fieldErrors) {
|
|
59
|
-
form.querySelectorAll("[gg-form-error-list]").forEach((list) => {
|
|
60
|
-
const template = list.querySelector("[gg-list-template]");
|
|
61
|
-
if (!template) {
|
|
62
|
-
console.warn(
|
|
63
|
-
"[gg-form-error-list] no [gg-list-template] inside list:",
|
|
64
|
-
list,
|
|
65
|
-
);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
fieldErrors.forEach((record) => {
|
|
69
|
-
const clone = template.cloneNode(true);
|
|
70
|
-
clone.removeAttribute("gg-list-template");
|
|
71
|
-
clone.style.display = "";
|
|
72
|
-
populateFields(clone, record);
|
|
73
|
-
list.appendChild(clone);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function applyFormError(form, error) {
|
|
79
|
-
if (error == null) return;
|
|
80
|
-
const message = typeof error === "string" ? error : String(error);
|
|
81
|
-
form.querySelectorAll("[gg-form-error]").forEach((el) => {
|
|
82
|
-
el.textContent = message;
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function bindFieldClearOnInput(form) {
|
|
87
|
-
if (form.__ggFormErrorBound) return;
|
|
88
|
-
form.__ggFormErrorBound = true;
|
|
89
|
-
form.addEventListener("input", (e) => {
|
|
90
|
-
const name = e.target?.getAttribute?.("name");
|
|
91
|
-
if (!name) return;
|
|
92
|
-
if (e.target.hasAttribute("gg-form-field-invalid")) {
|
|
93
|
-
clearFieldError(form, name);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function initFormActionEngine(context, { debug = false } = {}) {
|
|
99
|
-
async function handleSubmit(form, event) {
|
|
100
|
-
const id = form.getAttribute("gg-form-action");
|
|
101
|
-
const action = formActionRegistry[id];
|
|
102
|
-
if (!action) {
|
|
103
|
-
console.warn(`[gg-form-action] no form action registered for "${id}"`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
event.preventDefault();
|
|
108
|
-
clearFormErrors(form);
|
|
109
|
-
|
|
110
|
-
const formData = new FormData(form);
|
|
111
|
-
const params = getParams();
|
|
112
|
-
|
|
113
|
-
const result = await withDebugLog(
|
|
114
|
-
"[gg-form-action]",
|
|
115
|
-
id,
|
|
116
|
-
{
|
|
117
|
-
form,
|
|
118
|
-
formData: Object.fromEntries(formData),
|
|
119
|
-
params: Object.fromEntries(params),
|
|
120
|
-
},
|
|
121
|
-
debug,
|
|
122
|
-
() => action(context, formData, params),
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
if (result?.ok === false) {
|
|
126
|
-
const fieldErrors = Array.isArray(result.field_errors)
|
|
127
|
-
? result.field_errors
|
|
128
|
-
: [];
|
|
129
|
-
applyFieldErrors(form, fieldErrors);
|
|
130
|
-
applyErrorList(form, fieldErrors);
|
|
131
|
-
applyFormError(form, result.error);
|
|
132
|
-
if (!fieldErrors.length && result.error == null) {
|
|
133
|
-
console.warn(
|
|
134
|
-
`[gg-form-action] "${id}" returned ok:false with no error or field_errors`,
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
document.querySelectorAll("form[gg-form-action]").forEach(bindFieldClearOnInput);
|
|
141
|
-
|
|
142
|
-
document.addEventListener("submit", (e) => {
|
|
143
|
-
const form = e.target;
|
|
144
|
-
if (form?.tagName === "FORM" && form.hasAttribute("gg-form-action")) {
|
|
145
|
-
bindFieldClearOnInput(form);
|
|
146
|
-
handleSubmit(form, e);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
package/src/form-actions.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export const formActionRegistry = {};
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Register a form action triggered by submitting a form with gg-form-action="<id>".
|
|
5
|
-
*
|
|
6
|
-
* @param {string} id - The form action identifier, referenced by gg-form-action="<id>" on a form.
|
|
7
|
-
* @param {(context: object, formData: FormData, params: URLSearchParams) => Promise<{
|
|
8
|
-
* ok: boolean,
|
|
9
|
-
* error?: any,
|
|
10
|
-
* field_errors?: Array<{ name: string, message: string }>,
|
|
11
|
-
* }>} fn
|
|
12
|
-
* Receives the context object passed to init(), a FormData snapshot of the submitted form,
|
|
13
|
-
* and a URLSearchParams snapshot of the current URL query string. Return:
|
|
14
|
-
* - { ok: true } on success
|
|
15
|
-
* - { ok: false, field_errors: [{ name, message }, ...] } for validation errors
|
|
16
|
-
* - { ok: false, error: "..." } for a single form-level error
|
|
17
|
-
* - Both field_errors and error may be present together.
|
|
18
|
-
*/
|
|
19
|
-
export function registerFormAction(id, fn) {
|
|
20
|
-
formActionRegistry[id] = fn;
|
|
21
|
-
}
|
package/src/form-visibility.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
const TRANSITION_MS = 200;
|
|
2
|
-
|
|
3
|
-
function parseConditions(attr) {
|
|
4
|
-
return attr
|
|
5
|
-
.split(",")
|
|
6
|
-
.map((pair) => {
|
|
7
|
-
const [name, value] = pair.split(":");
|
|
8
|
-
return { name: name?.trim(), value: value?.trim() };
|
|
9
|
-
})
|
|
10
|
-
.filter((p) => p.name && p.value);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function getFieldValue(scope, name) {
|
|
14
|
-
const escaped = CSS.escape(name);
|
|
15
|
-
const inputs = scope.querySelectorAll(
|
|
16
|
-
`input[name="${escaped}"], select[name="${escaped}"], textarea[name="${escaped}"]`,
|
|
17
|
-
);
|
|
18
|
-
if (!inputs.length) return null;
|
|
19
|
-
|
|
20
|
-
const type = (inputs[0].getAttribute("type") || "").toLowerCase();
|
|
21
|
-
if (type === "radio" || type === "checkbox") {
|
|
22
|
-
for (const input of inputs) {
|
|
23
|
-
if (input.checked) return input.value;
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
return inputs[0].value.trim();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function matchesAny(scope, conditions) {
|
|
31
|
-
return conditions.some(
|
|
32
|
-
({ name, value }) => getFieldValue(scope, name) === value,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function initFormVisibility() {
|
|
37
|
-
// Group all [gg-visible-when] elements by their nearest scope —
|
|
38
|
-
// either an explicit [gg-form-scope] or a <form>.
|
|
39
|
-
const scopeTargets = new Map();
|
|
40
|
-
document.querySelectorAll("[gg-visible-when]").forEach((el) => {
|
|
41
|
-
if (!Boolean(el.getAttribute("gg-visible-when"))) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const scope = el.closest("[gg-form-scope], form");
|
|
45
|
-
if (!scope) {
|
|
46
|
-
console.warn(
|
|
47
|
-
"[gg-visible-when] element is not inside a <form> or [gg-form-scope]:",
|
|
48
|
-
el,
|
|
49
|
-
);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (!scopeTargets.has(scope)) scopeTargets.set(scope, []);
|
|
53
|
-
scopeTargets.get(scope).push(el);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
scopeTargets.forEach((targets, scope) => {
|
|
57
|
-
const conditions = new WeakMap();
|
|
58
|
-
const lastState = new WeakMap();
|
|
59
|
-
const hideTimers = new WeakMap();
|
|
60
|
-
|
|
61
|
-
targets.forEach((el) => {
|
|
62
|
-
conditions.set(
|
|
63
|
-
el,
|
|
64
|
-
parseConditions(el.getAttribute("gg-visible-when")),
|
|
65
|
-
);
|
|
66
|
-
el.style.transition = "none";
|
|
67
|
-
el.style.opacity = "0";
|
|
68
|
-
el.style.display = "none";
|
|
69
|
-
el.style.pointerEvents = "none";
|
|
70
|
-
el.setAttribute("inert", "");
|
|
71
|
-
el.setAttribute("aria-hidden", "true");
|
|
72
|
-
lastState.set(el, false);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
function show(el) {
|
|
76
|
-
const pending = hideTimers.get(el);
|
|
77
|
-
if (pending) {
|
|
78
|
-
clearTimeout(pending);
|
|
79
|
-
hideTimers.delete(el);
|
|
80
|
-
}
|
|
81
|
-
el.removeAttribute("inert");
|
|
82
|
-
el.removeAttribute("aria-hidden");
|
|
83
|
-
el.style.display = "";
|
|
84
|
-
el.style.pointerEvents = "";
|
|
85
|
-
requestAnimationFrame(() => {
|
|
86
|
-
el.style.opacity = "1";
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function hide(el) {
|
|
91
|
-
el.style.opacity = "0";
|
|
92
|
-
el.style.pointerEvents = "none";
|
|
93
|
-
el.setAttribute("inert", "");
|
|
94
|
-
el.setAttribute("aria-hidden", "true");
|
|
95
|
-
const t = setTimeout(() => {
|
|
96
|
-
el.style.display = "none";
|
|
97
|
-
hideTimers.delete(el);
|
|
98
|
-
}, TRANSITION_MS);
|
|
99
|
-
hideTimers.set(el, t);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function evaluate() {
|
|
103
|
-
targets.forEach((el) => {
|
|
104
|
-
const shouldShow = matchesAny(scope, conditions.get(el));
|
|
105
|
-
if (shouldShow === lastState.get(el)) return;
|
|
106
|
-
lastState.set(el, shouldShow);
|
|
107
|
-
if (shouldShow) show(el);
|
|
108
|
-
else hide(el);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
requestAnimationFrame(() => {
|
|
113
|
-
targets.forEach((el) => {
|
|
114
|
-
el.style.transition = `opacity ${TRANSITION_MS}ms ease`;
|
|
115
|
-
});
|
|
116
|
-
evaluate();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
scope.addEventListener("change", evaluate);
|
|
120
|
-
scope.addEventListener("input", evaluate);
|
|
121
|
-
});
|
|
122
|
-
}
|
package/src/helpers/dom.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { getPath } from "./path.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Set textContent on every [gg-field] descendant of `root` to the value
|
|
5
|
-
* at that field's dot-path on `record`. Null / missing values are left
|
|
6
|
-
* as-is (keeps whatever the markup's default content was).
|
|
7
|
-
*/
|
|
8
|
-
export function populateFields(root, record) {
|
|
9
|
-
root.querySelectorAll("[gg-field]").forEach((el) => {
|
|
10
|
-
const path = el.getAttribute("gg-field");
|
|
11
|
-
const value = getPath(record, path);
|
|
12
|
-
if (value != null) el.textContent = String(value);
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Write a normalized switch state onto an element.
|
|
18
|
-
* null / undefined become "" so gg-case="" can serve as the empty/default.
|
|
19
|
-
*/
|
|
20
|
-
export function setSwitchState(el, value) {
|
|
21
|
-
el.setAttribute("gg-switch-state", value == null ? "" : String(value));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Show the [gg-case] child whose value matches the container's current
|
|
26
|
-
* gg-switch-state; hide the rest with display:none.
|
|
27
|
-
*/
|
|
28
|
-
export function applySwitchState(container) {
|
|
29
|
-
const state = container.getAttribute("gg-switch-state") ?? "";
|
|
30
|
-
Array.from(container.children).forEach((child) => {
|
|
31
|
-
if (!child.hasAttribute("gg-case")) return;
|
|
32
|
-
const match = child.getAttribute("gg-case") === state;
|
|
33
|
-
child.style.display = match ? "" : "none";
|
|
34
|
-
});
|
|
35
|
-
}
|
package/src/helpers/log.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wrap an async handler with optional debug logging and uniform throw handling.
|
|
3
|
-
*
|
|
4
|
-
* On success, returns the handler's resolved value. On throw, logs the error and
|
|
5
|
-
* returns `undefined` so callers can branch on it.
|
|
6
|
-
*
|
|
7
|
-
* @param {string} prefix - Log prefix, e.g. "[gg-action]".
|
|
8
|
-
* @param {string} id - Handler identifier shown in the log group.
|
|
9
|
-
* @param {Record<string, any>} fields - Key/value pairs logged when debug is on.
|
|
10
|
-
* @param {boolean} debug - Whether to emit grouped debug logs.
|
|
11
|
-
* @param {() => Promise<any>} run - The handler invocation.
|
|
12
|
-
*/
|
|
13
|
-
export async function withDebugLog(prefix, id, fields, debug, run) {
|
|
14
|
-
if (debug) {
|
|
15
|
-
console.groupCollapsed(`${prefix} "${id}"`);
|
|
16
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
17
|
-
console.log(`${key}:`, value);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const startedAt = debug ? performance.now() : 0;
|
|
21
|
-
try {
|
|
22
|
-
const result = await run();
|
|
23
|
-
if (debug) {
|
|
24
|
-
const ms = (performance.now() - startedAt).toFixed(1);
|
|
25
|
-
console.log(`result (${ms}ms):`, result);
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
} catch (err) {
|
|
29
|
-
console.error(`${prefix} "${id}" threw:`, err);
|
|
30
|
-
return undefined;
|
|
31
|
-
} finally {
|
|
32
|
-
if (debug) console.groupEnd();
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/helpers/path.js
DELETED