{
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
  "name": "bank20baht-ui",
  "version": "0.14.0",
  "description-markup": "markdown",
  "contributions": {
    "html": {
      "elements": [
        {
          "name": "baht-form",
          "description": "<baht-form> — dumb controlled form renderer (spec 01).\nJSON in (form + value + registry) -> UI out; emits `baht-change` / `baht-validity`.\nNo buttons, no cross-field logic — that's <baht-workflow> or the host.\n---\n\n\n### **Events:**\n - **baht-validity**\n- **baht-change**\n- **name**\n\n### **Methods:**\n - **setValue(patch: _Record<string, unknown>_): _void_** - Merge values programmatically. Runs each key's `change` scripts (like\nServiceNow's g_form.setValue) but emits no `baht-change` — the host\nalready knows, and a store adapter would otherwise loop.\n- **validate(): _{ valid: boolean; errors: ValidationError[] }_** - Full validation pass: validators, then `submit` scripts (spec 01 §14). A\nscript returning `false` blocks; its `addErrorMessage()` calls become\n`{ key: '$' }` errors so hosts see why.\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "show-optional",
              "description": "Label every non-required field \"(optional)\" (localized) instead of relying on asterisks alone.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "view",
              "description": "Name of a view from `form.views`. Display-only: when set, only that view's\nfields are rendered and validated (the user can only fix what they see),\nbut getData() still returns the FULL form — the server always validates\nthe complete schema, views never change the submit payload.",
              "value": { "type": "string | undefined | undefined" }
            },
            {
              "name": "readonly",
              "description": "Force every field readonly — a display-only render of this form's own layout/widths.",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            { "name": "baht-validity", "type": "CustomEvent" },
            { "name": "baht-change", "type": "CustomEvent" },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              { "name": "form", "type": "FormDefinition" },
              { "name": "value", "type": "Record<string, unknown>" },
              { "name": "registry", "type": "Registry" },
              {
                "name": "showOptional",
                "description": "Label every non-required field \"(optional)\" (localized) instead of relying on asterisks alone.",
                "type": "boolean"
              },
              { "name": "options", "type": "BahtFormOptions" },
              {
                "name": "fieldStates",
                "description": "L2 (workflow) derived per-field UI state, keyed by field key. Optional.",
                "type": "Record<string, DerivedFieldState>"
              },
              {
                "name": "view",
                "description": "Name of a view from `form.views`. Display-only: when set, only that view's\nfields are rendered and validated (the user can only fix what they see),\nbut getData() still returns the FULL form — the server always validates\nthe complete schema, views never change the submit payload.",
                "type": "string | undefined | undefined"
              },
              {
                "name": "flags",
                "description": "Feature-flag values (spec 01 §12) — fields whose `flag` is off behave as\nif they were never in the schema: no render, no validation, no payload.",
                "type": "FeatureFlags"
              },
              {
                "name": "readonly",
                "description": "Force every field readonly — a display-only render of this form's own layout/widths.",
                "type": "boolean"
              }
            ],
            "events": [
              { "name": "baht-validity", "type": "CustomEvent" },
              { "name": "baht-change", "type": "CustomEvent" },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-workflow",
          "description": "<baht-workflow> — the optional brain (spec 03). Wraps the current form in an\ninternal <baht-form>, evaluates rules on every change, walks the visible forms\nin linear `order`, and owns the single Next/Submit button.\nForms with kind 'approval' render a decision step instead (spec 03 §4):\na summary of earlier answers plus Approve/Reject (+ optional comment); the\ndecision lands in data as `<form_name>.decision` so rules can branch on it.\nThe runtime navigates; the host acts (on baht-next / baht-submit / baht-approve /\nbaht-reject / baht-complete).\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n - **setValue(form_name: _string_, patch: _Record<string, unknown>_): _void_** - Host/store write into one form's data — same path a user edit takes\n(rules reevaluate, `baht-data-change` fires) but WITHOUT the step reset\nthat re-feeding the `value` prop causes.\n- **approve(): _void_** - Approve the current approval step (same as pressing its Approve button).\n- **reject(): _void_** - Reject the current approval step (same as pressing its Reject button).\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "validate-gate",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "next-label",
              "description": "Label attributes beat the i18n catalog; unset = t('next')/t('submit')/t('back').",
              "value": { "type": "string | undefined" }
            },
            {
              "name": "submit-label",
              "value": { "type": "string | undefined" }
            },
            { "name": "back-label", "value": { "type": "string | undefined" } },
            {
              "name": "show-back",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "show-steps",
              "description": "Render a <baht-steps> progress indicator above the current form.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "steps-variant",
              "description": "'default' horizontal markers, or 'sidebar' vertical nav list — forwarded to <baht-steps>.",
              "value": {
                "type": "'default' | 'sidebar'",
                "default": "'default'"
              }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "workflow", "type": "WorkflowDefinition" },
              { "name": "value", "type": "NestedData" },
              { "name": "registry", "type": "Registry" },
              { "name": "validateGate", "type": "boolean" },
              {
                "name": "nextLabel",
                "description": "Label attributes beat the i18n catalog; unset = t('next')/t('submit')/t('back').",
                "type": "string | undefined"
              },
              { "name": "submitLabel", "type": "string | undefined" },
              { "name": "backLabel", "type": "string | undefined" },
              { "name": "showBack", "type": "boolean" },
              {
                "name": "flags",
                "description": "Feature-flag values (spec 01 §12): flag-off steps/fields/rules vanish.",
                "type": "FeatureFlags"
              },
              {
                "name": "showSteps",
                "description": "Render a <baht-steps> progress indicator above the current form.",
                "type": "boolean"
              },
              {
                "name": "stepsVariant",
                "description": "'default' horizontal markers, or 'sidebar' vertical nav list — forwarded to <baht-steps>.",
                "type": "'default' | 'sidebar'"
              },
              { "name": "currentFormName", "type": "string | null" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-accordion",
          "description": "Accordion. `items` drive the panels; `multiple` allows several open at once.\nEmits `baht-toggle { index, open }`.\n\n  el.items = [\n    { title: 'What is a workflow?', content: 'Workflows run when something happens…' },\n    { title: 'Pricing', content: '฿20 / month.', open: true },\n  ];\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "multiple",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "items", "type": "AccordionItem[]" },
              { "name": "multiple", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-alert",
          "description": "Inline alert / callout.\n\n  <baht-alert tone=\"danger\" heading=\"Couldn't reach the webhook\"\n            message=\"Check the URL and try again.\" dismissible></baht-alert>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "tone",
              "value": {
                "type": "'info' | 'success' | 'warning' | 'danger'",
                "default": "'info'"
              }
            },
            {
              "name": "heading",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "message",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "dismissible",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "tone",
                "type": "'info' | 'success' | 'warning' | 'danger'"
              },
              { "name": "heading", "type": "string" },
              { "name": "message", "type": "string" },
              { "name": "dismissible", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-avatar",
          "description": "Small circular identity chip — initials derived from `label`, or an\nexplicit `initials` override.\n\n  <baht-avatar label=\"Pear Nattapong\"></baht-avatar>\n  <baht-avatar initials=\"P\" size=\"sm\"></baht-avatar>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "label",
              "description": "Full name; initials are derived from it when `initials` isn't set.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "initials",
              "description": "Explicit override — first 1-2 characters are shown as-is.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "size",
              "value": { "type": "AvatarSize", "default": "'md'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "label",
                "description": "Full name; initials are derived from it when `initials` isn't set.",
                "type": "string"
              },
              {
                "name": "initials",
                "description": "Explicit override — first 1-2 characters are shown as-is.",
                "type": "string"
              },
              { "name": "size", "type": "AvatarSize" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-badge",
          "description": "Status pill / tag / counter (20baht badges).\nStatus tones (neutral/success/danger/warning/info) show a colored dot;\ntag tones (outline/solid/gold/inverse/mono) don't.\n\n  <baht-badge tone=\"success\" label=\"Running\"></baht-badge>\n  <baht-badge tone=\"mono\" label=\"฿20 / mo\"></baht-badge>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "tone",
              "value": { "type": "BadgeTone", "default": "'neutral'" }
            },
            { "name": "label", "value": { "type": "string", "default": "''" } },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "tone", "type": "BadgeTone" },
              { "name": "label", "type": "string" },
              {
                "name": "dot",
                "description": "Force the dot on/off; defaults to on for status tones.",
                "type": "boolean | null"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-bar-chart",
          "description": "\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n\n\n\n\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "orientation",
              "description": "Bars across (default) or columns up.",
              "value": {
                "type": "'horizontal' | 'vertical'",
                "default": "'horizontal'"
              }
            },
            {
              "name": "stacked",
              "description": "Stack the series inside one band instead of grouping them side by side.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "compare-label",
              "description": "Names the muted `compare` bar in the key and the table twin.",
              "value": { "type": "string", "default": "'Previous period'" }
            },
            {
              "name": "label-values",
              "description": "`ends` prints the value at the bar tip; `none` leaves it to the tooltip.",
              "value": { "type": "'ends' | 'none'", "default": "'ends'" }
            },
            {
              "name": "emphasis",
              "description": "Index of the series (2+ series) or category (one series) to highlight.",
              "value": { "type": "number | null", "default": "null" }
            },
            {
              "name": "color",
              "description": "Single color for every bar; per-series palette otherwise.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "label",
              "description": "Accessible summary used by the vertical (graphics-only) rendering.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "empty-message",
              "description": "Shown instead of the marks when there is nothing to plot.",
              "value": { "type": "string", "default": "'No data to chart'" }
            },
            {
              "name": "loading",
              "description": "Hold the previous render at reduced opacity while new data loads.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-table",
              "description": "Render the \"View as table\" twin under the chart.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "format",
              "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
              "value": { "type": "ChartFormat", "default": "'number'" }
            },
            {
              "name": "currency",
              "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
              "value": { "type": "string", "default": "'THB'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "groups",
                "description": "Original API: `{ label, count }[]`, one bar each.",
                "type": "ChartGroup[]"
              },
              {
                "name": "series",
                "description": "Series API: one entry per series, one value per category.",
                "type": "ChartSeries[]"
              },
              {
                "name": "categories",
                "description": "Category names, one per value index.",
                "type": "string[]"
              },
              {
                "name": "orientation",
                "description": "Bars across (default) or columns up.",
                "type": "'horizontal' | 'vertical'"
              },
              {
                "name": "stacked",
                "description": "Stack the series inside one band instead of grouping them side by side.",
                "type": "boolean"
              },
              {
                "name": "compare",
                "description": "Previous-period values, drawn as a muted bar under each current bar.",
                "type": "number[]"
              },
              {
                "name": "compareLabel",
                "description": "Names the muted `compare` bar in the key and the table twin.",
                "type": "string"
              },
              {
                "name": "markers",
                "description": "Budget / target lines drawn over the bars.",
                "type": "ChartMarker[]"
              },
              {
                "name": "labelValues",
                "description": "`ends` prints the value at the bar tip; `none` leaves it to the tooltip.",
                "type": "'ends' | 'none'"
              },
              {
                "name": "emphasis",
                "description": "Index of the series (2+ series) or category (one series) to highlight.",
                "type": "number | null"
              },
              {
                "name": "color",
                "description": "Single color for every bar; per-series palette otherwise.",
                "type": "string"
              },
              {
                "name": "label",
                "description": "Accessible summary used by the vertical (graphics-only) rendering.",
                "type": "string"
              },
              {
                "name": "emptyMessage",
                "description": "Shown instead of the marks when there is nothing to plot.",
                "type": "string"
              },
              {
                "name": "loading",
                "description": "Hold the previous render at reduced opacity while new data loads.",
                "type": "boolean"
              },
              {
                "name": "showTable",
                "description": "Render the \"View as table\" twin under the chart.",
                "type": "boolean"
              },
              {
                "name": "format",
                "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
                "type": "ChartFormat"
              },
              {
                "name": "currency",
                "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-breadcrumbs",
          "description": "Breadcrumb trail. Last item renders as the current page (no link). Items\nwithout `href` emit `baht-navigate { item, index }` for router-driven apps.\n\n  el.items = [\n    { label: 'Workflows', href: '/workflows' },\n    { label: 'Signup', href: '/workflows/signup' },\n    { label: 'Run #812' },\n  ];\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "separator",
              "value": { "type": "string", "default": "'›'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "items", "type": "Crumb[]" },
              { "name": "separator", "type": "string" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-button",
          "description": "Design-system button (20baht: no scale, no shadow change — color does the work).\nStandalone primitive: children become the label, listen for the native `click`.\n(Light DOM has no <slot>, so the initial children are moved into the button.)\n\n  <baht-button variant=\"danger\" size=\"sm\">Delete</baht-button>\n  <baht-button label=\"Save workflow\"></baht-button>\n  <baht-button icon=\"copy\" variant=\"ghost\" size=\"sm\">Copy</baht-button>\n  <baht-button icon=\"close\" icon-only label=\"Close\"></baht-button>  <!-- label = a11y name -->\n  <baht-button aria-label=\"Previous month\">‹</baht-button>  <!-- glyph child, host aria-label wins -->\n  <baht-button variant=\"secondary\" shortcut=\"alt+q\">Close</baht-button>  <!-- ⌥Q / Alt+Q -->\n\n`shortcut` binds a document-wide key combo that clicks the button (NOTES 2026-10-31):\nhover/focus shows a tooltip with the label and the keys (`⌥Q` on Mac, `Alt+Q`\nelsewhere) and the inner button gets `aria-keyshortcuts`. Disabled buttons, buttons\noutside the topmost open `<baht-modal>`, and modifier-less combos while typing in a\nfield never fire.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "variant",
              "value": { "type": "ButtonVariant", "default": "'primary'" }
            },
            {
              "name": "size",
              "value": { "type": "ButtonSize", "default": "'md'" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "type",
              "description": "Forwarded to the native button — 'button' (default) or 'submit'.",
              "value": { "type": "'button' | 'submit'", "default": "'button'" }
            },
            {
              "name": "label",
              "description": "Alternative to children for setting the label programmatically.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "icon",
              "description": "Optional leading icon from the curated set (copy/check/download/…).",
              "value": { "type": "IconName | ''", "default": "''" }
            },
            {
              "name": "icon-only",
              "description": "Square icon-only button. Requires `label` for the accessible name.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "aria-label",
              "description": "A host `aria-label` never reaches assistive tech on its own — light DOM has no\nimplicit ARIA relationship between a custom element and its rendered children, so an\n`aria-label` written on `<baht-button>` stays on the host and the inner native\n`<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into\nthis property forwards it to the inner button's `aria-label`, taking priority over\n`label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real\naccessible name without `icon-only`. Mapped to the plain `aria-label` attribute so\nhosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to\navoid shadowing the platform's own ARIAMixin.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "shortcut",
              "description": "Keyboard shortcut that clicks this button from anywhere on the page, e.g. `alt+q`,\n`shift+alt+s`, `mod+enter` (`mod` = ⌘ on Mac, Ctrl elsewhere). Also shown in a\nhover/focus tooltip and announced through `aria-keyshortcuts`.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "tooltip-position",
              "description": "Where the shortcut tooltip sits.",
              "value": { "type": "'top' | 'bottom'", "default": "'bottom'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "variant", "type": "ButtonVariant" },
              { "name": "size", "type": "ButtonSize" },
              { "name": "disabled", "type": "boolean" },
              {
                "name": "type",
                "description": "Forwarded to the native button — 'button' (default) or 'submit'.",
                "type": "'button' | 'submit'"
              },
              {
                "name": "label",
                "description": "Alternative to children for setting the label programmatically.",
                "type": "string"
              },
              {
                "name": "icon",
                "description": "Optional leading icon from the curated set (copy/check/download/…).",
                "type": "IconName | ''"
              },
              {
                "name": "iconOnly",
                "description": "Square icon-only button. Requires `label` for the accessible name.",
                "type": "boolean"
              },
              {
                "name": "hostAriaLabel",
                "description": "A host `aria-label` never reaches assistive tech on its own — light DOM has no\nimplicit ARIA relationship between a custom element and its rendered children, so an\n`aria-label` written on `<baht-button>` stays on the host and the inner native\n`<button>` keeps whatever text (or none) is inside it. Mirroring the attribute into\nthis property forwards it to the inner button's `aria-label`, taking priority over\n`label`/icon-only fallback — this is how a glyph-only child (e.g. `‹`) gets a real\naccessible name without `icon-only`. Mapped to the plain `aria-label` attribute so\nhosts write it the way they naturally would; kept off the `ariaLabel` IDL accessor to\navoid shadowing the platform's own ARIAMixin.",
                "type": "string"
              },
              {
                "name": "shortcut",
                "description": "Keyboard shortcut that clicks this button from anywhere on the page, e.g. `alt+q`,\n`shift+alt+s`, `mod+enter` (`mod` = ⌘ on Mac, Ctrl elsewhere). Also shown in a\nhover/focus tooltip and announced through `aria-keyshortcuts`.",
                "type": "string"
              },
              {
                "name": "tooltipPosition",
                "description": "Where the shortcut tooltip sits.",
                "type": "'top' | 'bottom'"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-card",
          "description": "Surface card (20baht: white on cream, 1px border, no shadow at rest — the\nborder carries the separation). Children become the body.\n\n  <baht-card heading=\"Runs\" subtitle=\"Last 30 days\">…content…</baht-card>\n  <baht-card hero>…large feature card (20px radius)…</baht-card>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "heading",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "subtitle",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "hero",
              "description": "hero = 20px radius + roomier padding (large/feature cards).",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "heading", "type": "string" },
              { "name": "subtitle", "type": "string" },
              {
                "name": "hero",
                "description": "hero = 20px radius + roomier padding (large/feature cards).",
                "type": "boolean"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-checkbox",
          "description": "\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n\n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-code",
          "description": "Monospace code (20baht): an inline chip or a block. Content is the light-DOM\nchildren (or the `code` property). Not a syntax highlighter — just the\ndesign-system's canonical way to show code/identifiers/tokens.\n\n  <baht-code>baht-button</baht-code>\n  <baht-code variant=\"block\">npm i bank20baht-ui</baht-code>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "variant",
              "value": { "type": "CodeVariant", "default": "'inline'" }
            },
            {
              "name": "code",
              "description": "Alternative to children for setting the code text programmatically.",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "variant", "type": "CodeVariant" },
              {
                "name": "code",
                "description": "Alternative to children for setting the code text programmatically.",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-comments",
          "description": "A conversation on a record: the thread, oldest first, and a composer. With\n`allow-internal` the composer offers \"internal note\" next to a normal\ncomment (ServiceNow's work notes vs additional comments). Submitting emits\n`baht-submit` with `{ body, internal }` and clears the box; the host stores\nit and feeds the new `entries` back.\n\n  <baht-comments allow-internal></baht-comments>\n  el.entries = [{ id: 1, author: 'Bob', time: '14:44', body: 'Still broken' }];\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "allow-internal",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "placeholder",
              "value": { "type": "string", "default": "'Add a comment…'" }
            },
            {
              "name": "internal-label",
              "value": { "type": "string", "default": "'Internal note'" }
            },
            {
              "name": "empty-message",
              "value": { "type": "string", "default": "'No comments yet.'" }
            },
            {
              "name": "busy",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "entries", "type": "CommentEntry[]" },
              { "name": "allowInternal", "type": "boolean" },
              { "name": "placeholder", "type": "string" },
              { "name": "internalLabel", "type": "string" },
              { "name": "emptyMessage", "type": "string" },
              { "name": "busy", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-currency",
          "description": "Money input: plain digits while typing, thousands-separated on blur.\nReformatting every keystroke fights the caret and recomputes on every key\nfor no reason — so the separator only appears once the field is quiet.\nEmits a plain `number | null`, same as `number` (spec 01 §2).\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-datepicker",
          "description": "Calendar field on top of flatpickr. The VALUE is always ISO `yyyy-mm-dd`\nregardless of `field.date_format` (display only):\n  - `date_mode: 'single'` (default) -> `string | null`\n  - `date_mode: 'range'`            -> `[start, end] | null` (inclusive)\n  - `date_mode: 'multiple'`         -> `string[]`\n`min` / `max` bound the calendar; `disabled_dates` (ISO days or `a/b`\nintervals) and `disabled_weekdays` (0 = Sun … 6 = Sat) grey days out — and\nthe engine re-checks them on validate, so the calendar is UX, not the gate.\n\nThe calendar renders INSIDE the element (flatpickr `static`), so it lives in\nlight DOM like every other node and picks up a theme set on any ancestor.\nGive a clipping ancestor (`overflow: hidden`) room, or it hides the popup.\n\nA footer under the grid carries **Today** (picks today — greyed out when\nmin/max/disabled rules exclude it) and **Clear** (empties the value). With\n`field.enable_time` the first button reads **Now** (i18n key `now`) and picks\ntoday at the current time, to the minute (seconds dropped), under the same\nmin/max/disabled rules; like Today it closes the calendar. Both\n`await el.updateComplete` resolves only once the calendar is attached (the\nlazy flatpickr load is folded into it), so hosts and tests need no extra wait.\nToday / Clear go through flatpickr, so they emit exactly like a click on a day. `disableMobile`\nkeeps this calendar on phones too — never the OS date wheel.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-donut-chart",
          "description": "Pure-SVG donut with a legend that carries value and share.\n\n  const el = document.createElement('baht-donut-chart');\n  el.groups = [{ label: 'Open', count: 4 }, { label: 'Closed', count: 9 }];\n\n`total` defaults to the group sum; set it when the center number differs\n(e.g. records vs. groups). Segments are separated by a 2px gap that shows the\nsurface through, never by a stroke drawn around them.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n\n\n\n\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "total",
              "value": { "type": "number | null", "default": "null" }
            },
            {
              "name": "color",
              "description": "Single-series color override (first segment).",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "center-label",
              "value": { "type": "string", "default": "'records'" }
            },
            {
              "name": "other-label",
              "description": "Label of the folded tail bucket.",
              "value": { "type": "string", "default": "'Other'" }
            },
            {
              "name": "empty-message",
              "description": "Shown instead of the marks when there is nothing to plot.",
              "value": { "type": "string", "default": "'No data to chart'" }
            },
            {
              "name": "loading",
              "description": "Hold the previous render at reduced opacity while new data loads.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-table",
              "description": "Render the \"View as table\" twin under the chart.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "format",
              "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
              "value": { "type": "ChartFormat", "default": "'number'" }
            },
            {
              "name": "currency",
              "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
              "value": { "type": "string", "default": "'THB'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "groups", "type": "ChartGroup[]" },
              { "name": "total", "type": "number | null" },
              {
                "name": "color",
                "description": "Single-series color override (first segment).",
                "type": "string"
              },
              { "name": "centerLabel", "type": "string" },
              {
                "name": "otherLabel",
                "description": "Label of the folded tail bucket.",
                "type": "string"
              },
              {
                "name": "emptyMessage",
                "description": "Shown instead of the marks when there is nothing to plot.",
                "type": "string"
              },
              {
                "name": "loading",
                "description": "Hold the previous render at reduced opacity while new data loads.",
                "type": "boolean"
              },
              {
                "name": "showTable",
                "description": "Render the \"View as table\" twin under the chart.",
                "type": "boolean"
              },
              {
                "name": "format",
                "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
                "type": "ChartFormat"
              },
              {
                "name": "currency",
                "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-edit-table",
          "description": "A table whose cells are edited in place (reference lists, catalog items,\ngroups). Each changed row shows Save; Save emits `baht-row-save`\n`{ key, row }`, Remove emits `baht-row-delete` `{ key, row }`. With\n`addable`, a blank first row emits `baht-row-add` `{ row }`. The host\npersists and feeds the new `rows` back, which clears the edits.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "row-key",
              "value": { "type": "string", "default": "'id'" }
            },
            {
              "name": "addable",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "removable",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "value": { "type": "string", "default": "'Table'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "columns", "type": "EditColumn[]" },
              { "name": "rows", "type": "EditRow[]" },
              { "name": "rowKey", "type": "string" },
              { "name": "addable", "type": "boolean" },
              { "name": "removable", "type": "boolean" },
              { "name": "label", "type": "string" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-empty-state",
          "description": "Centered \"nothing here\" placeholder for lists, tables, and searches.\n\n  <baht-empty-state message=\"No records to display\" hint=\"Try clearing the filter\"></baht-empty-state>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "icon",
              "description": "Illustration name (`empty-box`, `search-slash`, `folder`, …), or any emoji / short text. Empty string hides it.",
              "value": { "type": "string", "default": "'empty-box'" }
            },
            {
              "name": "message",
              "value": { "type": "string", "default": "'Nothing to display'" }
            },
            { "name": "hint", "value": { "type": "string", "default": "''" } },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "icon",
                "description": "Illustration name (`empty-box`, `search-slash`, `folder`, …), or any emoji / short text. Empty string hides it.",
                "type": "string"
              },
              { "name": "message", "type": "string" },
              { "name": "hint", "type": "string" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-error-state",
          "description": "Centered failure placeholder with an optional retry action.\nEmits `baht-retry` when the retry button is clicked.\n\n  <baht-error-state heading=\"Backend unavailable\"\n                    message=\"The API is not responding.\"\n                    retry-label=\"Retry\"></baht-error-state>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "icon",
              "description": "Illustration name (`error` by default), or any emoji / short text. Empty string hides it.",
              "value": { "type": "string", "default": "'error'" }
            },
            {
              "name": "heading",
              "value": { "type": "string", "default": "'Something went wrong'" }
            },
            {
              "name": "message",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "retry-label",
              "description": "Renders a retry button with this label; omit for no action.",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "icon",
                "description": "Illustration name (`error` by default), or any emoji / short text. Empty string hides it.",
                "type": "string"
              },
              { "name": "heading", "type": "string" },
              { "name": "message", "type": "string" },
              {
                "name": "retryLabel",
                "description": "Renders a retry button with this label; omit for no action.",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-fieldset",
          "description": "Semantic group wrapper (native <fieldset>/<legend>). Children become the\ncontent — typically a set of baht-* fields.\n\n  <baht-fieldset legend=\"Billing\" help=\"Shown on invoices\">\n    <baht-input>…</baht-input>\n  </baht-fieldset>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "legend",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "help",
              "description": "Muted help line under the group (e.g. \"Fill both languages for accurate reading\").",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "legend", "type": "string" },
              {
                "name": "help",
                "description": "Muted help line under the group (e.g. \"Fill both languages for accurate reading\").",
                "type": "string"
              },
              { "name": "disabled", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-file",
          "description": "File field (spec 01 §2 + §13). Two modes:\n- capture (default): value = File handle(s); the lib never uploads.\n- upload (`field.uploader` + host `uploader` fn from the registry):\n  picking uploads immediately, value becomes serializable UploadedFile\n  ref(s), and the control shows chips with remove buttons.\nmax_size (bytes) is enforced here as a soft filter — oversize files rejected.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "uploader",
                "description": "Resolved registry uploader — <baht-form> injects it when `field.uploader` is set.",
                "type": "(file: File) => Promise<UploadedFile> | undefined"
              },
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-filter",
          "description": "Filter chip group. Controlled: emits `baht-change { selected }` (array of\noption keys), host re-feeds `selected`. Typical use: above an <baht-table>,\nthe handler refetches page 1 with the new filters.\n\n  const f = document.querySelector('baht-filter');\n  f.options = [{ key: 'running', label: 'Running' }, { key: 'failed', label: 'Failed' }];\n  f.selected = ['running'];\n  f.addEventListener('baht-change', (e) => refetch({ status: e.detail.selected }));\n\n`variant=\"segmented\"` draws the same options as one joined segmented\ncontrol (a single bordered group, active segment filled with the ink\ncolour) — for a period switch like Week / Month / Year. Pair it with\n`multiple=\"false\"`: a segmented control always holds one value, so clicking\nthe active segment does nothing (no `baht-change`) instead of clearing it.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "variant",
              "description": "Look: `'chips'` (default) = separate pill chips; `'segmented'` = one joined\nsegmented control. With `multiple=false` a segmented control never clears.",
              "value": { "type": "FilterVariant", "default": "'chips'" }
            },
            {
              "name": "multiple",
              "description": "false = radio behavior (max one active; clicking it again clears — except\nwith `variant=\"segmented\"`, where the active segment stays selected).",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "description": "Accessible name for the chip group.",
              "value": { "type": "string", "default": "'Filters'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "options", "type": "FieldOption[]" },
              { "name": "selected", "type": "string[]" },
              {
                "name": "variant",
                "description": "Look: `'chips'` (default) = separate pill chips; `'segmented'` = one joined\nsegmented control. With `multiple=false` a segmented control never clears.",
                "type": "FilterVariant"
              },
              {
                "name": "multiple",
                "description": "false = radio behavior (max one active; clicking it again clears — except\nwith `variant=\"segmented\"`, where the active segment stays selected).",
                "type": "boolean"
              },
              { "name": "disabled", "type": "boolean" },
              {
                "name": "label",
                "description": "Accessible name for the chip group.",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-heatmap",
          "description": "Calendar heat for one month or N weeks: one cell per day, colored on the\nsequential ramp, with a weekday header, a per-cell tooltip and a\n\"less / more\" scale legend.\n\n  const el = document.createElement('baht-heatmap');\n  el.values = [{ date: '2026-09-01', value: 420 }];\n  el.from = '2026-09-01'; el.to = '2026-09-30';\n\n`show-values` swaps the compact GitHub-style SVG (weeks as columns) for a\nmonth grid rendered as HTML (weeks as rows): seven weekday headers, one\nfocusable button per day carrying the day number and the formatted value,\ntoday's cell outlined. Being HTML, the grid fills its container while text\nkeeps its real size — nothing is scaled. Cell height follows\n`--baht-heatmap-cell-min` (56px). The two darkest ramp steps flip to light\ntext.\n\n  <baht-heatmap show-values format=\"currency\" zero-label=\"none\" .values=${daily}></baht-heatmap>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n\n\n\n\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "from",
              "description": "First day (inclusive), `YYYY-MM-DD`. Defaults to the earliest value.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "to",
              "description": "Last day (inclusive), `YYYY-MM-DD`. Defaults to the latest value.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "less-label",
              "description": "Caption on the low end of the scale legend.",
              "value": { "type": "string", "default": "'Less'" }
            },
            {
              "name": "more-label",
              "description": "Caption on the high end of the scale legend.",
              "value": { "type": "string", "default": "'More'" }
            },
            {
              "name": "zero-label",
              "description": "Text shown in a zero/empty cell in `show-values` mode.",
              "value": { "type": "string", "default": "'—'" }
            },
            {
              "name": "show-values",
              "description": "Roomier month-grid layout with the day number and value printed in each cell.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "description": "Accessible summary; falls back to a generated sentence.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "empty-message",
              "description": "Shown instead of the marks when there is nothing to plot.",
              "value": { "type": "string", "default": "'No data to chart'" }
            },
            {
              "name": "loading",
              "description": "Hold the previous render at reduced opacity while new data loads.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-table",
              "description": "Render the \"View as table\" twin under the chart.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "format",
              "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
              "value": { "type": "ChartFormat", "default": "'number'" }
            },
            {
              "name": "currency",
              "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
              "value": { "type": "string", "default": "'THB'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "values",
                "description": "`{ date: 'YYYY-MM-DD', value }[]`; days with no entry render as empty.",
                "type": "ChartDatePoint[]"
              },
              {
                "name": "from",
                "description": "First day (inclusive), `YYYY-MM-DD`. Defaults to the earliest value.",
                "type": "string"
              },
              {
                "name": "to",
                "description": "Last day (inclusive), `YYYY-MM-DD`. Defaults to the latest value.",
                "type": "string"
              },
              {
                "name": "lessLabel",
                "description": "Caption on the low end of the scale legend.",
                "type": "string"
              },
              {
                "name": "moreLabel",
                "description": "Caption on the high end of the scale legend.",
                "type": "string"
              },
              {
                "name": "zeroLabel",
                "description": "Text shown in a zero/empty cell in `show-values` mode.",
                "type": "string"
              },
              {
                "name": "showValues",
                "description": "Roomier month-grid layout with the day number and value printed in each cell.",
                "type": "boolean"
              },
              {
                "name": "label",
                "description": "Accessible summary; falls back to a generated sentence.",
                "type": "string"
              },
              {
                "name": "emptyMessage",
                "description": "Shown instead of the marks when there is nothing to plot.",
                "type": "string"
              },
              {
                "name": "loading",
                "description": "Hold the previous render at reduced opacity while new data loads.",
                "type": "boolean"
              },
              {
                "name": "showTable",
                "description": "Render the \"View as table\" twin under the chart.",
                "type": "boolean"
              },
              {
                "name": "format",
                "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
                "type": "ChartFormat"
              },
              {
                "name": "currency",
                "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-input",
          "description": "text + email + password share one control; email differs only in input type +\nvalidator, password only in input type (masked, never formatted or logged).\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-keyvalue-editor",
          "description": "Inline key = value row editor (field maps, config objects, …).\nControlled: set `value`, listen for `baht-change` ({ value }) — rows with an\nempty key are kept while editing but dropped from the emitted object.\n\n  const el = document.createElement('baht-keyvalue-editor');\n  el.value = { state: 'resolved' };\n  el.addEventListener('baht-change', (e) => save(e.detail.value));\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "key-placeholder",
              "value": { "type": "string", "default": "'key'" }
            },
            {
              "name": "value-placeholder",
              "value": { "type": "string", "default": "'value'" }
            },
            {
              "name": "add-label",
              "value": { "type": "string", "default": "'Add row'" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "value", "type": "Record<string, unknown>" },
              { "name": "keyPlaceholder", "type": "string" },
              { "name": "valuePlaceholder", "type": "string" },
              { "name": "add_label", "type": "string" },
              { "name": "disabled", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-line-chart",
          "description": "Multi-series line chart with a crosshair tooltip that lists every series at\nthe hovered x. One y axis only — never a second scale.\n\n  const el = document.createElement('baht-line-chart');\n  el.categories = ['Oct', 'Nov', 'Dec'];\n  el.series = [{ name: 'Income', values: [1, 2, 3] }];\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n\n\n\n\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "area",
              "description": "Fill under the line at 10% opacity. Single series only.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "forecast",
              "description": "Draw the last N points dashed — a projection, not measured data.",
              "value": { "type": "number", "default": "0" }
            },
            {
              "name": "baseline",
              "description": "Hairline at this value (0, a target). `null` draws none.",
              "value": { "type": "number | null", "default": "null" }
            },
            {
              "name": "label-values",
              "description": "`ends` labels the last point of each line when the labels do not collide.",
              "value": { "type": "'ends' | 'none'", "default": "'ends'" }
            },
            {
              "name": "label",
              "description": "Accessible summary; falls back to a generated sentence.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "empty-message",
              "description": "Shown instead of the marks when there is nothing to plot.",
              "value": { "type": "string", "default": "'No data to chart'" }
            },
            {
              "name": "loading",
              "description": "Hold the previous render at reduced opacity while new data loads.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-table",
              "description": "Render the \"View as table\" twin under the chart.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "format",
              "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
              "value": { "type": "ChartFormat", "default": "'number'" }
            },
            {
              "name": "currency",
              "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
              "value": { "type": "string", "default": "'THB'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "series",
                "description": "One entry per line: `{ name, values, color? }`.",
                "type": "ChartSeries[]"
              },
              {
                "name": "categories",
                "description": "X labels, one per value index.",
                "type": "string[]"
              },
              {
                "name": "area",
                "description": "Fill under the line at 10% opacity. Single series only.",
                "type": "boolean"
              },
              {
                "name": "markers",
                "description": "Reference lines (targets, averages) drawn across the plot.",
                "type": "ChartMarker[]"
              },
              {
                "name": "forecast",
                "description": "Draw the last N points dashed — a projection, not measured data.",
                "type": "number"
              },
              {
                "name": "baseline",
                "description": "Hairline at this value (0, a target). `null` draws none.",
                "type": "number | null"
              },
              {
                "name": "labelValues",
                "description": "`ends` labels the last point of each line when the labels do not collide.",
                "type": "'ends' | 'none'"
              },
              {
                "name": "label",
                "description": "Accessible summary; falls back to a generated sentence.",
                "type": "string"
              },
              {
                "name": "emptyMessage",
                "description": "Shown instead of the marks when there is nothing to plot.",
                "type": "string"
              },
              {
                "name": "loading",
                "description": "Hold the previous render at reduced opacity while new data loads.",
                "type": "boolean"
              },
              {
                "name": "showTable",
                "description": "Render the \"View as table\" twin under the chart.",
                "type": "boolean"
              },
              {
                "name": "format",
                "description": "How values are rendered in labels, ticks, tooltips and the table twin.",
                "type": "ChartFormat"
              },
              {
                "name": "currency",
                "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-link",
          "description": "Styled anchor (20baht underline treatment). `external` opens a new tab and\nshows the ↗ mark.\n\n  <baht-link href=\"/docs\" label=\"Read the docs\"></baht-link>\n  <baht-link href=\"https://…\" label=\"Status page\" external></baht-link>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "href", "value": { "type": "string", "default": "''" } },
            { "name": "label", "value": { "type": "string", "default": "''" } },
            {
              "name": "external",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "external-hint",
              "description": "Screen-reader suffix for external links (override to localize).",
              "value": { "type": "string", "default": "'opens in new tab'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "href", "type": "string" },
              { "name": "label", "type": "string" },
              { "name": "external", "type": "boolean" },
              {
                "name": "externalHint",
                "description": "Screen-reader suffix for external links (override to localize).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-list",
          "description": "Display list — same server-side contract as <baht-table>: `items` is the\ncurrent page, `loading` shows skeleton rows, the host fetches on\n`baht-page-change` (pair with <baht-pagination>).\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "loading",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "error",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "error-message",
              "value": {
                "type": "string",
                "default": "\"Couldn't load data. Try again.\""
              }
            },
            {
              "name": "empty-message",
              "value": { "type": "string", "default": "'Nothing here yet.'" }
            },
            {
              "name": "skeleton-rows",
              "value": { "type": "number", "default": "5" }
            },
            {
              "name": "clickable",
              "value": { "type": "boolean", "default": "true" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "items", "type": "ListItem[]" },
              { "name": "loading", "type": "boolean" },
              { "name": "error", "type": "boolean" },
              { "name": "errorMessage", "type": "string" },
              { "name": "emptyMessage", "type": "string" },
              { "name": "skeletonRows", "type": "number" },
              { "name": "clickable", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-loading-overlay",
          "description": "Full-viewport loading scrim with a centered spinner (0.4 ink scrim, same\nas `<baht-modal>`'s). Controlled: the host flips `open` — e.g. from an\nin-flight HTTP request counter.\n\n  <baht-loading-overlay [open]=\"loading()\"></baht-loading-overlay>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "open",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "value": { "type": "string", "default": "'Loading'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "open", "type": "boolean" },
              { "name": "label", "type": "string" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-menu",
          "description": "Action menu behind a button: the ⋯ \"more\" menu on a table row or card, or a labelled\n\"Export ▾\" menu. Same menu as `<baht-split-button>` — items with optional `shortcut`\nhints (bound page-wide), disabled and danger entries, the APG menu-button keyboard.\n\n  <baht-menu></baht-menu>                                  <!-- ⋯ icon trigger -->\n  <baht-menu trigger-label=\"Export\" align=\"start\"></baht-menu>\n  el.items = [{ value: 'rename', label: 'Rename', shortcut: 'f2' },\n              { value: 'delete', label: 'Delete', danger: true }];\n  el.addEventListener('baht-select', (e) => run(e.detail.value));\n\nA child marked `data-trigger` becomes the trigger's content (an avatar menu); `heading`\nadds a non-item first line (\"Signed in as …\"); items with `checked` are settings\n(`menuitemcheckbox`) and can carry a `description` line.\n\n  <baht-menu label=\"Account\" heading=\"somchai@20baht.com\">\n    <baht-avatar data-trigger label=\"Somchai\"></baht-avatar>\n  </baht-menu>\n---\n\n\n### **Events:**\n - **baht-select** - An item was picked (click, keyboard or its shortcut). `detail: { value, item }`.\n- **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "trigger-label",
              "description": "Visible trigger text; empty = an icon-only trigger.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "icon",
              "description": "Trigger icon from the curated set (default `more`, the ⋯). Ignored when `trigger-label` is set.",
              "value": { "type": "IconName | ''", "default": "'more'" }
            },
            {
              "name": "label",
              "description": "Accessible name of the trigger (and the menu). Defaults to the trigger text, else \"More actions\".",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "variant",
              "description": "Trigger look: `ghost` (default) or `secondary`.",
              "value": { "type": "'ghost' | 'secondary'", "default": "'ghost'" }
            },
            {
              "name": "size",
              "value": { "type": "'sm' | 'md'", "default": "'md'" }
            },
            {
              "name": "align",
              "description": "Menu edge aligned with the trigger: `'end'` (default) or `'start'`.",
              "value": { "type": "'start' | 'end'", "default": "'end'" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "heading",
              "description": "Non-item first line of the menu (\"Signed in as …\").",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-select",
              "description": "An item was picked (click, keyboard or its shortcut). `detail: { value, item }`."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              {
                "name": "items",
                "description": "Menu entries. Set as a property: `el.items = [...]`.",
                "type": "MenuItem[]"
              },
              {
                "name": "triggerLabel",
                "description": "Visible trigger text; empty = an icon-only trigger.",
                "type": "string"
              },
              {
                "name": "icon",
                "description": "Trigger icon from the curated set (default `more`, the ⋯). Ignored when `trigger-label` is set.",
                "type": "IconName | ''"
              },
              {
                "name": "label",
                "description": "Accessible name of the trigger (and the menu). Defaults to the trigger text, else \"More actions\".",
                "type": "string"
              },
              {
                "name": "variant",
                "description": "Trigger look: `ghost` (default) or `secondary`.",
                "type": "'ghost' | 'secondary'"
              },
              { "name": "size", "type": "'sm' | 'md'" },
              {
                "name": "align",
                "description": "Menu edge aligned with the trigger: `'end'` (default) or `'start'`.",
                "type": "'start' | 'end'"
              },
              { "name": "disabled", "type": "boolean" },
              {
                "name": "heading",
                "description": "Non-item first line of the menu (\"Signed in as …\").",
                "type": "string"
              }
            ],
            "events": [
              {
                "name": "baht-select",
                "description": "An item was picked (click, keyboard or its shortcut). `detail: { value, item }`."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-meter",
          "description": "Spent-against-budget meter. The fill carries severity (accent → warning →\ndanger) and the unfilled track is a lighter step of the same ramp, so state\nreads across the whole bar. Severity is always paired with an icon and a\nworded status, never color alone.\n\n  <baht-meter label=\"Food\" value=\"4200\" max=\"4000\" format=\"currency\"></baht-meter>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "value",
              "description": "Amount used so far.",
              "value": { "type": "number", "default": "0" }
            },
            {
              "name": "max",
              "description": "The budget / capacity the value is measured against.",
              "value": { "type": "number", "default": "100" }
            },
            {
              "name": "label",
              "description": "Sentence-case caption.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "format",
              "description": "How value and max read: number | currency | percent | compact.",
              "value": { "type": "ChartFormat", "default": "'number'" }
            },
            {
              "name": "currency",
              "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
              "value": { "type": "string", "default": "'THB'" }
            },
            {
              "name": "expected",
              "description": "Pace marker: where the value *should* be by now. `null` hides the tick.",
              "value": { "type": "number | null", "default": "null" }
            },
            {
              "name": "over-label",
              "description": "Word shown after the numbers when the meter is over budget.",
              "value": { "type": "string", "default": "'Over budget'" }
            },
            {
              "name": "at-label",
              "description": "Word shown when the value has reached the budget exactly, but not passed it.",
              "value": { "type": "string", "default": "'At budget'" }
            },
            {
              "name": "near-label",
              "description": "Word shown after the numbers when the meter is near its limit.",
              "value": { "type": "string", "default": "'Near limit'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "value",
                "description": "Amount used so far.",
                "type": "number"
              },
              {
                "name": "max",
                "description": "The budget / capacity the value is measured against.",
                "type": "number"
              },
              {
                "name": "label",
                "description": "Sentence-case caption.",
                "type": "string"
              },
              {
                "name": "format",
                "description": "How value and max read: number | currency | percent | compact.",
                "type": "ChartFormat"
              },
              {
                "name": "currency",
                "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
                "type": "string"
              },
              {
                "name": "thresholds",
                "description": "Fractions of `max` where the fill turns warning / danger.",
                "type": "MeterThresholds"
              },
              {
                "name": "expected",
                "description": "Pace marker: where the value *should* be by now. `null` hides the tick.",
                "type": "number | null"
              },
              {
                "name": "overLabel",
                "description": "Word shown after the numbers when the meter is over budget.",
                "type": "string"
              },
              {
                "name": "atLabel",
                "description": "Word shown when the value has reached the budget exactly, but not passed it.",
                "type": "string"
              },
              {
                "name": "nearLabel",
                "description": "Word shown after the numbers when the meter is near its limit.",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-modal",
          "description": "Modal dialog (20baht: 0.4 ink scrim, no blur; float shadow; 20px radius).\nControlled: the host flips `open`. Children become the body.\n\n  <baht-modal heading=\"Delete this workflow?\">\n    <p>Steps and run history will be removed.</p>\n  </baht-modal>\n  modal.open = true;\n  modal.addEventListener('baht-close', (e) => (modal.open = false));\n\n`placement` moves the panel: `'center'` (default) is the dialog above;\n`'sheet'` docks it to the bottom as a bottom sheet below 600px viewport\nwidth (full width, rounded top corners, a grab handle, max 90dvh with the\nbody scrolling, slides up) and is a centered dialog from 600px up; `'side'`\nis a full-height drawer at the inline end, `min(420px, 100vw)` wide, sliding\nin from that edge. Scrim, focus trap/restore and `baht-close` are the same in\nevery placement. Modifier classes: `.baht-modal--sheet` / `.baht-modal--side`\non the panel, `.baht-modal-scrim--sheet` / `.baht-modal-scrim--side` on the scrim.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n - **requestClose(reason: _'escape' | 'scrim' | 'button'_): _void_** - Ask the host to close (the host owns `open`).\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "open",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "heading",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "description",
              "description": "One muted line under the heading; also becomes the dialog's aria-describedby.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "show-close",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "placement",
              "description": "`'center'` (default) dialog, `'sheet'` bottom sheet below 600px (centered\nfrom 600px up), or `'side'` full-height drawer at the inline end.",
              "value": { "type": "ModalPlacement", "default": "'center'" }
            },
            {
              "name": "close-on-scrim",
              "description": "Scrim click requests close. Off by default: a stray click outside the panel\nmust not throw away what the user typed — close via ×, Escape or your buttons.",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "open", "type": "boolean" },
              { "name": "heading", "type": "string" },
              {
                "name": "description",
                "description": "One muted line under the heading; also becomes the dialog's aria-describedby.",
                "type": "string"
              },
              { "name": "showClose", "type": "boolean" },
              {
                "name": "placement",
                "description": "`'center'` (default) dialog, `'sheet'` bottom sheet below 600px (centered\nfrom 600px up), or `'side'` full-height drawer at the inline end.",
                "type": "ModalPlacement"
              },
              {
                "name": "closeOnScrim",
                "description": "Scrim click requests close. Off by default: a stray click outside the panel\nmust not throw away what the user typed — close via ×, Escape or your buttons.",
                "type": "boolean"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-multiselect",
          "description": "Multi-pick rendered as a checkbox group; value = array of option keys.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-notification-bell",
          "description": "Header bell: an icon button with the unread count and a panel listing the\nnotifications. Opening emits `baht-open` (refresh then); picking one emits\n`baht-select` `{ id }` and closes; \"Mark all read\" emits `baht-mark-all`.\nEscape and a click outside close the panel. The host owns the data.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "count",
              "description": "Unread count on the badge; defaults to the unread items.",
              "value": { "type": "number | null", "default": "null" }
            },
            {
              "name": "label",
              "value": { "type": "string", "default": "'Notifications'" }
            },
            {
              "name": "empty-message",
              "value": { "type": "string", "default": "'Nothing yet.'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "items", "type": "NotificationItem[]" },
              {
                "name": "count",
                "description": "Unread count on the badge; defaults to the unread items.",
                "type": "number | null"
              },
              { "name": "label", "type": "string" },
              { "name": "emptyMessage", "type": "string" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-number",
          "description": "\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-otp",
          "description": "One-time-code input: N boxes, auto-advance, backspace steps back, paste\ndistributes. Emits `baht-change { value }` per edit and `baht-otp-complete { value }`\nwhen every box is filled.\n\n  <baht-otp length=\"6\"></baht-otp>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "length", "value": { "type": "number", "default": "6" } },
            { "name": "value", "value": { "type": "string", "default": "''" } },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "mode",
              "description": "'numeric' restricts to digits; 'text' allows anything.",
              "value": { "type": "'numeric' | 'text'", "default": "'numeric'" }
            },
            {
              "name": "label",
              "description": "Accessible name for the group of boxes.",
              "value": { "type": "string", "default": "'One-time code'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "length", "type": "number" },
              { "name": "value", "type": "string" },
              { "name": "disabled", "type": "boolean" },
              {
                "name": "mode",
                "description": "'numeric' restricts to digits; 'text' allows anything.",
                "type": "'numeric' | 'text'"
              },
              {
                "name": "label",
                "description": "Accessible name for the group of boxes.",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-pagination",
          "description": "Server-side pagination control: it never slices data — it only reports the\npage the user asked for via `baht-page-change`; the host fetches that page.\n\n  <baht-pagination page=\"4\" total-pages=\"20\"></baht-pagination>\n  <baht-pagination variant=\"compact\" page=\"1\" total-pages=\"3\"></baht-pagination>\n\n`variant=\"cursor\"` drops page numbers entirely — the host doesn't know a\ntotal, only whether another page exists in either direction:\n\n  <baht-pagination variant=\"cursor\" has-next has-prev></baht-pagination>\n  el.addEventListener('baht-cursor-change', (e) => fetchPage(e.detail.direction));\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "page", "value": { "type": "number", "default": "1" } },
            {
              "name": "total-pages",
              "value": { "type": "number", "default": "1" }
            },
            {
              "name": "variant",
              "description": "'numbered' = 1 … 4 5 6 … 20 · 'compact' = « Page 4 of 20 · 'cursor' = « Prev / Next »",
              "value": {
                "type": "'numbered' | 'compact' | 'cursor'",
                "default": "'numbered'"
              }
            },
            {
              "name": "siblings",
              "description": "Numbered pages shown on each side of the current page.",
              "value": { "type": "number", "default": "1" }
            },
            {
              "name": "has-next",
              "description": "cursor variant only — whether a next/previous page is available.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "has-prev",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "page", "type": "number" },
              { "name": "totalPages", "type": "number" },
              {
                "name": "variant",
                "description": "'numbered' = 1 … 4 5 6 … 20 · 'compact' = « Page 4 of 20 · 'cursor' = « Prev / Next »",
                "type": "'numbered' | 'compact' | 'cursor'"
              },
              {
                "name": "siblings",
                "description": "Numbered pages shown on each side of the current page.",
                "type": "number"
              },
              {
                "name": "hasNext",
                "description": "cursor variant only — whether a next/previous page is available.",
                "type": "boolean"
              },
              { "name": "hasPrev", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-popover",
          "description": "A floating panel anchored to a trigger — filters, a quick-edit form, a hover card you click\nopen. The FIRST child is the trigger; everything after it becomes the panel (a non-modal\n`role=\"dialog\"`, named by `label`).\n\n  <baht-popover label=\"Filters\" placement=\"bottom-start\">\n    <baht-button variant=\"secondary\" icon=\"search\">Filters</baht-button>\n    <baht-select …></baht-select>\n    <baht-button size=\"sm\">Apply</baht-button>\n  </baht-popover>\n\nClicking the trigger toggles it; opening moves focus to the first field in the panel\n(or the panel). Escape closes it and returns focus to the trigger (marked handled, so a\nsurrounding `<baht-modal>` stays open); a click outside closes it too. `open` can also be\nset by the host. The trigger gets `aria-haspopup=\"dialog\"`, `aria-expanded`, `aria-controls`.\n---\n\n\n### **Events:**\n - **baht-toggle** - The user opened or closed it. `detail: { open }`.\n- **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "open",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "description": "Accessible name of the panel.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "placement",
              "description": "Where the panel sits relative to the trigger.",
              "value": {
                "type": "PopoverPlacement",
                "default": "'bottom-start'"
              }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-toggle",
              "description": "The user opened or closed it. `detail: { open }`."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              { "name": "open", "type": "boolean" },
              {
                "name": "label",
                "description": "Accessible name of the panel.",
                "type": "string"
              },
              {
                "name": "placement",
                "description": "Where the panel sits relative to the trigger.",
                "type": "PopoverPlacement"
              }
            ],
            "events": [
              {
                "name": "baht-toggle",
                "description": "The user opened or closed it. `detail: { open }`."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-progress-ring",
          "description": "Circular single-value \"percent used\" ring — the round counterpart to\n`<baht-progress>`'s linear bar. Center shows the rounded percentage; an\noptional caption reads what it's a percentage of.\n\n  <baht-progress-ring value=\"80\" label=\"of budget used\"></baht-progress-ring>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "value",
              "description": "0-100, clamped.",
              "value": { "type": "number", "default": "0" }
            },
            {
              "name": "tone",
              "value": { "type": "ProgressRingTone", "default": "'primary'" }
            },
            {
              "name": "label",
              "description": "Caption under the percentage, e.g. \"of budget used\".",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "size",
              "description": "Rendered pixel size (square).",
              "value": { "type": "number", "default": "112" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "value",
                "description": "0-100, clamped.",
                "type": "number"
              },
              { "name": "tone", "type": "ProgressRingTone" },
              {
                "name": "label",
                "description": "Caption under the percentage, e.g. \"of budget used\".",
                "type": "string"
              },
              {
                "name": "size",
                "description": "Rendered pixel size (square).",
                "type": "number"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-progress",
          "description": "Progress bar. `value` 0–100; omit (null) for indeterminate.\n\n  <baht-progress value=\"65\" show-label></baht-progress>\n  <baht-progress></baht-progress>                          <!-- indeterminate -->\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "value",
              "value": { "type": "number | null", "default": "null" }
            },
            {
              "name": "tone",
              "value": {
                "type": "'primary' | 'success' | 'warning' | 'danger'",
                "default": "'primary'"
              }
            },
            {
              "name": "show-label",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "description": "Accessible name for the progressbar.",
              "value": { "type": "string", "default": "'Progress'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "value", "type": "number | null" },
              {
                "name": "tone",
                "type": "'primary' | 'success' | 'warning' | 'danger'"
              },
              { "name": "showLabel", "type": "boolean" },
              {
                "name": "label",
                "description": "Accessible name for the progressbar.",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-radio",
          "description": "\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-range",
          "description": "Range slider. Two modes, one tag:\n\n- **Standalone** (no `field`): `value` / `min` / `max` / `step` / `label`; emits\n  `baht-change { value }` (a number) on every input.\n\n    <baht-range min=\"0\" max=\"100\" step=\"5\" value=\"40\" label=\"Retry limit\"></baht-range>\n\n- **Form field** (`type: 'range'`, set by `<baht-form>`): bounds from `field.min` /\n  `field.max` / `field.step` (default 0 / 100 / 1), field label / help / error; emits\n  `baht-input { key, value }` debounced while sliding and at once on release. `null`\n  (untouched) parks the thumb at `min` and reads \"—\".\n---\n\n\n### **Events:**\n - **baht-change** - Standalone mode: the slider moved. `detail: { value }`.\n- **baht-input** - Field mode: the slider moved. `detail: { key, value }`.\n- **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "value",
              "description": "Current value. Standalone: a number. Field mode: `number | null` (null = untouched).",
              "value": { "type": "unknown", "default": "0" }
            },
            {
              "name": "min",
              "description": "Standalone lower bound; a field uses `field.min`.",
              "value": { "type": "number", "default": "0" }
            },
            {
              "name": "max",
              "description": "Standalone upper bound; a field uses `field.max`.",
              "value": { "type": "number", "default": "100" }
            },
            {
              "name": "step",
              "description": "Standalone step; a field uses `field.step`.",
              "value": { "type": "number", "default": "1" }
            },
            {
              "name": "label",
              "description": "Standalone visible label; a field uses its label.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "show-value",
              "description": "Show the current value next to the slider.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-change",
              "description": "Standalone mode: the slider moved. `detail: { value }`."
            },
            {
              "name": "baht-input",
              "description": "Field mode: the slider moved. `detail: { key, value }`."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              {
                "name": "value",
                "description": "Current value. Standalone: a number. Field mode: `number | null` (null = untouched).",
                "type": "unknown"
              },
              {
                "name": "min",
                "description": "Standalone lower bound; a field uses `field.min`.",
                "type": "number"
              },
              {
                "name": "max",
                "description": "Standalone upper bound; a field uses `field.max`.",
                "type": "number"
              },
              {
                "name": "step",
                "description": "Standalone step; a field uses `field.step`.",
                "type": "number"
              },
              {
                "name": "label",
                "description": "Standalone visible label; a field uses its label.",
                "type": "string"
              },
              {
                "name": "showValue",
                "description": "Show the current value next to the slider.",
                "type": "boolean"
              },
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [
              {
                "name": "baht-change",
                "description": "Standalone mode: the slider moved. `detail: { value }`."
              },
              {
                "name": "baht-input",
                "description": "Field mode: the slider moved. `detail: { key, value }`."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-rating",
          "description": "Star rating. Two modes, one tag:\n\n- **Standalone** (no `field`): controlled by `value` / `max` / `label`; emits\n  `baht-change { value }`, where re-picking the current star clears to `0`.\n\n    <baht-rating value=\"3\" max=\"5\"></baht-rating>\n    <baht-rating value=\"4\" readonly></baht-rating>\n\n- **Form field** (`type: 'rating'`, set by `<baht-form>`): `field.max` stars\n  (1–10, default 5), field label / help / error, emits `baht-input { key, value }`\n  with a whole number of stars, or `null` when re-picking the current star clears it.\n\nKeyboard (APG radio group): arrows move the value, Home / End jump the ends.\n---\n\n\n### **Events:**\n - **baht-change** - Standalone mode: a star was picked. `detail: { value }` (0 = cleared).\n- **baht-input** - Field mode: a star was picked. `detail: { key, value }` (null = cleared).\n- **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "value",
              "description": "Picked star count. Standalone: a number (`0` = none). Field mode: `number | null`.",
              "value": { "type": "unknown", "default": "0" }
            },
            {
              "name": "max",
              "description": "Standalone star count; a field uses `field.max` instead.",
              "value": { "type": "number", "default": "5" }
            },
            {
              "name": "label",
              "description": "Standalone accessible name for the radiogroup; a field uses its label.",
              "value": { "type": "string", "default": "'Rating'" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-change",
              "description": "Standalone mode: a star was picked. `detail: { value }` (0 = cleared)."
            },
            {
              "name": "baht-input",
              "description": "Field mode: a star was picked. `detail: { key, value }` (null = cleared)."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              {
                "name": "value",
                "description": "Picked star count. Standalone: a number (`0` = none). Field mode: `number | null`.",
                "type": "unknown"
              },
              {
                "name": "max",
                "description": "Standalone star count; a field uses `field.max` instead.",
                "type": "number"
              },
              {
                "name": "label",
                "description": "Standalone accessible name for the radiogroup; a field uses its label.",
                "type": "string"
              },
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [
              {
                "name": "baht-change",
                "description": "Standalone mode: a star was picked. `detail: { value }` (0 = cleared)."
              },
              {
                "name": "baht-input",
                "description": "Field mode: a star was picked. `detail: { key, value }` (null = cleared)."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-reference-picker",
          "description": "Reference lookup: read-only input + magnifier opening a typeahead dropdown.\nThe host supplies the data via `fetcher(term)`; the component owns the\ndebounce, the dropdown, and the keyboard interaction.\n\n  const el = document.createElement('baht-reference-picker');\n  el.fetcher = (term) => api.search('sys_user', term);   // Promise<ReferenceOption[]>\n  el.value = '6816f79cc0a8016401c5a33be04be441';\n  el.display = 'Abel Tuter';\n  el.addEventListener('baht-change', (e) => onPick(e.detail)); // { value, label }\n\nClearing emits `baht-change` with `{ value: '', label: '' }`. Set `href` to\nrender an open-record link next to the lookup button.\n\n**Form field** (`type: 'reference'`, set by `<baht-form>`): `lookup` replaces `fetcher` /\n`display` — `<baht-form>` binds `registry.references[field.reference]` with the form data.\nThe value is the picked record's id (`null` when cleared), emitted as\n`baht-input { key, value }`; an existing id is shown by `lookup.resolve(id)`'s label (or\nraw, without a resolver). Field label / help / error as for every field.\n---\n\n\n### **Events:**\n - **baht-change** - Standalone mode: a record was picked or cleared. `detail: { value, label }`.\n- **baht-input** - Field mode: a record was picked or cleared. `detail: { key, value }`.\n- **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "label",
              "description": "Accessible name for the combobox + its option list (standalone; a field uses its label).",
              "value": { "type": "string", "default": "'Reference'" }
            },
            {
              "name": "value",
              "description": "Selected id (the reference's key). Field mode: `string | null`.",
              "value": { "type": "unknown", "default": "''" }
            },
            {
              "name": "display",
              "description": "Human-readable label for `value`; falls back to showing the raw id.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "placeholder",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "invalid",
              "description": "Marks the control invalid (styles + aria-invalid).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "href",
              "description": "Optional link to the selected record, rendered as an external-link button.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "lookup-label",
              "description": "Accessible name for the lookup button.",
              "value": { "type": "string", "default": "'Look up records'" }
            },
            {
              "name": "debounce",
              "description": "Debounce for search calls, ms (default 200).",
              "value": { "type": "number | undefined" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-change",
              "description": "Standalone mode: a record was picked or cleared. `detail: { value, label }`."
            },
            {
              "name": "baht-input",
              "description": "Field mode: a record was picked or cleared. `detail: { key, value }`."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              {
                "name": "label",
                "description": "Accessible name for the combobox + its option list (standalone; a field uses its label).",
                "type": "string"
              },
              {
                "name": "value",
                "description": "Selected id (the reference's key). Field mode: `string | null`.",
                "type": "unknown"
              },
              {
                "name": "display",
                "description": "Human-readable label for `value`; falls back to showing the raw id.",
                "type": "string"
              },
              { "name": "placeholder", "type": "string" },
              {
                "name": "invalid",
                "description": "Marks the control invalid (styles + aria-invalid).",
                "type": "boolean"
              },
              {
                "name": "href",
                "description": "Optional link to the selected record, rendered as an external-link button.",
                "type": "string"
              },
              {
                "name": "lookupLabel",
                "description": "Accessible name for the lookup button.",
                "type": "string"
              },
              {
                "name": "debounce",
                "description": "Debounce for search calls, ms (default 200).",
                "type": "number | undefined"
              },
              {
                "name": "fetcher",
                "description": "Async search: term in, options out. Required for the dropdown to work (standalone).",
                "type": "((term: string) => Promise<ReferenceOption[]>) | null"
              },
              {
                "name": "lookup",
                "description": "Field mode: the record lookup (set by `<baht-form>` from `registry.references`).",
                "type": "FieldReferenceLookup | null"
              },
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" }
            ],
            "events": [
              {
                "name": "baht-change",
                "description": "Standalone mode: a record was picked or cleared. `detail: { value, label }`."
              },
              {
                "name": "baht-input",
                "description": "Field mode: a record was picked or cleared. `detail: { key, value }`."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-repeater",
          "description": "<baht-repeater> — array-of-rows control (spec 01 §10). `value` is an array\nof row objects; every row renders the same `field.fields` sub-descriptors\n(one level deep — nested repeaters are skipped). Controlled like every\nprimitive: edits emit the WHOLE array via `baht-input`; the host re-feeds.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n - **flushTyped(): _void_** - Pending keystrokes in any row land in the array before the host reads it.\n\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "rowErrors",
                "description": "Row-scoped errors from the host form, keyed `key[i].subKey`.",
                "type": "ValidationError[]"
              },
              {
                "name": "rowStates",
                "description": "Rule-derived states of the row sub-fields, keyed `key[i].sub` (spec 02\n§2a). Absent = every sub-field visible with its descriptor defaults.",
                "type": "Record<string, DerivedFieldState>"
              },
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-rule-builder",
          "description": "Edits a workflow's `rules` without code: \"when all / any of <field> <is>\n<value>, then show / hide / make mandatory / set value / skip step\". Takes\nthe `workflow` (its fields and steps are the choices) and emits\n`baht-change` with `{ rules }` on every edit. A rule it cannot represent\n(nested conditions) is shown as such and returned untouched.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "workflow", "type": "WorkflowDefinition | null" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-section",
          "description": "Section divider (`type: \"section\"`): heading + rule, no value, no emits.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [{ "name": "field", "type": "FieldDescriptor" }],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-select",
          "description": "Single-choice dropdown. Renders ITS OWN list (light DOM, positioned under\nthe trigger) instead of the native `<select>` — so the picker looks the same\non iOS/macOS as everywhere else (no UIKit wheel, no AppKit menu), and every\noption is styleable with the tokens.\n\nARIA: APG \"select-only combobox\". The trigger is a `<button role=\"combobox\">`\n(so `<label for>` still names it); `aria-activedescendant` tracks the\nhighlighted option while focus stays on the trigger. Keys: Arrow Up/Down,\nHome/End, Enter/Space pick, Escape closes, Tab picks the highlighted option\nand moves on, typing jumps to the first option starting with those letters\n(while closed it picks it directly, like a native select).\n\nAn option's `description` renders as a muted second line under its label in\nthe list, and after the label on the closed trigger while it is selected\n(e.g. label \"Food\", description \"฿580 left\").\n\nValue contract unchanged: option `key`, or `null` for the placeholder row.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-skeleton",
          "description": "Loading placeholder (opacity pulse — the 20baht system bans gradients in\ncore UI, so no shimmer).\n\n  <baht-skeleton variant=\"text\" width=\"60%\"></baht-skeleton>\n  <baht-skeleton variant=\"circle\" width=\"40px\" height=\"40px\"></baht-skeleton>\n  <baht-skeleton height=\"120px\"></baht-skeleton>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "variant",
              "value": {
                "type": "'rect' | 'text' | 'circle'",
                "default": "'rect'"
              }
            },
            {
              "name": "width",
              "value": { "type": "string", "default": "'100%'" }
            },
            {
              "name": "height",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "variant", "type": "'rect' | 'text' | 'circle'" },
              { "name": "width", "type": "string" },
              { "name": "height", "type": "string" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-sparkline",
          "description": "Micro trend line — no axes, no ticks, no tooltip. Used inside `<baht-stat>`\nand anywhere a number needs its recent shape beside it.\n\n  <baht-sparkline values=\"[4,7,6,9,12]\" accent-last></baht-sparkline>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "values",
              "description": "The points, oldest first. Fewer than two points renders nothing.",
              "value": { "type": "number[]", "default": "[]" }
            },
            {
              "name": "width",
              "description": "Drawing width in px.",
              "value": { "type": "number", "default": "96" }
            },
            {
              "name": "height",
              "description": "Drawing height in px.",
              "value": { "type": "number", "default": "24" }
            },
            {
              "name": "accent-last",
              "description": "Mark the newest point with an accent dot.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "color",
              "description": "Line color; defaults to the de-emphasis chart token.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "label",
              "description": "Accessible name; falls back to a plain \"Trend of N points\" sentence.",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "values",
                "description": "The points, oldest first. Fewer than two points renders nothing.",
                "type": "number[]"
              },
              {
                "name": "width",
                "description": "Drawing width in px.",
                "type": "number"
              },
              {
                "name": "height",
                "description": "Drawing height in px.",
                "type": "number"
              },
              {
                "name": "accentLast",
                "description": "Mark the newest point with an accent dot.",
                "type": "boolean"
              },
              {
                "name": "color",
                "description": "Line color; defaults to the de-emphasis chart token.",
                "type": "string"
              },
              {
                "name": "label",
                "description": "Accessible name; falls back to a plain \"Trend of N points\" sentence.",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-spinner",
          "description": "Loading spinner — the single animated icon the 20baht system allows.\n\n  <baht-spinner></baht-spinner>\n  <baht-spinner size=\"lg\"></baht-spinner>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "size",
              "value": { "type": "'sm' | 'md' | 'lg'", "default": "'md'" }
            },
            {
              "name": "label",
              "value": { "type": "string", "default": "'Loading'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "size", "type": "'sm' | 'md' | 'lg'" },
              { "name": "label", "type": "string" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-split-button",
          "description": "Split button: a main action plus a ▾ toggle that opens a menu of related actions\n(NOTES 2026-10-31). Each action can carry a keyboard `shortcut` — the main one shows\nin the main button's tooltip, item ones as a right-aligned hint in the menu — and all\nof them are bound document-wide (same scoping as `<baht-button shortcut>`).\n\n  <baht-split-button label=\"Save\" shortcut=\"alt+s\"></baht-split-button>\n  el.items = [{ value: 'save-close', label: 'Save and close', shortcut: 'shift+alt+s' }];\n  el.addEventListener('baht-click', save);\n  el.addEventListener('baht-select', (e) => run(e.detail.value));\n\nKeyboard: ↓ / Enter / Space on the toggle opens on the first item, ↑ on the last;\nin the menu ↑ ↓ (wrapping, skipping disabled) · Home · End · Enter/Space picks ·\nEscape closes back to the toggle (and is marked handled, so a surrounding\n`<baht-modal>` stays open) · Tab closes and moves on. A click outside closes.\n---\n\n\n### **Events:**\n - **baht-click** - The main action was activated (click or its shortcut).\n- **baht-select** - A menu item was picked. `detail: { value, item }`.\n- **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "label",
              "description": "Main action label.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "variant",
              "value": { "type": "ButtonVariant", "default": "'primary'" }
            },
            {
              "name": "size",
              "value": { "type": "ButtonSize", "default": "'md'" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "shortcut",
              "description": "Shortcut for the main action, e.g. `alt+s` (see `<baht-button shortcut>`).",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "align",
              "description": "Menu edge aligned with the button: `'end'` (default, right in LTR) or `'start'`.",
              "value": { "type": "'start' | 'end'", "default": "'end'" }
            },
            {
              "name": "menu-label",
              "description": "Accessible name of the ▾ toggle. Defaults to the localized \"More actions\".",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-click",
              "description": "The main action was activated (click or its shortcut)."
            },
            {
              "name": "baht-select",
              "description": "A menu item was picked. `detail: { value, item }`."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              {
                "name": "label",
                "description": "Main action label.",
                "type": "string"
              },
              { "name": "variant", "type": "ButtonVariant" },
              { "name": "size", "type": "ButtonSize" },
              { "name": "disabled", "type": "boolean" },
              {
                "name": "shortcut",
                "description": "Shortcut for the main action, e.g. `alt+s` (see `<baht-button shortcut>`).",
                "type": "string"
              },
              {
                "name": "items",
                "description": "Menu entries. Set as a property: `el.items = [...]`.",
                "type": "MenuItem[]"
              },
              {
                "name": "align",
                "description": "Menu edge aligned with the button: `'end'` (default, right in LTR) or `'start'`.",
                "type": "'start' | 'end'"
              },
              {
                "name": "menuLabel",
                "description": "Accessible name of the ▾ toggle. Defaults to the localized \"More actions\".",
                "type": "string"
              }
            ],
            "events": [
              {
                "name": "baht-click",
                "description": "The main action was activated (click or its shortcut)."
              },
              {
                "name": "baht-select",
                "description": "A menu item was picked. `detail: { value, item }`."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-stat-chart",
          "description": "Big single-number stat (report type \"count\").\n\n  <baht-stat-chart total=\"1284\" label=\"Open incidents\"></baht-stat-chart>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "total", "value": { "type": "number", "default": "0" } },
            { "name": "label", "value": { "type": "string", "default": "''" } },
            {
              "name": "color",
              "description": "Overrides the number color (defaults to the primary token).",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "total", "type": "number" },
              { "name": "label", "type": "string" },
              {
                "name": "color",
                "description": "Overrides the number color (defaults to the primary token).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-stat",
          "description": "Stat tile: label, one figure, an optional delta against a named period and an\noptional sparkline. Supersedes `<baht-stat-chart>`.\n\n  <baht-stat label=\"Out\" value=\"4200\" format=\"currency\"\n             delta=\"-8\" delta-kind=\"pct\" delta-label=\"vs August\"\n             up-is-good=\"false\"></baht-stat>\n\nThe figure is never colored by a series hue — only the delta carries color,\nand it always ships an arrow glyph plus a worded label so direction never\nrides on color alone.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "label",
              "description": "Sentence-case caption above the figure, no trailing colon.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "value",
              "description": "The figure.",
              "value": { "type": "number", "default": "0" }
            },
            {
              "name": "format",
              "description": "How the figure reads: number | currency | percent | compact.",
              "value": { "type": "ChartFormat", "default": "'number'" }
            },
            {
              "name": "currency",
              "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
              "value": { "type": "string", "default": "'THB'" }
            },
            {
              "name": "delta",
              "description": "Change against the previous period. `null` hides the delta row.\nWith `delta-kind=\"pct\"` it is a percentage (18 → \"18%\"); with `\"abs\"` it is\na raw amount rendered with this tile's `format`.",
              "value": { "type": "number | null", "default": "null" }
            },
            {
              "name": "delta-kind",
              "description": "Whether `delta` is a percentage or an absolute amount.",
              "value": { "type": "'abs' | 'pct'", "default": "'pct'" }
            },
            {
              "name": "delta-label",
              "description": "What the delta is measured against, e.g. \"vs August\". With no `delta` it is\nstill rendered on its own as a muted caption, so a figure can name its period.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "up-is-good",
              "description": "Is a rise good news? Spending tiles set `up-is-good=\"false\"`.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "trend",
              "description": "Recent points drawn as a muted sparkline with an accent on the newest.",
              "value": { "type": "number[]", "default": "[]" }
            },
            {
              "name": "size",
              "description": "`hero` is the one 48px+ figure a view leads with.",
              "value": { "type": "'md' | 'hero'", "default": "'md'" }
            },
            {
              "name": "icon",
              "description": "Icon name from the curated set (see icons.ts), shown in a chip before the label.",
              "value": { "type": "string | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "label",
                "description": "Sentence-case caption above the figure, no trailing colon.",
                "type": "string"
              },
              {
                "name": "value",
                "description": "The figure.",
                "type": "number"
              },
              {
                "name": "format",
                "description": "How the figure reads: number | currency | percent | compact.",
                "type": "ChartFormat"
              },
              {
                "name": "currency",
                "description": "ISO currency code for `format=\"currency\"` (THB renders `฿`).",
                "type": "string"
              },
              {
                "name": "delta",
                "description": "Change against the previous period. `null` hides the delta row.\nWith `delta-kind=\"pct\"` it is a percentage (18 → \"18%\"); with `\"abs\"` it is\na raw amount rendered with this tile's `format`.",
                "type": "number | null"
              },
              {
                "name": "deltaKind",
                "description": "Whether `delta` is a percentage or an absolute amount.",
                "type": "'abs' | 'pct'"
              },
              {
                "name": "deltaLabel",
                "description": "What the delta is measured against, e.g. \"vs August\". With no `delta` it is\nstill rendered on its own as a muted caption, so a figure can name its period.",
                "type": "string"
              },
              {
                "name": "upIsGood",
                "description": "Is a rise good news? Spending tiles set `up-is-good=\"false\"`.",
                "type": "boolean"
              },
              {
                "name": "trend",
                "description": "Recent points drawn as a muted sparkline with an accent on the newest.",
                "type": "number[]"
              },
              {
                "name": "size",
                "description": "`hero` is the one 48px+ figure a view leads with.",
                "type": "'md' | 'hero'"
              },
              {
                "name": "icon",
                "description": "Icon name from the curated set (see icons.ts), shown in a chip before the label.",
                "type": "string | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-steps",
          "description": "<baht-steps> — wizard progress indicator (spec 03). Standalone display\nprimitive: numbered markers + labels, the current one highlighted, done\nones checked. <baht-workflow show-steps> renders it automatically from its\nvisible forms.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "current",
              "description": "0-based index of the current step.",
              "value": { "type": "number", "default": "0" }
            },
            {
              "name": "variant",
              "description": "'default' = horizontal numbered markers; 'sidebar' = vertical nav list, current pilled.",
              "value": {
                "type": "'default' | 'sidebar'",
                "default": "'default'"
              }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "steps",
                "description": "Step labels in order.",
                "type": "string[]"
              },
              {
                "name": "current",
                "description": "0-based index of the current step.",
                "type": "number"
              },
              {
                "name": "variant",
                "description": "'default' = horizontal numbered markers; 'sidebar' = vertical nav list, current pilled.",
                "type": "'default' | 'sidebar'"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-swipe-row",
          "description": "Row container for touch lists: the children are the row, `actions` sit behind\nit on the right and are revealed by swiping the row left.\n\n  <baht-swipe-row><my-expense-row …></my-expense-row></baht-swipe-row>\n  row.actions = [{ key: 'edit', label: 'Edit' }, { key: 'delete', label: 'Delete', tone: 'danger' }];\n  row.addEventListener('baht-action', (e) => handle(e.detail.key));\n\nPointer events (touch, pen, mouse): a horizontal drag moves the row; released\nmore than 40px left it snaps open, more than 40px right (from open) it snaps\nshut, otherwise it springs back. Vertical drags are left to the page\n(`touch-action: pan-y`). An open row closes on a pointerdown outside it, on a\ntap on the row (that tap is swallowed, so the row's own click handler does not\nfire), on Escape, and after an action is pressed. The click that ends a drag\nis swallowed too.\n\nKeyboard: the actions are real buttons after the row content in tab order;\nfocusing one reveals the row, focus leaving the row hides it again. The\ngesture is physical (swipe left, actions on the right) in either text direction.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "open",
              "description": "Revealed state. Reflected; set it to open or close the row from the host.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "description": "Accessible name of the action group (default: the i18n `rowActions` string).",
              "value": { "type": "string", "default": "''" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "actions",
                "description": "Actions revealed by the swipe, in order: `{ key, label, tone? }`.",
                "type": "SwipeAction[]"
              },
              {
                "name": "open",
                "description": "Revealed state. Reflected; set it to open or close the row from the host.",
                "type": "boolean"
              },
              {
                "name": "label",
                "description": "Accessible name of the action group (default: the i18n `rowActions` string).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-table",
          "description": "Server-side data table. `items` is ONE page of rows — the component never\nslices or fetches; on `baht-page-change` the host fetches the next page and\nre-feeds `items`/`page`. While that fetch runs, set `loading` to show\nskeleton rows.\n\n  const t = document.querySelector('baht-table');\n  t.columns = { name: 'Name', created_at: 'Created' };\n  t.items = pageRows;                 // current page only\n  t.loading = false; t.page = 1; t.totalPages = 12;\n  t.addEventListener('baht-page-change', (e) => fetchPage(e.detail.page));\n  t.addEventListener('baht-row-click', (e) => open(e.detail.id));\n\nSet `cursor-mode` for cursor-based APIs (no total count) — the footer\nbecomes a Prev/Next pair gated by `has-next`/`has-prev`, and the host\nlistens for `baht-cursor-change` instead of `baht-page-change`:\n\n  t.cursorMode = true; t.hasNext = true; t.hasPrev = false;\n  t.addEventListener('baht-cursor-change', (e) => fetchPage(e.detail.direction));\n\nA built-in \"Columns\" button is ON by default — it opens a ServiceNow-style\nAvailable/Selected dual-list modal (add/remove, reorder, Select all/Clear\nall) derived from `columns` (or the first row's keys), no host wiring\nrequired. Set `t.columnPicker = false` (JS property — a boolean attribute\ncan't express \"off\") to hide it. OK emits `baht-columns-change` with the\nchosen keys and their labels; persist that and feed it back as\n`visibleColumns` to remember a person's layout:\n\n  t.addEventListener('baht-columns-change', (e) => save(e.detail.visibleColumns));\n\nDot-walking (spec: reference columns). A column may declare `fields`, in\nwhich case the picker lets the person expand it and pick a sub-field —\nthe key becomes `requester.department`, the header reads\n\"Requested by › Department\", and the cell resolves the path on the row.\nRows whose values are plain objects get the same treatment automatically\nwhen no `columns` map is given.\n\n  t.columns = {\n    number: 'Number',\n    requester: { label: 'Requested by', fields: { name: 'Name', department: 'Department' } },\n  };\n  t.visibleColumns = ['number', 'requester.name'];\n\n`visibleColumns` still works standalone for a host-controlled subset (no\nrefetch) — pass it explicitly and the built-in picker (if also enabled)\nstarts from that selection and keeps emitting updates for it.\n\nToolbar, selection, sorting, row actions (all server-side, all off by default):\n\n  t.total = 143;                      // \"143 results\" in the toolbar (search/filter UI lives outside)\n  t.selectable = true; t.selected = ['g1'];   // checkbox column, controlled by row key\n  t.addEventListener('baht-selection-change', (e) => (t.selected = e.detail.selected));\n  t.columns = { amount: { label: 'Amount', numeric: true, sortable: true } };\n  t.sort = { key: 'amount', direction: 'desc' };\n  t.addEventListener('baht-sort-change', (e) => refetch({ sort: e.detail }));\n  t.rowActions = true;                // trailing \"...\" button -> baht-row-actions { row, index, id }\n\nGrouping (ServiceNow \"Group by\" on a list): `group-by` names a (dotted)\nrow key, or pass a function as the property. Every row on the CURRENT page\nis bucketed by that value in first-appearance order; each bucket is its own\n`<tbody>` headed by a `th[scope=rowgroup]` disclosure reading\n\"Column: value (n)\". Click the header to collapse/expand the group\n(`baht-group-toggle`); `group-collapsed` starts them all closed, and the\nopen/closed state resets when `items`/`page` change. Grouping is per page\nonly — it rides on the existing pagination and never fetches or sorts, so\na value split across pages shows up on each. `groupLabel` replaces the\ndefault header text (it still sits inside the toggle). Row numbers and\nevent indexes stay page-global. `hide-header` drops the column header row\nfor list-style layouts.\n\n  t.groupBy = 'state';                 // \"State: Open (12)\"\n  t.groupBy = (row) => row.occurred_at.slice(0, 10);\n  t.groupLabel = (day, rows) => `${formatDay(day)} · ${rows.length}`;\n  t.groupCollapsed = true;\n  t.addEventListener('baht-group-toggle', (e) => console.log(e.detail)); // { value, collapsed }\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "loading",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "error",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "error-message",
              "value": {
                "type": "string",
                "default": "\"Couldn't load data. Try again.\""
              }
            },
            {
              "name": "empty-message",
              "value": { "type": "string", "default": "'No records yet.'" }
            },
            {
              "name": "show-index",
              "description": "Row-number column (No.). Offset lets numbering continue across pages.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "index-offset",
              "value": { "type": "number", "default": "0" }
            },
            {
              "name": "skeleton-rows",
              "value": { "type": "number", "default": "10" }
            },
            {
              "name": "row-key",
              "description": "Which row key identifies a row in baht-row-click detail.id (e.g. 'group_id').",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "clickable",
              "description": "Rows emit baht-row-click + get hover/keyboard affordances.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "group-by",
              "description": "Group the page's rows by this (dotted) column key — or a function of the row (property). Unset = no grouping.",
              "value": { "type": "GroupKey | undefined" }
            },
            {
              "name": "group-collapsible",
              "description": "Group headers are disclosures (click to collapse). Set the JS property to `false` for static headers.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "group-collapsed",
              "description": "Start every group collapsed (per page — resets when `items`/`page` change).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "hide-header",
              "description": "Drop the column header row (list-style tables, e.g. grouped ledgers).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "column-picker",
              "description": "Built-in \"Columns\" toggle — derives its options from `columns`/rows. Set `column-picker=\"false\"` to hide it.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "total",
              "description": "Total matching rows across all pages — renders \"{n} results\" in the toolbar. Unset = no count.",
              "value": { "type": "number | undefined | undefined" }
            },
            {
              "name": "selectable",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "sortable",
              "description": "Make every column sortable (or set `sortable` per column in `columns`).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "row-actions",
              "description": "Trailing \"...\" button per row; emits `baht-row-actions { row, index, id }`.",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "page", "value": { "type": "number", "default": "1" } },
            {
              "name": "total-pages",
              "value": { "type": "number", "default": "1" }
            },
            {
              "name": "cursor-mode",
              "description": "Switch the footer to Prev/Next (cursor-based API, no total count).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "has-next",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "has-prev",
              "value": { "type": "boolean", "default": "false" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "items",
                "description": "Rows of the CURRENT page.",
                "type": "Record<string, unknown>[]"
              },
              {
                "name": "columns",
                "description": "column key -> display name, or a reference column with `fields` (property); empty = derive keys from the first row (`snake_case` -> `Title Case`).",
                "type": "ColumnMap"
              },
              { "name": "loading", "type": "boolean" },
              { "name": "error", "type": "boolean" },
              { "name": "errorMessage", "type": "string" },
              { "name": "emptyMessage", "type": "string" },
              {
                "name": "showIndex",
                "description": "Row-number column (No.). Offset lets numbering continue across pages.",
                "type": "boolean"
              },
              { "name": "indexOffset", "type": "number" },
              { "name": "skeletonRows", "type": "number" },
              {
                "name": "rowKey",
                "description": "Which row key identifies a row in baht-row-click detail.id (e.g. 'group_id').",
                "type": "string"
              },
              {
                "name": "clickable",
                "description": "Rows emit baht-row-click + get hover/keyboard affordances.",
                "type": "boolean"
              },
              {
                "name": "renderers",
                "description": "Custom cell renderers by column key, dotted keys included (functions — a property, never JSON).",
                "type": "Record<string, CellRenderer>"
              },
              {
                "name": "visibleColumns",
                "description": "Subset + order of columns to render (dotted keys allowed); empty/omitted = show all top-level columns.",
                "type": "string[]"
              },
              {
                "name": "groupBy",
                "description": "Group the page's rows by this (dotted) column key — or a function of the row (property). Unset = no grouping.",
                "type": "GroupKey | undefined"
              },
              {
                "name": "groupLabel",
                "description": "Group header content (property); default is \"Column: value (n)\".",
                "type": "GroupLabelRenderer | undefined"
              },
              {
                "name": "groupCollapsible",
                "description": "Group headers are disclosures (click to collapse). Set the JS property to `false` for static headers.",
                "type": "boolean"
              },
              {
                "name": "groupCollapsed",
                "description": "Start every group collapsed (per page — resets when `items`/`page` change).",
                "type": "boolean"
              },
              {
                "name": "hideHeader",
                "description": "Drop the column header row (list-style tables, e.g. grouped ledgers).",
                "type": "boolean"
              },
              {
                "name": "columnPicker",
                "description": "Built-in \"Columns\" toggle — derives its options from `columns`/rows. Set `column-picker=\"false\"` to hide it.",
                "type": "boolean"
              },
              {
                "name": "total",
                "description": "Total matching rows across all pages — renders \"{n} results\" in the toolbar. Unset = no count.",
                "type": "number | undefined | undefined"
              },
              { "name": "selectable", "type": "boolean" },
              { "name": "selected", "type": "unknown[]" },
              {
                "name": "sortable",
                "description": "Make every column sortable (or set `sortable` per column in `columns`).",
                "type": "boolean"
              },
              {
                "name": "sort",
                "description": "Current sort, mirrored in the header (`aria-sort`); the host keeps it in sync with the fetch.",
                "type": "SortState | null"
              },
              {
                "name": "rowActions",
                "description": "Trailing \"...\" button per row; emits `baht-row-actions { row, index, id }`.",
                "type": "boolean"
              },
              { "name": "page", "type": "number" },
              { "name": "totalPages", "type": "number" },
              {
                "name": "cursorMode",
                "description": "Switch the footer to Prev/Next (cursor-based API, no total count).",
                "type": "boolean"
              },
              { "name": "hasNext", "type": "boolean" },
              { "name": "hasPrev", "type": "boolean" }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-tabs",
          "description": "Tabs (APG tab pattern). The tab strip comes from `tabs`; the panels are your own children,\nmatched by `data-tab=\"<key>\"` — they get `role=\"tabpanel\"`, ids and `hidden` for you.\n\n  <baht-tabs label=\"Ticket\">\n    <section data-tab=\"details\">…</section>\n    <section data-tab=\"history\">…</section>\n  </baht-tabs>\n  el.tabs = [{ key: 'details', label: 'Details' }, { key: 'history', label: 'History', badge: '3' }];\n  el.addEventListener('baht-change', (e) => (location.hash = e.detail.value));\n\n`value` is the selected key (the first enabled tab when unset or unknown). Keyboard: ←/→\nmove between tabs (wrapping, skipping disabled), Home/End jump; with `activation=\"manual\"`\nthe arrows only move focus and Enter/Space selects. Only the selected tab is in the Tab order.\n---\n\n\n### **Events:**\n - **baht-change** - The user selected a tab. `detail: { value }`. Setting `value` does not fire it.\n- **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "value",
              "description": "Selected tab key.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "label",
              "description": "Accessible name of the tab list.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "activation",
              "description": "`automatic` (default): arrows select. `manual`: arrows focus, Enter/Space selects.",
              "value": {
                "type": "'automatic' | 'manual'",
                "default": "'automatic'"
              }
            },
            {
              "name": "variant",
              "description": "`line` (default): an underline strip. `pill`: rounded chips; the active fill is `--baht-tabs-pill-active`.",
              "value": { "type": "'line' | 'pill'", "default": "'line'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-change",
              "description": "The user selected a tab. `detail: { value }`. Setting `value` does not fire it."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              {
                "name": "tabs",
                "description": "The tab strip. Set as a property: `el.tabs = [...]`.",
                "type": "TabItem[]"
              },
              {
                "name": "value",
                "description": "Selected tab key.",
                "type": "string"
              },
              {
                "name": "label",
                "description": "Accessible name of the tab list.",
                "type": "string"
              },
              {
                "name": "activation",
                "description": "`automatic` (default): arrows select. `manual`: arrows focus, Enter/Space selects.",
                "type": "'automatic' | 'manual'"
              },
              {
                "name": "variant",
                "description": "`line` (default): an underline strip. `pill`: rounded chips; the active fill is `--baht-tabs-pill-active`.",
                "type": "'line' | 'pill'"
              },
              {
                "name": "selected",
                "description": "The tab actually shown: `value` when it names an enabled tab, else the first enabled one.",
                "type": "string"
              }
            ],
            "events": [
              {
                "name": "baht-change",
                "description": "The user selected a tab. `detail: { value }`. Setting `value` does not fire it."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-tags",
          "description": "Creatable chip input: value is `string[]` (unique, trimmed, in entry\norder). Type text + Enter or comma to add a chip; Backspace on an empty\ninput removes the last chip; pasting \"a, b, c\" adds three at once.\n\nSuggestions come from `field.options` — the SAME list every other\noption-bearing field reads, already resolved by `<baht-form>` when the\ndescriptor uses `options_from` (spec 01 §1: dynamic options are merged\ninto `field.options` before the primitive ever sees them, so this\ncomponent needs no fetcher of its own). They filter as you type\n(case-insensitive, prefix matches first) and exclude tags already chosen.\nFree text becomes a chip unless `field.allow_new === false`, in which case\nonly a suggestion's `key` (matched case-insensitively) can become one.\n\n  el.field = { type: 'tags', key: 'labels', options: [...], max_items: 5 };\n  el.addEventListener('baht-input', (e) => (draft.labels = e.detail.value));\n\nARIA: APG editable combobox with list autocomplete, same pattern as\n<baht-typeahead> — the input is the combobox, the chips sit before it in\nthe same control, `aria-activedescendant` walks the listbox.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-text",
          "description": "Static text block (`type: \"label\"`): no value, no emits.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [{ "name": "field", "type": "FieldDescriptor" }],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-textarea",
          "description": "\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-theme-toggle",
          "description": "Light / dark switch for the 20baht themes. Sets `data-baht-theme` on `<html>` (or on the\n`target` element), so every `--baht-*` token follows.\n\n  <baht-theme-toggle></baht-theme-toggle>\n  <baht-theme-toggle default=\"system\"></baht-theme-toggle>   <!-- first visit follows the OS -->\n  <baht-theme-toggle target=\"#preview\" storage-key=\"\"></baht-theme-toggle>\n\nStarting theme: the one saved in `localStorage[storage-key]`, else the theme already on the\ntarget, else `default`. Clicking flips it, saves it and fires `baht-theme-change`. Every\ntoggle watches its target's attribute, so several toggles (or the app's own settings page)\nstay in sync. To avoid a flash of the wrong theme on load, apply the saved theme before\npaint with an inline script (see the docs page).\n\nA11y: an APG toggle button — fixed localized name (\"Dark theme\"), `aria-pressed` = dark.\n---\n\n\n### **Events:**\n - **baht-theme-change** - The user switched theme. `detail: { theme: 'light' | 'dark' }`.\n- **name**\n\n### **Methods:**\n - **toggle(): _void_** - Flip light ↔ dark, save it and fire `baht-theme-change` (what a click does).\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "theme",
              "description": "Current theme. Setting it applies it to the target (not saved, no event).",
              "value": { "type": "ThemeName", "default": "'light'" }
            },
            {
              "name": "default",
              "description": "Theme when nothing is saved and the target has none: `light`, `dark`, or `system`.",
              "value": { "type": "ThemeName | 'system'", "default": "'light'" }
            },
            {
              "name": "target",
              "description": "CSS selector of the element that carries `data-baht-theme`. Default: `<html>`.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "storage-key",
              "description": "localStorage key for the user's choice; `\"\"` turns persistence off.",
              "value": { "type": "string", "default": "'baht-theme'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [
            {
              "name": "baht-theme-change",
              "description": "The user switched theme. `detail: { theme: 'light' | 'dark' }`."
            },
            { "name": "name", "type": "CustomEvent" }
          ],
          "js": {
            "properties": [
              {
                "name": "theme",
                "description": "Current theme. Setting it applies it to the target (not saved, no event).",
                "type": "ThemeName"
              },
              {
                "name": "default",
                "description": "Theme when nothing is saved and the target has none: `light`, `dark`, or `system`.",
                "type": "ThemeName | 'system'"
              },
              {
                "name": "target",
                "description": "CSS selector of the element that carries `data-baht-theme`. Default: `<html>`.",
                "type": "string"
              },
              {
                "name": "storageKey",
                "description": "localStorage key for the user's choice; `\"\"` turns persistence off.",
                "type": "string"
              }
            ],
            "events": [
              {
                "name": "baht-theme-change",
                "description": "The user switched theme. `detail: { theme: 'light' | 'dark' }`."
              },
              { "name": "name", "type": "CustomEvent" }
            ]
          }
        },
        {
          "name": "baht-timeline",
          "description": "Vertical timeline (run history, audit log).\n\n  el.items = [\n    { title: 'Run started', time: '09:12', tone: 'muted' },\n    { title: 'Webhook sent', time: '09:12' },\n    { title: 'Failed', description: \"Couldn't reach the webhook.\", time: '09:13', tone: 'danger' },\n  ];\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [{ "name": "items", "type": "TimelineItem[]" }],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-toast",
          "description": "Toast host — put ONE <baht-toast> on the page and call show():\n\n  const toast = document.querySelector('baht-toast');\n  toast.show({ message: 'Saved.', tone: 'success' });\n\nCopy: short and dry (\"Saved.\", not \"Successfully saved your changes!\").\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "duration",
              "description": "Default auto-dismiss (ms) when show() gets no duration.",
              "value": { "type": "number", "default": "4000" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "duration",
                "description": "Default auto-dismiss (ms) when show() gets no duration.",
                "type": "number"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-toggle",
          "description": "\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n \n- **flushTyped(): _void_** - Hand a pending typed edit to the host now (blur, disconnect, host reads). Idempotent.\n\n\n\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "readonly",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "show-optional",
              "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
              "value": { "type": "number | undefined" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "field", "type": "FieldDescriptor" },
              { "name": "value", "type": "unknown" },
              { "name": "disabled", "type": "boolean" },
              { "name": "readonly", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "showOptional",
                "description": "Append the localized \"(optional)\" marker to the label when not required (set by <baht-form show-optional>).",
                "type": "boolean"
              },
              { "name": "error", "type": "string | null" },
              {
                "name": "debounce",
                "description": "Debounce for typed edits (ms); unset = BahtFieldBase.defaultDebounce.",
                "type": "number | undefined"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-tooltip",
          "description": "Hover/focus tooltip. Wraps its children; pure CSS show/hide (120ms fade).\nEscape hides it without moving focus (WCAG 1.4.13); it re-arms on leave/blur.\n\n  <baht-tooltip text=\"Posted as JSON when the trigger fires\">\n    <baht-button variant=\"ghost\" label=\"?\"></baht-button>\n  </baht-tooltip>\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            { "name": "text", "value": { "type": "string", "default": "''" } },
            {
              "name": "position",
              "value": {
                "type": "'top' | 'bottom' | 'left' | 'right'",
                "default": "'top'"
              }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              { "name": "text", "type": "string" },
              {
                "name": "position",
                "type": "'top' | 'bottom' | 'left' | 'right'"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-typeahead",
          "description": "Text input with suggestions (autocomplete). The value IS the text — pick a\nsuggestion and the input shows its label; keep typing and the text stands\non its own (`free-text`, default) or is only a filter (`free-text=\"false\"`,\nblur then reverts to the last picked option).\n\nSuggestions come from a static `options` list (filtered here — labels that\nstart with the term rank first, then labels that contain it) or from an\nasync `fetcher(term)`. Typing is debounced (`debounce`, 150 ms) before the\nhost hears `baht-change` and before a fetcher runs.\n\n  const el = document.createElement('baht-typeahead');\n  el.options = [{ key: 'th', label: 'Thailand' }, { key: 'tw', label: 'Taiwan' }];\n  el.addEventListener('baht-change', (e) => (draft.country = e.detail.value));\n  el.addEventListener('baht-select', (e) => (draft.countryCode = e.detail.option.key));\n\nARIA: APG editable combobox with list autocomplete — the input is the\ncombobox, `aria-activedescendant` walks the listbox, Escape closes, Enter\npicks. Unlike <baht-reference-picker> the input is always editable and the\ntext itself is a valid value; unlike <baht-select> the list is a filter of\nwhat was typed, not a fixed menu.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n ",
          "doc-url": "",
          "attributes": [
            {
              "name": "label",
              "description": "Accessible name (also the listbox name).",
              "value": { "type": "string", "default": "'Search'" }
            },
            {
              "name": "value",
              "description": "The text. Controlled: re-feed after `baht-change` (or don't — the input keeps what was typed).",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "placeholder",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "invalid",
              "description": "Marks the control invalid (styles + aria-invalid).",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "debounce",
              "description": "Quiet time (ms) before the host hears the text and suggestions are (re)computed.",
              "value": { "type": "number", "default": "150" }
            },
            {
              "name": "min-chars",
              "description": "Characters typed before suggestions appear (0 = suggest on focus/arrow too).",
              "value": { "type": "number", "default": "1" }
            },
            {
              "name": "max-items",
              "description": "Cap on suggestions shown.",
              "value": { "type": "number", "default": "8" }
            },
            {
              "name": "free-text",
              "description": "false = the text must be a picked option; blur reverts otherwise.",
              "value": { "type": "boolean", "default": "true" }
            },
            {
              "name": "autocomplete",
              "description": "HTML autocomplete token forwarded to the input ('off' keeps the browser's own list away).",
              "value": { "type": "string", "default": "'off'" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "label",
                "description": "Accessible name (also the listbox name).",
                "type": "string"
              },
              {
                "name": "value",
                "description": "The text. Controlled: re-feed after `baht-change` (or don't — the input keeps what was typed).",
                "type": "string"
              },
              { "name": "placeholder", "type": "string" },
              { "name": "disabled", "type": "boolean" },
              {
                "name": "invalid",
                "description": "Marks the control invalid (styles + aria-invalid).",
                "type": "boolean"
              },
              {
                "name": "options",
                "description": "Static suggestion list, filtered client-side. Ignored when `fetcher` is set.",
                "type": "TypeaheadOption[]"
              },
              {
                "name": "fetcher",
                "description": "Async suggestions: term in, options out. Errors show as \"no matches\".",
                "type": "((term: string) => Promise<TypeaheadOption[]>) | null"
              },
              {
                "name": "debounce",
                "description": "Quiet time (ms) before the host hears the text and suggestions are (re)computed.",
                "type": "number"
              },
              {
                "name": "minChars",
                "description": "Characters typed before suggestions appear (0 = suggest on focus/arrow too).",
                "type": "number"
              },
              {
                "name": "maxItems",
                "description": "Cap on suggestions shown.",
                "type": "number"
              },
              {
                "name": "freeText",
                "description": "false = the text must be a picked option; blur reverts otherwise.",
                "type": "boolean"
              },
              {
                "name": "autocomplete",
                "description": "HTML autocomplete token forwarded to the input ('off' keeps the browser's own list away).",
                "type": "string"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        },
        {
          "name": "baht-upload",
          "description": "File upload against an HTTP endpoint. Picking (or dropping) a file sends it **immediately**\nas `multipart/form-data` to `url`; the endpoint answers with where it stored the file and\nthat URL becomes the value. Nothing waits for a form submit, and no host code is needed:\nthe whole contract is attributes.\n\n  <baht-upload url=\"/api/uploads\" accept=\".pdf,image/*\" max-size=\"10485760\"></baht-upload>\n\nExpected response: `2xx` with JSON `{ \"url\": \"…\" }` (change the key with `response-key`,\ndotted paths allowed) or a plain-text URL body. Anything else is an upload error the\nuser can retry.\n\n  el.addEventListener('baht-change', (e) => e.detail.value);   // string | string[] | null\n\nAuth: set `headers` (property) and/or `with-credentials`. Progress: `baht-upload-progress`.\nValue is controlled the usual way — feed it back on `baht-change`, or pre-fill with the\nURLs already on record and the chips render from them.\n---\n\n\n### **Events:**\n - **name**\n\n### **Methods:**\n - **clear(): _void_** - Empty the value (does not delete anything on the server).\n",
          "doc-url": "",
          "attributes": [
            {
              "name": "url",
              "description": "Endpoint that receives the file and returns its stored URL. Required.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "method",
              "description": "HTTP method for the upload request.",
              "value": { "type": "string", "default": "'POST'" }
            },
            {
              "name": "field-name",
              "description": "Form-data key the file is sent under.",
              "value": { "type": "string", "default": "'file'" }
            },
            {
              "name": "response-key",
              "description": "Where the stored URL lives in a JSON response (dotted path). Empty = the body itself.",
              "value": { "type": "string", "default": "'url'" }
            },
            {
              "name": "with-credentials",
              "description": "Send cookies on cross-origin requests.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "label",
              "description": "Visible label.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "hint",
              "description": "Helper text under the control. Defaults to the accept / size limits when set.",
              "value": { "type": "string", "default": "''" }
            },
            {
              "name": "accept",
              "description": "Native `accept` filter (`.pdf,image/*`).",
              "value": { "type": "string | undefined" }
            },
            {
              "name": "multiple",
              "description": "Allow several files; the value becomes an array.",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "max-size",
              "description": "Reject files over this many bytes before sending.",
              "value": { "type": "number | undefined" }
            },
            {
              "name": "disabled",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "required",
              "value": { "type": "boolean", "default": "false" }
            },
            {
              "name": "error",
              "description": "Validation message to display (marks the control invalid).",
              "value": { "type": "string | null", "default": "null" }
            },
            { "name": "defer-hydration", "value": { "type": "string" } }
          ],
          "events": [{ "name": "name", "type": "CustomEvent" }],
          "js": {
            "properties": [
              {
                "name": "url",
                "description": "Endpoint that receives the file and returns its stored URL. Required.",
                "type": "string"
              },
              {
                "name": "method",
                "description": "HTTP method for the upload request.",
                "type": "string"
              },
              {
                "name": "fieldName",
                "description": "Form-data key the file is sent under.",
                "type": "string"
              },
              {
                "name": "responseKey",
                "description": "Where the stored URL lives in a JSON response (dotted path). Empty = the body itself.",
                "type": "string"
              },
              {
                "name": "headers",
                "description": "Extra request headers (e.g. Authorization). Property only.",
                "type": "Record<string, string>"
              },
              {
                "name": "withCredentials",
                "description": "Send cookies on cross-origin requests.",
                "type": "boolean"
              },
              {
                "name": "value",
                "description": "Stored URL(s). `string`, `string[]` when `multiple`, or `null`. Property only.",
                "type": "string | string[] | null"
              },
              {
                "name": "label",
                "description": "Visible label.",
                "type": "string"
              },
              {
                "name": "hint",
                "description": "Helper text under the control. Defaults to the accept / size limits when set.",
                "type": "string"
              },
              {
                "name": "accept",
                "description": "Native `accept` filter (`.pdf,image/*`).",
                "type": "string | undefined"
              },
              {
                "name": "multiple",
                "description": "Allow several files; the value becomes an array.",
                "type": "boolean"
              },
              {
                "name": "maxSize",
                "description": "Reject files over this many bytes before sending.",
                "type": "number | undefined"
              },
              { "name": "disabled", "type": "boolean" },
              { "name": "required", "type": "boolean" },
              {
                "name": "error",
                "description": "Validation message to display (marks the control invalid).",
                "type": "string | null"
              }
            ],
            "events": [{ "name": "name", "type": "CustomEvent" }]
          }
        }
      ]
    },
    "css": {
      "properties": [],
      "pseudo-elements": []
    }
  }
}
