bank20baht-ui 0.7.3 → 0.8.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.
Files changed (61) hide show
  1. package/README.md +13 -3
  2. package/dist/components/baht-form.js +16 -26
  3. package/dist/components/baht-workflow.js +21 -21
  4. package/dist/components/base.d.ts +12 -70
  5. package/dist/components/base.js +51 -173
  6. package/dist/components/field-base.d.ts +71 -0
  7. package/dist/components/field-base.js +130 -0
  8. package/dist/components/primitives/baht-checkbox.d.ts +1 -1
  9. package/dist/components/primitives/baht-checkbox.js +1 -1
  10. package/dist/components/primitives/baht-currency.d.ts +1 -1
  11. package/dist/components/primitives/baht-currency.js +1 -1
  12. package/dist/components/primitives/baht-datepicker.d.ts +7 -2
  13. package/dist/components/primitives/baht-datepicker.js +69 -52
  14. package/dist/components/primitives/baht-file.d.ts +1 -1
  15. package/dist/components/primitives/baht-file.js +1 -1
  16. package/dist/components/primitives/baht-filter.js +7 -7
  17. package/dist/components/primitives/baht-heatmap.d.ts +13 -5
  18. package/dist/components/primitives/baht-heatmap.js +98 -93
  19. package/dist/components/primitives/baht-input.d.ts +1 -1
  20. package/dist/components/primitives/baht-input.js +1 -1
  21. package/dist/components/primitives/baht-list.js +22 -22
  22. package/dist/components/primitives/baht-modal.js +28 -28
  23. package/dist/components/primitives/baht-multiselect.d.ts +1 -1
  24. package/dist/components/primitives/baht-multiselect.js +1 -1
  25. package/dist/components/primitives/baht-number.d.ts +1 -1
  26. package/dist/components/primitives/baht-number.js +1 -1
  27. package/dist/components/primitives/baht-radio.d.ts +1 -1
  28. package/dist/components/primitives/baht-radio.js +1 -1
  29. package/dist/components/primitives/baht-range.js +17 -17
  30. package/dist/components/primitives/baht-repeater.d.ts +5 -3
  31. package/dist/components/primitives/baht-repeater.js +57 -67
  32. package/dist/components/primitives/baht-select.d.ts +1 -1
  33. package/dist/components/primitives/baht-select.js +2 -1
  34. package/dist/components/primitives/baht-sparkline.js +21 -21
  35. package/dist/components/primitives/baht-stat.js +8 -8
  36. package/dist/components/primitives/baht-table.d.ts +58 -0
  37. package/dist/components/primitives/baht-table.js +295 -207
  38. package/dist/components/primitives/baht-textarea.d.ts +1 -1
  39. package/dist/components/primitives/baht-textarea.js +1 -1
  40. package/dist/components/primitives/baht-toggle.d.ts +1 -1
  41. package/dist/components/primitives/baht-toggle.js +1 -1
  42. package/dist/components/primitives/baht-typeahead.js +6 -6
  43. package/dist/components/primitives/charts.js +19 -19
  44. package/dist/components/primitives/index.d.ts +1 -1
  45. package/dist/components/primitives/tag-map.d.ts +4 -0
  46. package/dist/components/primitives/tag-map.js +15 -3
  47. package/dist/core/dates.d.ts +0 -1
  48. package/dist/core/dates.js +34 -35
  49. package/dist/core/display.js +8 -6
  50. package/dist/core/validators.d.ts +0 -8
  51. package/dist/core/validators.js +44 -36
  52. package/dist/css/components.css +128 -31
  53. package/dist/css/tokens.css +3 -1
  54. package/dist/custom-elements.json +895 -457
  55. package/dist/index.d.ts +2 -1
  56. package/dist/index.js +12 -11
  57. package/dist/ssr.js +5 -3
  58. package/dist/tokens.css +132 -379
  59. package/dist/vscode.html-custom-data.json +23 -3
  60. package/dist/web-types.json +49 -4
  61. package/package.json +4 -3
package/README.md CHANGED
@@ -5,6 +5,7 @@ Components (Lit, light DOM) with a pure-TypeScript rules core.
5
5
 
6
6
  ```
7
7
  npm i bank20baht-ui
8
+ npm i flatpickr # peer dependency — only if you render date fields / <baht-datepicker>
8
9
  ```
9
10
 
10
11
  ## Quick start — one form (Level 1)
@@ -313,13 +314,22 @@ await registerElements(['baht-button', 'baht-avatar']);
313
314
  ```
314
315
 
315
316
  ```css
