bank20baht-ui 0.8.1 → 0.9.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.
@@ -167,7 +167,7 @@ let p = class extends E {
167
167
  return o`<div class="baht-bar-chart-row">
168
168
  <span class="baht-bar-chart-label" title=${y}>${y}</span>
169
169
  <div
170
- class="baht-bar-chart-track${this.stacked ? " baht-bar-chart-track--stacked" : ""}"
170
+ class="baht-bar-chart-track${this.stacked ? " baht-bar-chart-track--stacked" : ""}${this.categoryMarkers(y).length > 0 ? " baht-bar-chart-track--marked" : ""}"
171
171
  tabindex="0"
172
172
  role="button"
173
173
  aria-label=${u}
@@ -12,6 +12,7 @@ export type ButtonSize = 'sm' | 'md' | 'lg';
12
12
  * <baht-button label="Save workflow"></baht-button>
13
13
  * <baht-button icon="copy" variant="ghost" size="sm">Copy</baht-button>
14
14
  * <baht-button icon="close" icon-only label="Close"></baht-button> <!-- label = a11y name -->
15
+ * <baht-button aria-label="Previous month">‹</baht-button> <!-- glyph child, host aria-label wins -->
15
16
  */
16
17
  export declare class BahtButton extends BahtElement {
17
18
  variant: ButtonVariant;
@@ -25,6 +26,18 @@ export declare class BahtButton extends BahtElement {
25
26
  icon: IconName | '';
26
27
  /** Square icon-only button. Requires `label` for the accessible name. */
27
28
  iconOnly: boolean;
29
+ /**
30
+ * A host `aria-label` never reaches assistive tech on its own — light DOM has no
31
+ * implicit ARIA relationship between a custom element and its rendered children, so an
32
+ * `aria-label` written on `<baht-button>` stays on the host and the inner native
33
+ * `<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into
34
+ * this property forwards it to the inner button's `aria-label`, taking priority over
35
+ * `label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real
36
+ * accessible name without `icon-only`. Mapped to the plain `aria-label` attribute so
37
+ * hosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to
38
+ * avoid shadowing the platform's own ARIAMixin.
39
+ */
40
+ hostAriaLabel: string;
28
41
  private buttonEl;
29
42
  protected firstUpdated(): void;
30
43
  protected render(): TemplateResult;
@@ -1,27 +1,27 @@
1
- import { nothing as h, html as b } from "lit";
1
+ import { nothing as p, html as b } from "lit";
2
2
  import { property as e, query as c, customElement as y } from "lit/decorators.js";
3
3
  import { BahtElement as u, adoptLightChildren as m } from "../base.js";
4
4
  import { isIconName as d, renderIcon as f } from "./icons.js";
5
- var v = Object.defineProperty, O = Object.getOwnPropertyDescriptor, o = (a, n, r, s) => {
6
- for (var i = s > 1 ? void 0 : s ? O(n, r) : n, l = a.length - 1, p; l >= 0; l--)
7
- (p = a[l]) && (i = (s ? p(n, r, i) : p(i)) || i);
8
- return s && i && v(n, r, i), i;
5
+ var v = Object.defineProperty, O = Object.getOwnPropertyDescriptor, o = (r, s, a, n) => {
6
+ for (var i = n > 1 ? void 0 : n ? O(s, a) : s, l = r.length - 1, h; l >= 0; l--)
7
+ (h = r[l]) && (i = (n ? h(s, a, i) : h(i)) || i);
8
+ return n && i && v(s, a, i), i;
9
9
  };
10
10
  let t = class extends u {
11
11
  constructor() {
12
- super(...arguments), this.variant = "primary", this.size = "md", this.disabled = !1, this.type = "button", this.label = "", this.icon = "", this.iconOnly = !1;
12
+ super(...arguments), this.variant = "primary", this.size = "md", this.disabled = !1, this.type = "button", this.label = "", this.icon = "", this.iconOnly = !1, this.hostAriaLabel = "";
13
13
  }
14
14
  firstUpdated() {
15
15
  this.iconOnly || m(this, this.buttonEl);
16
16
  }
17
17
  render() {
18
- const a = this.variant === "primary" ? "" : ` baht-button--${this.variant}`, n = this.size === "md" ? "" : ` baht-button--${this.size}`, r = this.iconOnly ? " baht-button--icon" : "", s = this.icon && d(this.icon) ? f(this.icon) : h;
18
+ const r = this.variant === "primary" ? "" : ` baht-button--${this.variant}`, s = this.size === "md" ? "" : ` baht-button--${this.size}`, a = this.iconOnly ? " baht-button--icon" : "", n = this.icon && d(this.icon) ? f(this.icon) : p, i = this.hostAriaLabel || (this.iconOnly && this.label ? this.label : "");
19
19
  return b`<button
20
- class="baht-button${a}${n}${r}"
20
+ class="baht-button${r}${s}${a}"
21
21
  type=${this.type}
22
- aria-label=${this.iconOnly && this.label ? this.label : h}
22
+ aria-label=${i || p}
23
23
  ?disabled=${this.disabled}
24
- >${s}${this.iconOnly ? h : this.label}</button>`;
24
+ >${n}${this.iconOnly ? p : this.label}</button>`;
25
25
  }
26
26
  };
