bank20baht-ui 0.2.9 → 0.3.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 (44) hide show
  1. package/README.md +43 -14
  2. package/dist/chunks/baht-toast-CD1WrOYr.js +284 -0
  3. package/dist/chunks/baht-workflow-Gd0J8mGC.js +3952 -0
  4. package/dist/chunks/base-CajA2aCO.js +193 -0
  5. package/dist/chunks/display-DQRcvsn7.js +589 -0
  6. package/dist/chunks/i18n-BcPL9_Jh.js +138 -0
  7. package/dist/components/baht-form.d.ts +7 -8
  8. package/dist/components/baht-workflow.d.ts +4 -4
  9. package/dist/components/base.d.ts +35 -7
  10. package/dist/components/primitives/baht-accordion.d.ts +0 -2
  11. package/dist/components/primitives/baht-datepicker.d.ts +6 -6
  12. package/dist/components/primitives/baht-file.d.ts +5 -2
  13. package/dist/components/primitives/baht-keyvalue-editor.d.ts +1 -1
  14. package/dist/components/primitives/baht-modal.d.ts +1 -2
  15. package/dist/components/primitives/baht-range.d.ts +0 -2
  16. package/dist/components/primitives/baht-reference-picker.d.ts +1 -1
  17. package/dist/components/primitives/baht-repeater.d.ts +5 -6
  18. package/dist/components/primitives/baht-table.d.ts +1 -2
  19. package/dist/components/primitives/baht-tooltip.d.ts +0 -2
  20. package/dist/components/primitives/baht-typeahead.d.ts +0 -1
  21. package/dist/components/primitives/baht-upload.d.ts +96 -0
  22. package/dist/components/primitives/index.d.ts +1 -0
  23. package/dist/core/dates.d.ts +1 -1
  24. package/dist/core/display.d.ts +1 -1
  25. package/dist/core/engine/graph.d.ts +1 -1
  26. package/dist/core/i18n.d.ts +5 -0
  27. package/dist/core/index.d.ts +1 -0
  28. package/dist/core/legacy.d.ts +14 -0
  29. package/dist/core/types.d.ts +27 -27
  30. package/dist/custom-elements.json +1979 -360
  31. package/dist/engine.js +65 -22
  32. package/dist/hydrate.d.ts +9 -0
  33. package/dist/hydrate.js +46 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +86 -3936
  36. package/dist/overlay.js +1 -1
  37. package/dist/ssr.d.ts +16 -0
  38. package/dist/ssr.js +34 -0
  39. package/dist/tokens.css +191 -10
  40. package/dist/vscode.html-custom-data.json +159 -50
  41. package/dist/web-types.json +259 -52
  42. package/package.json +21 -1
  43. package/dist/chunks/index-yULskrKT.js +0 -714
  44. package/dist/chunks/motion-BRVCzB4Z.js +0 -16
package/README.md CHANGED
@@ -19,9 +19,9 @@ npm i bank20baht-ui
19
19
  <script>
20
20
  const el = document.getElementById('f');
