bank20baht-ui 0.8.0 → 0.8.2

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.
Files changed (78) hide show
  1. package/dist/components/baht-form.d.ts +17 -1
  2. package/dist/components/baht-form.js +178 -152
  3. package/dist/components/baht-workflow.d.ts +11 -0
  4. package/dist/components/baht-workflow.js +135 -101
  5. package/dist/components/base.d.ts +15 -1
  6. package/dist/components/base.js +52 -40
  7. package/dist/components/field-base.d.ts +7 -0
  8. package/dist/components/field-base.js +38 -23
  9. package/dist/components/primitives/baht-accordion.js +3 -1
  10. package/dist/components/primitives/baht-alert.js +3 -1
  11. package/dist/components/primitives/baht-bar-chart.d.ts +1 -12
  12. package/dist/components/primitives/baht-bar-chart.js +152 -137
  13. package/dist/components/primitives/baht-button.d.ts +13 -0
  14. package/dist/components/primitives/baht-button.js +13 -10
  15. package/dist/components/primitives/baht-checkbox.js +3 -1
  16. package/dist/components/primitives/baht-currency.js +20 -19
  17. package/dist/components/primitives/baht-datepicker.js +61 -55
  18. package/dist/components/primitives/baht-donut-chart.js +16 -15
  19. package/dist/components/primitives/baht-error-state.d.ts +1 -1
  20. package/dist/components/primitives/baht-error-state.js +3 -4
  21. package/dist/components/primitives/baht-file.js +9 -3
  22. package/dist/components/primitives/baht-filter.js +3 -1
  23. package/dist/components/primitives/baht-heatmap.js +61 -44
  24. package/dist/components/primitives/baht-input.js +11 -9
  25. package/dist/components/primitives/baht-keyvalue-editor.d.ts +1 -1
  26. package/dist/components/primitives/baht-keyvalue-editor.js +25 -20
  27. package/dist/components/primitives/baht-line-chart.js +70 -64
  28. package/dist/components/primitives/baht-list.js +3 -1
  29. package/dist/components/primitives/baht-meter.js +11 -12
  30. package/dist/components/primitives/baht-modal.js +19 -15
  31. package/dist/components/primitives/baht-multiselect.js +3 -1
  32. package/dist/components/primitives/baht-number.js +13 -13
  33. package/dist/components/primitives/baht-otp.js +9 -3
  34. package/dist/components/primitives/baht-pagination.js +15 -5
  35. package/dist/components/primitives/baht-radio.js +6 -2
  36. package/dist/components/primitives/baht-range.js +3 -1
  37. package/dist/components/primitives/baht-rating.js +12 -4
  38. package/dist/components/primitives/baht-reference-picker.d.ts +3 -2
  39. package/dist/components/primitives/baht-reference-picker.js +47 -34
  40. package/dist/components/primitives/baht-repeater.js +10 -3
  41. package/dist/components/primitives/baht-section.d.ts +2 -0
  42. package/dist/components/primitives/baht-section.js +15 -11
  43. package/dist/components/primitives/baht-select.js +28 -19
  44. package/dist/components/primitives/baht-sparkline.js +9 -9
  45. package/dist/components/primitives/baht-stat.js +4 -4
  46. package/dist/components/primitives/baht-table.d.ts +11 -11
  47. package/dist/components/primitives/baht-table.js +79 -45
  48. package/dist/components/primitives/baht-text.d.ts +2 -0
  49. package/dist/components/primitives/baht-text.js +21 -17
  50. package/dist/components/primitives/baht-textarea.js +10 -8
  51. package/dist/components/primitives/baht-toast.js +9 -3
  52. package/dist/components/primitives/baht-toggle.js +3 -1
  53. package/dist/components/primitives/baht-tooltip.d.ts +2 -2
  54. package/dist/components/primitives/baht-tooltip.js +8 -8
  55. package/dist/components/primitives/baht-typeahead.js +17 -7
  56. package/dist/components/primitives/baht-upload.d.ts +6 -3
  57. package/dist/components/primitives/baht-upload.js +116 -93
  58. package/dist/components/primitives/charts.js +34 -35
  59. package/dist/core/defaults.js +13 -3
  60. package/dist/core/display.d.ts +2 -2
  61. package/dist/core/display.js +17 -8
  62. package/dist/core/engine/conditions.js +51 -53
  63. package/dist/core/engine/evaluate.js +145 -136
  64. package/dist/core/engine/expressions.js +25 -22
  65. package/dist/core/engine/graph.d.ts +1 -1
  66. package/dist/core/engine/scripts.js +49 -36
  67. package/dist/core/flags.js +19 -16
  68. package/dist/core/types.d.ts +3 -3
  69. package/dist/core/validators.js +100 -98
  70. package/dist/css/components.css +57 -2
  71. package/dist/custom-elements.json +745 -185
  72. package/dist/hydrate.js +1 -1
  73. package/dist/overlay.js +33 -29
  74. package/dist/react.d.ts +4 -4
  75. package/dist/tokens.css +57 -2
  76. package/dist/vscode.html-custom-data.json +7 -2
  77. package/dist/web-types.json +17 -7
  78. package/package.json +4 -2
