bank20baht-ui 0.2.4 → 0.2.6

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.
@@ -1,6 +1,38 @@
1
- import { TemplateResult } from 'lit';
1
+ import { PropertyValues, TemplateResult } from 'lit';
2
2
  import { BahtFieldBase } from '../base.js';
3
- /** Value = ISO date string (yyyy-mm-dd). */
3
+ /**
4
+ * Calendar field on top of flatpickr. The VALUE is always ISO `yyyy-mm-dd`
5
+ * regardless of `field.dateFormat` (display only):
6
+ * - `dateMode: 'single'` (default) -> `string | null`
7
+ * - `dateMode: 'range'` -> `[start, end] | null` (inclusive)
8
+ * - `dateMode: 'multiple'` -> `string[]`
9
+ * `min` / `max` bound the calendar; `disabledDates` (ISO days or `a/b`
10
+ * intervals) and `disabledWeekdays` (0 = Sun … 6 = Sat) grey days out — and
11
+ * the engine re-checks them on validate, so the calendar is UX, not the gate.
12
+ *
13
+ * The calendar renders INSIDE the element (flatpickr `static`), so it lives in
14
+ * light DOM like every other node and picks up a theme set on any ancestor.
15
+ * Give a clipping ancestor (`overflow: hidden`) room, or it hides the popup.
16
+ */
4
17
  export declare class BahtDatepicker extends BahtFieldBase {
18
+ private fp;
19
+ /**
20
+ * What we last told the host. A controlled host re-feeds `value` after the
21
+ * event, but a slow (or absent) re-feed must not make onClose re-emit the
22
+ * pick onChange already sent — compare against this until `value` catches up.
23
+ */
24
+ private lastEmitted;
5
25
  protected renderControl(): TemplateResult;
26
+ connectedCallback(): void;
27
+ disconnectedCallback(): void;
28
+ protected updated(changed: PropertyValues<this>): void;
29
+ private teardown;
30
+ /** Push the host-fed `value` into the calendar without echoing an event. */
31
+ private syncValue;
32
+ private options;
33
+ private emitIfChanged;
34
+ /** Host value (any mode) -> the Date list flatpickr works with. */
35
+ private datesOf;
36
+ /** flatpickr's selection -> the value shape of the field's mode. */
37
+ private toValue;
6
38
  }
@@ -1,6 +1,23 @@
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
+ /**
5
+ * One column: a plain label, or a reference column with its own fields that
6
+ * the picker can dot-walk into (`requester.department`). Fields nest.
7
+ */
8
+ export type ColumnSpec = {
9
+ label: string;
10
+ fields?: ColumnMap;
11
+ };
12
+ export type ColumnMap = Record<string, string | ColumnSpec>;
13
+ /** What the picker committed: dotted keys plus the labels the header shows. */
14
+ export type ColumnsChangeDetail = {
15
+ visibleColumns: string[];
16
+ columns: Array<{
17
+ key: string;
18
+ label: string;
19
+ }>;
20
+ };
4
21
  /**
5
22
  * Server-side data table. `items` is ONE page of rows — the component never
6
23
  * slices or fetches; on `baht-page-change` the host fetches the next page and
@@ -25,9 +42,24 @@ export type CellRenderer = (value: unknown, row: Record<string, unknown>) => unk
25
42
  * Available/Selected dual-list modal (add/remove, reorder, Select all/Clear
26
43
  * all) derived from `columns` (or the first row's keys), no host wiring
27
44
  * required. Set `t.columnPicker = false` (JS property — a boolean attribute
28
- * can't express "off") to hide it:
45
+ * can't express "off") to hide it. OK emits `baht-columns-change` with the
46
+ * chosen keys and their labels; persist that and feed it back as
47
+ * `visibleColumns` to remember a person's layout:
48
+ *
49
+ * t.addEventListener('baht-columns-change', (e) => save(e.detail.visibleColumns));
29
50
  *
30
- * t.addEventListener('baht-columns-change', (e) => console.log(e.detail.visibleColumns));
51
+ * Dot-walking (spec: reference columns). A column may declare `fields`, in
52
+ * which case the picker lets the person expand it and pick a sub-field —
53
+ * the key becomes `requester.department`, the header reads
54
+ * "Requested by › Department", and the cell resolves the path on the row.
55
+ * Rows whose values are plain objects get the same treatment automatically
56
+ * when no `columns` map is given.
57
+ *
58
+ * t.columns = {
59
+ * number: 'Number',
60
+ * requester: { label: 'Requested by', fields: { name: 'Name', department: 'Department' } },
61
+ * };
62
+ * t.visibleColumns = ['number', 'requester.name'];
31
63
  *
32
64
  * `visibleColumns` still works standalone for a host-controlled subset (no
33
65
  * refetch) — pass it explicitly and the built-in picker (if also enabled)
@@ -36,8 +68,8 @@ export type CellRenderer = (value: unknown, row: Record<string, unknown>) => unk
36
68
  export declare class BahtTable extends BahtElement {
37
69
  /** Rows of the CURRENT page. */
