bank20baht-ui 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +3 -3
  2. package/dist/components/baht-form.d.ts +2 -0
  3. package/dist/components/baht-form.js +34 -24
  4. package/dist/components/primitives/baht-datepicker.d.ts +4 -1
  5. package/dist/components/primitives/baht-datepicker.js +29 -26
  6. package/dist/components/primitives/baht-filter.d.ts +12 -1
  7. package/dist/components/primitives/baht-filter.js +42 -32
  8. package/dist/components/primitives/baht-modal.d.ts +14 -0
  9. package/dist/components/primitives/baht-modal.js +42 -37
  10. package/dist/components/primitives/baht-repeater.d.ts +6 -1
  11. package/dist/components/primitives/baht-repeater.js +62 -55
  12. package/dist/components/primitives/baht-select.d.ts +4 -0
  13. package/dist/components/primitives/baht-select.js +50 -46
  14. package/dist/components/primitives/baht-swipe-row.d.ts +62 -0
  15. package/dist/components/primitives/baht-swipe-row.js +174 -0
  16. package/dist/components/primitives/index.d.ts +3 -2
  17. package/dist/core/engine/conditions.d.ts +21 -3
  18. package/dist/core/engine/conditions.js +62 -44
  19. package/dist/core/engine/evaluate.js +203 -111
  20. package/dist/core/i18n.d.ts +1 -0
  21. package/dist/core/i18n.js +2 -0
  22. package/dist/core/index.d.ts +2 -1
  23. package/dist/core/types.d.ts +3 -1
  24. package/dist/core/validators.d.ts +7 -2
  25. package/dist/core/validators.js +82 -74
  26. package/dist/css/components.css +205 -0
  27. package/dist/custom-elements.json +478 -6
  28. package/dist/engine.js +25 -23
  29. package/dist/index.js +168 -164
  30. package/dist/react.d.ts +10 -1
  31. package/dist/register.d.ts +1 -0
  32. package/dist/register.js +1 -0
  33. package/dist/ssr.js +3 -2
  34. package/dist/tokens.css +205 -0
  35. package/dist/vscode.html-custom-data.json +33 -5
  36. package/dist/web-types.json +71 -7
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -261,7 +261,7 @@ near-black ink (`#171717`) on white, hairline neutral borders, 44px controls, on
261
261
  (`#444FBF`), status as tint pills, hand-drawn koboyo spot illustrations for empty /
262
262
  error / upload states, IBM Plex Sans Thai / IBM Plex Mono (load the
263
263
  fonts yourself), borders-over-shadows, "paper" easing — plus an opt-in dark theme
264
- (`<html data-baht-theme="dark">`). 58 elements:
264
+ (`<html data-baht-theme="dark">`). 59 elements:
265
265
 
266
266
  ```html
267
267
  <baht-button variant="secondary" size="sm">Cancel</baht-button>
@@ -269,7 +269,7 @@ fonts yourself), borders-over-shadows, "paper" easing — plus an opt-in dark th
269
269
  <baht-table></baht-table> <baht-modal></baht-modal> <baht-toast></baht-toast> …
270
270
  ```
271
271
 
272
- - **Data (server-side paging)** — `baht-table` · `baht-list` · `baht-pagination` · `baht-filter`.
272
+ - **Data (server-side paging)** — `baht-table` · `baht-list` · `baht-swipe-row` · `baht-pagination` · `baht-filter`.
273
273
  `baht-table` ships a ServiceNow-style column picker with dot-walking into reference columns (`requester.department`) and emits `baht-columns-change` so the host can persist the layout.
274
274
  `items` is one page; `baht-page-change` asks your app to fetch the next one.
275
275
  - **Feedback** — `baht-modal` · `baht-toast` · `baht-alert` · `baht-tooltip` · `baht-progress` ·
@@ -347,7 +347,7 @@ no hand-written type declarations in your app:
347
347
  | **Angular** | template binding on properties/events | `CUSTOM_ELEMENTS_SCHEMA`, see below |
348
348
  | **Vue 3** | keep Vue from treating `baht-*` as components | `isCustomElement`, see below |
349
349
  | **Phoenix / Rails / plain HTML** | it's just HTML — works as-is | none |