316
- /* 3. split stylesheets instead of the all-in-one tokens.css (139 KB) */
317
+ /* 3. split stylesheets instead of the all-in-one tokens.css (~115 KB) */
317
318
  @import 'bank20baht-ui/css/tokens.css'; /* custom properties only, 18 KB */
318
319
  @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
320
  ```
321
321
 
322
- `bank20baht-ui/tokens.css` stays the one-import sheet (all three, flatpickr included).
322
+ `bank20baht-ui/tokens.css` = the two sheets above in one import. The calendar is opt-in:
323
+
324
+ ```css
325
+ /* with <baht-datepicker> / date fields — flatpickr's base sheet + the token re-skin */
326
+ @import 'bank20baht-ui/css/datepicker.css';
327
+ ```
328
+
329
+ flatpickr itself is a **peer dependency** (`npm i flatpickr`) loaded with a dynamic
330
+ `import()` the first time a `<baht-datepicker>` mounts, so apps without date fields never
331
+ bundle it and apps with them get it as its own chunk. `await el.updateComplete` on the
332
+ element resolves once the calendar is attached.
323
333
 
324
334
  ## One install, typed everywhere
325
335
 
@@ -1,15 +1,15 @@
1
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";
2
+ import { property as u, state as m, customElement as V } from "lit/decorators.js";
3
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";
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";
7
7
  import { defaultValue as C } from "../core/defaults.js";
8
8
  import { t as _ } from "../core/i18n.js";
9
9
  import { validateField as M } from "../core/validators.js";
10
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";
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
13
  import { displayValue as R } from "../core/display.js";
14
14
  import "./primitives/baht-input.js";
15
15
  import "./primitives/baht-textarea.js";
@@ -25,22 +25,12 @@ 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, 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--)
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
30
  (a = t[o]) && (r = (i ? a(e, s, r) : a(r)) || r);
31
31
  return i && r && U(e, s, r), r;
32
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 {
33
+ let n = class extends T {
44
34
  constructor() {
45
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 = {};
46
36
  }
@@ -48,7 +38,7 @@ let n = class extends B {
48
38
  return this.form ? ((this.flagCache.form !== this.form || this.flagCache.flags !== this.flags) && (this.flagCache = {
49
39
  form: this.form,
50
40
  flags: this.flags,
51
- out: j(this.form, this.flags)
41
+ out: L(this.form, this.flags)
52
42
  }), this.flagCache.out) : this.form;
53
43
  }
54
44
  willUpdate(t) {
@@ -183,7 +173,7 @@ let n = class extends B {
183
173
  // ---- client scripts (spec 01 §14) ----
184
174
  /** The `g` (g_form) for the current form — built lazily over this component's state. */
185
175
  g(t) {
186
- return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = L(t, {
176
+ return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = A(t, {
187
177
  getData: () => this.getData(),
188
178
  setValue: (e, s) => this.applyScriptValue(e, s),
189
179
  setState: (e, s) => {
@@ -303,16 +293,16 @@ let n = class extends B {
303
293
  }
304
294
  renderField(t) {
305
295
  var S, O;
306
- const e = A[t.type];
296
+ const e = B[t.type];
307
297
  if (!e)
308
298
  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;
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;
310
300
  if (this.readonly && t.type !== "label" && t.type !== "section")
311
301
  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);
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);
313
303
  let y;
314
304
  if (t.type === "repeater") {
315
- const d = this.errors.filter((V) => V.key.startsWith(`${t.key}[`));
305
+ const d = this.errors.filter((x) => x.key.startsWith(`${t.key}[`));
316
306
  y = w`<${f}
317
307
  .field=${o}
318
308
  .value=${a}
@@ -465,7 +455,7 @@ h([
465
455
  m()
466
456
  ], n.prototype, "formMessages", 2);
467
457
  n = h([
468
- T("baht-form")
458
+ V("baht-form")
469
459
  ], n);
470
460
  export {
471
461
  n as BahtForm
@@ -1,18 +1,18 @@
1
1
  import { nothing as d, html as l } from "lit";
2
- import { property as m, state as p, customElement as b } from "lit/decorators.js";
3
- import { BahtElement as w } from "./base.js";
2
+ import { property as m, state as c, customElement as w } from "lit/decorators.js";
3
+ import { boolOff as v, BahtElement as g } from "./base.js";
4
4
  import { t as f } from "../core/i18n.js";
5
- import { applyFlags as g } from "../core/flags.js";
6
- import { evaluate as _ } from "../core/engine/evaluate.js";
7
- import { resolveNext as k } from "../core/engine/graph.js";
5
+ import { applyFlags as _ } from "../core/flags.js";
6
+ import { evaluate as k } from "../core/engine/evaluate.js";
7
+ import { resolveNext as $ } from "../core/engine/graph.js";
8
8
  import "./baht-form.js";
9
9
  import "./primitives/baht-steps.js";
10
- var y = Object.defineProperty, $ = Object.getOwnPropertyDescriptor, n = (t, e, a, r) => {
11
- for (var i = r > 1 ? void 0 : r ? $(e, a) : e, o = t.length - 1, h; o >= 0; o--)
10
+ var y = Object.defineProperty, F = Object.getOwnPropertyDescriptor, n = (t, e, a, r) => {
11
+ for (var i = r > 1 ? void 0 : r ? F(e, a) : e, o = t.length - 1, h; o >= 0; o--)
12
12
  (h = t[o]) && (i = (r ? h(e, a, i) : h(i)) || i);
13
13
  return r && i && y(e, a, i), i;
14
14
  };
15
- let s = class extends w {
15
+ let s = class extends g {
16
16
  constructor() {
17
17
  super(...arguments), this.value = {}, this.registry = {}, this.validateGate = !0, this.showBack = !0, this.flags = {}, this.showSteps = !1, this.stepsVariant = "default", this.data = {}, this.derived = null, this.current = null, this.approvalError = null, this.history = [], this.flagCache = {};
18
18
  }
@@ -23,15 +23,15 @@ let s = class extends w {
23
23
  return this.workflow ? ((this.flagCache.workflow !== this.workflow || this.flagCache.flags !== this.flags) && (this.flagCache = {
24
24
  workflow: this.workflow,
25
25
  flags: this.flags,
26
- out: g(this.workflow, this.flags)
26
+ out: _(this.workflow, this.flags)
27
27
  }), this.flagCache.out) : this.workflow;
28
28
  }
29
29
  willUpdate(t) {
30
30
  var e, a;
31
- t.has("workflow") || t.has("value") ? (this.data = structuredClone(this.value ?? {}), this.reevaluate(), this.history = [], this.current = ((e = this.visibleForms[0]) == null ? void 0 : e.form_name) ?? null, this.approvalError = null) : t.has("flags") && (this.reevaluate(), this.current && !this.visibleForms.some((r) => r.form_name === this.current) && (this.current = ((a = this.visibleForms[0]) == null ? void 0 : a.form_name) ?? null));
31
+ this.workflow && (t.has("workflow") || t.has("value") ? (this.data = structuredClone(this.value ?? {}), this.reevaluate(), this.history = [], this.current = ((e = this.visibleForms[0]) == null ? void 0 : e.form_name) ?? null, this.approvalError = null) : t.has("flags") && (this.reevaluate(), this.current && !this.visibleForms.some((r) => r.form_name === this.current) && (this.current = ((a = this.visibleForms[0]) == null ? void 0 : a.form_name) ?? null)));
32
32
  }
33
33
  reevaluate() {
34
- this.derived = _(this.effectiveWorkflow, this.data), this.data = this.derived.data;
34
+ this.derived = k(this.effectiveWorkflow, this.data), this.data = this.derived.data;
35
35
  }
36
36
  /** Forms in ascending order whose derived visibility is true. */
37
37
  get visibleForms() {
@@ -51,7 +51,7 @@ let s = class extends w {
51
51
  if (!this.current) return "end";
52
52
  const t = this.effectiveWorkflow;
53
53
  if (t.transitions && t.transitions.length > 0)
54
- return k(t, this.current, this.data);
54
+ return $(t, this.current, this.data);
55
55
  const e = this.visibleForms, a = e.findIndex((i) => i.form_name === this.current);
56
56
  return ((r = e[a + 1]) == null ? void 0 : r.form_name) ?? "end";
57
57
  }
@@ -146,8 +146,8 @@ let s = class extends w {
146
146
  return;
147
147
  }
148
148
  this.emit("baht-reject", h);
149
- const c = a.on_reject ?? { action: "end" };
150
- c.action === "goto" && this.visibleForms.some((v) => v.form_name === c.form) ? this.gotoForm(c.form) : this.emit("baht-complete", { data: this.getData(), status: "rejected" });
149
+ const p = a.on_reject ?? { action: "end" };
150
+ p.action === "goto" && this.visibleForms.some((b) => b.form_name === p.form) ? this.gotoForm(p.form) : this.emit("baht-complete", { data: this.getData(), status: "rejected" });
151
151
  }
152
152
  /** Jump back to a visible form, truncating the history to that point. */
153
153
  gotoForm(t) {
@@ -288,7 +288,7 @@ n([
288
288
  m({ attribute: !1 })
289
289
  ], s.prototype, "registry", 2);
290
290
  n([
291
- m({ type: Boolean, attribute: "validate-gate" })
291
+ m({ attribute: "validate-gate", converter: v })
292
292
  ], s.prototype, "validateGate", 2);
293
293
  n([
294
294
  m({ attribute: "next-label" })
@@ -300,7 +300,7 @@ n([
300
300
  m({ attribute: "back-label" })
301
301
  ], s.prototype, "backLabel", 2);
302
302
  n([
303
- m({ type: Boolean, attribute: "show-back" })
303
+ m({ attribute: "show-back", converter: v })
304
304
  ], s.prototype, "showBack", 2);
305
305
  n([
306
306
  m({ attribute: !1 })
@@ -312,19 +312,19 @@ n([
312
312
  m({ attribute: "steps-variant" })
313
313
  ], s.prototype, "stepsVariant", 2);
314
314
  n([
315
- p()
315
+ c()
316
316
  ], s.prototype, "data", 2);
317
317
  n([
318
- p()
318
+ c()
319
319
  ], s.prototype, "derived", 2);
320
320
  n([
321
- p()
321
+ c()
322
322
  ], s.prototype, "current", 2);
323
323
  n([
324
- p()
324
+ c()
325
325
  ], s.prototype, "approvalError", 2);
326
326
  s = n([
327
- b("baht-workflow")
327
+ w("baht-workflow")
328
328
  ], s);
329
329
  export {
330
330
  s as BahtWorkflow
@@ -1,5 +1,4 @@
1
- import { LitElement, nothing, PropertyValues, RenderOptions, TemplateResult } from 'lit';
2
- import { FieldDescriptor } from '../core/types.js';
1
+ import { LitElement, PropertyValues, RenderOptions } from 'lit';
3
2
  /**
4
3
  * True when a real DOM is present. Lit's own `isServer` is hard-coded to `true` in its
5
4
  * node build, which jsdom/happy-dom test runners also load, so guards that need "is there
@@ -7,6 +6,17 @@ import { FieldDescriptor } from '../core/types.js';
7
6
  * `querySelector`.
8
7
  */
9
8
  export declare function hasDom(): boolean;
9
+ /**
10
+ * Attribute converter for booleans that DEFAULT TO TRUE. Lit's own `type: Boolean` reads
11
+ * `show-close="false"` as true (presence is all it checks), so a default-true switch could
12
+ * never be turned off from HTML. With this: absent = default, bare = true, `="false"` = false.
13
+ *
14
+ * @property({ attribute: 'show-close', converter: boolOff }) showClose = true;
15
+ */
16
+ export declare const boolOff: {
17
+ fromAttribute: (value: string | null) => boolean;
18
+ toAttribute: (value: boolean) => string | null;
19
+ };
10
20
  /** Signature of the light-DOM hydrate function installed by `bank20baht-ui/hydrate`. */
11
21
  export type BahtHydrate = (value: unknown, host: HTMLElement, options?: RenderOptions) => void;
12
22
  /**
@@ -43,71 +53,3 @@ export declare class BahtElement extends LitElement {
43
53
  * connectedCallback fires, so sweeping at first render is the reliable point.)
44
54
  */
45
55
  export declare function adoptLightChildren(host: HTMLElement, slot: HTMLElement): void;
46
- /**
47
- * Shared shape of every field primitive: descriptor + value in, `baht-input` out.
48
- * Dumb and controlled — never mutates its own `value` prop; the host re-feeds it.
49
- *
50
- * `controlId` carries the base class `uid`: the same field key renders once per repeater
51
- * row (and can repeat across forms on one page), so key-only ids collide — worst for
52
- * radios, whose shared native `name` merges groups.
53
- */
54
- export declare abstract class BahtFieldBase extends BahtElement {
55
- /**
56
- * Default trailing debounce (ms) for TYPED edits — text, email, password,
57
- * textarea, number. Every keystroke otherwise re-runs the host's rules,
58
- * validation and dependent-option fetches. Per element: the `debounce`
59
- * property/attribute; `<baht-form options.inputDebounce>` sets it for a whole
60
- * form. 0 = emit on every keystroke. Selects, checkboxes, radios, toggles,
61
- * dates and files are never debounced — a pick is one event.
62
- */
63
- static defaultDebounce: number;
64
- field: FieldDescriptor;
65
- value: unknown;
66
- disabled: boolean;
67
- readonly: boolean;
68
- required: boolean;
69
- /** Append the localized "(optional)" marker to the label when not required (set by <baht-form show-optional>). */
70
- showOptional: boolean;
71
- error: string | null;
72
- /** Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce. */
73
- debounce?: number;
74
- /**
75
- * The keystrokes not yet (or just) handed to the host. The control keeps
76
- * rendering `typed.value` until the host's `value` moves, so a re-render
77
- * mid-typing (a sibling field changing) can't snap the text back.
78
- */
79
- private typed;
80
- private typedTimer;
81
- private readonly onFocusOut;
82
- connectedCallback(): void;
83
- disconnectedCallback(): void;
84
- protected willUpdate(changed: PropertyValues<this>): void;
85
- protected get effectiveDebounce(): number;
86
- /** A typed edit: emitted after `debounce` ms of quiet (or at once when 0). */
87
- protected emitTyped(value: unknown): void;
88
- /** Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent. */
89
- flushTyped(): void;
90
- /** What a typed control shows: pending keystrokes win over a stale host value. */
91
- protected get shownValue(): unknown;
92
- /** Emit the user's edit upward (composed so it crosses any boundary). */
93
- protected emitInput(value: unknown): void;
94
- protected abstract renderControl(): TemplateResult;
95
- /** True when `field.icon` names a real icon — gates both the icon markup
96
- * and the control's icon-padding class, so a typo/removed name never
97
- * leaves the control indented for a glyph that isn't there. */
98
- protected get hasFieldIcon(): boolean;
99
- /** Leading icon for text/currency/textarea/dropdown controls (spec: field.icon). */
100
- protected renderFieldIcon(): TemplateResult | typeof nothing;
101
- protected renderLabel(): TemplateResult | typeof nothing;
102
- /** Required asterisk, or the "(optional)" marker when the form asks for it. */
103
- protected renderLabelMark(): TemplateResult | typeof nothing;
104
- protected get controlId(): string;
105
- protected get labelId(): string;
106
- protected get errorId(): string;
107
- protected get helpId(): string;
108
- /** id list for the control's aria-describedby: error first, then help. */
109
- protected get describedBy(): string | typeof nothing;
110
- /** 'true' while the field is in error, else absent (styles hook on [aria-invalid]). */
111
- protected get invalid(): string | typeof nothing;
112
- protected render(): TemplateResult | typeof nothing;
113
- }
@@ -1,37 +1,33 @@
1
- import { nothing as o, html as a, LitElement as y, ReactiveElement as m } from "lit";
2
- import { property as l } from "lit/decorators.js";
3
- import { t as v } from "../core/i18n.js";
4
- import { isIconName as c, renderIcon as C } from "./primitives/icons.js";
5
- var I = Object.defineProperty, d = (i, e, t, s) => {
6
- for (var r = void 0, u = i.length - 1, p; u >= 0; u--)
7
- (p = i[u]) && (r = p(e, t, r) || r);
8
- return r && I(e, t, r), r;
9
- };
10
- function f() {
1
+ import { LitElement as d, ReactiveElement as u } from "lit";
2
+ function n() {
11
3
  return typeof document < "u" && typeof document.querySelector == "function";
12
4
  }
13
- const T = Symbol.for("bahtui.ssrStash");
14
- function $(i) {
15
- if (i[T]) return !0;
16
- const e = i.firstChild;
17
- return (e == null ? void 0 : e.nodeType) === 8 && e.data.startsWith("lit-part");
5
+ const f = {
6
+ fromAttribute: (r) => r !== "false",
7
+ // null (absent) = default
8
+ toAttribute: (r) => r ? "" : "false"
9
+ }, h = Symbol.for("bahtui.ssrStash");
10
+ function l(r) {
11
+ if (r[h]) return !0;
12
+ const t = r.firstChild;
13
+ return (t == null ? void 0 : t.nodeType) === 8 && t.data.startsWith("lit-part");
18
14
  }
19
- function g(i) {
20
- let e = i.firstChild;
21
- if (!e || e.nodeType !== 8 || !e.data.startsWith("lit-part")) return;
22
- let t = 0;
23
- for (; e; ) {
24
- const s = e.nextSibling;
25
- if (e.nodeType === 8) {
26
- const r = e.data;
27
- r.startsWith("lit-part") ? t += 1 : r.startsWith("/lit-part") && (t -= 1);
15
+ function p(r) {
16
+ let t = r.firstChild;
17
+ if (!t || t.nodeType !== 8 || !t.data.startsWith("lit-part")) return;
18
+ let e = 0;
19
+ for (; t; ) {
20
+ const i = t.nextSibling;
21
+ if (t.nodeType === 8) {
22
+ const s = t.data;
23
+ s.startsWith("lit-part") ? e += 1 : s.startsWith("/lit-part") && (e -= 1);
28
24
  }
29
- if (e.remove(), t === 0) break;
30
- e = s;
25
+ if (t.remove(), e === 0) break;
26
+ t = i;
31
27
  }
32
28
  }
33
- let b = 0;
34
- class k extends y {
29
+ let a = 0;
30
+ class b extends d {
35
31
  constructor() {
36
32
  super(...arguments), this.uid = 0;
37
33
  }
@@ -49,164 +45,46 @@ class k extends y {
49
45
  static get observedAttributes() {
50
46
  return [...super.observedAttributes, "defer-hydration"];
51
47
  }
52
- attributeChangedCallback(e, t, s) {
53
- if (e === "defer-hydration") {
54
- s === null && this.isConnected && super.connectedCallback();
48
+ attributeChangedCallback(t, e, i) {
49
+ if (t === "defer-hydration") {
50
+ i === null && this.isConnected && super.connectedCallback();
55
51
  return;
56
52
  }
57
- super.attributeChangedCallback(e, t, s);
53
+ super.attributeChangedCallback(t, e, i);
58
54
  }
59
55
  connectedCallback() {
60
56
  this.hasAttribute("defer-hydration") || super.connectedCallback();
61
57
  }
62
- update(e) {
63
- const t = !this.hasUpdated, s = globalThis.bahtHydrate, r = t && f() && $(this);
64
- if (r)
65
- if (s) {
66
- const u = this.render();
67
- this.renderOptions.isConnected = this.isConnected, m.prototype.update.call(this, e), s(u, this, this.renderOptions);
58
+ update(t) {
59
+ const e = !this.hasUpdated, i = globalThis.bahtHydrate, s = e && n() && l(this);
60
+ if (s)
61
+ if (i) {
62
+ const o = this.render();
63
+ this.renderOptions.isConnected = this.isConnected, u.prototype.update.call(this, t), i(o, this, this.renderOptions);
68
64
  } else
69
- g(this), super.update(e);
65
+ p(this), super.update(t);
70
66
  else
71
- t && f() && this.uid === 0 && (this.uid = ++b), super.update(e);
72
- r && this.uid === 0 && (this.uid = ++b, this.requestUpdate());
67
+ e && n() && this.uid === 0 && (this.uid = ++a), super.update(t);
68
+ s && this.uid === 0 && (this.uid = ++a, this.requestUpdate());
73
69
  }
74
70
  /** Dispatch a bubbling, composed CustomEvent — crosses any light/shadow DOM boundary. */
75
- emit(e, t) {
76
- this.dispatchEvent(new CustomEvent(e, { detail: t, bubbles: !0, composed: !0 }));
71
+ emit(t, e) {
72
+ this.dispatchEvent(new CustomEvent(t, { detail: e, bubbles: !0, composed: !0 }));
77
73
  }
78
74
  }
79
- function E(i, e) {
80
- let t = e;
81
- for (; t.parentNode && t.parentNode !== i; ) t = t.parentNode;
82
- const s = Array.from(i.childNodes).filter(
83
- (r) => r !== t && r.nodeType !== Node.COMMENT_NODE
75
+ function y(r, t) {
76
+ let e = t;
77
+ for (; e.parentNode && e.parentNode !== r; ) e = e.parentNode;
78
+ const i = Array.from(r.childNodes).filter(
79
+ (s) => s !== e && s.nodeType !== Node.COMMENT_NODE
84
80
  );
85
- s.length > 0 && e.append(...s);
81
+ i.length > 0 && t.append(...i);
86
82
  }
87
- var h;
88
- const n = (h = class extends k {
89
- constructor() {
90
- super(...arguments), this.value = null, this.disabled = !1, this.readonly = !1, this.required = !1, this.showOptional = !1, this.error = null, this.typed = null, this.onFocusOut = () => this.flushTyped();
91
- }
92
- connectedCallback() {
93
- super.connectedCallback(), this.addEventListener("focusout", this.onFocusOut);
94
- }
95
- disconnectedCallback() {
96
- this.removeEventListener("focusout", this.onFocusOut), this.flushTyped(), super.disconnectedCallback();
97
- }
98
- willUpdate(e) {
99
- var t;
100
- super.willUpdate(e), e.has("value") && ((t = this.typed) != null && t.emitted) && (this.typed = null);
101
- }
102
- get effectiveDebounce() {
103
- return this.debounce ?? h.defaultDebounce;
104
- }
105
- /** A typed edit: emitted after `debounce` ms of quiet (or at once when 0). */
106
- emitTyped(e) {
107
- const t = this.effectiveDebounce;
108
- if (clearTimeout(this.typedTimer), t <= 0) {
109
- this.typed = null, this.emitInput(e);
110
- return;
111
- }
112
- this.typed = { value: e, emitted: !1 }, this.typedTimer = setTimeout(() => this.flushTyped(), t);
113
- }
114
- /** Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent. */
115
- flushTyped() {
116
- clearTimeout(this.typedTimer), this.typedTimer = void 0, !(!this.typed || this.typed.emitted) && (this.typed.emitted = !0, this.emitInput(this.typed.value));
117
- }
118
- /** What a typed control shows: pending keystrokes win over a stale host value. */
119
- get shownValue() {
120
- return this.typed ? this.typed.value : this.value;
121
- }
122
- /** Emit the user's edit upward (composed so it crosses any boundary). */
123
- emitInput(e) {
124
- this.emit("baht-input", { key: this.field.key, value: e });
125
- }
126
- /** True when `field.icon` names a real icon — gates both the icon markup
127
- * and the control's icon-padding class, so a typo/removed name never
128
- * leaves the control indented for a glyph that isn't there. */
129
- get hasFieldIcon() {
130
- const e = this.field.icon;
131
- return e != null && c(e);
132
- }
133
- /** Leading icon for text/currency/textarea/dropdown controls (spec: field.icon). */
134
- renderFieldIcon() {
135
- const e = this.field.icon;
136
- return e == null || !c(e) ? o : a`<span class="baht-field-icon" aria-hidden="true">${C(e)}</span>`;
137
- }
138
- renderLabel() {
139
- return this.field.label ? a`<label class="baht-label" id=${this.labelId} for=${this.controlId}>
140
- ${this.field.label}${this.renderLabelMark()}
141
- </label>` : o;
142
- }
143
- /** Required asterisk, or the "(optional)" marker when the form asks for it. */
144
- renderLabelMark() {
145
- return this.required ? a`<span class="baht-required-mark" aria-hidden="true">*</span>` : this.showOptional ? a`<span class="baht-optional">${v("optional")}</span>` : o;
146
- }
147
- get controlId() {
148
- var e;
149
- return `baht-${((e = this.field) == null ? void 0 : e.key) ?? ""}-${this.uid}`;
150
- }
151
- get labelId() {
152
- return `${this.controlId}-label`;
153
- }
154
- get errorId() {
155
- return `${this.controlId}-error`;
156
- }
157
- get helpId() {
158
- return `${this.controlId}-help`;
159
- }
160
- /** id list for the control's aria-describedby: error first, then help. */
161
- get describedBy() {
162
- const e = [this.error ? this.errorId : "", this.field.help ? this.helpId : ""].filter(
163
- Boolean
164
- );
165
- return e.length > 0 ? e.join(" ") : o;
166
- }
167
- /** 'true' while the field is in error, else absent (styles hook on [aria-invalid]). */
168
- get invalid() {
169
- return this.error ? "true" : o;
170
- }
171
- render() {
172
- return this.field ? a`
173
- ${this.renderLabel()}
174
- ${this.renderControl()}
175
- ${this.field.help ? a`<div class="baht-help" id=${this.helpId}>${this.field.help}</div>` : o}
176
- ${this.error ? a`<div class="baht-error" id=${this.errorId} role="alert">${this.error}</div>` : o}
177
- ` : o;
178
- }
179
- }, h.defaultDebounce = 150, h);
180
- d([
181
- l({ attribute: !1 })
182
- ], n.prototype, "field");
183
- d([
184
- l({ attribute: !1 })
185
- ], n.prototype, "value");
186
- d([
187
- l({ type: Boolean })
188
- ], n.prototype, "disabled");
189
- d([
190
- l({ type: Boolean })
191
- ], n.prototype, "readonly");
192
- d([
193
- l({ type: Boolean })
194
- ], n.prototype, "required");
195
- d([
196
- l({ type: Boolean, attribute: "show-optional" })
197
- ], n.prototype, "showOptional");
198
- d([
199
- l({ attribute: !1 })
200
- ], n.prototype, "error");
201
- d([
202
- l({ type: Number })
203
- ], n.prototype, "debounce");
204
- let N = n;
205
83
  export {
206
- k as BahtElement,
207
- N as BahtFieldBase,
208
- T as SSR_STASH,
209
- E as adoptLightChildren,
210
- f as hasDom,
211
- $ as hasSsrMarkup
84
+ b as BahtElement,
85
+ h as SSR_STASH,
86
+ y as adoptLightChildren,
87
+ f as boolOff,
88
+ n as hasDom,
89
+ l as hasSsrMarkup
212
90
  };
@@ -0,0 +1,71 @@
1
+ import { nothing, PropertyValues, TemplateResult } from 'lit';
2
+ import { FieldDescriptor } from '../core/types.js';
3
+ import { BahtElement } from './base.js';
4
+ /**
5
+ * Shared shape of every field primitive: descriptor + value in, `baht-input` out.
6
+ * Dumb and controlled — never mutates its own `value` prop; the host re-feeds it.
7
+ *
8
+ * `controlId` carries the base class `uid`: the same field key renders once per repeater
9
+ * row (and can repeat across forms on one page), so key-only ids collide — worst for
10
+ * radios, whose shared native `name` merges groups.
11
+ */
12
+ export declare abstract class BahtFieldBase extends BahtElement {
13
+ /**
14
+ * Default trailing debounce (ms) for TYPED edits — text, email, password,
15
+ * textarea, number. Every keystroke otherwise re-runs the host's rules,
16
+ * validation and dependent-option fetches. Per element: the `debounce`
17
+ * property/attribute; `<baht-form options.inputDebounce>` sets it for a whole
18
+ * form. 0 = emit on every keystroke. Selects, checkboxes, radios, toggles,
19
+ * dates and files are never debounced — a pick is one event.
20
+ */
21
+ static defaultDebounce: number;
22
+ field: FieldDescriptor;
23
+ value: unknown;
24
+ disabled: boolean;
25
+ readonly: boolean;
26
+ required: boolean;
27
+ /** Append the localized "(optional)" marker to the label when not required (set by <baht-form show-optional>). */
28
+ showOptional: boolean;
29
+ error: string | null;
30
+ /** Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce. */
31
+ debounce?: number;
32
+ /**
33
+ * The keystrokes not yet (or just) handed to the host. The control keeps
34
+ * rendering `typed.value` until the host's `value` moves, so a re-render
35
+ * mid-typing (a sibling field changing) can't snap the text back.
36
+ */
37
+ private typed;
38
+ private typedTimer;
39
+ private readonly onFocusOut;
40
+ connectedCallback(): void;
41
+ disconnectedCallback(): void;
42
+ protected willUpdate(changed: PropertyValues<this>): void;
43
+ protected get effectiveDebounce(): number;
44
+ /** A typed edit: emitted after `debounce` ms of quiet (or at once when 0). */
45
+ protected emitTyped(value: unknown): void;
46
+ /** Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent. */
47
+ flushTyped(): void;
48
+ /** What a typed control shows: pending keystrokes win over a stale host value. */
49
+ protected get shownValue(): unknown;
50
+ /** Emit the user's edit upward (composed so it crosses any boundary). */
51
+ protected emitInput(value: unknown): void;
52
+ protected abstract renderControl(): TemplateResult;
53
+ /** True when `field.icon` names a real icon — gates both the icon markup
54
+ * and the control's icon-padding class, so a typo/removed name never
55
+ * leaves the control indented for a glyph that isn't there. */
56
+ protected get hasFieldIcon(): boolean;
57
+ /** Leading icon for text/currency/textarea/dropdown controls (spec: field.icon). */
58
+ protected renderFieldIcon(): TemplateResult | typeof nothing;
59
+ protected renderLabel(): TemplateResult | typeof nothing;
60
+ /** Required asterisk, or the "(optional)" marker when the form asks for it. */
61
+ protected renderLabelMark(): TemplateResult | typeof nothing;
62
+ protected get controlId(): string;
63
+ protected get labelId(): string;
64
+ protected get errorId(): string;
65
+ protected get helpId(): string;
66
+ /** id list for the control's aria-describedby: error first, then help. */
67
+ protected get describedBy(): string | typeof nothing;
68
+ /** 'true' while the field is in error, else absent (styles hook on [aria-invalid]). */
69
+ protected get invalid(): string | typeof nothing;
70
+ protected render(): TemplateResult | typeof nothing;
71
+ }