@@ -26,7 +26,7 @@ export declare class BahtForm extends BahtElement {
26
26
  * but getData() still returns the FULL form — the server always validates
27
27
  * the complete schema, views never change the submit payload.
28
28
  */
29
- view?: string;
29
+ view?: string | undefined;
30
30
  /**
31
31
  * Feature-flag values (spec 01 §12) — fields whose `flag` is off behave as
32
32
  * if they were never in the schema: no render, no validation, no payload.
@@ -58,6 +58,11 @@ export declare class BahtForm extends BahtElement {
58
58
  private warnedViews;
59
59
  /** applyFlags result, memoized on (form, flags) identity. */
60
60
  private flagCache;
61
+ /**
62
+ * `form` as the host may actually have left it: a framework can connect the element
63
+ * before it binds the property, so internally it is optional until then.
64
+ */
65
+ private get assignedForm();
61
66
  private get effectiveForm();
62
67
  protected willUpdate(changed: Map<PropertyKey, unknown>): void;
63
68
  /**
@@ -73,6 +78,15 @@ export declare class BahtForm extends BahtElement {
73
78
  */
74
79
  private clearDependents;
75
80
  private get sortedFields();
81
+ /**
82
+ * Visibility as of the last completed render, by key. A field that was
83
+ * hidden there and is visible now gets `baht-field--revealed` for one
84
+ * render so the stylesheet can fade it in; fields visible from the first
85
+ * render never animate (a form should not shimmer on mount).
86
+ */
87
+ private lastVisible;
88
+ private nextVisible;
89
+ private revealClass;
76
90
  private stateFor;
77
91
  private valueFor;
78
92
  /** Debounced keystrokes still in a control land in `edits` before anyone reads them. */
@@ -116,6 +130,8 @@ export declare class BahtForm extends BahtElement {
116
130
  * `renderField`/`.baht-error`). No-op for 'change'/'submit' modes. */
117
131
  private onFieldBlur;
118
132
  private renderField;
133
+ private uploaderFor;
134
+ private renderFieldMsg;
119
135
  /**
120
136
  * Resolve the descriptor at event time — it may have changed since the control
121
137
  * was first rendered (the same node is reused across renders, see render()).
@@ -1,16 +1,16 @@
1
- import { nothing as b, html as p } from "lit";
2
- import { property as u, state as m, customElement as V } from "lit/decorators.js";
3
- import { repeat as P } from "lit/directives/repeat.js";
4
- import { html as w } from "lit/static-html.js";
5
- import { BahtElement as T, hasDom as D } from "./base.js";
6
- import { TAG_FOR_TYPE as B, WIDTH_CLASS as I, staticTag as j } from "./primitives/tag-map.js";
1
+ import { nothing as g, html as p } from "lit";
2
+ import { property as d, state as m, customElement as x } from "lit/decorators.js";
3
+ import { repeat as E } from "lit/directives/repeat.js";
4
+ import { html as $ } from "lit/static-html.js";
5
+ import { BahtElement as P, hasDom as O } from "./base.js";
6
+ import { TAG_FOR_TYPE as T, WIDTH_CLASS as B, staticTag as I } from "./primitives/tag-map.js";
7
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 L } from "../core/flags.js";
12
- import { hasScripts as k, runScripts as F, createScriptContext as A, MAX_SCRIPT_DEPTH as E } from "../core/engine/scripts.js";
13
- import { displayValue as R } from "../core/display.js";
8
+ import { t as D } from "../core/i18n.js";
9
+ import { validateField as _ } from "../core/validators.js";
10
+ import { resolveViewFields as M } from "../core/views.js";
11
+ import { applyFlags as j } from "../core/flags.js";
12
+ import { hasScripts as w, runScripts as F, createScriptContext as L, MAX_SCRIPT_DEPTH as V } from "../core/engine/scripts.js";
13
+ import { displayValue as A } from "../core/display.js";
14
14
  import "./primitives/baht-input.js";
15
15
  import "./primitives/baht-textarea.js";
16
16
  import "./primitives/baht-number.js";
@@ -25,21 +25,26 @@ import "./primitives/baht-file.js";
25
25
  import "./primitives/baht-text.js";
26
26
  import "./primitives/baht-section.js";
27
27
  import "./primitives/baht-repeater.js";
28
- var U = Object.defineProperty, H = Object.getOwnPropertyDescriptor, h = (t, e, s, i) => {
29
- for (var r = i > 1 ? void 0 : i ? H(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;
28
+ var R = Object.defineProperty, U = Object.getOwnPropertyDescriptor, h = (t, s, e, i) => {
29
+ for (var r = i > 1 ? void 0 : i ? U(s, e) : s, o = t.length - 1, a; o >= 0; o--)
30
+ (a = t[o]) && (r = (i ? a(s, e, r) : a(r)) || r);
31
+ return i && r && R(s, e, r), r;
32
32
  };
33
- let n = class extends T {
33
+ let n = class extends P {
34
34
  constructor() {
35
- 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 = {};
35
+ 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 = {}, this.lastVisible = {}, this.nextVisible = {};
36
+ }
37
+ /**
38
+ * `form` as the host may actually have left it: a framework can connect the element
39
+ * before it binds the property, so internally it is optional until then.
40
+ */
41
+ get assignedForm() {
42
+ return this.form;
36
43
  }
37
44
  get effectiveForm() {
38
- return this.form ? ((this.flagCache.form !== this.form || this.flagCache.flags !== this.flags) && (this.flagCache = {
39
- form: this.form,
40
- flags: this.flags,
41
- out: L(this.form, this.flags)
42
- }), this.flagCache.out) : this.form;
45
+ const t = this.assignedForm;
46
+ if (t)
47
+ return (this.flagCache.form !== t || this.flagCache.flags !== this.flags) && (this.flagCache = { form: t, flags: this.flags, out: j(t, this.flags) }), this.flagCache.out;
43
48
  }
44
49
  willUpdate(t) {
45
50
  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();
@@ -50,16 +55,16 @@ let n = class extends T {
50
55
  * dropped by epoch).
51
56
  */
52
57
  syncDynamicOptions() {
53
- var e, s;
58
+ var s, e;
54
59
  const t = this.getData();
55
- for (const i of ((e = this.effectiveForm) == null ? void 0 : e.fields) ?? []) {
60
+ for (const i of ((s = this.effectiveForm) == null ? void 0 : s.fields) ?? []) {
56
61
  if (!i.options_from) continue;
57
62
  const r = JSON.stringify((i.options_deps ?? []).map((c) => t[c] ?? null));
58
63
  if (this.optionsSnapshots.get(i.key) === r) continue;
59
64
  this.optionsSnapshots.set(i.key, r);
60
65
  const o = (this.optionsEpoch.get(i.key) ?? 0) + 1;
61
66
  this.optionsEpoch.set(i.key, o);
62
- const a = (s = this.registry.options) == null ? void 0 : s[i.options_from];
67
+ const a = (e = this.registry.options) == null ? void 0 : e[i.options_from];
63
68
  if (!a) {
64
69
  console.warn(`[bahtui] unknown options fetcher "${i.options_from}" — skipped`), this.dynamicOptions.set(i.key, []);
65
70
  continue;
@@ -79,27 +84,31 @@ let n = class extends T {
79
84
  */
80
85
  clearDependents(t) {
81
86
  var i, r;
82
- const e = [t], s = /* @__PURE__ */ new Set();
83
- for (; e.length > 0; ) {
84
- const o = e.shift();
87
+ const s = [t], e = /* @__PURE__ */ new Set();
88
+ for (; s.length > 0; ) {
89
+ const o = s.shift();
85
90
  for (const a of ((i = this.effectiveForm) == null ? void 0 : i.fields) ?? [])
86
- !((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));
91
+ !((r = a.options_deps) != null && r.includes(o)) || e.has(a.key) || (e.add(a.key), this.edits = { ...this.edits, [a.key]: C(a) }, s.push(a.key));
87
92
  }
88
93
  }
89
94
  get sortedFields() {
90
- if (!this.form) return [];
91
- const t = q(this.effectiveForm, this.view);
92
- 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) ?? []);
95
+ const t = this.effectiveForm;
96
+ if (!t) return [];
97
+ const s = M(t, this.view);
98
+ return s || (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`)), M(t) ?? []);
99
+ }
100
+ revealClass(t, s) {
101
+ return this.nextVisible[t] = s, s && this.lastVisible[t] === !1 ? " baht-field--revealed" : "";
93
102
  }
94
103
  stateFor(t) {
95
- const e = this.fieldStates[t.key] ?? {
104
+ const s = this.fieldStates[t.key] ?? {
96
105
  visible: !0,
97
106
  enabled: !t.disabled,
98
107
  readonly: !!t.readonly,
99
108
  required: !!t.required,
100
109
  computed: !1
101
- }, s = this.scriptStates[t.key];
102
- return s ? { ...e, ...s } : e;
110
+ }, e = this.scriptStates[t.key];
111
+ return e ? { ...s, ...e } : s;
103
112
  }
104
113
  valueFor(t) {
105
114
  return Object.hasOwn(this.edits, t.key) ? this.edits[t.key] : Object.hasOwn(this.value, t.key) ? this.value[t.key] : C(t);
@@ -108,16 +117,16 @@ let n = class extends T {
108
117
  /** Debounced keystrokes still in a control land in `edits` before anyone reads them. */
109
118
  flushTyped() {
110
119
  var t;
111
- if (D())
112
- for (const e of this.querySelectorAll(":scope > .baht-form > .baht-field > *"))
113
- (t = e.flushTyped) == null || t.call(e);
120
+ if (O())
121
+ for (const s of this.querySelectorAll(":scope > .baht-form > .baht-field > *"))
122
+ (t = s.flushTyped) == null || t.call(s);
114
123
  }
115
124
  getData() {
116
- var e;
125
+ var s;
117
126
  this.flushTyped();
118
127
  const t = {};
119
- for (const s of ((e = this.effectiveForm) == null ? void 0 : e.fields) ?? [])
120
- s.type === "label" || s.type === "section" || (t[s.key] = this.valueFor(s));
128
+ for (const e of ((s = this.effectiveForm) == null ? void 0 : s.fields) ?? [])
129
+ e.type === "label" || e.type === "section" || (t[e.key] = this.valueFor(e));
121
130
  return t;
122
131
  }
123
132
  /**
@@ -126,20 +135,20 @@ let n = class extends T {
126
135
  * already knows, and a store adapter would otherwise loop.
127
136
  */
128
137
  setValue(t) {
129
- const e = this.getData();
138
+ const s = this.getData();
130
139
  this.edits = { ...this.edits, ...t }, this.requestUpdate();
131
- for (const s of Object.keys(t))
132
- this.runChangeScripts(s, e[s], t[s], !1);
140
+ for (const e of Object.keys(t))
141
+ this.runChangeScripts(e, s[e], t[e], !1);
133
142
  }
134
143
  /** Declarative + registry validators over the visible fields (no scripts). */
135
144
  runValidators() {
136
- const t = [], e = this.getData();
137
- for (const s of this.sortedFields) {
138
- if (s.type === "label" || s.type === "section") continue;
139
- const i = this.stateFor(s);
145
+ const t = [], s = this.getData();
146
+ for (const e of this.sortedFields) {
147
+ if (e.type === "label" || e.type === "section") continue;
148
+ const i = this.stateFor(e);
140
149
  if (!i.visible) continue;
141
- const r = i.required === !!s.required ? s : { ...s, required: i.required };
142
- t.push(...M(r, this.valueFor(s), this.registry, e));
150
+ const r = i.required === !!e.required ? e : { ...e, required: i.required };
151
+ t.push(..._(r, this.valueFor(e), this.registry, s));
143
152
  }
144
153
  return t;
145
154
  }
@@ -149,23 +158,23 @@ let n = class extends T {
149
158
  * `{ key: '$' }` errors so hosts see why.
150
159
  */
151
160
  validate() {
152
- const t = this.runValidators(), e = this.effectiveForm;
153
- if (e && k(e, "submit")) {
161
+ const t = this.runValidators(), s = this.effectiveForm;
162
+ if (s && w(s, "submit")) {
154
163
  this.formMessages = [];
155
164
  const i = F({
156
- form: e,
165
+ form: s,
157
166
  registry: this.registry,
158
- g: this.g(e),
167
+ g: this.g(s),
159
168
  on: "submit",
160
169
  detail: { isLoading: !1, data: this.getData() }
161
170
  }), r = this.formMessages.filter((o) => o.type === "error").map((o) => ({ key: "$", message: o.message }));
162
- !i && r.length === 0 && r.push({ key: "$", message: _("scriptBlocked") }), t.push(...r);
171
+ !i && r.length === 0 && r.push({ key: "$", message: D("scriptBlocked") }), t.push(...r);
163
172
  }
164
173
  this.errors = t;
165
- const s = { valid: t.length === 0, errors: t };
174
+ const e = { valid: t.length === 0, errors: t };
166
175
  return this.dispatchEvent(
167
- new CustomEvent("baht-validity", { detail: s, bubbles: !0, composed: !0 })
168
- ), s;
176
+ new CustomEvent("baht-validity", { detail: e, bubbles: !0, composed: !0 })
177
+ ), e;
169
178
  }
170
179
  reset() {
171
180
  this.edits = {}, this.value = {}, this.errors = [], this.scriptStates = {}, this.fieldMessages = {}, this.formMessages = [], this.requestUpdate();
@@ -173,29 +182,31 @@ let n = class extends T {
173
182
  // ---- client scripts (spec 01 §14) ----
174
183
  /** The `g` (g_form) for the current form — built lazily over this component's state. */
175
184
  g(t) {
176
- return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = A(t, {
185
+ return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = L(t, {
177
186
  getData: () => this.getData(),
178
- setValue: (e, s) => this.applyScriptValue(e, s),
179
- setState: (e, s) => {
187
+ setValue: (s, e) => {
188
+ this.applyScriptValue(s, e);
189
+ },
190
+ setState: (s, e) => {
180
191
  this.scriptStates = {
181
192
  ...this.scriptStates,
182
- [e]: { ...this.scriptStates[e], ...s }
193
+ [s]: { ...this.scriptStates[s], ...e }
183
194
  };
184
195
  },
185
- resetState: (e) => {
186
- if (e === void 0) {
196
+ resetState: (s) => {
197
+ if (s === void 0) {
187
198
  this.scriptStates = {};
188
199
  return;
189
200
  }
190
- const s = { ...this.scriptStates };
191
- delete s[e], this.scriptStates = s;
201
+ const { [s]: e, ...i } = this.scriptStates;
202
+ this.scriptStates = i;
192
203
  },
193
- setFieldMessage: (e, s) => {
194
- const i = { ...this.fieldMessages };
195
- s ? i[e] = s : delete i[e], this.fieldMessages = i;
204
+ setFieldMessage: (s, e) => {
205
+ const { [s]: i, ...r } = this.fieldMessages;
206
+ this.fieldMessages = e ? { ...r, [s]: e } : r;
196
207
  },
197
- addFormMessage: (e) => {
198
- this.formMessages = [...this.formMessages, e];
208
+ addFormMessage: (s) => {
209
+ this.formMessages = [...this.formMessages, s];
199
210
  },
200
211
  clearFormMessages: () => {
201
212
  this.formMessages = [];
@@ -203,22 +214,22 @@ let n = class extends T {
203
214
  }), this.scriptContext);
204
215
  }
205
216
  /** A script wrote a value: same path as a user edit (emit + cascade), minus the formatter. */
206
- applyScriptValue(t, e) {
207
- const s = this.getData()[t];
208
- this.edits = { ...this.edits, [t]: e }, this.clearDependents(t), this.syncDynamicOptions(), this.dispatchEvent(
217
+ applyScriptValue(t, s) {
218
+ const e = this.getData()[t];
219
+ this.edits = { ...this.edits, [t]: s }, this.clearDependents(t), this.syncDynamicOptions(), this.dispatchEvent(
209
220
  new CustomEvent("baht-change", {
210
- detail: { form_name: this.form.form_name, key: t, value: e, data: this.getData() },
221
+ detail: { form_name: this.form.form_name, key: t, value: s, data: this.getData() },
211
222
  bubbles: !0,
212
223
  composed: !0
213
224
  })
214
- ), this.runChangeScripts(t, s, e, !1);
225
+ ), this.runChangeScripts(t, e, s, !1);
215
226
  }
216
- runChangeScripts(t, e, s, i) {
227
+ runChangeScripts(t, s, e, i) {
217
228
  const r = this.effectiveForm;
218
- if (!(!r || !k(r, "change", t))) {
219
- if (this.scriptDepth >= E) {
229
+ if (!(!r || !w(r, "change", t))) {
230
+ if (this.scriptDepth >= V) {
220
231
  console.warn(
221
- `[bahtui] script cascade on "${r.form_name}.${t}" exceeded ${E} levels — stopped (setValue ping-pong between change scripts?)`
232
+ `[bahtui] script cascade on "${r.form_name}.${t}" exceeded ${V} levels — stopped (setValue ping-pong between change scripts?)`
222
233
  );
223
234
  return;
224
235
  }
@@ -230,7 +241,7 @@ let n = class extends T {
230
241
  g: this.g(r),
231
242
  on: "change",
232
243
  key: t,
233
- detail: { oldValue: e, newValue: s, isLoading: i, data: this.getData() }
244
+ detail: { oldValue: s, newValue: e, isLoading: i, data: this.getData() }
234
245
  });
235
246
  } finally {
236
247
  this.scriptDepth--;
@@ -243,13 +254,13 @@ let n = class extends T {
243
254
  * workflow reuses this element across steps, so each step loads.
244
255
  */
245
256
  runLoadScripts() {
246
- var s;
257
+ var e;
247
258
  const t = this.effectiveForm;
248
- if (!((s = t == null ? void 0 : t.scripts) != null && s.length)) return;
249
- const e = this.getData();
259
+ if (!((e = t == null ? void 0 : t.scripts) != null && e.length)) return;
260
+ const s = this.getData();
250
261
  for (const i of t.fields)
251
- i.type === "label" || i.type === "section" || this.runChangeScripts(i.key, void 0, e[i.key], !0);
252
- k(t, "load") && F({
262
+ i.type === "label" || i.type === "section" || this.runChangeScripts(i.key, void 0, s[i.key], !0);
263
+ w(t, "load") && F({
253
264
  form: t,
254
265
  registry: this.registry,
255
266
  g: this.g(t),
@@ -258,117 +269,132 @@ let n = class extends T {
258
269
  });
259
270
  }
260
271
  updated(t) {
261
- super.updated(t), D() && this.form && this.loadedForm !== this.form && (this.loadedForm = this.form, this.runLoadScripts());
272
+ if (super.updated(t), this.lastVisible = this.nextVisible, this.nextVisible = {}, !O()) return;
273
+ const s = this.assignedForm;
274
+ s && this.loadedForm !== s && (this.loadedForm = s, this.runLoadScripts());
262
275
  }
263
276
  // ---- internal ----
264
- onFieldInput(t, e) {
277
+ onFieldInput(t, s) {
265
278
  var o;
266
- e.stopPropagation();
267
- let { value: s } = e.detail;
279
+ s.stopPropagation();
280
+ let { value: e } = s.detail;
268
281
  const i = t.formatter ? (o = this.registry.formatters) == null ? void 0 : o[t.formatter] : void 0;
269
- t.formatter && !i && console.warn(`[bahtui] unknown formatter "${t.formatter}" — skipped`), i && (s = i(s));
282
+ t.formatter && !i && console.warn(`[bahtui] unknown formatter "${t.formatter}" — skipped`), i && (e = i(e));
270
283
  const r = this.valueFor(t);
271
- 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(
284
+ this.edits = { ...this.edits, [t.key]: e }, this.clearDependents(t.key), this.syncDynamicOptions(), (this.options.validateOn ?? "change") === "change" && this.errors.length > 0 && (this.errors = this.runValidators()), this.dispatchEvent(
272
285
  new CustomEvent("baht-change", {
273
- detail: { form_name: this.form.form_name, key: t.key, value: s, data: this.getData() },
286
+ detail: { form_name: this.form.form_name, key: t.key, value: e, data: this.getData() },
274
287
  bubbles: !0,
275
288
  composed: !0
276
289
  })
277
- ), this.runChangeScripts(t.key, r, s, !1);
290
+ ), this.runChangeScripts(t.key, r, e, !1);
278
291
  }
279
292
  /** Validate a single field against current form data (siblings included). */
280
293
  validateOneField(t) {
281
- const e = this.stateFor(t);
282
- if (!e.visible) return [];
283
- const s = this.getData(), i = e.required === !!t.required ? t : { ...t, required: e.required };
284
- return M(i, this.valueFor(t), this.registry, s);
294
+ const s = this.stateFor(t);
295
+ if (!s.visible) return [];
296
+ const e = this.getData(), i = s.required === !!t.required ? t : { ...t, required: s.required };
297
+ return _(i, this.valueFor(t), this.registry, e);
285
298
  }
286
299
  /** `validateOn: 'blur'` — validate just the field the user left, same as
287
300
  * how errors are shown elsewhere (merged into `this.errors`, painted by
288
301
  * `renderField`/`.baht-error`). No-op for 'change'/'submit' modes. */
289
302
  onFieldBlur(t) {
290
303
  if ((this.options.validateOn ?? "change") !== "blur") return;
291
- const e = this.validateOneField(t), s = this.errors.filter((i) => i.key !== t.key);
292
- this.errors = [...s, ...e];
304
+ const s = this.validateOneField(t), e = this.errors.filter((i) => i.key !== t.key);
305
+ this.errors = [...e, ...s];
293
306
  }
294
307
  renderField(t) {
295
- var S, O;
296
- const e = B[t.type];
297
- if (!e)
298
- return console.warn(`[bahtui] unknown field type "${t.type}" — skipped`), b;
299
- const s = this.stateFor(t), i = I[t.width ?? "full"] ?? "baht-field--w-full", r = ((S = this.errors.find((d) => d.key === t.key)) == null ? void 0 : S.message) ?? null;
308
+ var S;
309
+ const s = T[t.type];
310
+ if (!s)
311
+ return console.warn(`[bahtui] unknown field type "${t.type}" — skipped`), g;
312
+ const e = this.stateFor(t), i = B[t.width ?? "full"] ?? "baht-field--w-full", r = ((S = this.errors.find((u) => u.key === t.key)) == null ? void 0 : S.message) ?? null;
300
313
  if (this.readonly && t.type !== "label" && t.type !== "section")
301
- return this.renderReadonlyField(t, i, s.visible);
302
- 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 = j(e);
314
+ return this.renderReadonlyField(t, i, e.visible);
315
+ const o = t.options_from ? { ...t, options: this.dynamicOptions.get(t.key) ?? t.options ?? [] } : t, a = this.valueFor(t), l = !e.enabled, c = this.readonly || e.readonly, b = this.options.inputDebounce, v = (u) => {
316
+ this.onControlInput(t.key, u);
317
+ }, f = I(s);
303
318
  let y;
304
319
  if (t.type === "repeater") {
305
- const d = this.errors.filter((x) => x.key.startsWith(`${t.key}[`));
306
- y = w`<${f}
320
+ const u = this.errors.filter((q) => q.key.startsWith(`${t.key}[`));
321
+ y = $`<${f}
307
322
  .field=${o}
308
323
  .value=${a}
309
324
  .disabled=${l}
310
325
  .readonly=${c}
311
- .required=${s.required}
326
+ .required=${e.required}
312
327
  .showOptional=${this.showOptional}
313
328
  .error=${r}
314
- .debounce=${v}
315
- .rowErrors=${d}
316
- @baht-input=${$}
329
+ .debounce=${b}
330
+ .rowErrors=${u}
331
+ @baht-input=${v}
317
332
  ></${f}>`;
318
333
  } else if (t.type === "file") {
319
- let d;
320
- 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}
334
+ const u = this.uploaderFor(t);
335
+ y = $`<${f}
321
336
  .field=${o}
322
337
  .value=${a}
323
338
  .disabled=${l}
324
339
  .readonly=${c}
325
- .required=${s.required}
340
+ .required=${e.required}
326
341
  .showOptional=${this.showOptional}
327
342
  .error=${r}
328
- .debounce=${v}
329
- .uploader=${d}
330
- @baht-input=${$}
343
+ .debounce=${b}
344
+ .uploader=${u}
345
+ @baht-input=${v}
331
346
  ></${f}>`;
332
347
  } else
333
- y = w`<${f}
348
+ y = $`<${f}
334
349
  .field=${o}
335
350
  .value=${a}
336
351
  .disabled=${l}
337
352
  .readonly=${c}
338
- .required=${s.required}
353
+ .required=${e.required}
339
354
  .showOptional=${this.showOptional}
340
355
  .error=${r}
341
- .debounce=${v}
342
- @baht-input=${$}
356
+ .debounce=${b}
357
+ @baht-input=${v}
343
358
  ></${f}>`;
344
- const g = this.fieldMessages[t.key];
359
+ const k = this.fieldMessages[t.key];
345
360
  return p`<div
346
- class="baht-field ${i} ${s.visible ? "" : "baht-field--hidden"}"
361
+ class="baht-field ${i} ${e.visible ? "" : "baht-field--hidden"}${this.revealClass(t.key, e.visible)}"
347
362
  data-key=${t.key}
348
363
  data-type=${t.type}
349
364
  data-testid="${this.form.form_id}.${t.key}"
350
- @focusout=${() => this.onFieldBlur(t)}
365
+ @focusout=${() => {
366
+ this.onFieldBlur(t);
367
+ }}
351
368
  >
352
369
  ${y}
353
- ${g ? p`<div
354
- class="baht-field-msg baht-field-msg--${g.type}"
355
- role=${g.type === "error" ? "alert" : "status"}
356
- >
357
- ${g.message}
358
- </div>` : b}
370
+ ${k ? this.renderFieldMsg(k) : g}
371
+ </div>`;
372
+ }
373
+ uploaderFor(t) {
374
+ var e;
375
+ if (!t.uploader) return;
376
+ const s = (e = this.registry.uploaders) == null ? void 0 : e[t.uploader];
377
+ return s || console.warn(`[bahtui] unknown uploader "${t.uploader}" — capture only`), s;
378
+ }
379
+ renderFieldMsg(t) {
380
+ return p`<div
381
+ class="baht-field-msg baht-field-msg--${t.type}"
382
+ role=${t.type === "error" ? "alert" : "status"}
383
+ >
384
+ ${t.message}
359
385
  </div>`;
360
386
  }
361
387
  /**
362
388
  * Resolve the descriptor at event time — it may have changed since the control
363
389
  * was first rendered (the same node is reused across renders, see render()).
364
390
  */
365
- onControlInput(t, e) {
391
+ onControlInput(t, s) {
366
392
  var i;
367
- const s = (i = this.effectiveForm) == null ? void 0 : i.fields.find((r) => r.key === t);
368
- s && this.onFieldInput(s, e);
393
+ const e = (i = this.effectiveForm) == null ? void 0 : i.fields.find((r) => r.key === t);
394
+ e && this.onFieldInput(e, s);
369
395
  }
370
- renderReadonlyField(t, e, s) {
371
- 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"
396
+ renderReadonlyField(t, s, e) {
397
+ const i = t.options_from ? this.dynamicOptions.get(t.key) ?? t.options ?? [] : t.options, r = A(t, this.valueFor(t), i), o = r.kind === "empty" ? p`<span class="baht-readonly-value baht-readonly-value--empty">${D("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"
372
398
  >${r.items.map((a) => p`<span class="baht-readonly-chip">${a}</span>`)}</span
373
399
  >` : p`<div class="baht-readonly-rows">
374
400
  ${r.rows.map(
@@ -383,12 +409,12 @@ let n = class extends T {
383
409
  )}
384
410
  </div>`;
385
411
  return p`<div
386
- class="baht-field ${e} baht-field--readonly ${s ? "" : "baht-field--hidden"}"
412
+ class="baht-field ${s} baht-field--readonly ${e ? "" : "baht-field--hidden"}${this.revealClass(t.key, e)}"
387
413
  data-key=${t.key}
388
414
  data-type=${t.type}
389
415
  data-testid="${this.form.form_id}.${t.key}"
390
416
  >
391
- ${t.label ? p`<span class="baht-label">${t.label}</span>` : b}
417
+ ${t.label ? p`<span class="baht-label">${t.label}</span>` : g}
392
418
  ${o}
393
419
  </div>`;
394
420
  }
@@ -403,8 +429,8 @@ let n = class extends T {
403
429
  ${t.message}
404
430
  </div>`
405
431
  )}
406
- </div>` : b}
407
- ${P(
432
+ </div>` : g}
433
+ ${E(
408
434
  this.sortedFields,
409
435
  (t) => t.key,
410
436
  (t) => this.renderField(t)
@@ -413,31 +439,31 @@ let n = class extends T {
413
439
  }
414
440
  };
415
441
  h([
416
- u({ attribute: !1 })
442
+ d({ attribute: !1 })
417
443
  ], n.prototype, "form", 2);
418
444
  h([
419
- u({ attribute: !1 })
445
+ d({ attribute: !1 })
420
446
  ], n.prototype, "value", 2);
421
447
  h([
422
- u({ attribute: !1 })
448
+ d({ attribute: !1 })
423
449
  ], n.prototype, "registry", 2);
424
450
  h([
425
- u({ type: Boolean, attribute: "show-optional" })
451
+ d({ type: Boolean, attribute: "show-optional" })
426
452
  ], n.prototype, "showOptional", 2);
427
453
  h([
428
- u({ attribute: !1 })
454
+ d({ attribute: !1 })
429
455
  ], n.prototype, "options", 2);
430
456
  h([
431
- u({ attribute: !1 })
457
+ d({ attribute: !1 })
432
458
  ], n.prototype, "fieldStates", 2);
433
459
  h([
434
- u({ type: String })
460
+ d({ type: String })
435
461
  ], n.prototype, "view", 2);
436
462
  h([
437
- u({ attribute: !1 })
463
+ d({ attribute: !1 })
438
464
  ], n.prototype, "flags", 2);
439
465
  h([
440
- u({ type: Boolean })
466
+ d({ type: Boolean })
441
467
  ], n.prototype, "readonly", 2);
442
468
  h([
443
469
  m()
@@ -455,7 +481,7 @@ h([
455
481
  m()
456
482
  ], n.prototype, "formMessages", 2);
457
483
  n = h([
458
- V("baht-form")
484
+ x("baht-form")
459
485
  ], n);
460
486
  export {
461
487
  n as BahtForm
@@ -21,6 +21,12 @@ export declare class BahtWorkflow extends BahtElement {
21
21
  submitLabel?: string;
22
22
  backLabel?: string;
23
23
  showBack: boolean;
24
+ /**
25
+ * Which way the last navigation went. Stamped on the step wrapper as
26
+ * `baht-workflow-step--forward|--back` so the stylesheet can slide the new
27
+ * step in from the matching side; null on first render (nothing to replay).
28
+ */
29
+ private direction;
24
30
  /** Feature-flag values (spec 01 §12): flag-off steps/fields/rules vanish. */
25
31
  flags: FeatureFlags;
26
32
  /** Render a <baht-steps> progress indicator above the current form. */
@@ -35,6 +41,11 @@ export declare class BahtWorkflow extends BahtElement {
35
41
  get currentFormName(): string | null;
36
42
  /** applyFlags result, memoized on (workflow, flags) identity. */
37
43
  private flagCache;
44
+ /**
45
+ * `workflow` as the host may actually have left it: a framework can connect the
46
+ * element before it binds the property, so internally it is optional until then.
47
+ */
48
+ private get assignedWorkflow();
38
49
  private get effectiveWorkflow();
39
50
  protected willUpdate(changed: Map<PropertyKey, unknown>): void;
40
51
  private reevaluate;