350
- | **Tooling** (Storybook, docgen, custom) | machine-readable API of all 58 elements | read `dist/custom-elements.json` ([CEM standard](https://github.com/webcomponents/custom-elements-manifest)) |
350
+ | **Tooling** (Storybook, docgen, custom) | machine-readable API of all 59 elements | read `dist/custom-elements.json` ([CEM standard](https://github.com/webcomponents/custom-elements-manifest)) |
351
351
 
352
352
  **VS Code** — `.vscode/settings.json`:
353
353
 
@@ -88,6 +88,8 @@ export declare class BahtForm extends BahtElement {
88
88
  private nextVisible;
89
89
  private revealClass;
90
90
  private stateFor;
91
+ /** The row sub-field states of one repeater: `fieldStates` keys starting `key[`. */
92
+ private rowStatesFor;
91
93
  private valueFor;
92
94
  /** Debounced keystrokes still in a control land in `edits` before anyone reads them. */
93
95
  private flushTyped;
@@ -1,16 +1,16 @@
1
1
  import { nothing as g, html as p } from "lit";
2
- import { property as d, state as m, customElement as x } from "lit/decorators.js";
3
- import { repeat as E } from "lit/directives/repeat.js";
2
+ import { property as d, state as m, customElement as E } from "lit/decorators.js";
3
+ import { repeat as P } from "lit/directives/repeat.js";
4
4
  import { html as $ } from "lit/static-html.js";
5
- import { BahtElement as P, hasDom as O } from "./base.js";
6
- import { TAG_FOR_TYPE as T, WIDTH_CLASS as B, staticTag as I } from "./primitives/tag-map.js";
5
+ import { BahtElement as T, hasDom as O } from "./base.js";
6
+ import { TAG_FOR_TYPE as B, WIDTH_CLASS as I, staticTag as j } from "./primitives/tag-map.js";
7
7
  import { defaultValue as C } from "../core/defaults.js";
8
8
  import { t as D } from "../core/i18n.js";
9
9
  import { validateField as _ } from "../core/validators.js";
10
10
  import { resolveViewFields as M } from "../core/views.js";
11
- import { applyFlags as j } from "../core/flags.js";
12
- import { hasScripts as w, runScripts as F, createScriptContext as L, MAX_SCRIPT_DEPTH as V } from "../core/engine/scripts.js";
13
- import { displayValue as A } from "../core/display.js";
11
+ import { applyFlags as L } from "../core/flags.js";
12
+ import { hasScripts as w, runScripts as F, createScriptContext as A, MAX_SCRIPT_DEPTH as x } from "../core/engine/scripts.js";
13
+ import { displayValue as R } from "../core/display.js";
14
14
  import "./primitives/baht-input.js";
15
15
  import "./primitives/baht-textarea.js";
16
16
  import "./primitives/baht-number.js";
@@ -26,12 +26,12 @@ import "./primitives/baht-file.js";
26
26
  import "./primitives/baht-text.js";
27
27
  import "./primitives/baht-section.js";
28
28
  import "./primitives/baht-repeater.js";
29
- var R = Object.defineProperty, U = Object.getOwnPropertyDescriptor, h = (t, s, e, i) => {
30
- for (var r = i > 1 ? void 0 : i ? U(s, e) : s, o = t.length - 1, a; o >= 0; o--)
29
+ var U = Object.defineProperty, W = Object.getOwnPropertyDescriptor, h = (t, s, e, i) => {
30
+ for (var r = i > 1 ? void 0 : i ? W(s, e) : s, o = t.length - 1, a; o >= 0; o--)
31
31
  (a = t[o]) && (r = (i ? a(s, e, r) : a(r)) || r);
32
- return i && r && R(s, e, r), r;
32
+ return i && r && U(s, e, r), r;
33
33
  };
34
- let n = class extends P {
34
+ let n = class extends T {
35
35
  constructor() {
36
36
  super(...arguments), this.value = {}, this.registry = {}, this.showOptional = !1, this.options = {}, this.fieldStates = {}, this.flags = {}, this.readonly = !1, this.edits = {}, this.errors = [], this.scriptStates = {}, this.fieldMessages = {}, this.formMessages = [], this.scriptContext = null, this.scriptContextForm = null, this.scriptDepth = 0, this.loadedForm = null, this.dynamicOptions = /* @__PURE__ */ new Map(), this.optionsSnapshots = /* @__PURE__ */ new Map(), this.optionsEpoch = /* @__PURE__ */ new Map(), this.warnedViews = /* @__PURE__ */ new Set(), this.flagCache = {}, this.lastVisible = {}, this.nextVisible = {};
37
37
  }
@@ -45,7 +45,7 @@ let n = class extends P {
45
45
  get effectiveForm() {
46
46
  const t = this.assignedForm;
47
47
  if (t)
48
- return (this.flagCache.form !== t || this.flagCache.flags !== this.flags) && (this.flagCache = { form: t, flags: this.flags, out: j(t, this.flags) }), this.flagCache.out;
48
+ return (this.flagCache.form !== t || this.flagCache.flags !== this.flags) && (this.flagCache = { form: t, flags: this.flags, out: L(t, this.flags) }), this.flagCache.out;
49
49
  }
50
50
  willUpdate(t) {
51
51
  t.has("value") && (this.edits = {}), (t.has("form") || t.has("flags")) && (this.dynamicOptions.clear(), this.optionsSnapshots.clear(), this.optionsEpoch.clear()), t.has("form") && (this.scriptStates = {}, this.fieldMessages = {}, this.formMessages = [], this.scriptContext = null), (t.has("form") || t.has("flags") || t.has("value") || t.has("registry")) && this.syncDynamicOptions();
@@ -111,6 +111,13 @@ let n = class extends P {
111
111
  }, e = this.scriptStates[t.key];
112
112
  return e ? { ...s, ...e } : s;
113
113
  }
114
+ /** The row sub-field states of one repeater: `fieldStates` keys starting `key[`. */
115
+ rowStatesFor(t) {
116
+ const s = `${t}[`, e = {};
117
+ for (const [i, r] of Object.entries(this.fieldStates))
118
+ i.startsWith(s) && (e[i] = r);
119
+ return e;
120
+ }
114
121
  valueFor(t) {
115
122
  return Object.hasOwn(this.edits, t.key) ? this.edits[t.key] : Object.hasOwn(this.value, t.key) ? this.value[t.key] : C(t);
116
123
  }
@@ -149,7 +156,9 @@ let n = class extends P {
149
156
  const i = this.stateFor(e);
150
157
  if (!i.visible) continue;
151
158
  const r = i.required === !!e.required ? e : { ...e, required: i.required };
152
- t.push(..._(r, this.valueFor(e), this.registry, s));
159
+ t.push(
160
+ ..._(r, this.valueFor(e), this.registry, s, this.fieldStates)
161
+ );
153
162
  }
154
163
  return t;
155
164
  }
@@ -183,7 +192,7 @@ let n = class extends P {
183
192
  // ---- client scripts (spec 01 §14) ----
184
193
  /** The `g` (g_form) for the current form — built lazily over this component's state. */
185
194
  g(t) {
186
- return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = L(t, {
195
+ return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = A(t, {
187
196
  getData: () => this.getData(),
188
197
  setValue: (s, e) => {
189
198
  this.applyScriptValue(s, e);
@@ -228,9 +237,9 @@ let n = class extends P {
228
237
  runChangeScripts(t, s, e, i) {
229
238
  const r = this.effectiveForm;
230
239
  if (!(!r || !w(r, "change", t))) {
231
- if (this.scriptDepth >= V) {
240
+ if (this.scriptDepth >= x) {
232
241
  console.warn(
233
- `[bahtui] script cascade on "${r.form_name}.${t}" exceeded ${V} levels — stopped (setValue ping-pong between change scripts?)`
242
+ `[bahtui] script cascade on "${r.form_name}.${t}" exceeded ${x} levels — stopped (setValue ping-pong between change scripts?)`
234
243
  );
235
244
  return;
236
245
  }
@@ -295,7 +304,7 @@ let n = class extends P {
295
304
  const s = this.stateFor(t);
296
305
  if (!s.visible) return [];
297
306
  const e = this.getData(), i = s.required === !!t.required ? t : { ...t, required: s.required };
298
- return _(i, this.valueFor(t), this.registry, e);
307
+ return _(i, this.valueFor(t), this.registry, e, this.fieldStates);
299
308
  }
300
309
  /** `validateOn: 'blur'` — validate just the field the user left, same as
301
310
  * how errors are shown elsewhere (merged into `this.errors`, painted by
@@ -307,18 +316,18 @@ let n = class extends P {
307
316
  }
308
317
  renderField(t) {
309
318
  var S;
310
- const s = T[t.type];
319
+ const s = B[t.type];
311
320
  if (!s)
312
321
  return console.warn(`[bahtui] unknown field type "${t.type}" — skipped`), g;
313
- const e = this.stateFor(t), i = B[t.width ?? "full"] ?? "baht-field--w-full", r = ((S = this.errors.find((u) => u.key === t.key)) == null ? void 0 : S.message) ?? null;
322
+ const e = this.stateFor(t), i = I[t.width ?? "full"] ?? "baht-field--w-full", r = ((S = this.errors.find((u) => u.key === t.key)) == null ? void 0 : S.message) ?? null;
314
323
  if (this.readonly && t.type !== "label" && t.type !== "section")
315
324
  return this.renderReadonlyField(t, i, e.visible);
316
325
  const o = t.options_from ? { ...t, options: this.dynamicOptions.get(t.key) ?? t.options ?? [] } : t, a = this.valueFor(t), l = !e.enabled, c = this.readonly || e.readonly, b = this.options.inputDebounce, v = (u) => {
317
326
  this.onControlInput(t.key, u);
318
- }, f = I(s);
327
+ }, f = j(s);
319
328
  let y;
320
329
  if (t.type === "repeater") {
321
- const u = this.errors.filter((q) => q.key.startsWith(`${t.key}[`));
330
+ const u = this.errors.filter((q) => q.key.startsWith(`${t.key}[`)), V = this.rowStatesFor(t.key);
322
331
  y = $`<${f}
323
332
  .field=${o}
324
333
  .value=${a}
@@ -329,6 +338,7 @@ let n = class extends P {
329
338
  .error=${r}
330
339
  .debounce=${b}
331
340
  .rowErrors=${u}
341
+ .rowStates=${V}
332
342
  @baht-input=${v}
333
343
  ></${f}>`;
334
344
  } else if (t.type === "file") {
@@ -395,7 +405,7 @@ let n = class extends P {
395
405
  e && this.onFieldInput(e, s);
396
406
  }
397
407
  renderReadonlyField(t, s, e) {
398
- const i = t.options_from ? this.dynamicOptions.get(t.key) ?? t.options ?? [] : t.options, r = A(t, this.valueFor(t), i), o = r.kind === "empty" ? p`<span class="baht-readonly-value baht-readonly-value--empty">${D("emptyValue")}</span>` : r.kind === "text" ? p`<span class="baht-readonly-value">${r.text}</span>` : r.kind === "chips" ? p`<span class="baht-readonly-value baht-readonly-chips"
408
+ const i = t.options_from ? this.dynamicOptions.get(t.key) ?? t.options ?? [] : t.options, r = R(t, this.valueFor(t), i), o = r.kind === "empty" ? p`<span class="baht-readonly-value baht-readonly-value--empty">${D("emptyValue")}</span>` : r.kind === "text" ? p`<span class="baht-readonly-value">${r.text}</span>` : r.kind === "chips" ? p`<span class="baht-readonly-value baht-readonly-chips"
399
409
  >${r.items.map((a) => p`<span class="baht-readonly-chip">${a}</span>`)}</span
400
410
  >` : p`<div class="baht-readonly-rows">
401
411
  ${r.rows.map(
@@ -431,7 +441,7 @@ let n = class extends P {
431
441
  </div>`
432
442
  )}
433
443
  </div>` : g}
434
- ${E(
444
+ ${P(
435
445
  this.sortedFields,
436
446
  (t) => t.key,
437
447
  (t) => this.renderField(t)
@@ -482,7 +492,7 @@ h([
482
492
  m()
483
493
  ], n.prototype, "formMessages", 2);
484
494
  n = h([
485
- x("baht-form")
495
+ E("baht-form")
486
496
  ], n);
487
497
  export {
488
498
  n as BahtForm
@@ -15,7 +15,10 @@ import { BahtFieldBase } from '../field-base.js';
15
15
  * Give a clipping ancestor (`overflow: hidden`) room, or it hides the popup.
16
16
  *
17
17
  * A footer under the grid carries **Today** (picks today — greyed out when
18
- * min/max/disabled rules exclude it) and **Clear** (empties the value). Both
18
+ * min/max/disabled rules exclude it) and **Clear** (empties the value). With
19
+ * `field.enable_time` the first button reads **Now** (i18n key `now`) and picks
20
+ * today at the current time, to the minute (seconds dropped), under the same
21
+ * min/max/disabled rules; like Today it closes the calendar. Both
19
22
  * `await el.updateComplete` resolves only once the calendar is attached (the
20
23
  * lazy flatpickr load is folded into it), so hosts and tests need no extra wait.
21
24
  * Today / Clear go through flatpickr, so they emit exactly like a click on a day. `disableMobile`
@@ -1,18 +1,18 @@
1
- import { html as g } from "lit";
2
- import { customElement as D } from "lit/decorators.js";
3
- import { ifDefined as k } from "lit/directives/if-defined.js";
1
+ import { html as y } from "lit";
2
+ import { customElement as g } from "lit/decorators.js";
3
+ import { ifDefined as D } from "lit/directives/if-defined.js";
4
4
  import { BahtFieldBase as w } from "../field-base.js";
5
- import { formatIsoDateTime as u, formatIsoDate as c, dateModeOf as h, parseIsoDate as l, isoDaysOf as C, parseIsoDateTime as v, parseDisabledEntry as _ } from "../../core/dates.js";
5
+ import { formatIsoDateTime as u, formatIsoDate as c, dateModeOf as h, parseIsoDate as l, isoDaysOf as k, parseIsoDateTime as C, parseDisabledEntry as v } from "../../core/dates.js";
6
6
  import { t as p } from "../../core/i18n.js";
7
- import { renderIcon as I } from "./icons.js";
8
- var B = Object.getOwnPropertyDescriptor, O = (t, e, a, s) => {
9
- for (var i = s > 1 ? void 0 : s ? B(e, a) : e, n = t.length - 1, r; n >= 0; n--)
7
+ import { renderIcon as _ } from "./icons.js";
8
+ var I = Object.getOwnPropertyDescriptor, B = (t, e, a, s) => {
9
+ for (var i = s > 1 ? void 0 : s ? I(e, a) : e, n = t.length - 1, r; n >= 0; n--)
10
10
  (r = t[n]) && (i = r(i) || i);
11
11
  return i;
12
12
  };
13
- let f, x;
14
- function E() {
15
- return x ?? (x = import("flatpickr").then((t) => f = t.default));
13
+ let f, O;
14
+ function x() {
15
+ return O ?? (O = import("flatpickr").then((t) => f = t.default));
16
16
  }
17
17
  let m = class extends w {
18
18
  constructor() {
@@ -25,20 +25,20 @@ let m = class extends w {
25
25
  return t;
26
26
  }
27
27
  renderControl() {
28
- return g`<span class="baht-datepicker-field">
28
+ return y`<span class="baht-datepicker-field">
29
29
  <input
30
30
  class="baht-control baht-control--date"
31
31
  id=${this.controlId}
32
32
  type="text"
33
33
  autocomplete="off"
34
- placeholder=${k(this.field.placeholder)}
34
+ placeholder=${D(this.field.placeholder)}
35
35
  ?disabled=${this.disabled}
36
36
  ?readonly=${this.readonly}
37
37
  ?required=${this.required}
38
38
  aria-invalid=${this.invalid}
39
39
  aria-describedby=${this.describedBy}
40
40
  />
41
- <span class="baht-datepicker-field-icon" aria-hidden="true">${I("calendar")}</span>
41
+ <span class="baht-datepicker-field-icon" aria-hidden="true">${_("calendar")}</span>
42
42
  </span>`;
43
43
  }
44
44
  connectedCallback() {
@@ -56,7 +56,7 @@ let m = class extends w {
56
56
  }
57
57
  if (((s = this.fp) == null ? void 0 : s.input) !== e || t.has("field") || t.has("disabled") || t.has("readonly")) {
58
58
  if (this.teardown(), !f) {
59
- this.pendingLoad ?? (this.pendingLoad = E().catch((i) => {
59
+ this.pendingLoad ?? (this.pendingLoad = x().catch((i) => {
60
60
  console.warn("[bahtui] <baht-datepicker> needs the `flatpickr` peer dependency", i);
61
61
  }).then(() => {
62
62
  this.pendingLoad = void 0, this.isConnected && this.requestUpdate();
@@ -119,7 +119,7 @@ let m = class extends w {
119
119
  const e = document.createElement("div");
120
120
  e.className = "baht-datepicker-footer";
121
121
  const a = document.createElement("button");
122
- a.type = "button", a.className = "baht-button baht-button--sm baht-datepicker-today", a.textContent = p("today"), a.addEventListener("click", () => {
122
+ a.type = "button", a.className = "baht-button baht-button--sm baht-datepicker-today", a.textContent = p(this.field.enable_time ? "now" : "today"), a.addEventListener("click", () => {
123
123
  this.pickToday();
124
124
  });
125
125
  const s = document.createElement("button");
@@ -136,18 +136,18 @@ let m = class extends w {
136
136
  }
137
137
  todayAllowed() {
138
138
  const t = o(/* @__PURE__ */ new Date()), e = l(this.field.min), a = l(this.field.max);
139
- return e && t < o(e) || a && t > o(a) ? !1 : !(b(this.field) ?? []).some((s) => F(s, t));
139
+ return e && t < o(e) || a && t > o(a) ? !1 : !(b(this.field) ?? []).some((s) => L(s, t));
140
140
  }
141
141
  pickToday() {
142
142
  const t = this.fp;
143
143
  if (!t || !this.todayAllowed()) return;
144
- const e = o(/* @__PURE__ */ new Date());
144
+ const e = this.field.enable_time ? E(/* @__PURE__ */ new Date()) : o(/* @__PURE__ */ new Date());
145
145
  switch (h(this.field)) {
146
146
  case "range":
147
147
  t.setDate([e, e], !0);
148
148
  break;
149
149
  case "multiple": {
150
- t.selectedDates.some((s) => y(s, e)) || t.setDate([...t.selectedDates, e], !0);
150
+ t.selectedDates.some((s) => this.sameInstants([s], [e])) || t.setDate([...t.selectedDates, e], !0);
151
151
  break;
152
152
  }
153
153
  case "single":
@@ -164,8 +164,8 @@ let m = class extends w {
164
164
  * a host may not always have a time to feed back (an edited record that
165
165
  * predates this field, for one). */
166
166
  datesOf(t) {
167
- const e = (a) => (this.field.enable_time ? v(a) : null) ?? l(a);
168
- return C(t).map(e).filter((a) => a !== null);
167
+ const e = (a) => (this.field.enable_time ? C(a) : null) ?? l(a);
168
+ return k(t).map(e).filter((a) => a !== null);
169
169
  }
170
170
  /** flatpickr's selection -> the value shape of the field's mode. */
171
171
  toValue(t) {
@@ -180,17 +180,20 @@ let m = class extends w {
180
180
  }
181
181
  }
182
182
  };
183
- m = O([
184
- D("baht-datepicker")
183
+ m = B([
184
+ g("baht-datepicker")
185
185
  ], m);
186
186
  function o(t) {
187
187
  return new Date(t.getFullYear(), t.getMonth(), t.getDate());
188
188
  }
189
- function y(t, e) {
190
- return c(t) === c(e);
189
+ function E(t) {
190
+ return new Date(t.getFullYear(), t.getMonth(), t.getDate(), t.getHours(), t.getMinutes());
191
191
  }
192
192
  function F(t, e) {
193
- if (t instanceof Date) return y(t, e);
193
+ return c(t) === c(e);
194
+ }
195
+ function L(t, e) {
196
+ if (t instanceof Date) return F(t, e);
194
197
  if (typeof t == "function") return t(e);
195
198
  if (typeof t == "object" && "from" in t) {
196
199
  const a = o(new Date(t.from)), s = o(new Date(t.to));
@@ -201,7 +204,7 @@ function F(t, e) {
201
204
  function b(t) {
202
205
  const e = [];
203
206
  for (const s of t.disabled_dates ?? []) {
204
- const i = _(s);
207
+ const i = v(s);
205
208
  i && e.push(i[0].getTime() === i[1].getTime() ? i[0] : { from: i[0], to: i[1] });
206
209
  }
207
210
  const a = t.disabled_weekdays;
@@ -1,6 +1,7 @@
1
1
  import { TemplateResult } from 'lit';
2
2
  import { BahtElement } from '../base.js';
3
3
  import { FieldOption } from '../../core/types.js';
4
+ export type FilterVariant = 'chips' | 'segmented';
4
5
  /**
5
6
  * Filter chip group. Controlled: emits `baht-change { selected }` (array of
6
7
  * option keys), host re-feeds `selected`. Typical use: above an <baht-table>,
@@ -10,11 +11,21 @@ import { FieldOption } from '../../core/types.js';
10
11
  * f.options = [{ key: 'running', label: 'Running' }, { key: 'failed', label: 'Failed' }];
11
12
  * f.selected = ['running'];
12
13
  * f.addEventListener('baht-change', (e) => refetch({ status: e.detail.selected }));
14
+ *
15
+ * `variant="segmented"` draws the same options as one joined segmented
16
+ * control (a single bordered group, active segment filled with the ink
17
+ * colour) — for a period switch like Week / Month / Year. Pair it with
18
+ * `multiple="false"`: a segmented control always holds one value, so clicking
19
+ * the active segment does nothing (no `baht-change`) instead of clearing it.
13
20
  */
14
21
  export declare class BahtFilter extends BahtElement {
15
22
  options: FieldOption[];
16
23
  selected: string[];
17
- /** false = radio behavior (max one active; clicking it again clears). */
24
+ /** Look: `'chips'` (default) = separate pill chips; `'segmented'` = one joined
25
+ * segmented control. With `multiple=false` a segmented control never clears. */
26
+ variant: FilterVariant;
27
+ /** false = radio behavior (max one active; clicking it again clears — except
28
+ * with `variant="segmented"`, where the active segment stays selected). */
18
29
  multiple: boolean;
19
30
  disabled: boolean;
20
31
  /** Accessible name for the chip group. */
@@ -1,22 +1,29 @@
1
1
  import { html as h } from "lit";
2
- import { property as p, customElement as c } from "lit/decorators.js";
3
- import { boolOff as d, BahtElement as b } from "../base.js";
4
- var u = Object.defineProperty, f = Object.getOwnPropertyDescriptor, i = (t, s, l, o) => {
5
- for (var e = o > 1 ? void 0 : o ? f(s, l) : s, a = t.length - 1, n; a >= 0; a--)
6
- (n = t[a]) && (e = (o ? n(s, l, e) : n(e)) || e);
7
- return o && e && u(s, l, e), e;
2
+ import { property as o, customElement as d } from "lit/decorators.js";
3
+ import { boolOff as c, BahtElement as b } from "../base.js";
4
+ var m = Object.defineProperty, u = Object.getOwnPropertyDescriptor, l = (s, t, r, a) => {
5
+ for (var e = a > 1 ? void 0 : a ? u(t, r) : t, p = s.length - 1, n; p >= 0; p--)
6
+ (n = s[p]) && (e = (a ? n(t, r, e) : n(e)) || e);
7
+ return a && e && m(t, r, e), e;
8
8
  };
9
- let r = class extends b {
9
+ let i = class extends b {
10
10
  constructor() {
11
- super(...arguments), this.options = [], this.selected = [], this.multiple = !0, this.disabled = !1, this.label = "Filters";
11
+ super(...arguments), this.options = [], this.selected = [], this.variant = "chips", this.multiple = !0, this.disabled = !1, this.label = "Filters";
12
12
  }
13
- toggle(t) {
14
- const s = this.selected.includes(t);
15
- let l;
16
- this.multiple ? (l = s ? this.selected.filter((e) => e !== t) : [...this.selected, t], l = this.options.map((e) => e.key).filter((e) => l.includes(e))) : l = s ? [] : [t], this.emit("baht-change", { selected: l });
13
+ toggle(s) {
14
+ const t = this.selected.includes(s);
15
+ let r;
16
+ if (this.multiple)
17
+ r = t ? this.selected.filter((e) => e !== s) : [...this.selected, s], r = this.options.map((e) => e.key).filter((e) => r.includes(e));
18
+ else {
19
+ if (t && this.variant === "segmented") return;
20
+ r = t ? [] : [s];
21
+ }
22
+ this.emit("baht-change", { selected: r });
17
23
  }
18
24
  render() {
19
- return h`<div class="baht-filter" role="group" aria-label=${this.label}>
25
+ const s = this.variant === "segmented" ? " baht-filter--segmented" : "";
26
+ return h`<div class="baht-filter${s}" role="group" aria-label=${this.label}>
20
27
  ${this.options.map(
21
28
  (t) => h`<button
22
29
  type="button"
@@ -30,24 +37,27 @@ let r = class extends b {
30
37
  </div>`;
31
38
  }
32
39
  };
33
- i([
34
- p({ attribute: !1 })
35
- ], r.prototype, "options", 2);
36
- i([
37
- p({ attribute: !1 })
38
- ], r.prototype, "selected", 2);
39
- i([
40
- p({ converter: d })
41
- ], r.prototype, "multiple", 2);
42
- i([
43
- p({ type: Boolean })
44
- ], r.prototype, "disabled", 2);
45
- i([
46
- p()
47
- ], r.prototype, "label", 2);
48
- r = i([
49
- c("baht-filter")
50
- ], r);
40
+ l([
41
+ o({ attribute: !1 })
42
+ ], i.prototype, "options", 2);
43
+ l([
44
+ o({ attribute: !1 })
45
+ ], i.prototype, "selected", 2);
46
+ l([
47
+ o()
48
+ ], i.prototype, "variant", 2);
49
+ l([
50
+ o({ converter: c })
51
+ ], i.prototype, "multiple", 2);
52
+ l([
53
+ o({ type: Boolean })
54
+ ], i.prototype, "disabled", 2);
55
+ l([
56
+ o()
57
+ ], i.prototype, "label", 2);
58
+ i = l([
59
+ d("baht-filter")
60
+ ], i);
51
61
  export {
52
- r as BahtFilter
62
+ i as BahtFilter
53
63
  };
@@ -1,5 +1,7 @@
1
1
  import { TemplateResult } from 'lit';
2
2
  import { BahtElement } from '../base.js';
3
+ /** Where the dialog panel sits. */
4
+ export type ModalPlacement = 'center' | 'sheet' | 'side';
3
5
  /**
4
6
  * Modal dialog (20baht: 0.4 ink scrim, no blur; float shadow; 20px radius).
5
7
  * Controlled: the host flips `open`. Children become the body.
@@ -9,6 +11,15 @@ import { BahtElement } from '../base.js';
9
11
  * </baht-modal>
10
12
  * modal.open = true;
11
13
  * modal.addEventListener('baht-close', (e) => (modal.open = false));
14
+ *
15
+ * `placement` moves the panel: `'center'` (default) is the dialog above;
16
+ * `'sheet'` docks it to the bottom as a bottom sheet below 600px viewport
17
+ * width (full width, rounded top corners, a grab handle, max 90dvh with the
18
+ * body scrolling, slides up) and is a centered dialog from 600px up; `'side'`
19
+ * is a full-height drawer at the inline end, `min(420px, 100vw)` wide, sliding
20
+ * in from that edge. Scrim, focus trap/restore and `baht-close` are the same in
21
+ * every placement. Modifier classes: `.baht-modal--sheet` / `.baht-modal--side`
22
+ * on the panel, `.baht-modal-scrim--sheet` / `.baht-modal-scrim--side` on the scrim.
12
23
  */
13
24
  export declare class BahtModal extends BahtElement {
14
25
  open: boolean;
@@ -16,6 +27,9 @@ export declare class BahtModal extends BahtElement {
16
27
  /** One muted line under the heading; also becomes the dialog's aria-describedby. */
17
28
  description: string;
18
29
  showClose: boolean;
30
+ /** `'center'` (default) dialog, `'sheet'` bottom sheet below 600px (centered
31
+ * from 600px up), or `'side'` full-height drawer at the inline end. */
32
+ placement: ModalPlacement;
19
33
  /**
20
34
  * Scrim click requests close. Off by default: a stray click outside the panel
21
35
  * must not throw away what the user typed — close via ×, Escape or your buttons.