27
27
  o([
@@ -45,6 +45,9 @@ o([
45
45
  o([
46
46
  e({ type: Boolean, attribute: "icon-only" })
47
47
  ], t.prototype, "iconOnly", 2);
48
+ o([
49
+ e({ attribute: "aria-label" })
50
+ ], t.prototype, "hostAriaLabel", 2);
48
51
  o([
49
52
  c("button")
50
53
  ], t.prototype, "buttonEl", 2);
@@ -25,6 +25,18 @@ export declare class BahtLineChart extends BahtChartBase {
25
25
  labelValues: 'ends' | 'none';
26
26
  /** Accessible summary; falls back to a generated sentence. */
27
27
  label: string;
28
+ /**
29
+ * Drawing width in SVG units. It follows the host up to `W`, so on a phone
30
+ * one unit is one CSS pixel and tick text keeps its real size instead of
31
+ * shrinking with a 640-wide drawing scaled into a 360px card.
32
+ */
33
+ private width;
34
+ private resizeObserver;
35
+ connectedCallback(): void;
36
+ disconnectedCallback(): void;
37
+ protected firstUpdated(): void;
38
+ /** Host width → drawing width. Never runs on the server (`hasDom` guard). */
39
+ private measure;
28
40
  private get plot();
29
41
  protected render(): TemplateResult;
30
42
  private crosshairAt;
@@ -1,151 +1,176 @@
1
- import { html as k, nothing as y, svg as l } from "lit";
2
- import { property as d, customElement as E } from "lit/decorators.js";
3
- import { arr as A } from "../base.js";
4
- import { BahtChartBase as N, niceTicks as V, linearScale as T, chartColor as g, renderTableTwin as q } from "./charts.js";
5
- var U = Object.defineProperty, R = Object.getOwnPropertyDescriptor, m = (b, a, n, o) => {
6
- for (var c = o > 1 ? void 0 : o ? R(a, n) : a, f = b.length - 1, u; f >= 0; f--)
7
- (u = b[f]) && (c = (o ? u(a, n, c) : u(c)) || c);
8
- return o && c && U(a, n, c), c;
1
+ import { html as _, nothing as y, svg as l } from "lit";
2
+ import { property as x, state as H, customElement as N } from "lit/decorators.js";
3
+ import { hasDom as k, arr as L } from "../base.js";
4
+ import { BahtChartBase as W, niceTicks as q, linearScale as O, chartColor as g, renderTableTwin as R } from "./charts.js";
5
+ var U = Object.defineProperty, V = Object.getOwnPropertyDescriptor, c = (o, s, $, r) => {
6
+ for (var d = r > 1 ? void 0 : r ? V(s, $) : s, f = o.length - 1, b; f >= 0; f--)
7
+ (b = o[f]) && (d = (r ? b(s, $, d) : b(d)) || d);
8
+ return r && d && U(s, $, d), d;
9
9
  };
10
- const C = 640, L = 260, s = { top: 14, right: 72, bottom: 30, left: 60 };
11
- let h = class extends N {
10
+ const T = 640, X = 280, A = 260, I = 56, a = { top: 14, right: 72, bottom: 30, left: 60 };
11
+ let h = class extends W {
12
12
  constructor() {
13
- super(...arguments), this.series = [], this.categories = [], this.area = !1, this.markers = [], this.forecast = 0, this.baseline = null, this.labelValues = "ends", this.label = "", this.crosshairAt = null;
13
+ super(...arguments), this.series = [], this.categories = [], this.area = !1, this.markers = [], this.forecast = 0, this.baseline = null, this.labelValues = "ends", this.label = "", this.width = T, this.crosshairAt = null;
14
+ }
15
+ connectedCallback() {
16
+ super.connectedCallback(), !(!k() || typeof ResizeObserver > "u") && (this.resizeObserver = new ResizeObserver(() => {
17
+ this.measure();
18
+ }), this.resizeObserver.observe(this));
19
+ }
20
+ disconnectedCallback() {
21
+ var o;
22
+ (o = this.resizeObserver) == null || o.disconnect(), this.resizeObserver = void 0, super.disconnectedCallback();
23
+ }
24
+ firstUpdated() {
25
+ k() && queueMicrotask(() => {
26
+ this.measure();
27
+ });
28
+ }
29
+ /** Host width → drawing width. Never runs on the server (`hasDom` guard). */
30
+ measure() {
31
+ if (!k() || !this.isConnected) return;
32
+ const o = this.clientWidth;
33
+ if (o <= 0) return;
34
+ const s = Math.min(T, Math.max(X, Math.round(o)));
35
+ s !== this.width && (this.width = s);
14
36
  }
15
37
  get plot() {
16
- return { w: C - s.left - s.right, h: L - s.top - s.bottom };
38
+ return { w: this.width - a.left - a.right, h: A - a.top - a.bottom };
17
39
  }
18
40
  render() {
19
- const b = this.hold({
20
- series: A(this.series).filter((t) => t.values.length > 0),
21
- categories: A(this.categories)
22
- }), { series: a, categories: n } = b;
23
- if (a.length === 0)
24
- return k`<div class=${this.rootClass("baht-line-chart")}>${this.renderEmpty()}</div>`;
25
- const o = Math.max(...a.map((t) => t.values.length)), c = a.flatMap((t) => t.values).filter((t) => Number.isFinite(t)), f = Math.max(0, ...c, ...this.markers.map((t) => t.value)), u = V(f, 4), j = u[u.length - 1] ?? 1, { w: v, h: F } = this.plot, $ = T([0, Math.max(1, o - 1)], [s.left, s.left + v]), r = T([0, j], [s.top + F, s.top]), M = Math.max(0, o - 1 - Math.max(0, this.forecast)), P = (t, e, i) => t.slice(e, i + 1).map((p, x) => `${x === 0 ? "M" : "L"}${$(e + x).toFixed(2)},${r(p).toFixed(2)}`).join(" "), w = a.map((t, e) => ({
41
+ const o = this.hold({
42
+ series: L(this.series).filter((t) => t.values.length > 0),
43
+ categories: L(this.categories)
44
+ }), { series: s, categories: $ } = o;
45
+ if (s.length === 0)
46
+ return _`<div class=${this.rootClass("baht-line-chart")}>${this.renderEmpty()}</div>`;
47
+ const r = Math.max(...s.map((t) => t.values.length)), d = s.flatMap((t) => t.values).filter((t) => Number.isFinite(t)), f = Math.max(0, ...d, ...this.markers.map((t) => t.value)), b = q(f, 4), z = b[b.length - 1] ?? 1, { w: v, h: F } = this.plot, p = O([0, Math.max(1, r - 1)], [a.left, a.left + v]), i = O([0, z], [a.top + F, a.top]), M = Math.max(0, r - 1 - Math.max(0, this.forecast)), w = (t, e, n) => t.slice(e, n + 1).map((u, m) => `${m === 0 ? "M" : "L"}${p(e + m).toFixed(2)},${i(u).toFixed(2)}`).join(" "), P = s.map((t, e) => ({
26
48
  i: e,
27
49
  name: t.name,
28
50
  value: t.values[t.values.length - 1] ?? 0,
29
- yPos: r(t.values[t.values.length - 1] ?? 0),
51
+ yPos: i(t.values[t.values.length - 1] ?? 0),
30
52
  color: g(e, t.color)
31
- })), _ = [...w].sort((t, e) => t.yPos - e.yPos), B = _.some((t, e) => e > 0 && t.yPos - _[e - 1].yPos < 15), H = this.labelValues === "ends" && !B, O = Math.max(1, Math.ceil(o / 8)), D = this.label || `Line chart, ${a.length} series over ${o} points. ${a.map((t, e) => `${t.name}: ${this.fmt(w[e].value)} at the end`).join(". ")}`;
32
- return k`<div class=${this.rootClass("baht-line-chart")}>
53
+ })), C = [...P].sort((t, e) => t.yPos - e.yPos), B = C.some((t, e) => e > 0 && t.yPos - C[e - 1].yPos < 15), j = this.labelValues === "ends" && !B, D = Math.max(1, Math.ceil(r / Math.max(2, Math.min(8, Math.floor(v / I))))), E = this.label || `Line chart, ${s.length} series over ${r} points. ${s.map((t, e) => `${t.name}: ${this.fmt(P[e].value)} at the end`).join(". ")}`;
54
+ return _`<div class=${this.rootClass("baht-line-chart")}>
33
55
  <svg
34
56
  class="baht-chart-svg"
35
- viewBox="0 0 ${C} ${L}"
57
+ viewBox="0 0 ${this.width} ${A}"
36
58
  preserveAspectRatio="xMidYMid meet"
37
59
  role="img"
38
- aria-label=${D}
60
+ aria-label=${E}
39
61
  >
40
- ${u.map(
62
+ ${b.map(
41
63
  (t) => l`
42
- <line class="baht-chart-grid-line" x1=${s.left} x2=${s.left + v}
43
- y1=${r(t).toFixed(2)} y2=${r(t).toFixed(2)}/>
44
- <text class="baht-chart-tick" x=${s.left - 8} y=${(r(t) + 4).toFixed(2)}
64
+ <line class="baht-chart-grid-line" x1=${a.left} x2=${a.left + v}
65
+ y1=${i(t).toFixed(2)} y2=${i(t).toFixed(2)}/>
66
+ <text class="baht-chart-tick" x=${a.left - 8} y=${(i(t) + 4).toFixed(2)}
45
67
  text-anchor="end">${this.fmt(t)}</text>`
46
68
  )}
47
69
  ${this.baseline != null ? [
48
- l`<line class="baht-chart-baseline" x1=${s.left} x2=${s.left + v}
49
- y1=${r(this.baseline).toFixed(2)} y2=${r(this.baseline).toFixed(2)}/>`
70
+ l`<line class="baht-chart-baseline" x1=${a.left} x2=${a.left + v}
71
+ y1=${i(this.baseline).toFixed(2)} y2=${i(this.baseline).toFixed(2)}/>`
50
72
  ] : []}
51
- ${A(this.markers).map(
73
+ ${L(this.markers).map(
52
74
  (t) => l`
53
- <line class="baht-chart-marker-line" x1=${s.left} x2=${s.left + v}
54
- y1=${r(t.value).toFixed(2)} y2=${r(t.value).toFixed(2)}/>
55
- <text class="baht-chart-marker-label" x=${s.left + v} y=${(r(t.value) - 5).toFixed(2)}
75
+ <line class="baht-chart-marker-line" x1=${a.left} x2=${a.left + v}
76
+ y1=${i(t.value).toFixed(2)} y2=${i(t.value).toFixed(2)}/>
77
+ <text class="baht-chart-marker-label" x=${a.left + v} y=${(i(t.value) - 5).toFixed(2)}
56
78
  text-anchor="end">${t.label ?? this.fmt(t.value)}</text>`
57
79
  )}
58
- ${this.area && a.length === 1 ? l`<path class="baht-chart-area" fill=${g(0, a[0].color)}
59
- d=${`${P(a[0].values, 0, o - 1)} L${$(o - 1).toFixed(2)},${r(0).toFixed(2)} L${$(0).toFixed(2)},${r(0).toFixed(2)} Z`}/>` : y}
60
- ${a.map((t, e) => {
61
- const i = g(e, t.color), p = [
62
- l`<path class="baht-chart-line" d=${P(t.values, 0, M)} stroke=${i} fill="none"/>`
80
+ ${this.area && s.length === 1 ? l`<path class="baht-chart-area" fill=${g(0, s[0].color)}
81
+ d=${`${w(s[0].values, 0, r - 1)} L${p(r - 1).toFixed(2)},${i(0).toFixed(2)} L${p(0).toFixed(2)},${i(0).toFixed(2)} Z`}/>` : y}
82
+ ${s.map((t, e) => {
83
+ const n = g(e, t.color), u = [
84
+ l`<path class="baht-chart-line" d=${w(t.values, 0, M)} stroke=${n} fill="none"/>`
63
85
  ];
64
- return M < o - 1 && p.push(
65
- l`<path class="baht-chart-line baht-chart-line--forecast" d=${P(t.values, M, o - 1)}
66
- stroke=${i} fill="none"/>`
67
- ), p.push(
68
- l`<circle class="baht-chart-end-dot" cx=${$(o - 1).toFixed(2)}
69
- cy=${r(t.values[t.values.length - 1] ?? 0).toFixed(2)} r="4" fill=${i}/>`
70
- ), p;
86
+ return M < r - 1 && u.push(
87
+ l`<path class="baht-chart-line baht-chart-line--forecast" d=${w(t.values, M, r - 1)}
88
+ stroke=${n} fill="none"/>`
89
+ ), u.push(
90
+ l`<circle class="baht-chart-end-dot" cx=${p(r - 1).toFixed(2)}
91
+ cy=${i(t.values[t.values.length - 1] ?? 0).toFixed(2)} r="4" fill=${n}/>`
92
+ ), u;
71
93
  })}
72
- ${H ? w.map(
94
+ ${j ? P.map(
73
95
  (t) => l`
74
- <text class="baht-chart-end-label" x=${$(o - 1) + 10} y=${(t.yPos + 4).toFixed(2)}
96
+ <text class="baht-chart-end-label" x=${p(r - 1) + 10} y=${(t.yPos + 4).toFixed(2)}
75
97
  text-anchor="start">${this.fmt(t.value)}</text>`
76
98
  ) : y}
77
- ${n.map(
78
- (t, e) => e % O === 0 ? l`<text class="baht-chart-tick" x=${$(e).toFixed(2)} y=${L - 10} text-anchor="middle">${t}</text>` : y
99
+ ${$.map(
100
+ (t, e) => e % D === 0 ? l`<text class="baht-chart-tick" x=${p(e).toFixed(2)} y=${A - 10} text-anchor="middle">${t}</text>` : y
79
101
  )}
80
- ${Array.from({ length: o }, (t, e) => {
81
- const i = v / Math.max(1, o - 1), p = `${n[e] ?? `Point ${e + 1}`}: ${a.map((x) => `${x.name} ${this.fmt(x.values[e] ?? 0)}`).join(", ")}`;
82
- return l`<rect class="baht-chart-hit" x=${($(e) - i / 2).toFixed(2)} y=${s.top}
83
- width=${Math.max(24, i).toFixed(2)} height=${F} tabindex="0" role="button"
84
- aria-label=${p}
85
- @pointermove=${(x) => {
86
- this.onHover(x, e, p);
102
+ ${Array.from({ length: r }, (t, e) => {
103
+ const n = v / Math.max(1, r - 1), u = `${$[e] ?? `Point ${e + 1}`}: ${s.map((m) => `${m.name} ${this.fmt(m.values[e] ?? 0)}`).join(", ")}`;
104
+ return l`<rect class="baht-chart-hit" x=${(p(e) - n / 2).toFixed(2)} y=${a.top}
105
+ width=${Math.max(24, n).toFixed(2)} height=${F} tabindex="0" role="button"
106
+ aria-label=${u}
107
+ @pointermove=${(m) => {
108
+ this.onHover(m, e, u);
87
109
  }}
88
110
  @pointerleave=${() => {
89
111
  this.onLeave();
90
112
  }}
91
- @focus=${(x) => {
92
- this.onHover(x, e, p);
113
+ @focus=${(m) => {
114
+ this.onHover(m, e, u);
93
115
  }}
94
116
  @blur=${() => {
95
117
  this.onLeave();
96
118
  }}/>`;
97
119
  })}
98
- ${this.crosshairAt != null ? l`<line class="baht-chart-crosshair" x1=${$(this.crosshairAt).toFixed(2)}
99
- x2=${$(this.crosshairAt).toFixed(2)} y1=${s.top} y2=${s.top + F}/>` : y}
120
+ ${this.crosshairAt != null ? l`<line class="baht-chart-crosshair" x1=${p(this.crosshairAt).toFixed(2)}
121
+ x2=${p(this.crosshairAt).toFixed(2)} y1=${a.top} y2=${a.top + F}/>` : y}
100
122
  </svg>
101
123
  ${this.renderTooltip()}
102
124
  ${this.renderLegend(
103
- a.map((t, e) => ({ label: t.name, color: g(e, t.color) })),
125
+ s.map((t, e) => ({ label: t.name, color: g(e, t.color) })),
104
126
  "line"
105
127
  )}
106
- ${this.showTable ? q(
128
+ ${this.showTable ? R(
107
129
  this.label || "Line chart data",
108
- ["Point", ...a.map((t) => t.name)],
109
- Array.from({ length: o }, (t, e) => [
110
- n[e] ?? `Point ${e + 1}`,
111
- ...a.map((i) => this.fmt(i.values[e] ?? 0))
130
+ ["Point", ...s.map((t) => t.name)],
131
+ Array.from({ length: r }, (t, e) => [
132
+ $[e] ?? `Point ${e + 1}`,
133
+ ...s.map((n) => this.fmt(n.values[e] ?? 0))
112
134
  ])
113
135
  ) : y}
114
136
  </div>`;
115
137
  }
116
- onHover(b, a, n) {
117
- this.crosshairAt = a, this.requestUpdate(), this.showTip(b, n);
138
+ onHover(o, s, $) {
139
+ this.crosshairAt = s, this.requestUpdate(), this.showTip(o, $);
118
140
  }
119
141
  onLeave() {
120
142
  this.crosshairAt = null, this.requestUpdate(), this.hideTip();
121
143
  }
122
144
  };
123
- m([
124
- d({ attribute: !1 })
145
+ c([
146
+ x({ attribute: !1 })
125
147
  ], h.prototype, "series", 2);
126
- m([
127
- d({ attribute: !1 })
148
+ c([
149
+ x({ attribute: !1 })
128
150
  ], h.prototype, "categories", 2);
129
- m([
130
- d({ type: Boolean })
151
+ c([
152
+ x({ type: Boolean })
131
153
  ], h.prototype, "area", 2);
132
- m([
133
- d({ attribute: !1 })
154
+ c([
155
+ x({ attribute: !1 })
134
156
  ], h.prototype, "markers", 2);
135
- m([
136
- d({ type: Number })
157
+ c([
158
+ x({ type: Number })
137
159
  ], h.prototype, "forecast", 2);
138
- m([
139
- d({ type: Number })
160
+ c([
161
+ x({ type: Number })
140
162
  ], h.prototype, "baseline", 2);
141
- m([
142
- d({ attribute: "label-values" })
163
+ c([
164
+ x({ attribute: "label-values" })
143
165
  ], h.prototype, "labelValues", 2);
144
- m([
145
- d()
166
+ c([
167
+ x()
146
168
  ], h.prototype, "label", 2);
147
- h = m([
148
- E("baht-line-chart")
169
+ c([
170
+ H()
171
+ ], h.prototype, "width", 2);
172
+ h = c([
173
+ N("baht-line-chart")
149
174
  ], h);
150
175
  export {
151
176
  h as BahtLineChart
@@ -28,6 +28,10 @@ export interface MessageCatalog {
28
28
  removeRow: string;
29
29
  minRows: string;
30
30
  maxRows: string;
31
+ /** `min_rows: 1`, where "{n} rows" would read "1 rows". */
32
+ minRowsOne: string;
33
+ /** `max_rows: 1`. */
34
+ maxRowsOne: string;
31
35
  next: string;
32
36
  submit: string;
33
37
  back: string;
package/dist/core/i18n.js CHANGED
@@ -28,6 +28,8 @@ const n = {
28
28
  removeRow: "Remove row",
29
29
  minRows: "Add at least {n} rows",
30
30
  maxRows: "Add at most {n} rows",
31
+ minRowsOne: "Add at least one row",
32
+ maxRowsOne: "Add at most one row",
31
33
  next: "Next",
32
34
  submit: "Submit",
33
35
  back: "Back",
@@ -93,6 +95,8 @@ const n = {
93
95
  removeRow: "ลบแถว",
94
96
  minRows: "ต้องมีอย่างน้อย {n} แถว",
95
97
  maxRows: "ต้องไม่เกิน {n} แถว",
98
+ minRowsOne: "ต้องมีอย่างน้อย 1 แถว",
99
+ maxRowsOne: "ต้องไม่เกิน 1 แถว",
96
100
  next: "ถัดไป",
97
101
  submit: "ส่งข้อมูล",
98
102
  back: "ย้อนกลับ",
@@ -1,9 +1,9 @@
1
- import { defaultValue as b } from "./defaults.js";
1
+ import { defaultValue as w } from "./defaults.js";
2
2
  import { compareValues as k } from "./engine/conditions.js";
3
- import { isoDaysOf as x, dateModeOf as w, rangeInteriorDays as _, isDateDisabled as A } from "./dates.js";
4
- import { t as u } from "./i18n.js";
3
+ import { isoDaysOf as x, dateModeOf as _, rangeInteriorDays as b, isDateDisabled as A } from "./dates.js";
4
+ import { t as i } from "./i18n.js";
5
5
  const L = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6
- function p(e) {
6
+ function l(e) {
7
7
  return e == null ? !0 : typeof e == "string" ? e === "" : Array.isArray(e) ? e.length === 0 : typeof e == "boolean" ? !e : !1;
8
8
  }
9
9
  function d(e) {
@@ -16,14 +16,14 @@ function h(e, t) {
16
16
  const r = Number(n.value);
17
17
  return Number.isNaN(r) ? (console.warn(
18
18
  `[bahtui] ${t} validator has non-numeric value ${JSON.stringify(n.value)} — skipped`
19
- ), null) : typeof s != "string" ? null : (e === "minLength" ? s.length < r : s.length > r) ? n.message ?? u(e, { n: n.value }) : null;
19
+ ), null) : typeof s != "string" ? null : (e === "minLength" ? s.length < r : s.length > r) ? n.message ?? i(e, { n: n.value }) : null;
20
20
  };
21
21
  }
22
22
  function f(e) {
23
23
  return (t, n) => E(n).some((r) => {
24
24
  const o = k(r, t.value);
25
25
  return o !== null && (e === "min" ? o < 0 : o > 0);
26
- }) ? t.message ?? u(e, { n: t.value }) : null;
26
+ }) ? t.message ?? i(e, { n: t.value }) : null;
27
27
  }
28
28
  const C = {
29
29
  eq: (e) => e === 0,
@@ -32,39 +32,39 @@ const C = {
32
32
  gte: (e) => e >= 0,
33
33
  lt: (e) => e < 0,
34
34
  lte: (e) => e <= 0
35
- }, $ = (e, t, n, s) => {
35
+ }, R = (e, t, n, s) => {
36
36
  const r = s == null ? void 0 : s[e.field ?? ""];
37
- if (r === void 0 || p(r)) return null;
37
+ if (r === void 0 || l(r)) return null;
38
38
  const o = k(t, r);
39
39
  if (o === null) return null;
40
40
  const a = e.op ?? "eq";
41
41
  if (C[a](o)) return null;
42
42
  const m = `compare${a[0].toUpperCase()}${a.slice(1)}`;
43
- return e.message ?? u(m, { other: e.field });
44
- }, q = (e, t) => {
43
+ return e.message ?? i(m, { other: e.field });
44
+ }, $ = (e, t) => {
45
45
  if (typeof t != "string") return null;
46
46
  const n = D(String(e.value));
47
- return n ? n.test(t) ? null : e.message ?? u("pattern") : null;
48
- }, N = (e, t, n) => {
47
+ return n ? n.test(t) ? null : e.message ?? i("pattern") : null;
48
+ }, O = (e, t, n) => {
49
49
  var o;
50
50
  const s = (o = n == null ? void 0 : n.validators) == null ? void 0 : o[e.fn ?? ""];
51
51
  if (!s)
52
52
  return console.warn(`[bahtui] unknown custom validator "${e.fn}" — skipped`), null;
53
53
  const r = s(t);
54
54
  return r === null ? null : e.message ?? r;
55
- }, R = {
56
- required: (e, t) => p(t) ? e.message ?? u("required") : null,
55
+ }, q = {
56
+ required: (e, t) => l(t) ? e.message ?? i("required") : null,
57
57
  minLength: h("minLength", "min_length"),
58
58
  maxLength: h("maxLength", "max_length"),
59
59
  min: f("min"),
60
60
  max: f("max"),
61
- pattern: q,
62
- email: (e, t) => typeof t == "string" && !L.test(t) ? e.message ?? u("email") : null,
63
- compare: $,
64
- custom: N
61
+ pattern: $,
62
+ email: (e, t) => typeof t == "string" && !L.test(t) ? e.message ?? i("email") : null,
63
+ compare: R,
64
+ custom: O
65
65
  };
66
- function O(e, t, n, s) {
67
- return R[e.type](e, t, n, s);
66
+ function N(e, t, n, s) {
67
+ return q[e.type](e, t, n, s);
68
68
  }
69
69
  const g = /* @__PURE__ */ new Map();
70
70
  function D(e) {
@@ -80,10 +80,10 @@ function D(e) {
80
80
  return t;
81
81
  }
82
82
  function S(e, t, n, s) {
83
- const r = [], o = d(e), a = p(t);
83
+ const r = [], o = d(e), a = l(t);
84
84
  for (const m of o) {
85
85
  if (m.type !== "required" && a) continue;
86
- const c = O(m, t, n, s);
86
+ const c = N(m, t, n, s);
87
87
  c !== null && r.push({ key: e.key, message: c });
88
88
  }
89
89
  return e.type === "repeater" && r.push(...V(e, t, n)), e.type === "file" && r.push(...T(e, t)), e.type === "date" && !a && r.push(...M(e, t)), r;
@@ -92,7 +92,7 @@ function M(e, t) {
92
92
  var s, r;
93
93
  if (!((s = e.disabled_dates) != null && s.length) && !((r = e.disabled_weekdays) != null && r.length)) return [];
94
94
  const n = x(t);
95
- return w(e) === "range" && n.length === 2 && n.push(..._(n[0], n[1])), n.some((o) => A(e, o)) ? [{ key: e.key, message: u("dateDisabled") }] : [];
95
+ return _(e) === "range" && n.length === 2 && n.push(...b(n[0], n[1])), n.some((o) => A(e, o)) ? [{ key: e.key, message: i("dateDisabled") }] : [];
96
96
  }
97
97
  function P(e) {
98
98
  return e !== null && typeof e == "object" && typeof e.url == "string" && typeof e.size == "number" && typeof e.name == "string";
@@ -106,22 +106,29 @@ function z(e, t, n) {
106
106
  function T(e, t) {
107
107
  const n = (Array.isArray(t) ? t : [t]).filter(P), s = [];
108
108
  for (const r of n)
109
- e.max_size !== void 0 && r.size > e.max_size && s.push({ key: e.key, message: u("fileTooLarge") }), e.accept && !z(e.accept, r.name, r.type ?? "") && s.push({ key: e.key, message: u("fileTypeNotAllowed") });
109
+ e.max_size !== void 0 && r.size > e.max_size && s.push({ key: e.key, message: i("fileTooLarge") }), e.accept && !z(e.accept, r.name, r.type ?? "") && s.push({ key: e.key, message: i("fileTypeNotAllowed") });
110
110
  return s;
111
111
  }
112
112
  function V(e, t, n) {
113
113
  if (t != null && !Array.isArray(t))
114
- return [{ key: e.key, message: u("pattern") }];
114
+ return [{ key: e.key, message: i("pattern") }];
115
115
  const s = Array.isArray(t) ? t : [], r = [];
116
- e.min_rows !== void 0 && s.length < e.min_rows && r.push({ key: e.key, message: u("minRows", { n: e.min_rows }) }), e.max_rows !== void 0 && s.length > e.max_rows && r.push({ key: e.key, message: u("maxRows", { n: e.max_rows }) });
116
+ if (e.min_rows !== void 0 && s.length < e.min_rows) {
117
+ const a = e.min_rows === 1 ? i("minRowsOne") : i("minRows", { n: e.min_rows });
118
+ r.push({ key: e.key, message: a });
119
+ }
120
+ if (e.max_rows !== void 0 && s.length > e.max_rows) {
121
+ const a = e.max_rows === 1 ? i("maxRowsOne") : i("maxRows", { n: e.max_rows });
122
+ r.push({ key: e.key, message: a });
123
+ }
117
124
  const o = [...e.fields ?? []].sort((a, m) => a.order - m.order);
118
125
  return s.forEach((a, m) => {
119
- const c = a !== null && typeof a == "object" && !Array.isArray(a) ? a : {}, l = {};
120
- for (const i of o)
121
- i.type === "label" || i.type === "section" || i.type === "repeater" || (l[i.key] = Object.hasOwn(c, i.key) ? c[i.key] : b(i));
122
- for (const i of o)
123
- if (!(i.type === "label" || i.type === "section" || i.type === "repeater"))
124
- for (const y of S(i, l[i.key], n, l))
126
+ const c = a !== null && typeof a == "object" && !Array.isArray(a) ? a : {}, p = {};
127
+ for (const u of o)
128
+ u.type === "label" || u.type === "section" || u.type === "repeater" || (p[u.key] = Object.hasOwn(c, u.key) ? c[u.key] : w(u));
129
+ for (const u of o)
130
+ if (!(u.type === "label" || u.type === "section" || u.type === "repeater"))
131
+ for (const y of S(u, p[u.key], n, p))
125
132
  r.push({ key: `${e.key}[${m}].${y.key}`, message: y.message });
126
133
  }), r;
127
134
  }
@@ -396,6 +396,10 @@
396
396
  display: flex;
397
397
  align-items: center;
398
398
  gap: var(--baht-space-2);
399
+ /* A narrow card (a 1/4 option width in a phone-width form) drops the label
400
+ under its icon instead of pushing it past the card edge into the next one. */
401
+ flex-wrap: wrap;
402
+ min-width: 0;
399
403
  }
400
404
  /* `option.width` — same fractions as `field.width`, against the card row's
401
405
  own gap (`--baht-space-3`) instead of the form grid's `--baht-field-gap`,
@@ -407,6 +411,7 @@
407
411
  .baht-option-card--w-1-4 {
408
412
  flex: 0 0 calc(25% - var(--baht-space-3) * 3 / 4);
409
413
  min-width: 0;
414
+ padding-inline: var(--baht-space-3);
410
415
  }
411
416
  .baht-option-card--w-1-2 {
412
417
  flex: 0 0 calc(50% - var(--baht-space-3) / 2);
@@ -449,6 +454,11 @@
449
454
  .baht-option-card-title {
450
455
  font-weight: 600;
451
456
  color: var(--baht-color-text);
457
+ /* Last resort when even its own line is too narrow: clip, never overflow. */
458
+ min-width: 0;
459
+ max-width: 100%;
460
+ overflow: hidden;
461
+ text-overflow: ellipsis;
452
462
  }
453
463
  .baht-option-card-desc {
454
464
  font-size: var(--baht-text-sm);
@@ -2546,16 +2556,17 @@ code.baht-code {
2546
2556
  vertical-align: middle;
2547
2557
  background: var(--baht-color-text-muted);
2548
2558
  }
2549
- /* A row's own marker, beside its line — on the surface so a bar behind it
2550
- never runs through the text. */
2559
+ /* A row with its own marker (a budget line) gets a caption lane above its
2560
+ bars: the label sits there beside its line and never covers a bar or the
2561
+ bar's value. */
2562
+ .baht-bar-chart-track--marked {
2563
+ padding-top: 1.1rem;
2564
+ }
2551
2565
  .baht-bar-chart-marker-label--row {
2552
- top: 50%;
2566
+ top: 0;
2553
2567
  bottom: auto;
2554
2568
  z-index: 2;
2555
- margin-top: -0.55rem;
2556
- background: var(--baht-chart-surface);
2557
- padding: 0 4px;
2558
- border-radius: var(--baht-radius-xs);
2569
+ padding: 0 2px;
2559
2570
  }
2560
2571
  .baht-bar-chart-marker-label--row::before {
2561
2572
  display: none; /* the line it names is right beside it */
@@ -4015,7 +4015,7 @@
4015
4015
  "declarations": [
4016
4016
  {
4017
4017
  "kind": "class",
4018
- "description": "Design-system button (20baht: no scale, no shadow change — color does the work).\nStandalone primitive: children become the label, listen for the native `click`.\n(Light DOM has no <slot>, so the initial children are moved into the button.)\n\n <baht-button variant=\"danger\" size=\"sm\">Delete</baht-button>\n <baht-button label=\"Save workflow\"></baht-button>\n <baht-button icon=\"copy\" variant=\"ghost\" size=\"sm\">Copy</baht-button>\n <baht-button icon=\"close\" icon-only label=\"Close\"></baht-button> <!-- label = a11y name -->",
4018
+ "description": "Design-system button (20baht: no scale, no shadow change — color does the work).\nStandalone primitive: children become the label, listen for the native `click`.\n(Light DOM has no <slot>, so the initial children are moved into the button.)\n\n <baht-button variant=\"danger\" size=\"sm\">Delete</baht-button>\n <baht-button label=\"Save workflow\"></baht-button>\n <baht-button icon=\"copy\" variant=\"ghost\" size=\"sm\">Copy</baht-button>\n <baht-button icon=\"close\" icon-only label=\"Close\"></baht-button> <!-- label = a11y name -->\n <baht-button aria-label=\"Previous month\">‹</baht-button> <!-- glyph child, host aria-label wins -->",
4019
4019
  "name": "BahtButton",
4020
4020
  "members": [
4021
4021
  {
@@ -4085,6 +4085,16 @@
4085
4085
  "description": "Square icon-only button. Requires `label` for the accessible name.",
4086
4086
  "attribute": "icon-only"
4087
4087
  },
4088
+ {
4089
+ "kind": "field",
4090
+ "name": "hostAriaLabel",
4091
+ "type": {
4092
+ "text": "string"
4093
+ },
4094
+ "default": "''",
4095
+ "description": "A host `aria-label` never reaches assistive tech on its own — light DOM has no\nimplicit ARIA relationship between a custom element and its rendered children, so an\n`aria-label` written on `<baht-button>` stays on the host and the inner native\n`<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into\nthis property forwards it to the inner button's `aria-label`, taking priority over\n`label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real\naccessible name without `icon-only`. Mapped to the plain `aria-label` attribute so\nhosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to\navoid shadowing the platform's own ARIAMixin.",
4096
+ "attribute": "aria-label"
4097
+ },
4088
4098
  {
4089
4099
  "kind": "field",
4090
4100
  "name": "buttonEl",
@@ -4199,6 +4209,15 @@
4199
4209
  "description": "Square icon-only button. Requires `label` for the accessible name.",
4200
4210
  "fieldName": "iconOnly"
4201
4211
  },
4212
+ {
4213
+ "name": "aria-label",
4214
+ "type": {
4215
+ "text": "string"
4216
+ },
4217
+ "default": "''",
4218
+ "description": "A host `aria-label` never reaches assistive tech on its own — light DOM has no\nimplicit ARIA relationship between a custom element and its rendered children, so an\n`aria-label` written on `<baht-button>` stays on the host and the inner native\n`<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into\nthis property forwards it to the inner button's `aria-label`, taking priority over\n`label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real\naccessible name without `icon-only`. Mapped to the plain `aria-label` attribute so\nhosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to\navoid shadowing the platform's own ARIAMixin.",
4219
+ "fieldName": "hostAriaLabel"
4220
+ },
4202
4221
  {
4203
4222
  "name": "defer-hydration",
4204
4223
  "inheritedFrom": {
@@ -10081,6 +10100,35 @@
10081
10100
  "description": "Accessible summary; falls back to a generated sentence.",
10082
10101
  "attribute": "label"
10083
10102
  },
10103
+ {
10104
+ "kind": "field",
10105
+ "name": "width",
10106
+ "privacy": "private",
10107
+ "default": "640",
10108
+ "description": "Drawing width in SVG units. It follows the host up to `W`, so on a phone\none unit is one CSS pixel and tick text keeps its real size instead of\nshrinking with a 640-wide drawing scaled into a 360px card.",
10109
+ "type": {
10110
+ "text": "number"
10111
+ }
10112
+ },
10113
+ {
10114
+ "kind": "field",
10115
+ "name": "resizeObserver",
10116
+ "type": {
10117
+ "text": "ResizeObserver | undefined"
10118
+ },
10119
+ "privacy": "private"
10120
+ },
10121
+ {
10122
+ "kind": "method",
10123
+ "name": "measure",
10124
+ "privacy": "private",
10125
+ "return": {
10126
+ "type": {
10127
+ "text": "void"
10128
+ }
10129
+ },
10130
+ "description": "Host width → drawing width. Never runs on the server (`hasDom` guard)."
10131
+ },
10084
10132
  {
10085
10133
  "kind": "field",
10086
10134
  "name": "plot",
package/dist/tokens.css CHANGED
@@ -830,6 +830,10 @@
830
830
  display: flex;
831
831
  align-items: center;
832
832
  gap: var(--baht-space-2);
833
+ /* A narrow card (a 1/4 option width in a phone-width form) drops the label
834
+ under its icon instead of pushing it past the card edge into the next one. */
835
+ flex-wrap: wrap;
836
+ min-width: 0;
833
837
  }
834
838
  /* `option.width` — same fractions as `field.width`, against the card row's
835
839
  own gap (`--baht-space-3`) instead of the form grid's `--baht-field-gap`,
@@ -841,6 +845,7 @@
841
845
  .baht-option-card--w-1-4 {
842
846
  flex: 0 0 calc(25% - var(--baht-space-3) * 3 / 4);
843
847
  min-width: 0;
848
+ padding-inline: var(--baht-space-3);
844
849
  }
845
850
  .baht-option-card--w-1-2 {
846
851
  flex: 0 0 calc(50% - var(--baht-space-3) / 2);
@@ -883,6 +888,11 @@
883
888
  .baht-option-card-title {
884
889
  font-weight: 600;
885
890
  color: var(--baht-color-text);
891
+ /* Last resort when even its own line is too narrow: clip, never overflow. */
892
+ min-width: 0;
893
+ max-width: 100%;
894
+ overflow: hidden;
895
+ text-overflow: ellipsis;
886
896
  }
887
897
  .baht-option-card-desc {
888
898
  font-size: var(--baht-text-sm);
@@ -2980,16 +2990,17 @@ code.baht-code {
2980
2990
  vertical-align: middle;
2981
2991
  background: var(--baht-color-text-muted);
2982
2992
  }
2983
- /* A row's own marker, beside its line — on the surface so a bar behind it
2984
- never runs through the text. */
2993
+ /* A row with its own marker (a budget line) gets a caption lane above its
2994
+ bars: the label sits there beside its line and never covers a bar or the
2995
+ bar's value. */
2996
+ .baht-bar-chart-track--marked {
2997
+ padding-top: 1.1rem;
2998
+ }
2985
2999
  .baht-bar-chart-marker-label--row {
2986
- top: 50%;
3000
+ top: 0;
2987
3001
  bottom: auto;
2988
3002
  z-index: 2;
2989
- margin-top: -0.55rem;
2990
- background: var(--baht-chart-surface);
2991
- padding: 0 4px;
2992
- border-radius: var(--baht-radius-xs);
3003
+ padding: 0 2px;
2993
3004
  }
2994
3005
  .baht-bar-chart-marker-label--row::before {
2995
3006
  display: none; /* the line it names is right beside it */
@@ -189,7 +189,7 @@
189
189
  },
190
190
  {
191
191
  "name": "baht-button",
192
- "description": "Design-system button (20baht: no scale, no shadow change — color does the work).\nStandalone primitive: children become the label, listen for the native `click`.\n(Light DOM has no <slot>, so the initial children are moved into the button.)\n\n <baht-button variant=\"danger\" size=\"sm\">Delete</baht-button>\n <baht-button label=\"Save workflow\"></baht-button>\n <baht-button icon=\"copy\" variant=\"ghost\" size=\"sm\">Copy</baht-button>\n <baht-button icon=\"close\" icon-only label=\"Close\"></baht-button> <!-- label = a11y name -->\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
192
+ "description": "Design-system button (20baht: no scale, no shadow change — color does the work).\nStandalone primitive: children become the label, listen for the native `click`.\n(Light DOM has no <slot>, so the initial children are moved into the button.)\n\n <baht-button variant=\"danger\" size=\"sm\">Delete</baht-button>\n <baht-button label=\"Save workflow\"></baht-button>\n <baht-button icon=\"copy\" variant=\"ghost\" size=\"sm\">Copy</baht-button>\n <baht-button icon=\"close\" icon-only label=\"Close\"></baht-button> <!-- label = a11y name -->\n <baht-button aria-label=\"Previous month\">‹</baht-button> <!-- glyph child, host aria-label wins -->\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
193
193
  "attributes": [
194
194
  { "name": "variant", "values": [{ "name": "ButtonVariant" }] },
195
195
  { "name": "size", "values": [{ "name": "ButtonSize" }] },
@@ -214,6 +214,11 @@
214
214
  "description": "Square icon-only button. Requires `label` for the accessible name.",
215
215
  "values": []
216
216
  },
217
+ {
218
+ "name": "aria-label",
219
+ "description": "A host `aria-label` never reaches assistive tech on its own — light DOM has no\nimplicit ARIA relationship between a custom element and its rendered children, so an\n`aria-label` written on `<baht-button>` stays on the host and the inner native\n`<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into\nthis property forwards it to the inner button's `aria-label`, taking priority over\n`label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real\naccessible name without `icon-only`. Mapped to the plain `aria-label` attribute so\nhosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to\navoid shadowing the platform's own ARIAMixin.",
220
+ "values": []
221
+ },
217
222
  { "name": "defer-hydration", "values": [] }
218
223
  ],
219
224
  "references": []
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "bank20baht-ui",
4
- "version": "0.8.1",
4
+ "version": "0.9.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -454,7 +454,7 @@
454
454
  },
455
455
  {
456
456
  "name": "baht-button",
457
- "description": "Design-system button (20baht: no scale, no shadow change — color does the work).\nStandalone primitive: children become the label, listen for the native `click`.\n(Light DOM has no <slot>, so the initial children are moved into the button.)\n\n <baht-button variant=\"danger\" size=\"sm\">Delete</baht-button>\n <baht-button label=\"Save workflow\"></baht-button>\n <baht-button icon=\"copy\" variant=\"ghost\" size=\"sm\">Copy</baht-button>\n <baht-button icon=\"close\" icon-only label=\"Close\"></baht-button> <!-- label = a11y name -->\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
457
+ "description": "Design-system button (20baht: no scale, no shadow change — color does the work).\nStandalone primitive: children become the label, listen for the native `click`.\n(Light DOM has no <slot>, so the initial children are moved into the button.)\n\n <baht-button variant=\"danger\" size=\"sm\">Delete</baht-button>\n <baht-button label=\"Save workflow\"></baht-button>\n <baht-button icon=\"copy\" variant=\"ghost\" size=\"sm\">Copy</baht-button>\n <baht-button icon=\"close\" icon-only label=\"Close\"></baht-button> <!-- label = a11y name -->\n <baht-button aria-label=\"Previous month\">‹</baht-button> <!-- glyph child, host aria-label wins -->\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
458
458
  "doc-url": "",
459
459
  "attributes": [
460
460
  {
@@ -489,6 +489,11 @@
489
489
  "description": "Square icon-only button. Requires `label` for the accessible name.",
490
490
  "value": { "type": "boolean", "default": "false" }
491
491
  },
492
+ {
493
+ "name": "aria-label",
494
+ "description": "A host `aria-label` never reaches assistive tech on its own — light DOM has no\nimplicit ARIA relationship between a custom element and its rendered children, so an\n`aria-label` written on `<baht-button>` stays on the host and the inner native\n`<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into\nthis property forwards it to the inner button's `aria-label`, taking priority over\n`label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real\naccessible name without `icon-only`. Mapped to the plain `aria-label` attribute so\nhosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to\navoid shadowing the platform's own ARIAMixin.",
495
+ "value": { "type": "string", "default": "''" }
496
+ },
492
497
  { "name": "defer-hydration", "value": { "type": "string" } }
493
498
  ],
494
499
  "events": [{ "name": "name", "type": "CustomEvent" }],
@@ -516,6 +521,11 @@
516
521
  "name": "iconOnly",
517
522
  "description": "Square icon-only button. Requires `label` for the accessible name.",
518
523
  "type": "boolean"
524
+ },
525
+ {
526
+ "name": "hostAriaLabel",
527
+ "description": "A host `aria-label` never reaches assistive tech on its own — light DOM has no\nimplicit ARIA relationship between a custom element and its rendered children, so an\n`aria-label` written on `<baht-button>` stays on the host and the inner native\n`<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into\nthis property forwards it to the inner button's `aria-label`, taking priority over\n`label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real\naccessible name without `icon-only`. Mapped to the plain `aria-label` attribute so\nhosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to\navoid shadowing the platform's own ARIAMixin.",
528
+ "type": "string"
519
529
  }
520
530
  ],
521
531
  "events": [{ "name": "name", "type": "CustomEvent" }]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bank20baht-ui",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "JSON-driven form + workflow engine. Send JSON in, get an interactive form out.",
6
6
  "license": "MIT",