21
21
  el.form = {
22
- formId: 'f1',
23
- formName: 'Person',
24
- field_data: [
22
+ form_id: 'f1',
23
+ form_name: 'Person',
24
+ fields: [
25
25
  { order: 1, type: 'text', key: 'firstName', label: 'First name', width: '1/2', required: true, value: '' },
26
26
  { order: 2, type: 'text', key: 'lastName', label: 'Last name', width: '1/2', required: true, value: '' },
27
27
  { order: 3, type: 'number', key: 'age', label: 'Age', width: '1/4', min: 0, value: null }
@@ -63,7 +63,7 @@ gives you the same formatting for tables.
63
63
  ### Repeater — line items / repeating rows
64
64
 
65
65
  ```js
66
- { order: 5, type: 'repeater', key: 'items', label: 'Line items', minRows: 1,
66
+ { order: 5, type: 'repeater', key: 'items', label: 'Line items', min_rows: 1,
67
67
  fields: [
68
68
  { order: 1, type: 'text', key: 'name', width: '1/2', required: true },
69
69
  { order: 2, type: 'number', key: 'qty', width: '1/4', min: 1 },
@@ -71,14 +71,14 @@ gives you the same formatting for tables.
71
71
  ```
72
72
 
73
73
  Value is an array of row objects (`getData().items → [{ name, qty }, …]`). Rows add/remove in
74
- the UI (`minRows`/`maxRows` bound the count), each row validates its sub-fields — errors come
74
+ the UI (`min_rows`/`max_rows` bound the count), each row validates its sub-fields — errors come
75
75
  back as `items[0].name`, on both the client and `bank20baht-validator`. One level deep only.
76
76
 
77
77
  ### Dependent / async dropdowns
78
78
 
79
79
  ```js
80
80
  // schema — functions never live in JSON, only registry names:
81
- { order: 2, type: 'dropdown', key: 'amphoe', optionsFrom: 'amphoeFor', optionsDeps: ['province'] }
81
+ { order: 2, type: 'dropdown', key: 'amphoe', options_from: 'amphoeFor', options_deps: ['province'] }
82
82
 
83
83
  // registry:
84
84
  el.registry = {
@@ -119,7 +119,7 @@ with the same JSON — `bank20baht-validator` re-exports `applyFlags`.
119
119
 
120
120
  ```js
121
121
  // schema:
122
- { order: 6, type: 'file', key: 'doc', uploader: 'default', accept: '.pdf,image/*', maxSize: 10_485_760 }
122
+ { order: 6, type: 'file', key: 'doc', uploader: 'default', accept: '.pdf,image/*', max_size: 10_485_760 }
123
123
 
124
124
  // registry — File in, serializable ref out:
125
125
  el.registry = {
@@ -131,9 +131,17 @@ el.registry = {
131
131
  ```
132
132
 
133
133
  Picking a file uploads immediately; the value becomes `{ name, size, type, url }` — plain
134
- JSON, chips with remove buttons in the UI, `maxSize`/`accept` validated on both client and
134
+ JSON, chips with remove buttons in the UI, `max_size`/`accept` validated on both client and
135
135
  server. Omit `uploader` for the old capture-only mode.
136
136
 
137
+ No registry function at all? `<baht-upload>` talks to an endpoint straight from attributes:
138
+ the file is POSTed as multipart on pick, the endpoint returns the stored URL, and that URL is
139
+ the value (`baht-change` → `{ value, files }`; progress and error events included).
140
+
141
+ ```html
142
+ <baht-upload url="/api/uploads" accept=".pdf,image/*" max-size="10485760" label="Attachment"></baht-upload>
143
+ ```
144
+
137
145
  ### Drafts / autosave (host pattern)
138
146
 
139
147
  The library is controlled — autosave is three lines in your app:
@@ -160,7 +168,7 @@ setLocale('th', { required: 'ห้ามว่าง' }); // locale + your own
160
168
  ```
161
169
 
162
170
  Call it once before mounting. Per-field `message` and label attributes
163
- (`next-label`, `approval.approveLabel`, …) still win over the catalog. The server can
171
+ (`next-label`, `approval.approve_label`, …) still win over the catalog. The server can
164
172
  localize too: `bank20baht-validator` re-exports `setLocale`, so validation issues come
165
173
  back in the same language the client shows.
166
174
 
@@ -168,8 +176,8 @@ back in the same language the client shows.
168
176
 
169
177
  ```js
170
178
  document.querySelector('baht-workflow').workflow = {
171
- workflowId: 'wf-1',
172
- forms: [/* {formId, formName, order, field_data}, ... */],
179
+ workflow_id: 'wf-1',
180
+ forms: [/* {form_id, form_name, order, fields}, ... */],
173
181
  rules: [
174
182
  { id: 'skip-person',
175
183
  when: { field: 'Intro.kind', op: 'eq', value: 'company' },
@@ -305,6 +313,27 @@ list of gotchas: [docs/frameworks/angular.md](./docs/frameworks/angular.md#gotch
305
313
  vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('baht-') } } })
306
314
  ```
307
315
 
316
+ ## Server-side rendering
317
+
318
+ `bank20baht-ui/ssr` (Node) renders any Lit template with `@lit-labs/ssr` and moves each
319
+ `baht-*` element's shadow template into its light DOM, so the HTML string it returns is
320
+ exactly what the client would have rendered. `bank20baht-ui/hydrate` (browser, imported
321
+ **before** `bank20baht-ui`) picks that markup up instead of re-rendering it:
322
+
323
+ ```js
324
+ // server
325
+ import { html } from 'lit';
326
+ import { renderToString } from 'bank20baht-ui/ssr';
327
+ const page = await renderToString(html`<baht-button variant="primary" label="Save"></baht-button>`);
328
+
329
+ // client — import order matters
330
+ import 'bank20baht-ui/hydrate';
331
+ import 'bank20baht-ui';
332
+ ```
333
+
334
+ `@lit-labs/ssr` is an optional peer dependency (server only). Full guide, limitations
335
+ (light-DOM children, ids, i18n locale) and troubleshooting: [docs/ssr.md](./docs/ssr.md).
336
+
308
337
  ## Docs, Storybook, examples
309
338
 
310
339
  - **Per-component docs**: [`docs/`](./docs/README.md) — one .md per component + framework
@@ -319,10 +348,10 @@ vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('b
319
348
  file (capture only, no upload) · label (static)`
320
349
 
321
350
  Each field descriptor: `{ order, type, key, label, placeholder, width: '1/4'|'1/2'|'3/4'|'full',
322
- value, disabled, required, options, maxLength, min, max, validators[], formatter }`.
351
+ value, disabled, required, options, max_length, min, max, validators[], formatter }`.
323
352
 
324
353
  ## Visual Builder
325
354
 
326
- The playground app ships a two-mode Visual Builder (Form mode: palette/WYSIWYG canvas/properties;
355
+ The docs site ships a two-mode Visual Builder (Form mode: palette/WYSIWYG canvas/properties;
327
356
  Workflow mode: sequence + rules with a live preview running the real `<baht-workflow>`), with
328
- Import/Export JSON. Run it from the monorepo: `pnpm --filter playground dev` → `/builder`.
357
+ Import/Export JSON. Run it from the monorepo: `bun run --filter docs dev` → `/builder`.
@@ -0,0 +1,284 @@
1
+ import { svg as c, html as p, nothing as d } from "lit";
2
+ import { property as r, query as $, customElement as f, state as w } from "lit/decorators.js";
3
+ import { B as v, b as C, h as g } from "./base-CajA2aCO.js";
4
+ import { t as E } from "./i18n-BcPL9_Jh.js";
5
+ const O = {
6
+ copy: c`<rect x="6" y="6" width="8" height="8" rx="1.5"/><path d="M10 6V4.5A1.5 1.5 0 0 0 8.5 3H4.5A1.5 1.5 0 0 0 3 4.5v4A1.5 1.5 0 0 0 4.5 10H6"/>`,
7
+ check: c`<path d="M3.5 8.5 6.5 11.5 12.5 4.5"/>`,
8
+ download: c`<path d="M8 3v7m0 0 3-3m-3 3L5 7"/><path d="M3 12h10"/>`,
9
+ upload: c`<path d="M8 12V5m0 0 3 3M8 5 5 8"/><path d="M3 4h10"/>`,
10
+ plus: c`<path d="M8 3.5v9M3.5 8h9"/>`,
11
+ external: c`<path d="M6 3H3.5v9.5h9.5V10"/><path d="M9 3h4v4"/><path d="M13 3 7.5 8.5"/>`,
12
+ close: c`<path d="M4 4l8 8M12 4l-8 8"/>`,
13
+ play: c`<path d="M5 3.5v9l7-4.5z"/>`,
14
+ search: c`<circle cx="7" cy="7" r="4"/><path d="m13 13-3.2-3.2"/>`
15
+ };
16
+ function S(t) {
17
+ return p`<svg
18
+ class="baht-icon"
19
+ width="16"
20
+ height="16"
21
+ viewBox="0 0 16 16"
22
+ fill="none"
23
+ stroke="currentColor"
24
+ stroke-width="1.5"
25
+ stroke-linecap="round"
26
+ stroke-linejoin="round"
27
+ aria-hidden="true"
28
+ >${O[t]}</svg>`;
29
+ }
30
+ function M(t) {
31
+ return t in O;
32
+ }
33
+ var k = Object.defineProperty, x = Object.getOwnPropertyDescriptor, u = (t, s, o, i) => {
34
+ for (var e = i > 1 ? void 0 : i ? x(s, o) : s, a = t.length - 1, l; a >= 0; a--)
35
+ (l = t[a]) && (e = (i ? l(s, o, e) : l(e)) || e);
36
+ return i && e && k(s, o, e), e;
37
+ };
38
+ let h = class extends v {
39
+ constructor() {
40
+ super(...arguments), this.variant = "primary", this.size = "md", this.disabled = !1, this.type = "button", this.label = "", this.icon = "", this.iconOnly = !1;
41
+ }
42
+ firstUpdated() {
43
+ this.iconOnly || C(this, this.buttonEl);
44
+ }
45
+ render() {
46
+ const t = this.variant === "primary" ? "" : ` baht-button--${this.variant}`, s = this.size === "md" ? "" : ` baht-button--${this.size}`, o = this.iconOnly ? " baht-button--icon" : "", i = this.icon && M(this.icon) ? S(this.icon) : d;
47
+ return p`<button
48
+ class="baht-button${t}${s}${o}"
49
+ type=${this.type}
50
+ aria-label=${this.iconOnly && this.label ? this.label : d}
51
+ ?disabled=${this.disabled}
52
+ >${i}${this.iconOnly ? d : this.label}</button>`;
53
+ }
54
+ };
55
+ u([
56
+ r()
57
+ ], h.prototype, "variant", 2);
58
+ u([
59
+ r()
60
+ ], h.prototype, "size", 2);
61
+ u([
62
+ r({ type: Boolean })
63
+ ], h.prototype, "disabled", 2);
64
+ u([
65
+ r()
66
+ ], h.prototype, "type", 2);
67
+ u([
68
+ r()
69
+ ], h.prototype, "label", 2);
70
+ u([
71
+ r()
72
+ ], h.prototype, "icon", 2);
73
+ u([
74
+ r({ type: Boolean, attribute: "icon-only" })
75
+ ], h.prototype, "iconOnly", 2);
76
+ u([
77
+ $("button")
78
+ ], h.prototype, "buttonEl", 2);
79
+ h = u([
80
+ f("baht-button")
81
+ ], h);
82
+ const P = 600;
83
+ function _(t, s) {
84
+ const o = typeof t.getAnimations == "function" ? t.getAnimations({ subtree: !0 }) : [];
85
+ if (o.length === 0) {
86
+ s();
87
+ return;
88
+ }
89
+ let i = !1;
90
+ const e = () => {
91
+ i || (i = !0, s());
92
+ };
93
+ Promise.all(o.map((a) => a.finished.catch(() => {
94
+ }))).then(e, e), setTimeout(e, P);
95
+ }
96
+ var B = Object.defineProperty, T = Object.getOwnPropertyDescriptor, b = (t, s, o, i) => {
97
+ for (var e = i > 1 ? void 0 : i ? T(s, o) : s, a = t.length - 1, l; a >= 0; a--)
98
+ (l = t[a]) && (e = (i ? l(s, o, e) : l(e)) || e);
99
+ return i && e && B(s, o, e), e;
100
+ };
101
+ let n = class extends v {
102
+ constructor() {
103
+ super(...arguments), this.open = !1, this.heading = "", this.showClose = !0, this.closeOnScrim = !0, this.closing = !1, this.closeToken = 0, this.previousFocus = null, this.onKeydown = (t) => {
104
+ !this.open || n.openStack.at(-1) !== this || (t.key === "Escape" ? this.requestClose("escape") : t.key === "Tab" && this.trapFocus(t));
105
+ };
106
+ }
107
+ get titleId() {
108
+ return `baht-modal-title-${this.uid}`;
109
+ }
110
+ focusables() {
111
+ const t = this.querySelector(".baht-modal");
112
+ return t ? Array.from(
113
+ t.querySelectorAll(
114
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
115
+ )
116
+ ) : [];
117
+ }
118
+ /** Keep Tab cycling inside the dialog (no keyboard escape to the page behind). */
119
+ trapFocus(t) {
120
+ const s = this.focusables(), o = this.querySelector(".baht-modal");
121
+ if (s.length === 0) {
122
+ o == null || o.focus(), t.preventDefault();
123
+ return;
124
+ }
125
+ const i = s[0], e = s[s.length - 1], a = document.activeElement;
126
+ if (!this.contains(a)) {
127
+ i.focus(), t.preventDefault();
128
+ return;
129
+ }
130
+ t.shiftKey && (a === i || a === o) ? (e.focus(), t.preventDefault()) : !t.shiftKey && a === e && (i.focus(), t.preventDefault());
131
+ }
132
+ connectedCallback() {
133
+ super.connectedCallback(), g() && document.addEventListener("keydown", this.onKeydown);
134
+ }
135
+ disconnectedCallback() {
136
+ g() && document.removeEventListener("keydown", this.onKeydown), n.openStack = n.openStack.filter((t) => t !== this), super.disconnectedCallback();
137
+ }
138
+ firstUpdated() {
139
+ C(this, this.bodyEl);
140
+ }
141
+ willUpdate(t) {
142
+ t.has("open") && (this.open ? this.closing = !1 : t.get("open") === !0 && (this.closing = !0));
143
+ }
144
+ updated(t) {
145
+ var s, o;
146
+ if (t.has("open") && (n.openStack = n.openStack.filter((i) => i !== this), this.open && n.openStack.push(this)), t.has("open") && !this.open && this.closing) {
147
+ const i = ++this.closeToken, e = this.querySelector(".baht-modal-scrim"), a = () => {
148
+ i === this.closeToken && !this.open && (this.closing = !1);
149
+ };
150
+ e ? _(e, a) : a();
151
+ }
152
+ if (t.has("open") && this.open && (this.previousFocus = document.activeElement, (s = this.querySelector(".baht-modal")) == null || s.focus()), t.has("open") && !this.open && t.get("open") === !0) {
153
+ const i = this.previousFocus;
154
+ this.previousFocus = null, i != null && i.isConnected && (!document.activeElement || document.activeElement === document.body || this.contains(document.activeElement)) && ((o = i.focus) == null || o.call(i));
155
+ }
156
+ }
157
+ /** Ask the host to close (the host owns `open`). */
158
+ requestClose(t = "button") {
159
+ this.dispatchEvent(
160
+ new CustomEvent("baht-close", { detail: { reason: t }, bubbles: !0, composed: !0 })
161
+ );
162
+ }
163
+ render() {
164
+ return p`<div
165
+ class="baht-modal-scrim${this.closing ? " baht-modal-scrim--closing" : ""}"
166
+ style=${this.open || this.closing ? d : "display:none"}
167
+ @click=${(t) => this.closeOnScrim && t.target === t.currentTarget && this.requestClose("scrim")}
168
+ >
169
+ <div
170
+ class="baht-modal"
171
+ role="dialog"
172
+ aria-modal="true"
173
+ aria-labelledby=${this.heading ? this.titleId : d}
174
+ aria-label=${this.heading ? d : "Dialog"}
175
+ tabindex="-1"
176
+ >
177
+ ${this.heading || this.showClose ? p`<header class="baht-modal-header">
178
+ <div class="baht-modal-title" id=${this.titleId}>${this.heading}</div>
179
+ ${this.showClose ? p`<button type="button" class="baht-modal-close" aria-label=${E("close")} @click=${() => this.requestClose("button")}>×</button>` : d}
180
+ </header>` : d}
181
+ <div class="baht-modal-body"></div>
182
+ </div>
183
+ </div>`;
184
+ }
185
+ };
186
+ n.openStack = [];
187
+ b([
188
+ r({ type: Boolean, reflect: !0 })
189
+ ], n.prototype, "open", 2);
190
+ b([
191
+ r()
192
+ ], n.prototype, "heading", 2);
193
+ b([
194
+ r({ type: Boolean, attribute: "show-close" })
195
+ ], n.prototype, "showClose", 2);
196
+ b([
197
+ r({ type: Boolean, attribute: "close-on-scrim" })
198
+ ], n.prototype, "closeOnScrim", 2);
199
+ b([
200
+ $(".baht-modal-body")
201
+ ], n.prototype, "bodyEl", 2);
202
+ b([
203
+ w()
204
+ ], n.prototype, "closing", 2);
205
+ n = b([
206
+ f("baht-modal")
207
+ ], n);
208
+ var D = Object.defineProperty, q = Object.getOwnPropertyDescriptor, y = (t, s, o, i) => {
209
+ for (var e = i > 1 ? void 0 : i ? q(s, o) : s, a = t.length - 1, l; a >= 0; a--)
210
+ (l = t[a]) && (e = (i ? l(s, o, e) : l(e)) || e);
211
+ return i && e && D(s, o, e), e;
212
+ };
213
+ let m = class extends v {
214
+ constructor() {
215
+ super(...arguments), this.duration = 4e3, this.entries = [], this.timers = /* @__PURE__ */ new Map();
216
+ }
217
+ show(t) {
218
+ const s = {
219
+ id: ++m.counter,
220
+ message: t.message,
221
+ tone: t.tone ?? "neutral",
222
+ duration: t.duration ?? this.duration
223
+ };
224
+ return this.entries = [...this.entries, s], s.duration > 0 && this.timers.set(
225
+ s.id,
226
+ setTimeout(() => this.dismiss(s.id), s.duration)
227
+ ), s.id;
228
+ }
229
+ dismiss(t) {
230
+ const s = this.entries.find((e) => e.id === t);
231
+ if (!s || s.leaving) return;
232
+ clearTimeout(this.timers.get(t)), this.timers.delete(t), this.entries = this.entries.map((e) => e.id === t ? { ...e, leaving: !0 } : e), this.dispatchEvent(
233
+ new CustomEvent("baht-dismiss", {
234
+ detail: { id: t, message: s.message },
235
+ bubbles: !0,
236
+ composed: !0
237
+ })
238
+ );
239
+ const o = () => {
240
+ this.entries = this.entries.filter((e) => e.id !== t);
241
+ }, i = this.querySelector(`.baht-toast[data-toast="${t}"]`);
242
+ if (!i) {
243
+ o();
244
+ return;
245
+ }
246
+ i.classList.add("baht-toast--leaving"), _(i, o);
247
+ }
248
+ disconnectedCallback() {
249
+ this.timers.forEach((t) => clearTimeout(t)), this.timers.clear(), super.disconnectedCallback();
250
+ }
251
+ render() {
252
+ return p`<div class="baht-toaster" role="status" aria-live="polite">
253
+ ${this.entries.map(
254
+ // Danger/warning interrupt (role=alert); the rest ride the polite container.
255
+ (t) => p`<div
256
+ class="baht-toast ${t.tone === "neutral" ? "" : `baht-toast--${t.tone}`}${t.leaving ? " baht-toast--leaving" : ""}"
257
+ data-toast=${t.id}
258
+ role=${t.tone === "danger" || t.tone === "warning" ? "alert" : d}
259
+ >
260
+ <span class="baht-toast-message">${t.message}</span>
261
+ <button type="button" class="baht-toast-dismiss" aria-label=${E("dismiss")} @click=${() => this.dismiss(t.id)}>×</button>
262
+ </div>`
263
+ )}
264
+ </div>`;
265
+ }
266
+ };
267
+ m.counter = 0;
268
+ y([
269
+ r({ type: Number })
270
+ ], m.prototype, "duration", 2);
271
+ y([
272
+ w()
273
+ ], m.prototype, "entries", 2);
274
+ m = y([
275
+ f("baht-toast")
276
+ ], m);
277
+ export {
278
+ h as B,
279
+ n as a,
280
+ m as b,
281
+ _ as c,
282
+ M as i,
283
+ S as r
284
+ };