bank20baht-ui 0.7.2 → 0.7.4

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.
@@ -9,11 +9,15 @@ import { BahtChartBase, ChartDatePoint } from './charts.js';
9
9
  * el.values = [{ date: '2026-09-01', value: 420 }];
10
10
  * el.from = '2026-09-01'; el.to = '2026-09-30';
11
11
  *
12
- * `show-values` swaps the compact GitHub-style weeks-as-columns layout for
13
- * a roomier month grid (weeks as rows) that prints the day number and the
14
- * formatted value inside every cell, with today's cell outlined:
12
+ * `show-values` swaps the compact GitHub-style SVG (weeks as columns) for a
13
+ * month grid rendered as HTML (weeks as rows): seven weekday headers, one
14
+ * focusable button per day carrying the day number and the formatted value,
15
+ * today's cell outlined. Being HTML, the grid fills its container while text
16
+ * keeps its real size — nothing is scaled. Cell height follows
17
+ * `--baht-heatmap-cell-min` (56px). The two darkest ramp steps flip to light
18
+ * text.
15
19
  *
16
- * <baht-heatmap show-values format="currency" .values=${daily}></baht-heatmap>
20
+ * <baht-heatmap show-values format="currency" zero-label="none" .values=${daily}></baht-heatmap>
17
21
  */
