bank20baht-ui 0.5.8 → 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 +25 -0
- package/dist/components/baht-form.js +472 -0
- package/dist/components/baht-workflow.js +331 -0
- package/dist/components/base.d.ts +6 -0
- package/dist/components/base.js +212 -0
- package/dist/components/motion.js +16 -0
- package/dist/components/primitives/baht-accordion.js +63 -0
- package/dist/components/primitives/baht-alert.js +59 -0
- package/dist/components/primitives/baht-avatar.js +42 -0
- package/dist/components/primitives/baht-badge.js +37 -0
- package/dist/components/primitives/baht-bar-chart.js +345 -0
- package/dist/components/primitives/baht-breadcrumbs.js +43 -0
- package/dist/components/primitives/baht-button.js +56 -0
- package/dist/components/primitives/baht-card.js +41 -0
- package/dist/components/primitives/baht-checkbox.js +38 -0
- package/dist/components/primitives/baht-code.js +32 -0
- package/dist/components/primitives/baht-currency.js +67 -0
- package/dist/components/primitives/baht-datepicker.js +189 -0
- package/dist/components/primitives/baht-donut-chart.js +86 -0
- package/dist/components/primitives/baht-empty-state.js +36 -0
- package/dist/components/primitives/baht-error-state.js +44 -0
- package/dist/components/primitives/baht-fieldset.js +41 -0
- package/dist/components/primitives/baht-file.js +149 -0
- package/dist/components/primitives/baht-filter.js +51 -0
- package/dist/components/primitives/baht-heatmap.js +145 -0
- package/dist/components/primitives/baht-input.js +38 -0
- package/dist/components/primitives/baht-keyvalue-editor.js +100 -0
- package/dist/components/primitives/baht-line-chart.js +146 -0
- package/dist/components/primitives/baht-link.js +39 -0
- package/dist/components/primitives/baht-list.js +76 -0
- package/dist/components/primitives/baht-loading-overlay.js +31 -0
- package/dist/components/primitives/baht-meter.js +99 -0
- package/dist/components/primitives/baht-modal.js +135 -0
- package/dist/components/primitives/baht-multiselect.js +45 -0
- package/dist/components/primitives/baht-number.js +39 -0
- package/dist/components/primitives/baht-otp.js +102 -0
- package/dist/components/primitives/baht-pagination.js +75 -0
- package/dist/components/primitives/baht-progress-ring.js +61 -0
- package/dist/components/primitives/baht-progress.js +47 -0
- package/dist/components/primitives/baht-radio.js +63 -0
- package/dist/components/primitives/baht-range.js +67 -0
- package/dist/components/primitives/baht-rating.js +80 -0
- package/dist/components/primitives/baht-reference-picker.js +193 -0
- package/dist/components/primitives/baht-repeater.js +142 -0
- package/dist/components/primitives/baht-section.js +24 -0
- package/dist/components/primitives/baht-select.js +181 -0
- package/dist/components/primitives/baht-skeleton.js +36 -0
- package/dist/components/primitives/baht-sparkline.js +55 -0
- package/dist/components/primitives/baht-spinner.js +29 -0
- package/dist/components/primitives/baht-stat-chart.js +37 -0
- package/dist/components/primitives/baht-stat.js +90 -0
- package/dist/components/primitives/baht-steps.js +48 -0
- package/dist/components/primitives/baht-table.js +528 -0
- package/dist/components/primitives/baht-text.js +24 -0
- package/dist/components/primitives/baht-textarea.js +38 -0
- package/dist/components/primitives/baht-timeline.js +41 -0
- package/dist/components/primitives/baht-toast.js +71 -0
- package/dist/components/primitives/baht-toggle.js +40 -0
- package/dist/components/primitives/baht-tooltip.js +65 -0
- package/dist/components/primitives/baht-typeahead.js +244 -0
- package/dist/components/primitives/baht-upload.js +289 -0
- package/dist/components/primitives/charts.js +176 -0
- package/dist/components/primitives/icons.js +114 -0
- package/dist/components/primitives/illustrations.js +78 -0
- package/dist/components/primitives/tag-map.js +21 -0
- package/dist/core/approval.js +9 -0
- package/dist/core/dates.js +74 -0
- package/dist/core/defaults.js +22 -0
- package/dist/core/display.js +45 -0
- package/dist/core/engine/conditions.js +71 -0
- package/dist/core/engine/evaluate.js +168 -0
- package/dist/core/engine/expressions.js +49 -0
- package/dist/core/engine/graph.js +41 -0
- package/dist/core/engine/scripts.js +69 -0
- package/dist/core/flags.js +34 -0
- package/dist/{chunks/i18n-CWv5lrun.js → core/i18n.js} +8 -8
- package/dist/core/legacy.js +44 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/core/validators.js +120 -0
- package/dist/core/views.js +11 -0
- package/dist/css/components.css +3694 -0
- package/dist/css/datepicker.css +344 -0
- package/dist/css/tokens.css +431 -0
- package/dist/custom-elements.json +369 -0
- package/dist/engine.js +42 -70
- package/dist/hydrate.js +6 -6
- package/dist/index.js +179 -109
- package/dist/overlay.js +20 -17
- package/dist/register.d.ts +76 -0
- package/dist/register.js +69 -0
- package/dist/ssr.js +84 -26
- package/dist/tokens.css +54 -6
- package/dist/vscode.html-custom-data.json +12 -12
- package/dist/web-types.json +13 -13
- package/package.json +24 -4
- package/dist/chunks/baht-toast-DkZc1diO.js +0 -448
- package/dist/chunks/baht-workflow-LtDlqYce.js +0 -5360
- package/dist/chunks/base-D1pT3J5T.js +0 -199
- package/dist/chunks/display-Cm3Pmp9U.js +0 -676
package/README.md
CHANGED
|
@@ -296,6 +296,31 @@ Styling API: `.baht-form`, `.baht-field`, `.baht-field--w-1-2`, `.baht-field--hi
|
|
|
296
296
|
`.baht-control`, `.baht-error`, `.baht-button`, `.baht-badge` (+ the token list in `tokens.css`).
|
|
297
297
|
Full reference: [docs/theming.md](./docs/theming.md).
|
|
298
298
|
|
|
299
|
+
## Import only what you use
|
|
300
|
+
|
|
301
|
+
`import 'bank20baht-ui'` registers every element (~230 KB min). Three lighter routes:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
// 1. per-element subpath — one module per tag, plus only what it imports
|
|
305
|
+
import 'bank20baht-ui/components/button'; // <baht-button>
|
|
306
|
+
import { BahtTable } from 'bank20baht-ui/components/table';
|
|
307
|
+
import 'bank20baht-ui/components/form'; // <baht-form> + the 14 field primitives
|
|
308
|
+
import 'bank20baht-ui/components/workflow';
|
|
309
|
+
|
|
310
|
+
// 2. by tag list — each tag is a dynamic import, fetched on demand
|
|
311
|
+
import { registerElements } from 'bank20baht-ui/register';
|
|
312
|
+
await registerElements(['baht-button', 'baht-avatar']);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```css
|
|
316
|
+
/* 3. split stylesheets instead of the all-in-one tokens.css (139 KB) */
|
|
317
|
+
@import 'bank20baht-ui/css/tokens.css'; /* custom properties only, 18 KB */
|
|
318
|
+
@import 'bank20baht-ui/css/components.css'; /* .baht-* classes */
|
|
319
|
+
@import 'bank20baht-ui/css/datepicker.css'; /* flatpickr base + re-skin — only with <baht-datepicker> / date fields */
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`bank20baht-ui/tokens.css` stays the one-import sheet (all three, flatpickr included).
|
|
323
|
+
|
|
299
324
|
## One install, typed everywhere
|
|
300
325
|
|
|
301
326
|
`npm i bank20baht-ui` ships the runtime **and** the metadata every editor/framework needs —
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { nothing as b, html as p } from "lit";
|
|
2
|
+
import { property as u, state as m, customElement as T } from "lit/decorators.js";
|
|
3
|
+
import { repeat as P } from "lit/directives/repeat.js";
|
|
4
|
+
import { html as w, unsafeStatic as I } from "lit/static-html.js";
|
|
5
|
+
import { BahtElement as B, hasDom as D } from "./base.js";
|
|
6
|
+
import { TAG_FOR_TYPE as A } from "./primitives/tag-map.js";
|
|
7
|
+
import { defaultValue as C } from "../core/defaults.js";
|
|
8
|
+
import { t as _ } from "../core/i18n.js";
|
|
9
|
+
import { validateField as M } from "../core/validators.js";
|
|
10
|
+
import { resolveViewFields as q } from "../core/views.js";
|
|
11
|
+
import { applyFlags as j } from "../core/flags.js";
|
|
12
|
+
import { hasScripts as k, runScripts as F, createScriptContext as L, MAX_SCRIPT_DEPTH as E } from "../core/engine/scripts.js";
|
|
13
|
+
import { displayValue as R } from "../core/display.js";
|
|
14
|
+
import "./primitives/baht-input.js";
|
|
15
|
+
import "./primitives/baht-textarea.js";
|
|
16
|
+
import "./primitives/baht-number.js";
|
|
17
|
+
import "./primitives/baht-currency.js";
|
|
18
|
+
import "./primitives/baht-select.js";
|
|
19
|
+
import "./primitives/baht-multiselect.js";
|
|
20
|
+
import "./primitives/baht-checkbox.js";
|
|
21
|
+
import "./primitives/baht-radio.js";
|
|
22
|
+
import "./primitives/baht-toggle.js";
|
|
23
|
+
import "./primitives/baht-datepicker.js";
|
|
24
|
+
import "./primitives/baht-file.js";
|
|
25
|
+
import "./primitives/baht-text.js";
|
|
26
|
+
import "./primitives/baht-section.js";
|
|
27
|
+
import "./primitives/baht-repeater.js";
|
|
28
|
+
var U = Object.defineProperty, G = Object.getOwnPropertyDescriptor, h = (t, e, s, i) => {
|
|
29
|
+
for (var r = i > 1 ? void 0 : i ? G(e, s) : e, o = t.length - 1, a; o >= 0; o--)
|
|
30
|
+
(a = t[o]) && (r = (i ? a(e, s, r) : a(r)) || r);
|
|
31
|
+
return i && r && U(e, s, r), r;
|
|
32
|
+
};
|
|
33
|
+
const H = {
|
|
34
|
+
"1/4": "baht-field--w-1-4",
|
|
35
|
+
"1/2": "baht-field--w-1-2",
|
|
36
|
+
"3/4": "baht-field--w-3-4",
|
|
37
|
+
full: "baht-field--w-full"
|
|
38
|
+
}, x = /* @__PURE__ */ new Map();
|
|
39
|
+
function W(t) {
|
|
40
|
+
let e = x.get(t);
|
|
41
|
+
return e || (e = I(t), x.set(t, e)), e;
|
|
42
|
+
}
|
|
43
|
+
let n = class extends B {
|
|
44
|
+
constructor() {
|
|
45
|
+
super(...arguments), this.value = {}, this.registry = {}, this.showOptional = !1, this.options = {}, this.fieldStates = {}, this.flags = {}, this.readonly = !1, this.edits = {}, this.errors = [], this.scriptStates = {}, this.fieldMessages = {}, this.formMessages = [], this.scriptContext = null, this.scriptContextForm = null, this.scriptDepth = 0, this.loadedForm = null, this.dynamicOptions = /* @__PURE__ */ new Map(), this.optionsSnapshots = /* @__PURE__ */ new Map(), this.optionsEpoch = /* @__PURE__ */ new Map(), this.warnedViews = /* @__PURE__ */ new Set(), this.flagCache = {};
|
|
46
|
+
}
|
|
47
|
+
get effectiveForm() {
|
|
48
|
+
return this.form ? ((this.flagCache.form !== this.form || this.flagCache.flags !== this.flags) && (this.flagCache = {
|
|
49
|
+
form: this.form,
|
|
50
|
+
flags: this.flags,
|
|
51
|
+
out: j(this.form, this.flags)
|
|
52
|
+
}), this.flagCache.out) : this.form;
|
|
53
|
+
}
|
|
54
|
+
willUpdate(t) {
|
|
55
|
+
t.has("value") && (this.edits = {}), (t.has("form") || t.has("flags")) && (this.dynamicOptions.clear(), this.optionsSnapshots.clear(), this.optionsEpoch.clear()), t.has("form") && (this.scriptStates = {}, this.fieldMessages = {}, this.formMessages = [], this.scriptContext = null), (t.has("form") || t.has("flags") || t.has("value") || t.has("registry")) && this.syncDynamicOptions();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Refetch `options_from` options whose dep snapshot changed. Sync fetchers
|
|
59
|
+
* land immediately; async ones re-render on resolve (stale responses are
|
|
60
|
+
* dropped by epoch).
|
|
61
|
+
*/
|
|
62
|
+
syncDynamicOptions() {
|
|
63
|
+
var e, s;
|
|
64
|
+
const t = this.getData();
|
|
65
|
+
for (const i of ((e = this.effectiveForm) == null ? void 0 : e.fields) ?? []) {
|
|
66
|
+
if (!i.options_from) continue;
|
|
67
|
+
const r = JSON.stringify((i.options_deps ?? []).map((c) => t[c] ?? null));
|
|
68
|
+
if (this.optionsSnapshots.get(i.key) === r) continue;
|
|
69
|
+
this.optionsSnapshots.set(i.key, r);
|
|
70
|
+
const o = (this.optionsEpoch.get(i.key) ?? 0) + 1;
|
|
71
|
+
this.optionsEpoch.set(i.key, o);
|
|
72
|
+
const a = (s = this.registry.options) == null ? void 0 : s[i.options_from];
|
|
73
|
+
if (!a) {
|
|
74
|
+
console.warn(`[bahtui] unknown options fetcher "${i.options_from}" — skipped`), this.dynamicOptions.set(i.key, []);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const l = a(t);
|
|
78
|
+
l instanceof Promise ? l.then((c) => {
|
|
79
|
+
this.optionsEpoch.get(i.key) === o && (this.dynamicOptions.set(i.key, c), this.requestUpdate());
|
|
80
|
+
}).catch((c) => {
|
|
81
|
+
console.warn(`[bahtui] options fetcher "${i.options_from}" failed`, c), this.optionsEpoch.get(i.key) === o && this.optionsSnapshots.delete(i.key);
|
|
82
|
+
}) : this.dynamicOptions.set(i.key, l);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A user edit invalidates every field whose options_deps include the edited
|
|
87
|
+
* key — their values reset to defaults, cascading down chains
|
|
88
|
+
* (province -> district -> subdistrict).
|
|
89
|
+
*/
|
|
90
|
+
clearDependents(t) {
|
|
91
|
+
var i, r;
|
|
92
|
+
const e = [t], s = /* @__PURE__ */ new Set();
|
|
93
|
+
for (; e.length > 0; ) {
|
|
94
|
+
const o = e.shift();
|
|
95
|
+
for (const a of ((i = this.effectiveForm) == null ? void 0 : i.fields) ?? [])
|
|
96
|
+
!((r = a.options_deps) != null && r.includes(o)) || s.has(a.key) || (s.add(a.key), this.edits = { ...this.edits, [a.key]: C(a) }, e.push(a.key));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
get sortedFields() {
|
|
100
|
+
if (!this.form) return [];
|
|
101
|
+
const t = q(this.effectiveForm, this.view);
|
|
102
|
+
return t || (this.view !== void 0 && !this.warnedViews.has(this.view) && (this.warnedViews.add(this.view), console.warn(`[bahtui] unknown view "${this.view}" — showing the full form`)), q(this.effectiveForm) ?? []);
|
|
103
|
+
}
|
|
104
|
+
stateFor(t) {
|
|
105
|
+
const e = this.fieldStates[t.key] ?? {
|
|
106
|
+
visible: !0,
|
|
107
|
+
enabled: !t.disabled,
|
|
108
|
+
readonly: !!t.readonly,
|
|
109
|
+
required: !!t.required,
|
|
110
|
+
computed: !1
|
|
111
|
+
}, s = this.scriptStates[t.key];
|
|
112
|
+
return s ? { ...e, ...s } : e;
|
|
113
|
+
}
|
|
114
|
+
valueFor(t) {
|
|
115
|
+
return Object.hasOwn(this.edits, t.key) ? this.edits[t.key] : Object.hasOwn(this.value, t.key) ? this.value[t.key] : C(t);
|
|
116
|
+
}
|
|
117
|
+
// ---- public API (spec 01 §6) ----
|
|
118
|
+
/** Debounced keystrokes still in a control land in `edits` before anyone reads them. */
|
|
119
|
+
flushTyped() {
|
|
120
|
+
var t;
|
|
121
|
+
if (D())
|
|
122
|
+
for (const e of this.querySelectorAll(":scope > .baht-form > .baht-field > *"))
|
|
123
|
+
(t = e.flushTyped) == null || t.call(e);
|
|
124
|
+
}
|
|
125
|
+
getData() {
|
|
126
|
+
var e;
|
|
127
|
+
this.flushTyped();
|
|
128
|
+
const t = {};
|
|
129
|
+
for (const s of ((e = this.effectiveForm) == null ? void 0 : e.fields) ?? [])
|
|
130
|
+
s.type === "label" || s.type === "section" || (t[s.key] = this.valueFor(s));
|
|
131
|
+
return t;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Merge values programmatically. Runs each key's `change` scripts (like
|
|
135
|
+
* ServiceNow's g_form.setValue) but emits no `baht-change` — the host
|
|
136
|
+
* already knows, and a store adapter would otherwise loop.
|
|
137
|
+
*/
|
|
138
|
+
setValue(t) {
|
|
139
|
+
const e = this.getData();
|
|
140
|
+
this.edits = { ...this.edits, ...t }, this.requestUpdate();
|
|
141
|
+
for (const s of Object.keys(t))
|
|
142
|
+
this.runChangeScripts(s, e[s], t[s], !1);
|
|
143
|
+
}
|
|
144
|
+
/** Declarative + registry validators over the visible fields (no scripts). */
|
|
145
|
+
runValidators() {
|
|
146
|
+
const t = [], e = this.getData();
|
|
147
|
+
for (const s of this.sortedFields) {
|
|
148
|
+
if (s.type === "label" || s.type === "section") continue;
|
|
149
|
+
const i = this.stateFor(s);
|
|
150
|
+
if (!i.visible) continue;
|
|
151
|
+
const r = i.required === !!s.required ? s : { ...s, required: i.required };
|
|
152
|
+
t.push(...M(r, this.valueFor(s), this.registry, e));
|
|
153
|
+
}
|
|
154
|
+
return t;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Full validation pass: validators, then `submit` scripts (spec 01 §14). A
|
|
158
|
+
* script returning `false` blocks; its `addErrorMessage()` calls become
|
|
159
|
+
* `{ key: '$' }` errors so hosts see why.
|
|
160
|
+
*/
|
|
161
|
+
validate() {
|
|
162
|
+
const t = this.runValidators(), e = this.effectiveForm;
|
|
163
|
+
if (e && k(e, "submit")) {
|
|
164
|
+
this.formMessages = [];
|
|
165
|
+
const i = F({
|
|
166
|
+
form: e,
|
|
167
|
+
registry: this.registry,
|
|
168
|
+
g: this.g(e),
|
|
169
|
+
on: "submit",
|
|
170
|
+
detail: { isLoading: !1, data: this.getData() }
|
|
171
|
+
}), r = this.formMessages.filter((o) => o.type === "error").map((o) => ({ key: "$", message: o.message }));
|
|
172
|
+
!i && r.length === 0 && r.push({ key: "$", message: _("scriptBlocked") }), t.push(...r);
|
|
173
|
+
}
|
|
174
|
+
this.errors = t;
|
|
175
|
+
const s = { valid: t.length === 0, errors: t };
|
|
176
|
+
return this.dispatchEvent(
|
|
177
|
+
new CustomEvent("baht-validity", { detail: s, bubbles: !0, composed: !0 })
|
|
178
|
+
), s;
|
|
179
|
+
}
|
|
180
|
+
reset() {
|
|
181
|
+
this.edits = {}, this.value = {}, this.errors = [], this.scriptStates = {}, this.fieldMessages = {}, this.formMessages = [], this.requestUpdate();
|
|
182
|
+
}
|
|
183
|
+
// ---- client scripts (spec 01 §14) ----
|
|
184
|
+
/** The `g` (g_form) for the current form — built lazily over this component's state. */
|
|
185
|
+
g(t) {
|
|
186
|
+
return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = L(t, {
|
|
187
|
+
getData: () => this.getData(),
|
|
188
|
+
setValue: (e, s) => this.applyScriptValue(e, s),
|
|
189
|
+
setState: (e, s) => {
|
|
190
|
+
this.scriptStates = {
|
|
191
|
+
...this.scriptStates,
|
|
192
|
+
[e]: { ...this.scriptStates[e], ...s }
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
resetState: (e) => {
|
|
196
|
+
if (e === void 0) {
|
|
197
|
+
this.scriptStates = {};
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const s = { ...this.scriptStates };
|
|
201
|
+
delete s[e], this.scriptStates = s;
|
|
202
|
+
},
|
|
203
|
+
setFieldMessage: (e, s) => {
|
|
204
|
+
const i = { ...this.fieldMessages };
|
|
205
|
+
s ? i[e] = s : delete i[e], this.fieldMessages = i;
|
|
206
|
+
},
|
|
207
|
+
addFormMessage: (e) => {
|
|
208
|
+
this.formMessages = [...this.formMessages, e];
|
|
209
|
+
},
|
|
210
|
+
clearFormMessages: () => {
|
|
211
|
+
this.formMessages = [];
|
|
212
|
+
}
|
|
213
|
+
}), this.scriptContext);
|
|
214
|
+
}
|
|
215
|
+
/** A script wrote a value: same path as a user edit (emit + cascade), minus the formatter. */
|
|
216
|
+
applyScriptValue(t, e) {
|
|
217
|
+
const s = this.getData()[t];
|
|
218
|
+
this.edits = { ...this.edits, [t]: e }, this.clearDependents(t), this.syncDynamicOptions(), this.dispatchEvent(
|
|
219
|
+
new CustomEvent("baht-change", {
|
|
220
|
+
detail: { form_name: this.form.form_name, key: t, value: e, data: this.getData() },
|
|
221
|
+
bubbles: !0,
|
|
222
|
+
composed: !0
|
|
223
|
+
})
|
|
224
|
+
), this.runChangeScripts(t, s, e, !1);
|
|
225
|
+
}
|
|
226
|
+
runChangeScripts(t, e, s, i) {
|
|
227
|
+
const r = this.effectiveForm;
|
|
228
|
+
if (!(!r || !k(r, "change", t))) {
|
|
229
|
+
if (this.scriptDepth >= E) {
|
|
230
|
+
console.warn(
|
|
231
|
+
`[bahtui] script cascade on "${r.form_name}.${t}" exceeded ${E} levels — stopped (setValue ping-pong between change scripts?)`
|
|
232
|
+
);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this.scriptDepth++;
|
|
236
|
+
try {
|
|
237
|
+
F({
|
|
238
|
+
form: r,
|
|
239
|
+
registry: this.registry,
|
|
240
|
+
g: this.g(r),
|
|
241
|
+
on: "change",
|
|
242
|
+
key: t,
|
|
243
|
+
detail: { oldValue: e, newValue: s, isLoading: i, data: this.getData() }
|
|
244
|
+
});
|
|
245
|
+
} finally {
|
|
246
|
+
this.scriptDepth--;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Form load (ServiceNow order): every field's `change` scripts with
|
|
252
|
+
* isLoading=true, then the `load` scripts. Once per `form` identity — a
|
|
253
|
+
* workflow reuses this element across steps, so each step loads.
|
|
254
|
+
*/
|
|
255
|
+
runLoadScripts() {
|
|
256
|
+
var s;
|
|
257
|
+
const t = this.effectiveForm;
|
|
258
|
+
if (!((s = t == null ? void 0 : t.scripts) != null && s.length)) return;
|
|
259
|
+
const e = this.getData();
|
|
260
|
+
for (const i of t.fields)
|
|
261
|
+
i.type === "label" || i.type === "section" || this.runChangeScripts(i.key, void 0, e[i.key], !0);
|
|
262
|
+
k(t, "load") && F({
|
|
263
|
+
form: t,
|
|
264
|
+
registry: this.registry,
|
|
265
|
+
g: this.g(t),
|
|
266
|
+
on: "load",
|
|
267
|
+
detail: { isLoading: !0, data: this.getData() }
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
updated(t) {
|
|
271
|
+
super.updated(t), D() && this.form && this.loadedForm !== this.form && (this.loadedForm = this.form, this.runLoadScripts());
|
|
272
|
+
}
|
|
273
|
+
// ---- internal ----
|
|
274
|
+
onFieldInput(t, e) {
|
|
275
|
+
var o;
|
|
276
|
+
e.stopPropagation();
|
|
277
|
+
let { value: s } = e.detail;
|
|
278
|
+
const i = t.formatter ? (o = this.registry.formatters) == null ? void 0 : o[t.formatter] : void 0;
|
|
279
|
+
t.formatter && !i && console.warn(`[bahtui] unknown formatter "${t.formatter}" — skipped`), i && (s = i(s));
|
|
280
|
+
const r = this.valueFor(t);
|
|
281
|
+
this.edits = { ...this.edits, [t.key]: s }, this.clearDependents(t.key), this.syncDynamicOptions(), (this.options.validateOn ?? "change") === "change" && this.errors.length > 0 && (this.errors = this.runValidators()), this.dispatchEvent(
|
|
282
|
+
new CustomEvent("baht-change", {
|
|
283
|
+
detail: { form_name: this.form.form_name, key: t.key, value: s, data: this.getData() },
|
|
284
|
+
bubbles: !0,
|
|
285
|
+
composed: !0
|
|
286
|
+
})
|
|
287
|
+
), this.runChangeScripts(t.key, r, s, !1);
|
|
288
|
+
}
|
|
289
|
+
/** Validate a single field against current form data (siblings included). */
|
|
290
|
+
validateOneField(t) {
|
|
291
|
+
const e = this.stateFor(t);
|
|
292
|
+
if (!e.visible) return [];
|
|
293
|
+
const s = this.getData(), i = e.required === !!t.required ? t : { ...t, required: e.required };
|
|
294
|
+
return M(i, this.valueFor(t), this.registry, s);
|
|
295
|
+
}
|
|
296
|
+
/** `validateOn: 'blur'` — validate just the field the user left, same as
|
|
297
|
+
* how errors are shown elsewhere (merged into `this.errors`, painted by
|
|
298
|
+
* `renderField`/`.baht-error`). No-op for 'change'/'submit' modes. */
|
|
299
|
+
onFieldBlur(t) {
|
|
300
|
+
if ((this.options.validateOn ?? "change") !== "blur") return;
|
|
301
|
+
const e = this.validateOneField(t), s = this.errors.filter((i) => i.key !== t.key);
|
|
302
|
+
this.errors = [...s, ...e];
|
|
303
|
+
}
|
|
304
|
+
renderField(t) {
|
|
305
|
+
var S, O;
|
|
306
|
+
const e = A[t.type];
|
|
307
|
+
if (!e)
|
|
308
|
+
return console.warn(`[bahtui] unknown field type "${t.type}" — skipped`), b;
|
|
309
|
+
const s = this.stateFor(t), i = H[t.width ?? "full"] ?? "baht-field--w-full", r = ((S = this.errors.find((d) => d.key === t.key)) == null ? void 0 : S.message) ?? null;
|
|
310
|
+
if (this.readonly && t.type !== "label" && t.type !== "section")
|
|
311
|
+
return this.renderReadonlyField(t, i, s.visible);
|
|
312
|
+
const o = t.options_from ? { ...t, options: this.dynamicOptions.get(t.key) ?? t.options ?? [] } : t, a = this.valueFor(t), l = !s.enabled, c = this.readonly || s.readonly, v = this.options.inputDebounce, $ = (d) => this.onControlInput(t.key, d), f = W(e);
|
|
313
|
+
let y;
|
|
314
|
+
if (t.type === "repeater") {
|
|
315
|
+
const d = this.errors.filter((V) => V.key.startsWith(`${t.key}[`));
|
|
316
|
+
y = w`<${f}
|
|
317
|
+
.field=${o}
|
|
318
|
+
.value=${a}
|
|
319
|
+
.disabled=${l}
|
|
320
|
+
.readonly=${c}
|
|
321
|
+
.required=${s.required}
|
|
322
|
+
.showOptional=${this.showOptional}
|
|
323
|
+
.error=${r}
|
|
324
|
+
.debounce=${v}
|
|
325
|
+
.rowErrors=${d}
|
|
326
|
+
@baht-input=${$}
|
|
327
|
+
></${f}>`;
|
|
328
|
+
} else if (t.type === "file") {
|
|
329
|
+
let d;
|
|
330
|
+
t.uploader && (d = (O = this.registry.uploaders) == null ? void 0 : O[t.uploader], d || console.warn(`[bahtui] unknown uploader "${t.uploader}" — capture only`)), y = w`<${f}
|
|
331
|
+
.field=${o}
|
|
332
|
+
.value=${a}
|
|
333
|
+
.disabled=${l}
|
|
334
|
+
.readonly=${c}
|
|
335
|
+
.required=${s.required}
|
|
336
|
+
.showOptional=${this.showOptional}
|
|
337
|
+
.error=${r}
|
|
338
|
+
.debounce=${v}
|
|
339
|
+
.uploader=${d}
|
|
340
|
+
@baht-input=${$}
|
|
341
|
+
></${f}>`;
|
|
342
|
+
} else
|
|
343
|
+
y = w`<${f}
|
|
344
|
+
.field=${o}
|
|
345
|
+
.value=${a}
|
|
346
|
+
.disabled=${l}
|
|
347
|
+
.readonly=${c}
|
|
348
|
+
.required=${s.required}
|
|
349
|
+
.showOptional=${this.showOptional}
|
|
350
|
+
.error=${r}
|
|
351
|
+
.debounce=${v}
|
|
352
|
+
@baht-input=${$}
|
|
353
|
+
></${f}>`;
|
|
354
|
+
const g = this.fieldMessages[t.key];
|
|
355
|
+
return p`<div
|
|
356
|
+
class="baht-field ${i} ${s.visible ? "" : "baht-field--hidden"}"
|
|
357
|
+
data-key=${t.key}
|
|
358
|
+
data-type=${t.type}
|
|
359
|
+
data-testid="${this.form.form_id}.${t.key}"
|
|
360
|
+
@focusout=${() => this.onFieldBlur(t)}
|
|
361
|
+
>
|
|
362
|
+
${y}
|
|
363
|
+
${g ? p`<div
|
|
364
|
+
class="baht-field-msg baht-field-msg--${g.type}"
|
|
365
|
+
role=${g.type === "error" ? "alert" : "status"}
|
|
366
|
+
>
|
|
367
|
+
${g.message}
|
|
368
|
+
</div>` : b}
|
|
369
|
+
</div>`;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Resolve the descriptor at event time — it may have changed since the control
|
|
373
|
+
* was first rendered (the same node is reused across renders, see render()).
|
|
374
|
+
*/
|
|
375
|
+
onControlInput(t, e) {
|
|
376
|
+
var i;
|
|
377
|
+
const s = (i = this.effectiveForm) == null ? void 0 : i.fields.find((r) => r.key === t);
|
|
378
|
+
s && this.onFieldInput(s, e);
|
|
379
|
+
}
|
|
380
|
+
renderReadonlyField(t, e, s) {
|
|
381
|
+
const i = t.options_from ? this.dynamicOptions.get(t.key) ?? t.options ?? [] : t.options, r = R(t, this.valueFor(t), i), o = r.kind === "empty" ? p`<span class="baht-readonly-value baht-readonly-value--empty">${_("emptyValue")}</span>` : r.kind === "text" ? p`<span class="baht-readonly-value">${r.text}</span>` : r.kind === "chips" ? p`<span class="baht-readonly-value baht-readonly-chips"
|
|
382
|
+
>${r.items.map((a) => p`<span class="baht-readonly-chip">${a}</span>`)}</span
|
|
383
|
+
>` : p`<div class="baht-readonly-rows">
|
|
384
|
+
${r.rows.map(
|
|
385
|
+
(a) => p`<div class="baht-readonly-row">
|
|
386
|
+
${a.map(
|
|
387
|
+
(l) => p`<span class="baht-readonly-cell"
|
|
388
|
+
><span class="baht-readonly-cell-label">${l.label}</span
|
|
389
|
+
><span class="baht-readonly-cell-value">${l.text}</span></span
|
|
390
|
+
>`
|
|
391
|
+
)}
|
|
392
|
+
</div>`
|
|
393
|
+
)}
|
|
394
|
+
</div>`;
|
|
395
|
+
return p`<div
|
|
396
|
+
class="baht-field ${e} baht-field--readonly ${s ? "" : "baht-field--hidden"}"
|
|
397
|
+
data-key=${t.key}
|
|
398
|
+
data-type=${t.type}
|
|
399
|
+
data-testid="${this.form.form_id}.${t.key}"
|
|
400
|
+
>
|
|
401
|
+
${t.label ? p`<span class="baht-label">${t.label}</span>` : b}
|
|
402
|
+
${o}
|
|
403
|
+
</div>`;
|
|
404
|
+
}
|
|
405
|
+
render() {
|
|
406
|
+
return p`<div class="baht-form ${this.readonly ? "baht-form--readonly" : ""}">
|
|
407
|
+
${this.formMessages.length > 0 ? p`<div class="baht-form-messages">
|
|
408
|
+
${this.formMessages.map(
|
|
409
|
+
(t) => p`<div
|
|
410
|
+
class="baht-form-msg baht-form-msg--${t.type}"
|
|
411
|
+
role=${t.type === "error" ? "alert" : "status"}
|
|
412
|
+
>
|
|
413
|
+
${t.message}
|
|
414
|
+
</div>`
|
|
415
|
+
)}
|
|
416
|
+
</div>` : b}
|
|
417
|
+
${P(
|
|
418
|
+
this.sortedFields,
|
|
419
|
+
(t) => t.key,
|
|
420
|
+
(t) => this.renderField(t)
|
|
421
|
+
)}
|
|
422
|
+
</div>`;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
h([
|
|
426
|
+
u({ attribute: !1 })
|
|
427
|
+
], n.prototype, "form", 2);
|
|
428
|
+
h([
|
|
429
|
+
u({ attribute: !1 })
|
|
430
|
+
], n.prototype, "value", 2);
|
|
431
|
+
h([
|
|
432
|
+
u({ attribute: !1 })
|
|
433
|
+
], n.prototype, "registry", 2);
|
|
434
|
+
h([
|
|
435
|
+
u({ type: Boolean, attribute: "show-optional" })
|
|
436
|
+
], n.prototype, "showOptional", 2);
|
|
437
|
+
h([
|
|
438
|
+
u({ attribute: !1 })
|
|
439
|
+
], n.prototype, "options", 2);
|
|
440
|
+
h([
|
|
441
|
+
u({ attribute: !1 })
|
|
442
|
+
], n.prototype, "fieldStates", 2);
|
|
443
|
+
h([
|
|
444
|
+
u({ type: String })
|
|
445
|
+
], n.prototype, "view", 2);
|
|
446
|
+
h([
|
|
447
|
+
u({ attribute: !1 })
|
|
448
|
+
], n.prototype, "flags", 2);
|
|
449
|
+
h([
|
|
450
|
+
u({ type: Boolean })
|
|
451
|
+
], n.prototype, "readonly", 2);
|
|
452
|
+
h([
|
|
453
|
+
m()
|
|
454
|
+
], n.prototype, "edits", 2);
|
|
455
|
+
h([
|
|
456
|
+
m()
|
|
457
|
+
], n.prototype, "errors", 2);
|
|
458
|
+
h([
|
|
459
|
+
m()
|
|
460
|
+
], n.prototype, "scriptStates", 2);
|
|
461
|
+
h([
|
|
462
|
+
m()
|
|
463
|
+
], n.prototype, "fieldMessages", 2);
|
|
464
|
+
h([
|
|
465
|
+
m()
|
|
466
|
+
], n.prototype, "formMessages", 2);
|
|
467
|
+
n = h([
|
|
468
|
+
T("baht-form")
|
|
469
|
+
], n);
|
|
470
|
+
export {
|
|
471
|
+
n as BahtForm
|
|
472
|
+
};
|