bank20baht-ui 0.7.3 → 0.8.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.
- package/README.md +13 -3
- package/dist/components/baht-form.js +16 -26
- package/dist/components/baht-workflow.js +21 -21
- package/dist/components/base.d.ts +12 -70
- package/dist/components/base.js +51 -173
- package/dist/components/field-base.d.ts +71 -0
- package/dist/components/field-base.js +130 -0
- package/dist/components/primitives/baht-checkbox.d.ts +1 -1
- package/dist/components/primitives/baht-checkbox.js +1 -1
- package/dist/components/primitives/baht-currency.d.ts +1 -1
- package/dist/components/primitives/baht-currency.js +1 -1
- package/dist/components/primitives/baht-datepicker.d.ts +7 -2
- package/dist/components/primitives/baht-datepicker.js +69 -52
- package/dist/components/primitives/baht-file.d.ts +1 -1
- package/dist/components/primitives/baht-file.js +1 -1
- package/dist/components/primitives/baht-filter.js +7 -7
- package/dist/components/primitives/baht-heatmap.d.ts +13 -5
- package/dist/components/primitives/baht-heatmap.js +98 -93
- package/dist/components/primitives/baht-input.d.ts +1 -1
- package/dist/components/primitives/baht-input.js +1 -1
- package/dist/components/primitives/baht-list.js +22 -22
- package/dist/components/primitives/baht-modal.js +28 -28
- package/dist/components/primitives/baht-multiselect.d.ts +1 -1
- package/dist/components/primitives/baht-multiselect.js +1 -1
- package/dist/components/primitives/baht-number.d.ts +1 -1
- package/dist/components/primitives/baht-number.js +1 -1
- package/dist/components/primitives/baht-radio.d.ts +1 -1
- package/dist/components/primitives/baht-radio.js +1 -1
- package/dist/components/primitives/baht-range.js +17 -17
- package/dist/components/primitives/baht-repeater.d.ts +5 -3
- package/dist/components/primitives/baht-repeater.js +57 -67
- package/dist/components/primitives/baht-select.d.ts +1 -1
- package/dist/components/primitives/baht-select.js +2 -1
- package/dist/components/primitives/baht-sparkline.js +21 -21
- package/dist/components/primitives/baht-stat.js +8 -8
- package/dist/components/primitives/baht-table.d.ts +58 -0
- package/dist/components/primitives/baht-table.js +295 -207
- package/dist/components/primitives/baht-textarea.d.ts +1 -1
- package/dist/components/primitives/baht-textarea.js +1 -1
- package/dist/components/primitives/baht-toggle.d.ts +1 -1
- package/dist/components/primitives/baht-toggle.js +1 -1
- package/dist/components/primitives/baht-typeahead.js +6 -6
- package/dist/components/primitives/charts.js +19 -19
- package/dist/components/primitives/index.d.ts +1 -1
- package/dist/components/primitives/tag-map.d.ts +4 -0
- package/dist/components/primitives/tag-map.js +15 -3
- package/dist/core/dates.d.ts +0 -1
- package/dist/core/dates.js +34 -35
- package/dist/core/display.js +8 -6
- package/dist/core/validators.d.ts +0 -8
- package/dist/core/validators.js +44 -36
- package/dist/css/components.css +128 -31
- package/dist/css/tokens.css +3 -1
- package/dist/custom-elements.json +895 -457
- package/dist/index.d.ts +2 -1
- package/dist/index.js +12 -11
- package/dist/ssr.js +5 -3
- package/dist/tokens.css +132 -379
- package/dist/vscode.html-custom-data.json +23 -3
- package/dist/web-types.json +49 -4
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Components (Lit, light DOM) with a pure-TypeScript rules core.
|
|
|
5
5
|
|
|
6
6
|
```
|
|
7
7
|
npm i bank20baht-ui
|
|
8
|
+
npm i flatpickr # peer dependency — only if you render date fields / <baht-datepicker>
|
|
8
9
|
```
|
|
9
10
|
|
|
10
11
|
## Quick start — one form (Level 1)
|
|
@@ -313,13 +314,22 @@ await registerElements(['baht-button', 'baht-avatar']);
|
|
|
313
314
|
```
|
|
314
315
|
|
|
315
316
|
```css
|
|
316
|
-
/* 3. split stylesheets instead of the all-in-one tokens.css (
|
|
317
|
+
/* 3. split stylesheets instead of the all-in-one tokens.css (~115 KB) */
|
|
317
318
|
@import 'bank20baht-ui/css/tokens.css'; /* custom properties only, 18 KB */
|
|
318
319
|
@import 'bank20baht-ui/css/components.css'; /* .baht-* classes */
|
|
319
|
-
@import 'bank20baht-ui/css/datepicker.css'; /* flatpickr base + re-skin — only with <baht-datepicker> / date fields */
|
|
320
320
|
```
|
|
321
321
|
|
|
322
|
-
`bank20baht-ui/tokens.css`
|
|
322
|
+
`bank20baht-ui/tokens.css` = the two sheets above in one import. The calendar is opt-in:
|
|
323
|
+
|
|
324
|
+
```css
|
|
325
|
+
/* with <baht-datepicker> / date fields — flatpickr's base sheet + the token re-skin */
|
|
326
|
+
@import 'bank20baht-ui/css/datepicker.css';
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
flatpickr itself is a **peer dependency** (`npm i flatpickr`) loaded with a dynamic
|
|
330
|
+
`import()` the first time a `<baht-datepicker>` mounts, so apps without date fields never
|
|
331
|
+
bundle it and apps with them get it as its own chunk. `await el.updateComplete` on the
|
|
332
|
+
element resolves once the calendar is attached.
|
|
323
333
|
|
|
324
334
|
## One install, typed everywhere
|
|
325
335
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { nothing as b, html as p } from "lit";
|
|
2
|
-
import { property as u, state as m, customElement as
|
|
2
|
+
import { property as u, state as m, customElement as V } from "lit/decorators.js";
|
|
3
3
|
import { repeat as P } from "lit/directives/repeat.js";
|
|
4
|
-
import { html as w
|
|
5
|
-
import { BahtElement as
|
|
6
|
-
import { TAG_FOR_TYPE as
|
|
4
|
+
import { html as w } from "lit/static-html.js";
|
|
5
|
+
import { BahtElement as T, hasDom as D } 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 _ } from "../core/i18n.js";
|
|
9
9
|
import { validateField as M } from "../core/validators.js";
|
|
10
10
|
import { resolveViewFields as q } from "../core/views.js";
|
|
11
|
-
import { applyFlags as
|
|
12
|
-
import { hasScripts as k, runScripts as F, createScriptContext as
|
|
11
|
+
import { applyFlags as L } from "../core/flags.js";
|
|
12
|
+
import { hasScripts as k, runScripts as F, createScriptContext as A, MAX_SCRIPT_DEPTH as E } from "../core/engine/scripts.js";
|
|
13
13
|
import { displayValue as R } from "../core/display.js";
|
|
14
14
|
import "./primitives/baht-input.js";
|
|
15
15
|
import "./primitives/baht-textarea.js";
|
|
@@ -25,22 +25,12 @@ import "./primitives/baht-file.js";
|
|
|
25
25
|
import "./primitives/baht-text.js";
|
|
26
26
|
import "./primitives/baht-section.js";
|
|
27
27
|
import "./primitives/baht-repeater.js";
|
|
28
|
-
var U = Object.defineProperty,
|
|
29
|
-
for (var r = i > 1 ? void 0 : i ?
|
|
28
|
+
var U = Object.defineProperty, H = Object.getOwnPropertyDescriptor, h = (t, e, s, i) => {
|
|
29
|
+
for (var r = i > 1 ? void 0 : i ? H(e, s) : e, o = t.length - 1, a; o >= 0; o--)
|
|
30
30
|
(a = t[o]) && (r = (i ? a(e, s, r) : a(r)) || r);
|
|
31
31
|
return i && r && U(e, s, r), r;
|
|
32
32
|
};
|
|
33
|
-
|
|
34
|
-
"1/4": "baht-field--w-1-4",
|
|
35
|
-
"1/2": "baht-field--w-1-2",
|
|
36
|
-
"3/4": "baht-field--w-3-4",
|
|
37
|
-
full: "baht-field--w-full"
|
|
38
|
-
}, x = /* @__PURE__ */ new Map();
|
|
39
|
-
function W(t) {
|
|
40
|
-
let e = x.get(t);
|
|
41
|
-
return e || (e = I(t), x.set(t, e)), e;
|
|
42
|
-
}
|
|
43
|
-
let n = class extends B {
|
|
33
|
+
let n = class extends T {
|
|
44
34
|
constructor() {
|
|
45
35
|
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 = {};
|
|
46
36
|
}
|
|
@@ -48,7 +38,7 @@ let n = class extends B {
|
|
|
48
38
|
return this.form ? ((this.flagCache.form !== this.form || this.flagCache.flags !== this.flags) && (this.flagCache = {
|
|
49
39
|
form: this.form,
|
|
50
40
|
flags: this.flags,
|
|
51
|
-
out:
|
|
41
|
+
out: L(this.form, this.flags)
|
|
52
42
|
}), this.flagCache.out) : this.form;
|
|
53
43
|
}
|
|
54
44
|
willUpdate(t) {
|
|
@@ -183,7 +173,7 @@ let n = class extends B {
|
|
|
183
173
|
// ---- client scripts (spec 01 §14) ----
|
|
184
174
|
/** The `g` (g_form) for the current form — built lazily over this component's state. */
|
|
185
175
|
g(t) {
|
|
186
|
-
return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext =
|
|
176
|
+
return this.scriptContext && this.scriptContextForm === t ? this.scriptContext : (this.scriptContextForm = t, this.scriptContext = A(t, {
|
|
187
177
|
getData: () => this.getData(),
|
|
188
178
|
setValue: (e, s) => this.applyScriptValue(e, s),
|
|
189
179
|
setState: (e, s) => {
|
|
@@ -303,16 +293,16 @@ let n = class extends B {
|
|
|
303
293
|
}
|
|
304
294
|
renderField(t) {
|
|
305
295
|
var S, O;
|
|
306
|
-
const e =
|
|
296
|
+
const e = B[t.type];
|
|
307
297
|
if (!e)
|
|
308
298
|
return console.warn(`[bahtui] unknown field type "${t.type}" — skipped`), b;
|
|
309
|
-
const s = this.stateFor(t), i =
|
|
299
|
+
const s = this.stateFor(t), i = I[t.width ?? "full"] ?? "baht-field--w-full", r = ((S = this.errors.find((d) => d.key === t.key)) == null ? void 0 : S.message) ?? null;
|
|
310
300
|
if (this.readonly && t.type !== "label" && t.type !== "section")
|
|
311
301
|
return this.renderReadonlyField(t, i, s.visible);
|
|
312
|
-
const o = t.options_from ? { ...t, options: this.dynamicOptions.get(t.key) ?? t.options ?? [] } : t, a = this.valueFor(t), l = !s.enabled, c = this.readonly || s.readonly, v = this.options.inputDebounce, $ = (d) => this.onControlInput(t.key, d), f =
|
|
302
|
+
const o = t.options_from ? { ...t, options: this.dynamicOptions.get(t.key) ?? t.options ?? [] } : t, a = this.valueFor(t), l = !s.enabled, c = this.readonly || s.readonly, v = this.options.inputDebounce, $ = (d) => this.onControlInput(t.key, d), f = j(e);
|
|
313
303
|
let y;
|
|
314
304
|
if (t.type === "repeater") {
|
|
315
|
-
const d = this.errors.filter((
|
|
305
|
+
const d = this.errors.filter((x) => x.key.startsWith(`${t.key}[`));
|
|
316
306
|
y = w`<${f}
|
|
317
307
|
.field=${o}
|
|
318
308
|
.value=${a}
|
|
@@ -465,7 +455,7 @@ h([
|
|
|
465
455
|
m()
|
|
466
456
|
], n.prototype, "formMessages", 2);
|
|
467
457
|
n = h([
|
|
468
|
-
|
|
458
|
+
V("baht-form")
|
|
469
459
|
], n);
|
|
470
460
|
export {
|
|
471
461
|
n as BahtForm
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { nothing as d, html as l } from "lit";
|
|
2
|
-
import { property as m, state as
|
|
3
|
-
import { BahtElement as
|
|
2
|
+
import { property as m, state as c, customElement as w } from "lit/decorators.js";
|
|
3
|
+
import { boolOff as v, BahtElement as g } from "./base.js";
|
|
4
4
|
import { t as f } from "../core/i18n.js";
|
|
5
|
-
import { applyFlags as
|
|
6
|
-
import { evaluate as
|
|
7
|
-
import { resolveNext as
|
|
5
|
+
import { applyFlags as _ } from "../core/flags.js";
|
|
6
|
+
import { evaluate as k } from "../core/engine/evaluate.js";
|
|
7
|
+
import { resolveNext as $ } from "../core/engine/graph.js";
|
|
8
8
|
import "./baht-form.js";
|
|
9
9
|
import "./primitives/baht-steps.js";
|
|
10
|
-
var y = Object.defineProperty,
|
|
11
|
-
for (var i = r > 1 ? void 0 : r ?
|
|
10
|
+
var y = Object.defineProperty, F = Object.getOwnPropertyDescriptor, n = (t, e, a, r) => {
|
|
11
|
+
for (var i = r > 1 ? void 0 : r ? F(e, a) : e, o = t.length - 1, h; o >= 0; o--)
|
|
12
12
|
(h = t[o]) && (i = (r ? h(e, a, i) : h(i)) || i);
|
|
13
13
|
return r && i && y(e, a, i), i;
|
|
14
14
|
};
|
|
15
|
-
let s = class extends
|
|
15
|
+
let s = class extends g {
|
|
16
16
|
constructor() {
|
|
17
17
|
super(...arguments), this.value = {}, this.registry = {}, this.validateGate = !0, this.showBack = !0, this.flags = {}, this.showSteps = !1, this.stepsVariant = "default", this.data = {}, this.derived = null, this.current = null, this.approvalError = null, this.history = [], this.flagCache = {};
|
|
18
18
|
}
|
|
@@ -23,15 +23,15 @@ let s = class extends w {
|
|
|
23
23
|
return this.workflow ? ((this.flagCache.workflow !== this.workflow || this.flagCache.flags !== this.flags) && (this.flagCache = {
|
|
24
24
|
workflow: this.workflow,
|
|
25
25
|
flags: this.flags,
|
|
26
|
-
out:
|
|
26
|
+
out: _(this.workflow, this.flags)
|
|
27
27
|
}), this.flagCache.out) : this.workflow;
|
|
28
28
|
}
|
|
29
29
|
willUpdate(t) {
|
|
30
30
|
var e, a;
|
|
31
|
-
t.has("workflow") || t.has("value") ? (this.data = structuredClone(this.value ?? {}), this.reevaluate(), this.history = [], this.current = ((e = this.visibleForms[0]) == null ? void 0 : e.form_name) ?? null, this.approvalError = null) : t.has("flags") && (this.reevaluate(), this.current && !this.visibleForms.some((r) => r.form_name === this.current) && (this.current = ((a = this.visibleForms[0]) == null ? void 0 : a.form_name) ?? null));
|
|
31
|
+
this.workflow && (t.has("workflow") || t.has("value") ? (this.data = structuredClone(this.value ?? {}), this.reevaluate(), this.history = [], this.current = ((e = this.visibleForms[0]) == null ? void 0 : e.form_name) ?? null, this.approvalError = null) : t.has("flags") && (this.reevaluate(), this.current && !this.visibleForms.some((r) => r.form_name === this.current) && (this.current = ((a = this.visibleForms[0]) == null ? void 0 : a.form_name) ?? null)));
|
|
32
32
|
}
|
|
33
33
|
reevaluate() {
|
|
34
|
-
this.derived =
|
|
34
|
+
this.derived = k(this.effectiveWorkflow, this.data), this.data = this.derived.data;
|
|
35
35
|
}
|
|
36
36
|
/** Forms in ascending order whose derived visibility is true. */
|
|
37
37
|
get visibleForms() {
|
|
@@ -51,7 +51,7 @@ let s = class extends w {
|
|
|
51
51
|
if (!this.current) return "end";
|
|
52
52
|
const t = this.effectiveWorkflow;
|
|
53
53
|
if (t.transitions && t.transitions.length > 0)
|
|
54
|
-
return
|
|
54
|
+
return $(t, this.current, this.data);
|
|
55
55
|
const e = this.visibleForms, a = e.findIndex((i) => i.form_name === this.current);
|
|
56
56
|
return ((r = e[a + 1]) == null ? void 0 : r.form_name) ?? "end";
|
|
57
57
|
}
|
|
@@ -146,8 +146,8 @@ let s = class extends w {
|
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
this.emit("baht-reject", h);
|
|
149
|
-
const
|
|
150
|
-
|
|
149
|
+
const p = a.on_reject ?? { action: "end" };
|
|
150
|
+
p.action === "goto" && this.visibleForms.some((b) => b.form_name === p.form) ? this.gotoForm(p.form) : this.emit("baht-complete", { data: this.getData(), status: "rejected" });
|
|
151
151
|
}
|
|
152
152
|
/** Jump back to a visible form, truncating the history to that point. */
|
|
153
153
|
gotoForm(t) {
|
|
@@ -288,7 +288,7 @@ n([
|
|
|
288
288
|
m({ attribute: !1 })
|
|
289
289
|
], s.prototype, "registry", 2);
|
|
290
290
|
n([
|
|
291
|
-
m({
|
|
291
|
+
m({ attribute: "validate-gate", converter: v })
|
|
292
292
|
], s.prototype, "validateGate", 2);
|
|
293
293
|
n([
|
|
294
294
|
m({ attribute: "next-label" })
|
|
@@ -300,7 +300,7 @@ n([
|
|
|
300
300
|
m({ attribute: "back-label" })
|
|
301
301
|
], s.prototype, "backLabel", 2);
|
|
302
302
|
n([
|
|
303
|
-
m({
|
|
303
|
+
m({ attribute: "show-back", converter: v })
|
|
304
304
|
], s.prototype, "showBack", 2);
|
|
305
305
|
n([
|
|
306
306
|
m({ attribute: !1 })
|
|
@@ -312,19 +312,19 @@ n([
|
|
|
312
312
|
m({ attribute: "steps-variant" })
|
|
313
313
|
], s.prototype, "stepsVariant", 2);
|
|
314
314
|
n([
|
|
315
|
-
|
|
315
|
+
c()
|
|
316
316
|
], s.prototype, "data", 2);
|
|
317
317
|
n([
|
|
318
|
-
|
|
318
|
+
c()
|
|
319
319
|
], s.prototype, "derived", 2);
|
|
320
320
|
n([
|
|
321
|
-
|
|
321
|
+
c()
|
|
322
322
|
], s.prototype, "current", 2);
|
|
323
323
|
n([
|
|
324
|
-
|
|
324
|
+
c()
|
|
325
325
|
], s.prototype, "approvalError", 2);
|
|
326
326
|
s = n([
|
|
327
|
-
|
|
327
|
+
w("baht-workflow")
|
|
328
328
|
], s);
|
|
329
329
|
export {
|
|
330
330
|
s as BahtWorkflow
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { LitElement,
|
|
2
|
-
import { FieldDescriptor } from '../core/types.js';
|
|
1
|
+
import { LitElement, PropertyValues, RenderOptions } from 'lit';
|
|
3
2
|
/**
|
|
4
3
|
* True when a real DOM is present. Lit's own `isServer` is hard-coded to `true` in its
|
|
5
4
|
* node build, which jsdom/happy-dom test runners also load, so guards that need "is there
|
|
@@ -7,6 +6,17 @@ import { FieldDescriptor } from '../core/types.js';
|
|
|
7
6
|
* `querySelector`.
|
|
8
7
|
*/
|
|
9
8
|
export declare function hasDom(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Attribute converter for booleans that DEFAULT TO TRUE. Lit's own `type: Boolean` reads
|
|
11
|
+
* `show-close="false"` as true (presence is all it checks), so a default-true switch could
|
|
12
|
+
* never be turned off from HTML. With this: absent = default, bare = true, `="false"` = false.
|
|
13
|
+
*
|
|
14
|
+
* @property({ attribute: 'show-close', converter: boolOff }) showClose = true;
|
|
15
|
+
*/
|
|
16
|
+
export declare const boolOff: {
|
|
17
|
+
fromAttribute: (value: string | null) => boolean;
|
|
18
|
+
toAttribute: (value: boolean) => string | null;
|
|
19
|
+
};
|
|
10
20
|
/** Signature of the light-DOM hydrate function installed by `bank20baht-ui/hydrate`. */
|
|
11
21
|
export type BahtHydrate = (value: unknown, host: HTMLElement, options?: RenderOptions) => void;
|
|
12
22
|
/**
|
|
@@ -43,71 +53,3 @@ export declare class BahtElement extends LitElement {
|
|
|
43
53
|
* connectedCallback fires, so sweeping at first render is the reliable point.)
|
|
44
54
|
*/
|
|
45
55
|
export declare function adoptLightChildren(host: HTMLElement, slot: HTMLElement): void;
|
|
46
|
-
/**
|
|
47
|
-
* Shared shape of every field primitive: descriptor + value in, `baht-input` out.
|
|
48
|
-
* Dumb and controlled — never mutates its own `value` prop; the host re-feeds it.
|
|
49
|
-
*
|
|
50
|
-
* `controlId` carries the base class `uid`: the same field key renders once per repeater
|
|
51
|
-
* row (and can repeat across forms on one page), so key-only ids collide — worst for
|
|
52
|
-
* radios, whose shared native `name` merges groups.
|
|
53
|
-
*/
|
|
54
|
-
export declare abstract class BahtFieldBase extends BahtElement {
|
|
55
|
-
/**
|
|
56
|
-
* Default trailing debounce (ms) for TYPED edits — text, email, password,
|
|
57
|
-
* textarea, number. Every keystroke otherwise re-runs the host's rules,
|
|
58
|
-
* validation and dependent-option fetches. Per element: the `debounce`
|
|
59
|
-
* property/attribute; `<baht-form options.inputDebounce>` sets it for a whole
|
|
60
|
-
* form. 0 = emit on every keystroke. Selects, checkboxes, radios, toggles,
|
|
61
|
-
* dates and files are never debounced — a pick is one event.
|
|
62
|
-
*/
|
|
63
|
-
static defaultDebounce: number;
|
|
64
|
-
field: FieldDescriptor;
|
|
65
|
-
value: unknown;
|
|
66
|
-
disabled: boolean;
|
|
67
|
-
readonly: boolean;
|
|
68
|
-
required: boolean;
|
|
69
|
-
/** Append the localized "(optional)" marker to the label when not required (set by <baht-form show-optional>). */
|
|
70
|
-
showOptional: boolean;
|
|
71
|
-
error: string | null;
|
|
72
|
-
/** Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce. */
|
|
73
|
-
debounce?: number;
|
|
74
|
-
/**
|
|
75
|
-
* The keystrokes not yet (or just) handed to the host. The control keeps
|
|
76
|
-
* rendering `typed.value` until the host's `value` moves, so a re-render
|
|
77
|
-
* mid-typing (a sibling field changing) can't snap the text back.
|
|
78
|
-
*/
|
|
79
|
-
private typed;
|
|
80
|
-
private typedTimer;
|
|
81
|
-
private readonly onFocusOut;
|
|
82
|
-
connectedCallback(): void;
|
|
83
|
-
disconnectedCallback(): void;
|
|
84
|
-
protected willUpdate(changed: PropertyValues<this>): void;
|
|
85
|
-
protected get effectiveDebounce(): number;
|
|
86
|
-
/** A typed edit: emitted after `debounce` ms of quiet (or at once when 0). */
|
|
87
|
-
protected emitTyped(value: unknown): void;
|
|
88
|
-
/** Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent. */
|
|
89
|
-
flushTyped(): void;
|
|
90
|
-
/** What a typed control shows: pending keystrokes win over a stale host value. */
|
|
91
|
-
protected get shownValue(): unknown;
|
|
92
|
-
/** Emit the user's edit upward (composed so it crosses any boundary). */
|
|
93
|
-
protected emitInput(value: unknown): void;
|
|
94
|
-
protected abstract renderControl(): TemplateResult;
|
|
95
|
-
/** True when `field.icon` names a real icon — gates both the icon markup
|
|
96
|
-
* and the control's icon-padding class, so a typo/removed name never
|
|
97
|
-
* leaves the control indented for a glyph that isn't there. */
|
|
98
|
-
protected get hasFieldIcon(): boolean;
|
|
99
|
-
/** Leading icon for text/currency/textarea/dropdown controls (spec: field.icon). */
|
|
100
|
-
protected renderFieldIcon(): TemplateResult | typeof nothing;
|
|
101
|
-
protected renderLabel(): TemplateResult | typeof nothing;
|
|
102
|
-
/** Required asterisk, or the "(optional)" marker when the form asks for it. */
|
|
103
|
-
protected renderLabelMark(): TemplateResult | typeof nothing;
|
|
104
|
-
protected get controlId(): string;
|
|
105
|
-
protected get labelId(): string;
|
|
106
|
-
protected get errorId(): string;
|
|
107
|
-
protected get helpId(): string;
|
|
108
|
-
/** id list for the control's aria-describedby: error first, then help. */
|
|
109
|
-
protected get describedBy(): string | typeof nothing;
|
|
110
|
-
/** 'true' while the field is in error, else absent (styles hook on [aria-invalid]). */
|
|
111
|
-
protected get invalid(): string | typeof nothing;
|
|
112
|
-
protected render(): TemplateResult | typeof nothing;
|
|
113
|
-
}
|
package/dist/components/base.js
CHANGED
|
@@ -1,37 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { t as v } from "../core/i18n.js";
|
|
4
|
-
import { isIconName as c, renderIcon as C } from "./primitives/icons.js";
|
|
5
|
-
var I = Object.defineProperty, d = (i, e, t, s) => {
|
|
6
|
-
for (var r = void 0, u = i.length - 1, p; u >= 0; u--)
|
|
7
|
-
(p = i[u]) && (r = p(e, t, r) || r);
|
|
8
|
-
return r && I(e, t, r), r;
|
|
9
|
-
};
|
|
10
|
-
function f() {
|
|
1
|
+
import { LitElement as d, ReactiveElement as u } from "lit";
|
|
2
|
+
function n() {
|
|
11
3
|
return typeof document < "u" && typeof document.querySelector == "function";
|
|
12
4
|
}
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
const f = {
|
|
6
|
+
fromAttribute: (r) => r !== "false",
|
|
7
|
+
// null (absent) = default
|
|
8
|
+
toAttribute: (r) => r ? "" : "false"
|
|
9
|
+
}, h = Symbol.for("bahtui.ssrStash");
|
|
10
|
+
function l(r) {
|
|
11
|
+
if (r[h]) return !0;
|
|
12
|
+
const t = r.firstChild;
|
|
13
|
+
return (t == null ? void 0 : t.nodeType) === 8 && t.data.startsWith("lit-part");
|
|
18
14
|
}
|
|
19
|
-
function
|
|
20
|
-
let
|
|
21
|
-
if (!
|
|
22
|
-
let
|
|
23
|
-
for (;
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
const
|
|
27
|
-
|
|
15
|
+
function p(r) {
|
|
16
|
+
let t = r.firstChild;
|
|
17
|
+
if (!t || t.nodeType !== 8 || !t.data.startsWith("lit-part")) return;
|
|
18
|
+
let e = 0;
|
|
19
|
+
for (; t; ) {
|
|
20
|
+
const i = t.nextSibling;
|
|
21
|
+
if (t.nodeType === 8) {
|
|
22
|
+
const s = t.data;
|
|
23
|
+
s.startsWith("lit-part") ? e += 1 : s.startsWith("/lit-part") && (e -= 1);
|
|
28
24
|
}
|
|
29
|
-
if (
|
|
30
|
-
|
|
25
|
+
if (t.remove(), e === 0) break;
|
|
26
|
+
t = i;
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
|
-
let
|
|
34
|
-
class
|
|
29
|
+
let a = 0;
|
|
30
|
+
class b extends d {
|
|
35
31
|
constructor() {
|
|
36
32
|
super(...arguments), this.uid = 0;
|
|
37
33
|
}
|
|
@@ -49,164 +45,46 @@ class k extends y {
|
|
|
49
45
|
static get observedAttributes() {
|
|
50
46
|
return [...super.observedAttributes, "defer-hydration"];
|
|
51
47
|
}
|
|
52
|
-
attributeChangedCallback(
|
|
53
|
-
if (
|
|
54
|
-
|
|
48
|
+
attributeChangedCallback(t, e, i) {
|
|
49
|
+
if (t === "defer-hydration") {
|
|
50
|
+
i === null && this.isConnected && super.connectedCallback();
|
|
55
51
|
return;
|
|
56
52
|
}
|
|
57
|
-
super.attributeChangedCallback(
|
|
53
|
+
super.attributeChangedCallback(t, e, i);
|
|
58
54
|
}
|
|
59
55
|
connectedCallback() {
|
|
60
56
|
this.hasAttribute("defer-hydration") || super.connectedCallback();
|
|
61
57
|
}
|
|
62
|
-
update(
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
if (
|
|
66
|
-
const
|
|
67
|
-
this.renderOptions.isConnected = this.isConnected,
|
|
58
|
+
update(t) {
|
|
59
|
+
const e = !this.hasUpdated, i = globalThis.bahtHydrate, s = e && n() && l(this);
|
|
60
|
+
if (s)
|
|
61
|
+
if (i) {
|
|
62
|
+
const o = this.render();
|
|
63
|
+
this.renderOptions.isConnected = this.isConnected, u.prototype.update.call(this, t), i(o, this, this.renderOptions);
|
|
68
64
|
} else
|
|
69
|
-
|
|
65
|
+
p(this), super.update(t);
|
|
70
66
|
else
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
e && n() && this.uid === 0 && (this.uid = ++a), super.update(t);
|
|
68
|
+
s && this.uid === 0 && (this.uid = ++a, this.requestUpdate());
|
|
73
69
|
}
|
|
74
70
|
/** Dispatch a bubbling, composed CustomEvent — crosses any light/shadow DOM boundary. */
|
|
75
|
-
emit(
|
|
76
|
-
this.dispatchEvent(new CustomEvent(
|
|
71
|
+
emit(t, e) {
|
|
72
|
+
this.dispatchEvent(new CustomEvent(t, { detail: e, bubbles: !0, composed: !0 }));
|
|
77
73
|
}
|
|
78
74
|
}
|
|
79
|
-
function
|
|
80
|
-
let
|
|
81
|
-
for (;
|
|
82
|
-
const
|
|
83
|
-
(
|
|
75
|
+
function y(r, t) {
|
|
76
|
+
let e = t;
|
|
77
|
+
for (; e.parentNode && e.parentNode !== r; ) e = e.parentNode;
|
|
78
|
+
const i = Array.from(r.childNodes).filter(
|
|
79
|
+
(s) => s !== e && s.nodeType !== Node.COMMENT_NODE
|
|
84
80
|
);
|
|
85
|
-
|
|
81
|
+
i.length > 0 && t.append(...i);
|
|
86
82
|
}
|
|
87
|
-
var h;
|
|
88
|
-
const n = (h = class extends k {
|
|
89
|
-
constructor() {
|
|
90
|
-
super(...arguments), this.value = null, this.disabled = !1, this.readonly = !1, this.required = !1, this.showOptional = !1, this.error = null, this.typed = null, this.onFocusOut = () => this.flushTyped();
|
|
91
|
-
}
|
|
92
|
-
connectedCallback() {
|
|
93
|
-
super.connectedCallback(), this.addEventListener("focusout", this.onFocusOut);
|
|
94
|
-
}
|
|
95
|
-
disconnectedCallback() {
|
|
96
|
-
this.removeEventListener("focusout", this.onFocusOut), this.flushTyped(), super.disconnectedCallback();
|
|
97
|
-
}
|
|
98
|
-
willUpdate(e) {
|
|
99
|
-
var t;
|
|
100
|
-
super.willUpdate(e), e.has("value") && ((t = this.typed) != null && t.emitted) && (this.typed = null);
|
|
101
|
-
}
|
|
102
|
-
get effectiveDebounce() {
|
|
103
|
-
return this.debounce ?? h.defaultDebounce;
|
|
104
|
-
}
|
|
105
|
-
/** A typed edit: emitted after `debounce` ms of quiet (or at once when 0). */
|
|
106
|
-
emitTyped(e) {
|
|
107
|
-
const t = this.effectiveDebounce;
|
|
108
|
-
if (clearTimeout(this.typedTimer), t <= 0) {
|
|
109
|
-
this.typed = null, this.emitInput(e);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
this.typed = { value: e, emitted: !1 }, this.typedTimer = setTimeout(() => this.flushTyped(), t);
|
|
113
|
-
}
|
|
114
|
-
/** Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent. */
|
|
115
|
-
flushTyped() {
|
|
116
|
-
clearTimeout(this.typedTimer), this.typedTimer = void 0, !(!this.typed || this.typed.emitted) && (this.typed.emitted = !0, this.emitInput(this.typed.value));
|
|
117
|
-
}
|
|
118
|
-
/** What a typed control shows: pending keystrokes win over a stale host value. */
|
|
119
|
-
get shownValue() {
|
|
120
|
-
return this.typed ? this.typed.value : this.value;
|
|
121
|
-
}
|
|
122
|
-
/** Emit the user's edit upward (composed so it crosses any boundary). */
|
|
123
|
-
emitInput(e) {
|
|
124
|
-
this.emit("baht-input", { key: this.field.key, value: e });
|
|
125
|
-
}
|
|
126
|
-
/** True when `field.icon` names a real icon — gates both the icon markup
|
|
127
|
-
* and the control's icon-padding class, so a typo/removed name never
|
|
128
|
-
* leaves the control indented for a glyph that isn't there. */
|
|
129
|
-
get hasFieldIcon() {
|
|
130
|
-
const e = this.field.icon;
|
|
131
|
-
return e != null && c(e);
|
|
132
|
-
}
|
|
133
|
-
/** Leading icon for text/currency/textarea/dropdown controls (spec: field.icon). */
|
|
134
|
-
renderFieldIcon() {
|
|
135
|
-
const e = this.field.icon;
|
|
136
|
-
return e == null || !c(e) ? o : a`<span class="baht-field-icon" aria-hidden="true">${C(e)}</span>`;
|
|
137
|
-
}
|
|
138
|
-
renderLabel() {
|
|
139
|
-
return this.field.label ? a`<label class="baht-label" id=${this.labelId} for=${this.controlId}>
|
|
140
|
-
${this.field.label}${this.renderLabelMark()}
|
|
141
|
-
</label>` : o;
|
|
142
|
-
}
|
|
143
|
-
/** Required asterisk, or the "(optional)" marker when the form asks for it. */
|
|
144
|
-
renderLabelMark() {
|
|
145
|
-
return this.required ? a`<span class="baht-required-mark" aria-hidden="true">*</span>` : this.showOptional ? a`<span class="baht-optional">${v("optional")}</span>` : o;
|
|
146
|
-
}
|
|
147
|
-
get controlId() {
|
|
148
|
-
var e;
|
|
149
|
-
return `baht-${((e = this.field) == null ? void 0 : e.key) ?? ""}-${this.uid}`;
|
|
150
|
-
}
|
|
151
|
-
get labelId() {
|
|
152
|
-
return `${this.controlId}-label`;
|
|
153
|
-
}
|
|
154
|
-
get errorId() {
|
|
155
|
-
return `${this.controlId}-error`;
|
|
156
|
-
}
|
|
157
|
-
get helpId() {
|
|
158
|
-
return `${this.controlId}-help`;
|
|
159
|
-
}
|
|
160
|
-
/** id list for the control's aria-describedby: error first, then help. */
|
|
161
|
-
get describedBy() {
|
|
162
|
-
const e = [this.error ? this.errorId : "", this.field.help ? this.helpId : ""].filter(
|
|
163
|
-
Boolean
|
|
164
|
-
);
|
|
165
|
-
return e.length > 0 ? e.join(" ") : o;
|
|
166
|
-
}
|
|
167
|
-
/** 'true' while the field is in error, else absent (styles hook on [aria-invalid]). */
|
|
168
|
-
get invalid() {
|
|
169
|
-
return this.error ? "true" : o;
|
|
170
|
-
}
|
|
171
|
-
render() {
|
|
172
|
-
return this.field ? a`
|
|
173
|
-
${this.renderLabel()}
|
|
174
|
-
${this.renderControl()}
|
|
175
|
-
${this.field.help ? a`<div class="baht-help" id=${this.helpId}>${this.field.help}</div>` : o}
|
|
176
|
-
${this.error ? a`<div class="baht-error" id=${this.errorId} role="alert">${this.error}</div>` : o}
|
|
177
|
-
` : o;
|
|
178
|
-
}
|
|
179
|
-
}, h.defaultDebounce = 150, h);
|
|
180
|
-
d([
|
|
181
|
-
l({ attribute: !1 })
|
|
182
|
-
], n.prototype, "field");
|
|
183
|
-
d([
|
|
184
|
-
l({ attribute: !1 })
|
|
185
|
-
], n.prototype, "value");
|
|
186
|
-
d([
|
|
187
|
-
l({ type: Boolean })
|
|
188
|
-
], n.prototype, "disabled");
|
|
189
|
-
d([
|
|
190
|
-
l({ type: Boolean })
|
|
191
|
-
], n.prototype, "readonly");
|
|
192
|
-
d([
|
|
193
|
-
l({ type: Boolean })
|
|
194
|
-
], n.prototype, "required");
|
|
195
|
-
d([
|
|
196
|
-
l({ type: Boolean, attribute: "show-optional" })
|
|
197
|
-
], n.prototype, "showOptional");
|
|
198
|
-
d([
|
|
199
|
-
l({ attribute: !1 })
|
|
200
|
-
], n.prototype, "error");
|
|
201
|
-
d([
|
|
202
|
-
l({ type: Number })
|
|
203
|
-
], n.prototype, "debounce");
|
|
204
|
-
let N = n;
|
|
205
83
|
export {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
84
|
+
b as BahtElement,
|
|
85
|
+
h as SSR_STASH,
|
|
86
|
+
y as adoptLightChildren,
|
|
87
|
+
f as boolOff,
|
|
88
|
+
n as hasDom,
|
|
89
|
+
l as hasSsrMarkup
|
|
212
90
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { nothing, PropertyValues, TemplateResult } from 'lit';
|
|
2
|
+
import { FieldDescriptor } from '../core/types.js';
|
|
3
|
+
import { BahtElement } from './base.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared shape of every field primitive: descriptor + value in, `baht-input` out.
|
|
6
|
+
* Dumb and controlled — never mutates its own `value` prop; the host re-feeds it.
|
|
7
|
+
*
|
|
8
|
+
* `controlId` carries the base class `uid`: the same field key renders once per repeater
|
|
9
|
+
* row (and can repeat across forms on one page), so key-only ids collide — worst for
|
|
10
|
+
* radios, whose shared native `name` merges groups.
|
|
11
|
+
*/
|
|
12
|
+
export declare abstract class BahtFieldBase extends BahtElement {
|
|
13
|
+
/**
|
|
14
|
+
* Default trailing debounce (ms) for TYPED edits — text, email, password,
|
|
15
|
+
* textarea, number. Every keystroke otherwise re-runs the host's rules,
|
|
16
|
+
* validation and dependent-option fetches. Per element: the `debounce`
|
|
17
|
+
* property/attribute; `<baht-form options.inputDebounce>` sets it for a whole
|
|
18
|
+
* form. 0 = emit on every keystroke. Selects, checkboxes, radios, toggles,
|
|
19
|
+
* dates and files are never debounced — a pick is one event.
|
|
20
|
+
*/
|
|
21
|
+
static defaultDebounce: number;
|
|
22
|
+
field: FieldDescriptor;
|
|
23
|
+
value: unknown;
|
|
24
|
+
disabled: boolean;
|
|
25
|
+
readonly: boolean;
|
|
26
|
+
required: boolean;
|
|
27
|
+
/** Append the localized "(optional)" marker to the label when not required (set by <baht-form show-optional>). */
|
|
28
|
+
showOptional: boolean;
|
|
29
|
+
error: string | null;
|
|
30
|
+
/** Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce. */
|
|
31
|
+
debounce?: number;
|
|
32
|
+
/**
|
|
33
|
+
* The keystrokes not yet (or just) handed to the host. The control keeps
|
|
34
|
+
* rendering `typed.value` until the host's `value` moves, so a re-render
|
|
35
|
+
* mid-typing (a sibling field changing) can't snap the text back.
|
|
36
|
+
*/
|
|
37
|
+
private typed;
|
|
38
|
+
private typedTimer;
|
|
39
|
+
private readonly onFocusOut;
|
|
40
|
+
connectedCallback(): void;
|
|
41
|
+
disconnectedCallback(): void;
|
|
42
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
43
|
+
protected get effectiveDebounce(): number;
|
|
44
|
+
/** A typed edit: emitted after `debounce` ms of quiet (or at once when 0). */
|
|
45
|
+
protected emitTyped(value: unknown): void;
|
|
46
|
+
/** Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent. */
|
|
47
|
+
flushTyped(): void;
|
|
48
|
+
/** What a typed control shows: pending keystrokes win over a stale host value. */
|
|
49
|
+
protected get shownValue(): unknown;
|
|
50
|
+
/** Emit the user's edit upward (composed so it crosses any boundary). */
|
|
51
|
+
protected emitInput(value: unknown): void;
|
|
52
|
+
protected abstract renderControl(): TemplateResult;
|
|
53
|
+
/** True when `field.icon` names a real icon — gates both the icon markup
|
|
54
|
+
* and the control's icon-padding class, so a typo/removed name never
|
|
55
|
+
* leaves the control indented for a glyph that isn't there. */
|
|
56
|
+
protected get hasFieldIcon(): boolean;
|
|
57
|
+
/** Leading icon for text/currency/textarea/dropdown controls (spec: field.icon). */
|
|
58
|
+
protected renderFieldIcon(): TemplateResult | typeof nothing;
|
|
59
|
+
protected renderLabel(): TemplateResult | typeof nothing;
|
|
60
|
+
/** Required asterisk, or the "(optional)" marker when the form asks for it. */
|
|
61
|
+
protected renderLabelMark(): TemplateResult | typeof nothing;
|
|
62
|
+
protected get controlId(): string;
|
|
63
|
+
protected get labelId(): string;
|
|
64
|
+
protected get errorId(): string;
|
|
65
|
+
protected get helpId(): string;
|
|
66
|
+
/** id list for the control's aria-describedby: error first, then help. */
|
|
67
|
+
protected get describedBy(): string | typeof nothing;
|
|
68
|
+
/** 'true' while the field is in error, else absent (styles hook on [aria-invalid]). */
|
|
69
|
+
protected get invalid(): string | typeof nothing;
|
|
70
|
+
protected render(): TemplateResult | typeof nothing;
|
|
71
|
+
}
|