bank20baht-ui 0.8.2 → 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}
@@ -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 */
@@ -10100,6 +10100,35 @@
10100
10100
  "description": "Accessible summary; falls back to a generated sentence.",
10101
10101
  "attribute": "label"
10102
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
+ },
10103
10132
  {
10104
10133
  "kind": "field",
10105
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 */
@@ -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.2",
4
+ "version": "0.9.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bank20baht-ui",
3
- "version": "0.8.2",
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",