18
22
  export declare class BahtHeatmap extends BahtChartBase {
19
23
  /** `{ date: 'YYYY-MM-DD', value }[]`; days with no entry render as empty. */
@@ -36,6 +40,10 @@ export declare class BahtHeatmap extends BahtChartBase {
36
40
  protected render(): TemplateResult;
37
41
  /** Compact GitHub-style layout: weeks as columns, weekdays as rows. */
38
42
  private renderCompact;
39
- /** `show-values` layout: weeks as rows, weekdays as columns, day number + value in each cell. */
43
+ /**
44
+ * `show-values` layout: an HTML month grid — weeks as rows, weekdays as
45
+ * columns, the day number and value printed in each cell. Buttons give
46
+ * every day focus and a role for free; the tooltip rides on focus/hover.
47
+ */
40
48
  private renderCalendar;
41
49
  }
@@ -1,145 +1,150 @@
1
- import { html as y, nothing as A, svg as w } from "lit";
2
- import { property as m, customElement as O } from "lit/decorators.js";
3
- import { BahtChartBase as R, chartSeqColor as D, renderTableTwin as P } from "./charts.js";
4
- var V = Object.defineProperty, H = Object.getOwnPropertyDescriptor, p = (o, e, h, a) => {
5
- for (var r = a > 1 ? void 0 : a ? H(e, h) : e, n = o.length - 1, s; n >= 0; n--)
6
- (s = o[n]) && (r = (a ? s(e, h, r) : s(r)) || r);
7
- return a && r && V(e, h, r), r;
1
+ import { html as u, nothing as T, svg as k } from "lit";
2
+ import { property as m, customElement as _ } from "lit/decorators.js";
3
+ import { BahtChartBase as z, chartSeqColor as x, renderTableTwin as A } from "./charts.js";
4
+ var O = Object.defineProperty, P = Object.getOwnPropertyDescriptor, c = (t, a, r, e) => {
5
+ for (var n = e > 1 ? void 0 : e ? P(a, r) : a, i = t.length - 1, s; i >= 0; i--)
6
+ (s = t[i]) && (n = (e ? s(a, r, n) : s(n)) || n);
7
+ return e && n && O(a, r, n), n;
8
8
  };
9
- const L = 22, g = 24, B = 34, M = 18, _ = 46, v = 52, k = 16, S = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
10
- function E(o) {
11
- const e = /^(\d{4})-(\d{2})-(\d{2})/.exec(o ?? "");
12
- return e ? new Date(Date.UTC(+e[1], +e[2] - 1, +e[3])) : null;
9
+ const D = 22, y = 24, S = 34, C = 18, E = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
10
+ function M(t) {
11
+ const a = /^(\d{4})-(\d{2})-(\d{2})/.exec(t ?? "");
12
+ return a ? new Date(Date.UTC(+a[1], +a[2] - 1, +a[3])) : null;
13
13
  }
14
- function x(o) {
15
- return o.toISOString().slice(0, 10);
14
+ function L(t) {
15
+ return t.toISOString().slice(0, 10);
16
16
  }
17
- let c = class extends R {
17
+ function V() {
18
+ const t = /* @__PURE__ */ new Date(), a = String(t.getMonth() + 1).padStart(2, "0"), r = String(t.getDate()).padStart(2, "0");
19
+ return `${t.getFullYear()}-${a}-${r}`;
20
+ }
21
+ let p = class extends z {
18
22
  constructor() {
19
23
  super(...arguments), this.values = [], this.from = "", this.to = "", this.lessLabel = "Less", this.moreLabel = "More", this.zeroLabel = "—", this.showValues = !1, this.label = "";
20
24
  }
21
25
  get range() {
22
- const o = (this.values ?? []).map((a) => E(a.date)).filter((a) => !!a), e = E(this.from) ?? (o.length ? new Date(Math.min(...o.map((a) => +a))) : null), h = E(this.to) ?? (o.length ? new Date(Math.max(...o.map((a) => +a))) : null);
23
- return !e || !h || +h < +e ? null : { start: e, end: h };
26
+ const t = (this.values ?? []).map((e) => M(e.date)).filter((e) => !!e), a = M(this.from) ?? (t.length ? new Date(Math.min(...t.map((e) => +e))) : null), r = M(this.to) ?? (t.length ? new Date(Math.max(...t.map((e) => +e))) : null);
27
+ return !a || !r || +r < +a ? null : { start: a, end: r };
24
28
  }
25
29
  render() {
26
- const o = this.hold({ values: this.values ?? [], range: this.range }), e = o.range;
27
- if (!e || o.values.length === 0)
28
- return y`<div class=${this.rootClass("baht-heatmap")}>${this.renderEmpty()}</div>`;
29
- const h = new Map(o.values.map((l) => [l.date.slice(0, 10), l.value])), a = [], r = new Date(e.start), n = (e.start.getUTCDay() + 6) % 7;
30
+ const t = this.hold({ values: this.values ?? [], range: this.range }), a = t.range;
31
+ if (!a || t.values.length === 0)
32
+ return u`<div class=${this.rootClass("baht-heatmap")}>${this.renderEmpty()}</div>`;
33
+ const r = new Map(t.values.map((l) => [l.date.slice(0, 10), l.value])), e = [], n = new Date(a.start), i = (a.start.getUTCDay() + 6) % 7;
30
34
  let s = 0;
31
- for (; +r <= +e.end; ) {
32
- const l = x(r), $ = (r.getUTCDay() + 6) % 7, d = Math.floor((n + s) / 7);
33
- a.push({ iso: l, value: h.has(l) ? h.get(l) : null, weekday: $, week: d }), r.setUTCDate(r.getUTCDate() + 1), s += 1;
35
+ for (; +n <= +a.end; ) {
36
+ const l = L(n), v = (n.getUTCDay() + 6) % 7, f = Math.floor((i + s) / 7);
37
+ e.push({ iso: l, value: r.has(l) ? r.get(l) : null, weekday: v, week: f }), n.setUTCDate(n.getUTCDate() + 1), s += 1;
34
38
  }
35
- const t = Math.max(1, ...a.map((l) => l.value ?? 0)), u = (l) => Math.min(4, Math.max(0, Math.ceil(l / t * 5) - 1)), i = a.reduce((l, $) => l + ($.value ?? 0), 0), b = this.label || `Calendar heatmap, ${a.length} days from ${x(e.start)} to ${x(e.end)}, total ${this.fmt(i)}`, f = x(/* @__PURE__ */ new Date()), T = this.showValues ? this.renderCalendar(a, u, f, b) : this.renderCompact(a, u, b);
36
- return y`<div class=${this.rootClass("baht-heatmap")}>
37
- ${T}
39
+ const h = Math.max(1, ...e.map((l) => l.value ?? 0)), o = (l) => Math.min(4, Math.max(0, Math.ceil(l / h * 5) - 1)), b = e.reduce((l, v) => l + (v.value ?? 0), 0), $ = this.label || `Calendar heatmap, ${e.length} days from ${L(a.start)} to ${L(a.end)}, total ${this.fmt(b)}`, d = V(), g = this.showValues ? this.renderCalendar(e, o, d, $) : this.renderCompact(e, o, $);
40
+ return u`<div class=${this.rootClass("baht-heatmap")}>
41
+ ${g}
38
42
  ${this.renderTooltip()}
39
43
  <div class="baht-heatmap-scale">
40
44
  <span class="baht-heatmap-scale-label">${this.lessLabel}</span>
41
45
  ${[0, 1, 2, 3, 4].map(
42
- (l) => y`<span
46
+ (l) => u`<span
43
47
  class="baht-chart-swatch"
44
- style=${`background:${D(l)}`}
48
+ style=${`background:${x(l)}`}
45
49
  aria-hidden="true"
46
50
  ></span>`
47
51
  )}
48
52
  <span class="baht-heatmap-scale-label">${this.moreLabel}</span>
49
53
  </div>
50
- ${this.showTable ? P(
54
+ ${this.showTable ? A(
51
55
  this.label || "Calendar heatmap data",
52
56
  ["Date", "Value"],
53
- a.map((l) => [l.iso, l.value == null ? "—" : this.fmt(l.value)])
54
- ) : A}
57
+ e.map((l) => [l.iso, l.value == null ? "—" : this.fmt(l.value)])
58
+ ) : T}
55
59
  </div>`;
56
60
  }
57
61
  /** Compact GitHub-style layout: weeks as columns, weekdays as rows. */
58
- renderCompact(o, e, h) {
59
- const a = Math.max(1, ...o.map((s) => s.week + 1)), r = B + a * g, n = M + 7 * g + 4;
60
- return y`<svg
62
+ renderCompact(t, a, r) {
63
+ const e = Math.max(1, ...t.map((s) => s.week + 1)), n = S + e * y, i = C + 7 * y + 4;
64
+ return u`<svg
61
65
  class="baht-chart-svg baht-heatmap-svg"
62
- width=${r}
63
- height=${n}
64
- viewBox="0 0 ${r} ${n}"
66
+ width=${n}
67
+ height=${i}
68
+ viewBox="0 0 ${n} ${i}"
65
69
  preserveAspectRatio="xMinYMin meet"
66
70
  role="img"
67
- aria-label=${h}
71
+ aria-label=${r}
68
72
  >
69
- ${S.map(
70
- (s, t) => t % 2 === 0 ? w`<text class="baht-chart-tick" x="0" y=${M + t * g + L / 2 + 4}>${s}</text>` : A
73
+ ${E.map(
74
+ (s, h) => h % 2 === 0 ? k`<text class="baht-chart-tick" x="0" y=${C + h * y + D / 2 + 4}>${s}</text>` : T
71
75
  )}
72
- ${o.map((s) => {
73
- const t = s.value == null ? `${s.iso}: no activity` : `${s.iso}: ${this.fmt(s.value)}`, u = s.value == null || s.value <= 0 ? "var(--baht-chart-grid)" : D(e(s.value));
74
- return w`<rect class="baht-heatmap-cell" x=${B + s.week * g} y=${M + s.weekday * g}
75
- width=${L} height=${L} rx="3" fill=${u} tabindex="0" role="button" aria-label=${t}
76
- @pointermove=${(i) => this.showTip(i, t)}
76
+ ${t.map((s) => {
77
+ const h = s.value == null ? `${s.iso}: no activity` : `${s.iso}: ${this.fmt(s.value)}`, o = s.value == null || s.value <= 0 ? "var(--baht-chart-grid)" : x(a(s.value));
78
+ return k`<rect class="baht-heatmap-cell" x=${S + s.week * y} y=${C + s.weekday * y}
79
+ width=${D} height=${D} rx="3" fill=${o} tabindex="0" role="button" aria-label=${h}
80
+ @pointermove=${(b) => this.showTip(b, h)}
77
81
  @pointerleave=${() => this.hideTip()}
78
- @focus=${(i) => this.showTip(i, t)}
82
+ @focus=${(b) => this.showTip(b, h)}
79
83
  @blur=${() => this.hideTip()}/>`;
80
84
  })}
81
85
  </svg>`;
82
86
  }
83
- /** `show-values` layout: weeks as rows, weekdays as columns, day number + value in each cell. */
84
- renderCalendar(o, e, h, a) {
85
- const r = Math.max(1, ...o.map((t) => t.week + 1)), n = 7 * v, s = k + r * v;
86
- return y`<svg
87
- class="baht-chart-svg baht-heatmap-svg baht-heatmap-svg--calendar"
88
- width=${n}
89
- height=${s}
90
- viewBox="0 0 ${n} ${s}"
91
- preserveAspectRatio="xMinYMin meet"
92
- role="img"
93
- aria-label=${a}
94
- >
95
- ${S.map(
96
- (t, u) => w`<text class="baht-chart-tick baht-heatmap-weekday" x=${u * v + v / 2} y=${k - 4} text-anchor="middle">${t}</text>`
97
- )}
98
- ${o.map((t) => {
99
- const u = t.iso === h, i = t.value == null || t.value <= 0, b = i ? null : e(t.value), f = i ? "var(--baht-color-surface)" : D(b), T = b != null && b <= 1, l = t.weekday * v, $ = k + t.week * v, d = t.value == null ? `${t.iso}: no activity` : `${t.iso}: ${this.fmt(t.value)}`, U = +t.iso.slice(8, 10), z = i ? this.zeroLabel : this.fmt(t.value);
100
- return w`<g
101
- class="baht-heatmap-day${u ? " baht-heatmap-day--today" : ""}${i ? " baht-heatmap-day--zero" : ""}${T ? " baht-heatmap-day--dark" : ""}"
102
- tabindex="0" role="button" aria-label=${d}
103
- @pointermove=${(C) => this.showTip(C, d)}
87
+ /**
88
+ * `show-values` layout: an HTML month grid — weeks as rows, weekdays as
89
+ * columns, the day number and value printed in each cell. Buttons give
90
+ * every day focus and a role for free; the tooltip rides on focus/hover.
91
+ */
92
+ renderCalendar(t, a, r, e) {
93
+ var h;
94
+ const n = ((h = t[0]) == null ? void 0 : h.weekday) ?? 0, i = t.length ? 6 - t[t.length - 1].weekday : 0, s = (o) => Array.from(
95
+ { length: o },
96
+ () => u`<span class="baht-heatmap-day baht-heatmap-day--pad" aria-hidden="true"></span>`
97
+ );
98
+ return u`<div class="baht-heatmap-cal" role="group" aria-label=${e}>
99
+ ${E.map((o) => u`<span class="baht-heatmap-weekday" aria-hidden="true">${o}</span>`)}
100
+ ${s(n)}
101
+ ${t.map((o) => {
102
+ const b = o.iso === r, $ = o.value == null || o.value <= 0, d = $ ? null : a(o.value), g = d != null && d >= 3, l = o.value == null ? `${o.iso}: no activity` : `${o.iso}: ${this.fmt(o.value)}`, v = +o.iso.slice(8, 10), f = $ ? this.zeroLabel : this.fmt(o.value), B = `baht-heatmap-day${b ? " baht-heatmap-day--today" : ""}${$ ? " baht-heatmap-day--zero" : ""}${g ? " baht-heatmap-day--dark" : ""}`, U = d == null ? "" : `--baht-heatmap-fill:${x(d)}`;
103
+ return u`<button
104
+ type="button"
105
+ class=${B}
106
+ style=${U || T}
107
+ aria-label=${l}
108
+ @pointermove=${(w) => this.showTip(w, l)}
104
109
  @pointerleave=${() => this.hideTip()}
105
- @focus=${(C) => this.showTip(C, d)}
110
+ @focus=${(w) => this.showTip(w, l)}
106
111
  @blur=${() => this.hideTip()}
107
112
  >
108
- <rect class="baht-heatmap-day-cell" x=${l + 2} y=${$ + 2} width=${_} height=${_} rx="7" fill=${f}/>
109
- <text class="baht-heatmap-day-num" x=${l + 8} y=${$ + 16}>${U}</text>
110
- <text class="baht-heatmap-day-value" x=${l + 8} y=${$ + 33}>${z}</text>
111
- </g>`;
113
+ <span class="baht-heatmap-day-num">${v}</span>
114
+ <span class="baht-heatmap-day-value">${f}</span>
115
+ </button>`;
112
116
  })}
113
- </svg>`;
117
+ ${s(i)}
118
+ </div>`;
114
119
  }
115
120
  };
116
- p([
121
+ c([
117
122
  m({ attribute: !1 })
118
- ], c.prototype, "values", 2);
119
- p([
123
+ ], p.prototype, "values", 2);
124
+ c([
120
125
  m()
121
- ], c.prototype, "from", 2);
122
- p([
126
+ ], p.prototype, "from", 2);
127
+ c([
123
128
  m()
124
- ], c.prototype, "to", 2);
125
- p([
129
+ ], p.prototype, "to", 2);
130
+ c([
126
131
  m({ attribute: "less-label" })
127
- ], c.prototype, "lessLabel", 2);
128
- p([
132
+ ], p.prototype, "lessLabel", 2);
133
+ c([
129
134
  m({ attribute: "more-label" })
130
- ], c.prototype, "moreLabel", 2);
131
- p([
135
+ ], p.prototype, "moreLabel", 2);
136
+ c([
132
137
  m({ attribute: "zero-label" })
133
- ], c.prototype, "zeroLabel", 2);
134
- p([
138
+ ], p.prototype, "zeroLabel", 2);
139
+ c([
135
140
  m({ type: Boolean, attribute: "show-values" })
136
- ], c.prototype, "showValues", 2);
137
- p([
141
+ ], p.prototype, "showValues", 2);
142
+ c([
138
143
  m()
139
- ], c.prototype, "label", 2);
140
- c = p([
141
- O("baht-heatmap")
142
- ], c);
144
+ ], p.prototype, "label", 2);
145
+ p = c([
146
+ _("baht-heatmap")
147
+ ], p);
143
148
  export {
144
- c as BahtHeatmap
149
+ p as BahtHeatmap
145
150
  };
@@ -1,6 +1,15 @@
1
1
  import { TemplateResult } from 'lit';
2
2
  import { BahtElement } from '../base.js';
3
3
  export type CellRenderer = (value: unknown, row: Record<string, unknown>) => unknown;
4
+ /** Group key for `groupBy`: a (dotted) row key, or a function of the row. */
5
+ export type GroupKey = string | ((row: Record<string, unknown>) => unknown);
6
+ /** Group header content: the group's value plus the rows in that group. */
7
+ export type GroupLabelRenderer = (value: unknown, rows: Record<string, unknown>[]) => unknown;
8
+ /** `baht-group-toggle` detail: which group and whether it is now collapsed. */
9
+ export type GroupToggleDetail = {
10
+ value: unknown;
11
+ collapsed: boolean;
12
+ };
4
13
  export type ColumnAlign = 'start' | 'center' | 'end';
5
14
  /**
6
15
  * One column: a plain label, or a spec. A spec may carry `fields` (a
@@ -85,6 +94,25 @@ export type ColumnsChangeDetail = {
85
94
  * t.sort = { key: 'amount', direction: 'desc' };
86
95
  * t.addEventListener('baht-sort-change', (e) => refetch({ sort: e.detail }));
87
96
  * t.rowActions = true; // trailing "..." button -> baht-row-actions { row, index, id }
97
+ *
98
+ * Grouping (ServiceNow "Group by" on a list): `group-by` names a (dotted)
99
+ * row key, or pass a function as the property. Every row on the CURRENT page
100
+ * is bucketed by that value in first-appearance order; each bucket is its own
101
+ * `<tbody>` headed by a `th[scope=rowgroup]` disclosure reading
102
+ * "Column: value (n)". Click the header to collapse/expand the group
103
+ * (`baht-group-toggle`); `group-collapsed` starts them all closed, and the
104
+ * open/closed state resets when `items`/`page` change. Grouping is per page
105
+ * only — it rides on the existing pagination and never fetches or sorts, so
106
+ * a value split across pages shows up on each. `groupLabel` replaces the
107
+ * default header text (it still sits inside the toggle). Row numbers and
108
+ * event indexes stay page-global. `hide-header` drops the column header row
109
+ * for list-style layouts.
110
+ *
111
+ * t.groupBy = 'state'; // "State: Open (12)"
112
+ * t.groupBy = (row) => row.occurred_at.slice(0, 10);
113
+ * t.groupLabel = (day, rows) => `${formatDay(day)} · ${rows.length}`;
114
+ * t.groupCollapsed = true;
115
+ * t.addEventListener('baht-group-toggle', (e) => console.log(e.detail)); // { value, collapsed }
88
116
  */
89
117
  export declare class BahtTable extends BahtElement {
90
118
  /** Rows of the CURRENT page. */
@@ -107,6 +135,16 @@ export declare class BahtTable extends BahtElement {
107
135
  renderers: Record<string, CellRenderer>;
108
136
  /** Subset + order of columns to render (dotted keys allowed); empty/omitted = show all top-level columns. */
109
137
  visibleColumns: string[];
138
+ /** Group the page's rows by this (dotted) column key — or a function of the row (property). Unset = no grouping. */
139
+ groupBy?: GroupKey;
140
+ /** Group header content (property); default is "Column: value (n)". */
141
+ groupLabel?: GroupLabelRenderer;
142
+ /** Group headers are disclosures (click to collapse). Set the JS property to `false` for static headers. */
143
+ groupCollapsible: boolean;
144
+ /** Start every group collapsed (per page — resets when `items`/`page` change). */
145
+ groupCollapsed: boolean;
146
+ /** Drop the column header row (list-style tables, e.g. grouped ledgers). */
147
+ hideHeader: boolean;
110
148
  /** Built-in "Columns" toggle — derives its options from `columns`/rows. Set `column-picker="false"` to hide it. */
111
149
  columnPicker: boolean;
112
150
  /** Total matching rows across all pages — renders "{n} results" in the toolbar. Unset = no count. */
@@ -136,6 +174,8 @@ export declare class BahtTable extends BahtElement {
136
174
  private pickSel;
137
175
  /** Reference columns unfolded in the Available tree (dot-walk). */
138
176
  private expanded;
177
+ /** Group keys the person toggled away from `groupCollapsed`; reset on a new page. */
178
+ private toggledGroups;
139
179
  /** Element focused before the picker opened — focus returns there on close. */
140
180
  private pickerPreviousFocus;
141
181
  private readonly onPickerKeydown;
@@ -183,7 +223,23 @@ export declare class BahtTable extends BahtElement {
183
223
  /** Alignment class for a column (explicit `align` beats `numeric`). */
184
224
  private alignClass;
185
225
  private cell;
226
+ private span;
227
+ private groupValue;
228
+ /** Stable identity of a group value for the collapsed set (objects by JSON, else by type+string). */
229
+ private static groupId;
230
+ /**
231
+ * The page's rows bucketed by group value, in first-appearance order. Each
232
+ * row keeps its page index so numbering and event indexes stay host-order.
233
+ */
234
+ private get groups();
235
+ private get isGrouped();
236
+ private isCollapsed;
237
+ private toggleGroup;
238
+ /** Default header: "Column: value (n)" — no column prefix for a function key. */
239
+ private defaultGroupLabel;
240
+ private renderGroups;
186
241
  private renderBody;
242
+ private renderRow;
187
243
  private renderHeaderCell;
188
244
  private get hasToolbar();
189
245
  private renderToolbar;
@@ -196,4 +252,6 @@ export declare class BahtTable extends BahtElement {
196
252
  private renderAvailRow;
197
253
  private renderColumnModal;
198
254
  protected render(): TemplateResult;
255
+ private renderHead;
256
+ private renderFooter;
199
257
  }