38
70
  items: Record<string, unknown>[];
39
- /** column key -> display name; omitted = derive keys from the first row. */
40
- columns: Record<string, string>;
71
+ /** column key -> display name, or a reference column with `fields` (property); empty = derive keys from the first row (`snake_case` -> `Title Case`). */
72
+ columns: ColumnMap;
41
73
  loading: boolean;
42
74
  error: boolean;
43
75
  errorMessage: string;
@@ -50,9 +82,9 @@ export declare class BahtTable extends BahtElement {
50
82
  rowKey: string;
51
83
  /** Rows emit baht-row-click + get hover/keyboard affordances. */
52
84
  clickable: boolean;
53
- /** Custom cell renderers by column key (functions — a property, never JSON). */
85
+ /** Custom cell renderers by column key, dotted keys included (functions — a property, never JSON). */
54
86
  renderers: Record<string, CellRenderer>;
55
- /** Subset + order of `columns` to render; empty/omitted = show all. */
87
+ /** Subset + order of columns to render (dotted keys allowed); empty/omitted = show all top-level columns. */
56
88
  visibleColumns: string[];
57
89
  /** Built-in "Columns" toggle — derives its options from `columns`/rows. Set `column-picker="false"` to hide it. */
58
90
  columnPicker: boolean;
@@ -70,22 +102,44 @@ export declare class BahtTable extends BahtElement {
70
102
  /** Highlighted (not yet moved) options in each list. */
71
103
  private pickAvail;
72
104
  private pickSel;
105
+ /** Reference columns unfolded in the Available tree (dot-walk). */
106
+ private expanded;
73
107
  private readonly onPickerKeydown;
74
108
  disconnectedCallback(): void;
109
+ protected willUpdate(changed: Map<PropertyKey, unknown>): void;
110
+ /** The declared map, or one derived from the first row (nested objects become reference columns). */
111
+ private get spec();
112
+ /** Walk a dotted key through the spec; undefined when no such column exists. */
113
+ private specAt;
114
+ private isReference;
115
+ private childrenOf;
116
+ /** Every pickable key, reference fields included (`requester`, `requester.name`, ...). */
117
+ private allPaths;
75
118
  private get allColumnKeys();
76
119
  private get columnKeys();
120
+ /** "Requested by › Department" for a dotted key; the plain label otherwise. */
121
+ private labelFor;
122
+ /** The row's value at a dotted key (`requester.department`). */
123
+ private static valueAt;
77
124
  private openPicker;
78
125
  private closePicker;
79
126
  private static selectedValues;
127
+ private toggleAvail;
128
+ private toggleExpanded;
80
129
  private addToSelected;
81
130
  private removeFromSelected;
82
131
  private moveSelected;
83
132
  private confirmPicker;
84
- private labelFor;
85
133
  private emitRow;
86
134
  private cell;
87
135
  private renderBody;
88
136
  private renderColumnToggle;
137
+ /**
138
+ * One row of the Available tree. A reference column gets an expander that
139
+ * unfolds its fields (the dot-walk); every row is a toggle button so the
140
+ * list works from the keyboard without the native multi-select.
141
+ */
142
+ private renderAvailRow;
89
143
  private renderColumnModal;
90
144
  protected render(): TemplateResult;
91
145
  }
@@ -2,7 +2,7 @@ export { BahtButton, type ButtonVariant, type ButtonSize } from './baht-button.j
2
2
  export { renderIcon, isIconName, type IconName } from './icons.js';
3
3
  export { BahtBadge, type BadgeTone } from './baht-badge.js';
4
4
  export { BahtCode, type CodeVariant } from './baht-code.js';
5
- export { BahtTable, type CellRenderer } from './baht-table.js';
5
+ export { BahtTable, type CellRenderer, type ColumnMap, type ColumnSpec, type ColumnsChangeDetail, } from './baht-table.js';
6
6
  export { BahtList, type ListItem } from './baht-list.js';
7
7
  export { BahtPagination } from './baht-pagination.js';
8
8
  export { BahtModal } from './baht-modal.js';
@@ -0,0 +1,29 @@
1
+ import { DateMode, FieldDescriptor } from './types.js';
2
+ /** ISO day -> local Date at midnight, or null when malformed / not a real day. */
3
+ export declare function parseIsoDate(iso: unknown): Date | null;
4
+ /** Local Date -> ISO day. */
5
+ export declare function formatIsoDate(date: Date): string;
6
+ export declare const dateModeOf: (field: FieldDescriptor) => DateMode;
7
+ /**
8
+ * Every ISO day a date value holds, whatever the mode: a single string, a
9
+ * `[start, end]` range (both ends only — the interior is implied) or a list.
10
+ * Non-string entries are dropped; shape errors are the validator's job.
11
+ */
12
+ export declare function isoDaysOf(value: unknown): string[];
13
+ /**
14
+ * One `disabledDates` entry: an ISO day or an inclusive ISO interval
15
+ * `start/end`. Returns `[from, to]` (from === to for a single day) or null.
16
+ */
17
+ export declare function parseDisabledEntry(entry: string): [Date, Date] | null;
18
+ /**
19
+ * True when `iso` falls on a disabled weekday or inside a disabled date /
20
+ * interval of the descriptor. Malformed entries never disable anything.
21
+ */
22
+ export declare function isDateDisabled(field: FieldDescriptor, iso: string): boolean;
23
+ /**
24
+ * Days between a range's ends (inclusive) that a picker would step over —
25
+ * so a range can be rejected when a holiday sits INSIDE it. Capped so a
26
+ * hostile `["1900-01-01","2999-12-31"]` submission cannot spin the server;
27
+ * beyond the cap only the ends are checked.
28
+ */
29
+ export declare function rangeInteriorDays(start: string, end: string, cap?: number): string[];
@@ -6,6 +6,7 @@ export interface MessageCatalog {
6
6
  max: string;
7
7
  pattern: string;
8
8
  email: string;
9
+ dateDisabled: string;
9
10
  compareEq: string;
10
11
  compareNe: string;
11
12
  compareGt: string;
@@ -11,3 +11,4 @@ export { resolveNext, walkPath } from './engine/graph.js';
11
11
  export { evalCondition, readRef } from './engine/conditions.js';
12
12
  export { evalValueExpr } from './engine/expressions.js';
13
13
  export { displayValue, type DisplayValue } from './display.js';
14
+ export { formatIsoDate, isDateDisabled, parseDisabledEntry, parseIsoDate } from './dates.js';
@@ -1,6 +1,8 @@
1
1
  /** Control types supported by v1 (spec 01 §2). */
2
2
  export type FieldType = 'text' | 'textarea' | 'number' | 'email' | 'password' | 'dropdown' | 'multiselect' | 'checkbox' | 'radio' | 'toggle' | 'date' | 'file' | 'label' | 'section' | 'repeater';
3
3
  export type FieldWidth = '1/4' | '1/2' | '3/4' | 'full';
4
+ /** `date` field selection mode (spec 01 §2). */
5
+ export type DateMode = 'single' | 'range' | 'multiple';
4
6
  export interface FieldOption {
5
7
  key: string;
6
8
  label: string;
@@ -48,6 +50,25 @@ export interface FieldDescriptor {
48
50
  minLength?: number;
49
51
  min?: number | string;
50
52
  max?: number | string;
53
+ /**
54
+ * date: selection mode. 'single' (default) -> value is one ISO string;
55
+ * 'range' -> value is `[start, end]` (both ISO, inclusive); 'multiple' ->
56
+ * value is an ISO string[] in pick order.
57
+ */
58
+ dateMode?: DateMode;
59
+ /**
60
+ * date: DISPLAY format of the typed/rendered text (flatpickr tokens, e.g.
61
+ * 'd/m/Y', 'j M Y'). The VALUE stays ISO yyyy-mm-dd regardless.
62
+ */
63
+ dateFormat?: string;
64
+ /**
65
+ * date: days that cannot be picked — each entry an ISO date ('2026-04-13')
66
+ * or an inclusive ISO interval ('2026-04-13/2026-04-15'). Enforced by the
67
+ * engine too, so a hand-crafted submission cannot bypass the calendar.
68
+ */
69
+ disabledDates?: string[];
70
+ /** date: weekdays that cannot be picked, 0 = Sunday … 6 = Saturday. */
71
+ disabledWeekdays?: number[];
51
72
  step?: number;
52
73
  rows?: number;
53
74
  accept?: string;