# HTML Bricks `hb-*` web components — agent & LLM reference

## What this document is

A **consumer-oriented reference** for **`hb-*` custom elements**: tags, attributes, events, slots, and how to theme and interact with them from **HTML, JavaScript, or any UI stack**. It does **not** describe how to build or publish the library.

## How to use these components

1. **Attributes** — Public property **names** use **snake_case**. **Values are always strings** from HTML or `setAttribute`: you cannot pass native objects, numbers, or booleans. **Objects** (and structured data) must be **serialized** (typically **JSON strings**). **Numbers** must be passed as their **string form** (e.g. `"42"`). **Booleans** must be the strings **`yes`** or **`no`**. Arrays are also passed as **strings** (usually JSON). A component’s README is authoritative if it documents a special encoding.
2. **Custom events (listening)** — Outputs are **`CustomEvent`** instances fired on the **host element**. The **event `type` string** is the key from that component’s **`Events`** contract (same names as in `list.json` → `definitions.events` and in each README): usually **camelCase** (e.g. `clipboardCopyText`, `footerClick`).
   - **HTML / vanilla JavaScript** — Call **`element.addEventListener(<eventType>, handler)`** with that exact string. Read the payload from **`event.detail`** (shape matches the JSON Schema for that event in the manifest). Use **`removeEventListener`** or **`{ once: true }`** as usual.
   - **Svelte 5** — On **`<hb-…>`** (or any DOM element), use an **attribute named `on` + the exact event type** (case-sensitive). Examples: **`onclick={…}`** for `click`; **`onfooterClick={…}`** for `footerClick`; **`onclipboardCopyText={…}`** for `clipboardCopyText`. This follows Svelte’s element event rules ([Basic markup → Events](https://svelte.dev/docs/svelte/basic-markup#Events)): listener attributes start with `on`, and casing distinguishes e.g. `click` from `Click`. Shorthand **`{onfooterClick}`** works when the handler variable name matches. With **`svelte-elements.d.ts`** from each package (or **hb-bundle**), **`on<EventKey>`**, **`on<EventKey>capture`**, and **`'on:<EventKey>'`** are typed per tag.
3. **Theming** — The layout/CSS framework is **Bulma 1.x**; icons use **Bootstrap Icons**. To change appearance (colors, theme, spacing where exposed), set **Bulma CSS custom properties** (`--bulma-*`, see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)) on the **host element**, a parent wrapper, or higher in the cascade. Those variables typically flow into **shadow DOM** content. Prefer **`--bulma-*`** (and any **component-documented** custom properties) over unrelated ad-hoc rules. Each component section below may list extra **CSS variables** and **`::part(...)`** names when exposed.
4. **Shadow DOM** — Most hosts attach a **shadow root**. Style via inherited **variables** and documented **`::part(...)`** selectors; use **`addEventListener`** or Svelte **`on…`** attributes on the host for outputs described in each section.
5. **Reading the entries** — Sections may use tables or TypeScript-like snippets for props and events; treat those as the **public contract** for that tag.

### Machine-readable contracts: `list.json` (per-component JSON Schema)

The published **@htmlbricks/hb-bundle** file `list.json` has shape `{ "version": "…", "packages": [ … ] }`. Each `packages[]` item is a **component manifest** (same shape as `manifest.json` on npm, minus `iifeIntegrity`). Use it when you need **exact** typings beyond prose.

#### How to find **custom events** and **`detail`** types

1. Pick the manifest where `name` matches the tag (e.g. `"hb-area-code"`).
2. Open `definitions.events`: it is a **JSON Schema** document (draft-07) generated from the component’s `Events` TypeScript type.
3. Inside that document, locate the schema for the `Events` object — typically `definitions.events.definitions.Events` (an object with `type: "object"` and `properties`).
4. **Each property name under `properties` is the `CustomEvent` name** (the string passed to `addEventListener` and emitted as `event.type`). Example: `clipboardCopyText`, `footerClick`.
5. The **value** for that property is the JSON Schema for **`event.detail`**: usually `type: "object"` with its own `properties`, `required`, and nested `$ref` to `#/definitions/...` for structured payloads. Resolve `$ref` inside the same `definitions.events` document.

Human-readable event names and examples still appear in each component’s README section below; use `list.json` when you need the **schema-level** breakdown.

#### How to find **property** types (`Component` props)

1. Same manifest entry; open `definitions.component` (JSON Schema for the `Component` TypeScript interface).
2. Locate `definitions.component.definitions.Component` (or the object referenced by the root `$ref` for `Component`).
3. Under **`properties`**, each key is a **prop name as in TypeScript** (often **camelCase**). The schema gives `type`, `enum`, `items` (arrays), and `$ref` to shared shapes (`ICompany`, `IColumn`, …) in the same document’s `definitions` map.
4. For **HTML markup**, this library documents **attribute names** in each README (**snake_case** on the host). **All attribute values are strings**: numbers as string digits, booleans as **`yes`/`no`**, objects/arrays as **JSON strings** (or another encoding only if that README says so). When the schema uses camelCase (`companyName`) and the README shows a snake_case attribute (`disable_expanding_small`), **follow the README for attribute names** and use the schema for **structure and types** inside JSON-valued props (e.g. shape of `company`).

Cross-check: README table (authoritative for attributes) + `definitions.component` (authoritative for JSON types and nesting).

### TypeScript & Svelte typings (`types/` on each npm package)

Every published **`@htmlbricks/hb-<folder>`** tarball includes a **`types/`** folder next to the IIFE (generated on `npm run build:wc`, not committed under `src/wc`):

| File | Role |
|------|------|
| `webcomponent.type.d.ts` | Authoring types: `Component`, `Events`, and shared interfaces (same logical model as `list.json` schemas). |
| `webcomponent.type.d.json`, `webcomponent_events.type.d.json` | JSON Schema for props and custom-event `detail` (machine-readable). |
| `html-elements.d.ts` | **DOM**: augments `HTMLElementTagNameMap` and listener overloads for `querySelector` / `createElement` / `addEventListener` in plain TypeScript. Package `types` field points here. Subpath: `@htmlbricks/hb-<folder>/types/html-elements`. |
| `svelte-elements.d.ts` | **Svelte**: augments `SvelteHTMLElements` for **`<hb-…>`** in **`.svelte`**: host attributes from `Component` (optional strings, excluding DOM key clashes) and **custom events** from `Events` as `on<EventKey>`, `on<EventKey>capture`, and `'on:<EventKey>'` with typed `CustomEvent` / `detail`. Requires **`svelte`**. Subpath: `@htmlbricks/hb-<folder>/types/svelte-elements`. |

**`@htmlbricks/hb-bundle`** publishes **`types/html-elements.d.ts`** and **`types/svelte-elements.d.ts`**, each aggregating all tags via `/// <reference path="./elements/<folder>/…" />` and copied `webcomponent.type.d.ts` shims under `types/elements/` (imports rewritten for the flat layout).

**Usage (examples):** `compilerOptions.types`: `["@htmlbricks/hb-bundle"]` for bundle DOM typings; in a Svelte app add `import "@htmlbricks/hb-bundle/types/svelte-elements"` (or the matching `@htmlbricks/hb-<folder>` path) in `app.d.ts` or a global `.d.ts` so the compiler loads `SvelteHTMLElements` augmentations.

### Table of contents — how to use this with LLMs

- The **full text** of each component’s `README.md` is **included later in this same document**, under an HTML anchor `id="wc-<folder>"` (Markdown link: `#wc-<folder>`).
- The **jsDelivr** URL is the **canonical** `README.md` from npm (`@htmlbricks/hb-<folder>` at version **0.76.5**). The prose merged **below this TOC** is the **same document body** (duplicate for single-file reading).
- When answering **from this file only**, use the **in-document** link (`#wc-…`) so the model stays in one context. Use the **jsDelivr** link when you need the standalone README URL (e.g. fetching from the CDN).

## Table of contents

Each line lists: **tag — folder**, then **canonical README (npm/jsDelivr)**, then **same content inside this file**.

- **`hb-area-code` — `area-code`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-area-code@0.76.5/README.md) · [same text in this document](#wc-area-code)
- **`hb-auth` — `auth`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-auth@0.76.5/README.md) · [same text in this document](#wc-auth)
- **`hb-auth-social-login-button` — `auth-social-login-button`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-auth-social-login-button@0.76.5/README.md) · [same text in this document](#wc-auth-social-login-button)
- **`hb-banner` — `banner`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-banner@0.76.5/README.md) · [same text in this document](#wc-banner)
- **`hb-calendar-appointments` — `calendar-appointments`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-calendar-appointments@0.76.5/README.md) · [same text in this document](#wc-calendar-appointments)
- **`hb-calendar-events` — `calendar-events`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-calendar-events@0.76.5/README.md) · [same text in this document](#wc-calendar-events)
- **`hb-captcha-google-recaptcha-v2-invisible` — `captcha-google-recaptcha-v2-invisible`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-captcha-google-recaptcha-v2-invisible@0.76.5/README.md) · [same text in this document](#wc-captcha-google-recaptcha-v2-invisible)
- **`hb-card-video` — `card-video`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-card-video@0.76.5/README.md) · [same text in this document](#wc-card-video)
- **`hb-chartjs` — `chartjs`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-chartjs@0.76.5/README.md) · [same text in this document](#wc-chartjs)
- **`hb-checkout` — `checkout`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-checkout@0.76.5/README.md) · [same text in this document](#wc-checkout)
- **`hb-checkout-shopping-cart` — `checkout-shopping-cart`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-checkout-shopping-cart@0.76.5/README.md) · [same text in this document](#wc-checkout-shopping-cart)
- **`hb-contact-card` — `contact-card`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-contact-card@0.76.5/README.md) · [same text in this document](#wc-contact-card)
- **`hb-contact-item` — `contact-item`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-contact-item@0.76.5/README.md) · [same text in this document](#wc-contact-item)
- **`hb-cookie-law-banner` — `cookie-law-banner`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-cookie-law-banner@0.76.5/README.md) · [same text in this document](#wc-cookie-law-banner)
- **`hb-dashboard-card1` — `dashboard-card1`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dashboard-card1@0.76.5/README.md) · [same text in this document](#wc-dashboard-card1)
- **`hb-dashboard-counter-lines` — `dashboard-counter-lines`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dashboard-counter-lines@0.76.5/README.md) · [same text in this document](#wc-dashboard-counter-lines)
- **`hb-dashboard-indicator` — `dashboard-indicator`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dashboard-indicator@0.76.5/README.md) · [same text in this document](#wc-dashboard-indicator)
- **`hb-dialog` — `dialog`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dialog@0.76.5/README.md) · [same text in this document](#wc-dialog)
- **`hb-dialog-loader` — `dialog-loader`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dialog-loader@0.76.5/README.md) · [same text in this document](#wc-dialog-loader)
- **`hb-dialogform` — `dialogform`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dialogform@0.76.5/README.md) · [same text in this document](#wc-dialogform)
- **`hb-downloader` — `downloader`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-downloader@0.76.5/README.md) · [same text in this document](#wc-downloader)
- **`hb-dropdown-notifications` — `dropdown-notifications`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dropdown-notifications@0.76.5/README.md) · [same text in this document](#wc-dropdown-notifications)
- **`hb-dropdown-simple` — `dropdown-simple`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-dropdown-simple@0.76.5/README.md) · [same text in this document](#wc-dropdown-simple)
- **`hb-editor-video` — `editor-video`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-editor-video@0.76.5/README.md) · [same text in this document](#wc-editor-video)
- **`hb-faq-component` — `faq-component`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-faq-component@0.76.5/README.md) · [same text in this document](#wc-faq-component)
- **`hb-footer` — `footer`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-footer@0.76.5/README.md) · [same text in this document](#wc-footer)
- **`hb-form` — `form`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-form@0.76.5/README.md) · [same text in this document](#wc-form)
- **`hb-form-composer` — `form-composer`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-form-composer@0.76.5/README.md) · [same text in this document](#wc-form-composer)
- **`hb-form-contact` — `form-contact`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-form-contact@0.76.5/README.md) · [same text in this document](#wc-form-contact)
- **`hb-funnel` — `funnel`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-funnel@0.76.5/README.md) · [same text in this document](#wc-funnel)
- **`hb-gallery-video` — `gallery-video`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-gallery-video@0.76.5/README.md) · [same text in this document](#wc-gallery-video)
- **`hb-gauge` — `gauge`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-gauge@0.76.5/README.md) · [same text in this document](#wc-gauge)
- **`hb-input-area` — `input-area`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-area@0.76.5/README.md) · [same text in this document](#wc-input-area)
- **`hb-input-array-objects` — `input-array-objects`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-array-objects@0.76.5/README.md) · [same text in this document](#wc-input-array-objects)
- **`hb-input-array-tags` — `input-array-tags`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-array-tags@0.76.5/README.md) · [same text in this document](#wc-input-array-tags)
- **`hb-input-checkbox` — `input-checkbox`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-checkbox@0.76.5/README.md) · [same text in this document](#wc-input-checkbox)
- **`hb-input-color` — `input-color`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-color@0.76.5/README.md) · [same text in this document](#wc-input-color)
- **`hb-input-coords` — `input-coords`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-coords@0.76.5/README.md) · [same text in this document](#wc-input-coords)
- **`hb-input-date` — `input-date`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-date@0.76.5/README.md) · [same text in this document](#wc-input-date)
- **`hb-input-datetime` — `input-datetime`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-datetime@0.76.5/README.md) · [same text in this document](#wc-input-datetime)
- **`hb-input-email` — `input-email`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-email@0.76.5/README.md) · [same text in this document](#wc-input-email)
- **`hb-input-file` — `input-file`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-file@0.76.5/README.md) · [same text in this document](#wc-input-file)
- **`hb-input-number` — `input-number`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-number@0.76.5/README.md) · [same text in this document](#wc-input-number)
- **`hb-input-radio` — `input-radio`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-radio@0.76.5/README.md) · [same text in this document](#wc-input-radio)
- **`hb-input-range` — `input-range`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-range@0.76.5/README.md) · [same text in this document](#wc-input-range)
- **`hb-input-select` — `input-select`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-select@0.76.5/README.md) · [same text in this document](#wc-input-select)
- **`hb-input-text` — `input-text`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-input-text@0.76.5/README.md) · [same text in this document](#wc-input-text)
- **`hb-json-viewer` — `json-viewer`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-json-viewer@0.76.5/README.md) · [same text in this document](#wc-json-viewer)
- **`hb-layout` — `layout`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-layout@0.76.5/README.md) · [same text in this document](#wc-layout)
- **`hb-layout-desktop` — `layout-desktop`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-layout-desktop@0.76.5/README.md) · [same text in this document](#wc-layout-desktop)
- **`hb-layout-mobile` — `layout-mobile`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-layout-mobile@0.76.5/README.md) · [same text in this document](#wc-layout-mobile)
- **`hb-map` — `map`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-map@0.76.5/README.md) · [same text in this document](#wc-map)
- **`hb-markdown-viewer` — `markdown-viewer`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-markdown-viewer@0.76.5/README.md) · [same text in this document](#wc-markdown-viewer)
- **`hb-matrix-video` — `matrix-video`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-matrix-video@0.76.5/README.md) · [same text in this document](#wc-matrix-video)
- **`hb-messages-box` — `messages-box`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-messages-box@0.76.5/README.md) · [same text in this document](#wc-messages-box)
- **`hb-messages-list` — `messages-list`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-messages-list@0.76.5/README.md) · [same text in this document](#wc-messages-list)
- **`hb-messages-send` — `messages-send`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-messages-send@0.76.5/README.md) · [same text in this document](#wc-messages-send)
- **`hb-messages-topics-card` — `messages-topics-card`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-messages-topics-card@0.76.5/README.md) · [same text in this document](#wc-messages-topics-card)
- **`hb-modal-video` — `modal-video`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-modal-video@0.76.5/README.md) · [same text in this document](#wc-modal-video)
- **`hb-navbar` — `navbar`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-navbar@0.76.5/README.md) · [same text in this document](#wc-navbar)
- **`hb-offcanvas` — `offcanvas`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-offcanvas@0.76.5/README.md) · [same text in this document](#wc-offcanvas)
- **`hb-order-list` — `order-list`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-order-list@0.76.5/README.md) · [same text in this document](#wc-order-list)
- **`hb-pad-joystick` — `pad-joystick`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-pad-joystick@0.76.5/README.md) · [same text in this document](#wc-pad-joystick)
- **`hb-page-checkout` — `page-checkout`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-page-checkout@0.76.5/README.md) · [same text in this document](#wc-page-checkout)
- **`hb-page-invoice` — `page-invoice`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-page-invoice@0.76.5/README.md) · [same text in this document](#wc-page-invoice)
- **`hb-paginate` — `paginate`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-paginate@0.76.5/README.md) · [same text in this document](#wc-paginate)
- **`hb-paragraps-around-image` — `paragraps-around-image`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-paragraps-around-image@0.76.5/README.md) · [same text in this document](#wc-paragraps-around-image)
- **`hb-paragraps-around-image-cell` — `paragraps-around-image-cell`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-paragraps-around-image-cell@0.76.5/README.md) · [same text in this document](#wc-paragraps-around-image-cell)
- **`hb-payment-paypal` — `payment-paypal`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-payment-paypal@0.76.5/README.md) · [same text in this document](#wc-payment-paypal)
- **`hb-player-input-streaming` — `player-input-streaming`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-player-input-streaming@0.76.5/README.md) · [same text in this document](#wc-player-input-streaming)
- **`hb-player-live` — `player-live`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-player-live@0.76.5/README.md) · [same text in this document](#wc-player-live)
- **`hb-player-live-camera-ptz` — `player-live-camera-ptz`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-player-live-camera-ptz@0.76.5/README.md) · [same text in this document](#wc-player-live-camera-ptz)
- **`hb-product-comparison` — `product-comparison`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-product-comparison@0.76.5/README.md) · [same text in this document](#wc-product-comparison)
- **`hb-range-slider` — `range-slider`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-range-slider@0.76.5/README.md) · [same text in this document](#wc-range-slider)
- **`hb-searchbar` — `searchbar`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-searchbar@0.76.5/README.md) · [same text in this document](#wc-searchbar)
- **`hb-shop-item-cell` — `shop-item-cell`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-shop-item-cell@0.76.5/README.md) · [same text in this document](#wc-shop-item-cell)
- **`hb-shop-item-row` — `shop-item-row`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-shop-item-row@0.76.5/README.md) · [same text in this document](#wc-shop-item-row)
- **`hb-sidebar-cards-navigator` — `sidebar-cards-navigator`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-sidebar-cards-navigator@0.76.5/README.md) · [same text in this document](#wc-sidebar-cards-navigator)
- **`hb-sidebar-desktop` — `sidebar-desktop`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-sidebar-desktop@0.76.5/README.md) · [same text in this document](#wc-sidebar-desktop)
- **`hb-sidenav-button` — `sidenav-button`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-sidenav-button@0.76.5/README.md) · [same text in this document](#wc-sidenav-button)
- **`hb-sidenav-link` — `sidenav-link`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-sidenav-link@0.76.5/README.md) · [same text in this document](#wc-sidenav-link)
- **`hb-site-contacts-row` — `site-contacts-row`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-site-contacts-row@0.76.5/README.md) · [same text in this document](#wc-site-contacts-row)
- **`hb-site-paragraph-with-image` — `site-paragraph-with-image`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-site-paragraph-with-image@0.76.5/README.md) · [same text in this document](#wc-site-paragraph-with-image)
- **`hb-site-slideshow` — `site-slideshow`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-site-slideshow@0.76.5/README.md) · [same text in this document](#wc-site-slideshow)
- **`hb-site-slideshow-horizontal` — `site-slideshow-horizontal`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-site-slideshow-horizontal@0.76.5/README.md) · [same text in this document](#wc-site-slideshow-horizontal)
- **`hb-skeleton-component` — `skeleton-component`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-skeleton-component@0.76.5/README.md) · [same text in this document](#wc-skeleton-component)
- **`hb-stylus-notebook` — `stylus-notebook`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-stylus-notebook@0.76.5/README.md) · [same text in this document](#wc-stylus-notebook)
- **`hb-stylus-paper` — `stylus-paper`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-stylus-paper@0.76.5/README.md) · [same text in this document](#wc-stylus-paper)
- **`hb-table` — `table`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-table@0.76.5/README.md) · [same text in this document](#wc-table)
- **`hb-terms-doc-templates` — `terms-doc-templates`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-terms-doc-templates@0.76.5/README.md) · [same text in this document](#wc-terms-doc-templates)
- **`hb-toast` — `toast`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-toast@0.76.5/README.md) · [same text in this document](#wc-toast)
- **`hb-tooltip` — `tooltip`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-tooltip@0.76.5/README.md) · [same text in this document](#wc-tooltip)
- **`hb-uploader` — `uploader`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-uploader@0.76.5/README.md) · [same text in this document](#wc-uploader)
- **`hb-vertical-img-txt-archive` — `vertical-img-txt-archive`** — [README on jsDelivr](https://cdn.jsdelivr.net/npm/@htmlbricks/hb-vertical-img-txt-archive@0.76.5/README.md) · [same text in this document](#wc-vertical-img-txt-archive)

---

<a id="wc-area-code"></a>

# hb-area-code

**Category:** utilities · **Tags:** utilities, developer

## Summary

`hb-area-code` renders `content` in a **frameless** root (no Bulma **`box`**): copy + optional “Copied” tag are **positioned top-right over** the **`<pre><code>`** snippet. Copy uses **`navigator.clipboard.writeText`** and emits **`clipboardCopyText`** with **`{ text }`**. The host and root wrapper have **no extra margin or padding**; spacing around the snippet is up to the parent or **`::part(content)`**.

## Behaviour

1. **Copy:** each click writes **`content`** to the clipboard, shows confirmation (~1.6s, timer resets on repeat), and dispatches **`clipboardCopyText`** with **`detail.text`**.
2. **`content`:** single source for display and clipboard.
3. **`id`:** optional; when set, it is applied to the root **`hb-area-code`** wrapper inside the shadow tree.

## Markup

- Root: **`hb-area-code`** (no **`box`** / outer card frame; **no margin or padding** on the host root wrapper).
- Wrapper **`hb-area-code-body`** (`position: relative`): floating **`hb-area-code-float`** (absolute top-right) with optional **`tag is-success is-size-7`**, then **`button is-small`** (theme-default fill, no **`is-light`**) with Bootstrap Icons **`bi-clipboard`** / **`bi-clipboard-check`**.
- Snippet: **`<pre part="content" class="hb-area-code-snippet">`** and **`<code class="hb-area-code-snippet-code">`** (uniform insets; extra **`padding-inline-end`** keeps long first lines clear of the copy controls).

## Custom element

`hb-area-code`

## Attributes

| Attribute | Notes |
| --- | --- |
| `content` | Shown in the snippet and copied (string). |
| `id` | Optional string on the inner root **`hb-area-code`** element. |

## Events

| Event | `detail` |
| --- | --- |
| `clipboardCopyText` | `{ text: string }` |

## Styling

- **Parts:** `content` → `<pre>` (see `extra/docs.ts`).
- **Theme:** same stack as other hb-* components (`bulma-wc-host-theme-tokens` on the shadow `:host`): system **`prefers-color-scheme`**, then **`data-theme` / `.theme-*`** on **`html`**, **`body`**, or the custom element host. **`color-scheme`** on the host follows that order so native chrome stays coherent. The snippet background and border are built with **`hsl(var(--bulma-scheme-h), var(--bulma-scheme-s), var(--bulma-scheme-main-bis-l))`** and **`border-l`** primitives so the surface stays valid in the shadow tree; text prefers **`--bulma-text`** with the same **`hsl(…, --bulma-text-l)`** fallback.
- **Corners:** snippet, copy **button**, and **tag** use **`border-radius: 0`** (no rounded corners inside this component).
- **Dark mode:** the default strip follows Bulma tokens on the shadow `:host` (system **`prefers-color-scheme`** is handled by **`bulma-wc-host-theme-tokens`**, so **`main-bis-l`** is already a dark gray on a dark scheme). The deeper “well” (**`scheme-main-l`**, **`border-l`**, inset highlight) applies **only** when dark is **explicit** — **`data-theme="dark"`** or **`.theme-dark`** on **`html`**, **`body`**, or the host — so we never mix OS dark with light-token **`scheme-main-l`** (that would read as **white**).

## TypeScript

`types/webcomponent.type.d.ts`

## Example

```html
<hb-area-code content="npm install @htmlbricks/hb-area-code"></hb-area-code>
```

---

<a id="wc-auth"></a>

# `hb-auth`

**Category:** auth · **Tags:** auth · **Package:** `@htmlbricks/hb-auth`

## Summary

`hb-auth` is a self-contained authentication card for the shadow DOM. It switches between **login**, **register**, **forgot password**, **mail recovery info**, **account activation**, and **password recovery** flows based on the `type` property. It can call optional HTTP endpoints for login and registration, persist session payloads under a configurable storage key, embed **OAuth2** providers via `hb-auth-social-login-button`, and emit **custom events** so the host app can handle credentials, recovery, and OAuth steps. Strings are **internationalized** (Italian and English) when `i18nlang` is set.

## What it does

- **Local auth UI:** email and password fields (with optional HTML `pattern` via `passwordpattern`), “remember me” on login, full-width primary actions, and ghost links to switch modes (register / login / forgot password) according to `disableregister`, `enable_recover_password`, and the active `type`.
- **Server-assisted login/register:** If `loginuri` or `registeruri` is set, the component performs `fetch` with `requestmethod` (default `POST`, normalized to uppercase). **POST** sends JSON (`email`, `password`, and `rememberMe` for login). **GET** omits a body. Optional `appendbodyparams` must be a **JSON object string** merged into the body. On success, the JSON response is stored in **localStorage** (when “remember me” is checked) or **sessionStorage** under `sessionkey` (default `_auth`), then `redirectonlogin` / `redirectoncreate` may run as a full navigation. The **`login`** or **`register`** event is dispatched with the server payload (or a minimal local payload if no URI is configured).
- **Recovery / activation:** For `type` `activate` or `recover`, the user enters recovery code, email, new password, and repeat password; submitting dispatches **`recoverOrActivate`** with `{ password, recoverycode, email }`. **`forgotpassword`** collects email and dispatches **`recoverPassword`**. **`mailrecoverinfo`** shows informational copy and a link back to login.
- **OAuth:** When `oauth2providers` is a non-empty list, a row of **`hb-auth-social-login-button`** instances is rendered. The host receives **`oauthFlowRedirectStart`**, **`oauthFlowInit`**, **`oauthFlowSuccess`**, and **`oauthFlowCustom`** bubbled from the child. While a flow is in progress, an **opaque overlay** matching the card surface covers the card and shows **only a centered CSS spinner** (no copy); after success, a **dismissible success notification** appears above the card (hidden again during a subsequent OAuth load).
- **URL hints:** If `recoverycode`, `email`, or `type` are not set, the component may read `recoverycode=`, `recoveryemail=`, and `recoverytype=` from `location.href` (query segment after each key, up to `&`).
- **OAuth-only layout:** With `disablelocal` true (and providers configured), the divider and “credentials” subtitle are omitted; only social buttons show for supported `type` values.

The `Component` type also allows `type` values **`otp`**, **`2fa_code`**, and **`2fa_config`**, and includes **`activateuri`** and **`recoveruri`**. In the current markup, **only** `login`, `register`, `forgotpassword`, `mailrecoverinfo`, `activate`, and `recover` drive complete titles, fields, and actions. Other `type` strings are accepted at the type level but **do not** map to dedicated UI blocks in `component.wc.svelte`; **`activateuri` / `recoveruri`** are declared on the component but are **not** read by this file’s logic (hosts can still use them in their own layers).

## Appearance and layout

- **Host:** Block-level, full width of the parent, default text color `var(--bulma-text)`.
- **Width:** Inner wrapper `max-width: 24rem`, centered (`margin-inline: auto`).
- **Card:** Main content uses Bulma’s **`box`** on `.hb-auth` with an extra **1px** border using `var(--bulma-border-weak)` for contrast in light and dark schemes.
- **Header:** Default slot fills with a centered logo image when `logouri` is non-empty; consumers can replace the whole header with the **`header`** slot.
- **Titles:** Centered `title is-4` for login, register, forgot password, and recover/activate headings (i18n strings).
- **OAuth row:** Flex-wrapped, centered list of provider tiles. Each **`hb-auth-social-login-button`** is **5rem × 5rem**, bordered, rounded with `var(--bulma-radius)`, background `var(--bulma-scheme-main-ter)`, with hover ring and focus-visible outline using `var(--bulma-link)`.
- **Divider:** When both OAuth and local form are shown, a horizontal rule (`hb-auth__divider`) separates providers from the “credentials” subtitle and fields.
- **Fields:** Bulma `field` / `control` / `input` patterns; validation toggles Bulma **`is-success` / `is-danger`** on inputs after submit attempts.
- **Overlay:** Absolutely positioned over the **card shell** (`inset: 0`), same background as the Bulma **`box`** (`var(--bulma-box-background-color)` with fallback to `var(--bulma-scheme-main)`), matching **border radius** to the card; **no** extra border or shadow on the overlay. Only **`.hb-auth__spinner`** is visible (track from `color-mix` on `var(--bulma-text)`, accent `var(--bulma-link)`). `aria-busy` on `<main>` reflects loading state.
- **Success banner:** Bulma `notification is-success is-light` above the card when OAuth completes successfully (not shown while OAuth loading is active).
- **Icons:** **Bootstrap Icons** are loaded inside the shadow tree from `styles/webcomponent.scss` (required because `<svelte:head>` link tags do not apply inside shadow roots).

## Logic (sign-in and related flows)

| Mode | Primary action | Event / side effects |
| --- | --- | --- |
| `login` | Validates email/password (length and simple email shape). | With `loginuri`: `fetch`, then storage + optional `redirectonlogin`, then **`login`**. Without `loginuri`: **`login`** with `{ email, password, rememberMe }` only. |
| `register` | Same base validation as login. | With `registeruri`: `fetch`, augments answer with `requestSent`, optional `redirectoncreate`, **`register`**. Without: **`register`** with `{ email, password }`. |
| `forgotpassword` | Email validation. | **`recoverPassword`** with `{ email }`. |
| `activate` / `recover` | Requires `recoverycode`, valid email, password, matching repeat password. | **`recoverOrActivate`** with `{ password, recoverycode, email }`. Recovery code or email read from URL may lock those inputs as disabled. |
| `mailrecoverinfo` | Navigation via footer link. | Switches `type` to `login` in UI only (no dispatch listed for that click beyond user changing mode). |

**Query/body extras:** `appendqueryparams` is appended to `loginuri` and `registeruri` when set (as `?` or `&` depending on whether the path already contains `?`). If unset internally, it becomes `undefined` after the effect.

**Booleans from attributes:** For web components, use string **`yes`** / **`no`** where applicable. `enable_recover_password` is also treated as true when the string is **`true`** or **empty** (`""`) in the effect’s coercion.

**OAuth list:** `oauth2providers` may arrive as a **JSON string** (from attributes); it is parsed and normalized (default scopes for named providers when `url` is empty and `params` exist; `redirect_url` may get a `provider=` query suffix).

**Enter key:** Inside the card, **Enter** triggers the primary action for `login`, `register`, `forgotpassword`, `activate`, and `recover`.

## Custom element

| Item | Value |
| --- | --- |
| Tag name | **`hb-auth`** |
| Svelte option | `customElement="hb-auth"` |
| Dependency | Registers **`hb-auth-social-login-button`** at runtime (`addComponent`) for OAuth tiles. |

## Attributes

All reflected / HTML-facing names use **snake_case**. Complex values (`oauth2providers`, `appendbodyparams`) are **JSON strings** in markup. Booleans follow project convention **`yes`** / **`no`** unless noted.

| Attribute | Purpose |
| --- | --- |
| `id` | Optional host element id. |
| `style` | Optional inline style on the host (standard HTML). |
| `type` | Screen mode: `login` \| `register` \| `activate` \| `recover` \| `forgotpassword` \| `mailrecoverinfo` \| `otp` \| `2fa_code` \| `2fa_config` (see note above on UI coverage). Defaults to `login` when omitted and not taken from URL. |
| `email` | Initial or bound email; may be filled from `recoveryemail=` in the URL. |
| `i18nlang` | Language code (e.g. `en`, `it`) for built-in strings. |
| `sessionkey` | Storage key for persisted auth JSON (default `_auth`). Passed to social login as `auth_cookie_name`. |
| `social_auth_server_url` | Optional base URL forwarded to each **`hb-auth-social-login-button`**. |
| `redirectonlogin` | Optional full navigation after successful stored login; also passed to social buttons. |
| `redirectoncreate` | Optional full navigation after successful registration. |
| `loginuri` | Login API URL for `fetch`. |
| `registeruri` | Register API URL for `fetch`. |
| `activateuri` | Declared on the component type; not used by `component.wc.svelte` logic in this version. |
| `recoveruri` | Declared on the component type; not used by `component.wc.svelte` logic in this version. |
| `requestmethod` | HTTP verb for login/register `fetch` (default `POST`). |
| `appendqueryparams` | String appended to `loginuri` and `registeruri` as query parameters. |
| `appendbodyparams` | JSON object string merged into POST bodies for login/register. |
| `logouri` | Logo image URL for the default header. |
| `oauth2providers` | JSON array of provider objects (`name`, optional `url`, optional `params`) compatible with **`hb-auth-social-login-button`** expectations. |
| `disableregister` | When enabled, hides registration entry points and related UI. |
| `enable_recover_password` | When enabled, shows “forgot password” on the login screen. |
| `passwordpattern` | Optional HTML `pattern` on password inputs. |
| `recoverycode` | Recovery/activation code; may be locked if present in URL. |
| `disablelocal` | When `yes`, hides local email/password block when OAuth providers are present (OAuth-only card). |

## Events

Listen with `addEventListener` or framework bindings; names are **exactly** as below. **`detail`** shapes match `types/webcomponent.type.d.ts` (`Events`).

| Event | `detail` (TypeScript shape) |
| --- | --- |
| `login` | `{ token?: string; email?: string; password?: string; rememberMe?: boolean }` — server response fields when `loginuri` succeeds; otherwise credential payload. |
| `register` | `Record<string, unknown> & { email?: string; password?: string; requestSent?: { email: string; password: string } }` — server JSON when `registeruri` succeeds (component adds `requestSent`); `{ email, password }` when no `registeruri`. |
| `recoverOrActivate` | `{ password: string; recoverycode: string; email: string }` |
| `recoverPassword` | `{ email: string }` |
| `oauthFlowInit` | `{ token?: string; provider: "facebook" \| "google" \| "gitlab" \| "github" \| "authentik"; tmpCode?: string; redirect_uri?: string }` |
| `oauthFlowRedirectStart` | `{ provider: "facebook" \| "google" \| "gitlab" \| "github" \| "authentik" }` |
| `oauthFlowSuccess` | `{ token: string }` |
| `oauthFlowCustom` | `{ provider: "facebook" \| "google" \| "gitlab" \| "github" \| "authentik" }` |

The `provider` literals match `auth-social-login-button`’s `types/webcomponent.type.d.ts` (`IProvider`), which the auth `Events` type reuses for these fields.

## Styling

### CSS custom properties (`:host` / theme)

Documented in `extra/docs.ts` (`styleSetup.vars`). They align with **Bulma 1** tokens; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| Variable | Role |
| --- | --- |
| `--bulma-scheme-main` | Card surface; fallback for OAuth overlay fill when box vars are unavailable. |
| `--bulma-box-background-color` | OAuth overlay fill (matches Bulma `box` when defined on the theme). |
| `--bulma-box-radius` | OAuth overlay corner radius (matches Bulma `box` when defined). |
| `--bulma-scheme-main-ter` | OAuth tile background. |
| `--bulma-border-weak` | Card, tiles, divider. |
| `--bulma-radius` | Social tiles and related rounding. |
| `--bulma-shadow` | Bulma `box` elevation on the card. |
| `--bulma-text` | Default text on `:host`; spinner track tint (`color-mix`). |
| `--bulma-text-strong` | Headings. |
| `--bulma-link` | Ghost links, tile hover ring, focus-visible outlines, spinner accent. |

**Light / dark:** `styles/bulma.scss` installs Bulma light theme on `:host`, dark theme under `prefers-color-scheme: dark`, and overrides when `html` / `body` use `data-theme` / `.theme-light` / `.theme-dark`, or when the host element sets `data-theme`.

### CSS parts

None (`cssParts` is empty in `extra/docs.ts`).

### Slots

| Slot | Description |
| --- | --- |
| `header` | Optional branding above the form; **replaces** the default logo block when content is provided. |

## Typings

Authoring types for consumers and wrappers:

`src/wc/auth/types/webcomponent.type.d.ts`

- **`Component`** — props interface.  
- **`Events`** — custom event name → `detail` map.

## Minimal HTML example

```html
<hb-auth
  type="login"
  i18nlang="en"
  logouri="https://example.com/logo.svg"
  loginuri="https://api.example.com/auth/login"
  sessionkey="_auth"
  enable_recover_password="yes"
  disableregister="no"
></hb-auth>
```

OAuth-only example (attributes as strings):

```html
<hb-auth
  type="login"
  disablelocal="yes"
  oauth2providers='[{"name":"google","url":"https://accounts.google.com/o/oauth2/v2/auth?..."}]'
  sessionkey="_auth"
></hb-auth>
```

---

<a id="wc-auth-social-login-button"></a>

# `hb-auth-social-login-button`

## Summary

Web component: a square, keyboard-accessible control that starts an OAuth-style redirect for a configured provider (built-in SVGs for GitLab, GitHub, Facebook, Google, and Authentik), or emits a custom event when the provider cannot be started from built-in URL rules. On return to the same page, it can detect tokens or codes in `location.href` and exchange them via `simple-serverless-auth-client` when a server URL and cookie name are set.

## What it does

- Renders a clickable `#icon-content` region (`role="button"`, `tabindex="0"`) that runs `socialLogin()` on click or Enter/Space.
- If `provider.url` is set, dispatches `oauthFlowRedirectStart` and sets `location.href` to that URL.
- Else, if `provider.params` includes `client_id` and `redirect_url`, builds a provider-specific authorize URL (see Logic), dispatches `oauthFlowRedirectStart`, then redirects.
- Else logs a warning and dispatches `oauthFlowCustom`.
- On mount, `detectByUri()` inspects `location.href` for provider-specific return patterns; when matched, dispatches `oauthFlowInit` and may call `Authorize.socialLoginOauthAnswer` if both `social_auth_server_url` and `auth_cookie_name` are truthy. On success, either navigates to `redirectonlogin` or dispatches `oauthFlowSuccess` with `{ token }`.

## Appearance

| Aspect | Behavior |
| --- | --- |
| Host | `display: block`, fixed size `2.5rem` × `2.5rem`, padding `0.625rem`. |
| `#icon` | Fills host, transparent background, `position: relative`. |
| `#icon-content` | Absolutely positioned, vertically centered, `cursor: pointer`, transparent background. |
| Default artwork | Inline SVGs with `part="provider_icon"` for known `provider.name` values; unknown name shows literal text `btn` inside the default slot fallback. |
| Focus | `:focus-visible` uses outline and radius from CSS variables (see Styling). |

## Logic

| Step | Condition | Action |
| --- | --- | --- |
| Parse `provider` | `provider` is a string | `JSON.parse` in an `$effect` (errors logged to console). |
| Start login | Missing `provider.name`, or both `provider.url` and `provider.params` missing | `console.error`, return. |
| Prebuilt URL | `provider.url` set | `oauthFlowRedirectStart`, then `location.href = provider.url`. |
| Composed URL | `provider.params.client_id` and `provider.params.redirect_url` | Build URL by `provider.name`, then `oauthFlowRedirectStart` and redirect. |
| No URL | Otherwise | `console.warn`, `oauthFlowCustom`. |
| Google URL | `google` | `https://accounts.google.com/o/oauth2/v2/auth` with `response_type=token`, `scope`, `client_id`, `redirect_uri`, etc. |
| GitHub URL | `github` | `https://github.com/login/oauth/authorize` with `scope`, `client_id`, `redirect_uri`. |
| GitLab URL | `gitlab` | `https://gitlab.com/oauth/authorize` with `response_type=code`, `state`, `scope`, `client_id`, `redirect_uri`. |
| Facebook URL | `facebook` | Same host/path pattern as GitLab in code: `https://gitlab.com/oauth/authorize?...` (same query shape as `gitlab`). |
| Authentik URL | `authentik` | `` `${provider.params.auth_server_url}/application/o/authorize/?` `` plus `scope`, `response_type=code`, `state`, `client_id`, `redirect_uri`. |
| Return: Google | `provider.name === "google"`, URL contains `access_token=` and `google` | Extract token after `access_token=`, call server exchange with `{ provider, token }`, dispatch `oauthFlowInit` with `{ provider, token }`. |
| Return: GitHub | `github`, `code=`, `provider=github` | Code as `token` / `tmpCode`, server exchange, `oauthFlowInit` with `token` and `tmpCode`. |
| Return: Facebook | `facebook`, `code=`, `provider=facebook` | Derives `redirect_uri` by stripping `state` and `code` segments from `location.href`, server exchange, `oauthFlowInit` includes `redirect_uri`. |
| Return: GitLab | `gitlab`, `code=`, `provider=gitlab` | Same style of `redirect_uri` derivation and `oauthFlowInit` as other code-based providers. |
| Return: Authentik | `authentik`, `code=`, `provider=authentik` | Same pattern as GitLab/Facebook for exchange and `oauthFlowInit`. |
| Server exchange | `auth_cookie_name` and `social_auth_server_url` both truthy | `Authorize.socialLoginOauthAnswer(providerPayload, { authUrl: social_auth_server_url, authCookieName: auth_cookie_name })`. |
| After exchange | `redirectonlogin` truthy | `location.href = redirectonlogin`. |
| After exchange | No `redirectonlogin` | `oauthFlowSuccess` with `{ token: resolvedToken }`. |

## Custom element

| Tag |
| --- |
| `hb-auth-social-login-button` |

## Attributes

Web component attributes are strings; complex values use JSON strings. Boolean-like project rules elsewhere do not apply to the props used here beyond normal string attributes.

| Attribute | Role in code |
| --- | --- |
| `id` | Bound to `id`; default `""` in destructuring. |
| `provider` | Provider config; coerced from JSON string if passed as string. Must include `name` and either `url` or `params` (see Logic). |
| `social_auth_server_url` | Passed to `Authorize.socialLoginOauthAnswer` as `authUrl` when set with `auth_cookie_name`. |
| `auth_cookie_name` | Passed as `authCookieName`; default `"hb_session"` in destructuring. |
| `redirectonlogin` | If set after successful exchange, full-page redirect to this URL instead of emitting `oauthFlowSuccess`. |

The TypeScript `Component` type also includes optional `style`, which is not read from `$props()` in `component.wc.svelte` (the `style` binding is commented out).

## Events

All events are `CustomEvent` dispatched on the host element.

| Event | `detail` |
| --- | --- |
| `oauthFlowRedirectStart` | `{ provider: IProvider }` |
| `oauthFlowInit` | `{ token?: string; provider: IProvider; tmpCode?: string; redirect_uri?: string }` |
| `oauthFlowSuccess` | `{ token: string }` |
| `oauthFlowCustom` | `{ provider: IProvider }` |

`IProvider` in types: `"facebook" \| "google" \| "gitlab" \| "github" \| "authentik"`.

## Styling (vars / parts / slots)

### CSS custom properties

| Variable | Used in | Fallback in SCSS |
| --- | --- | --- |
| `--bulma-link` | `#icon-content:focus-visible` outline color | `#485fc7` |
| `--bulma-radius` | `#icon-content:focus-visible` border radius | `0.25rem` |

### `::part`

| Part | Target |
| --- | --- |
| `provider_icon` | Default provider `<svg>` elements (`part="provider_icon"`). |

### Slots

| Slot | Description |
| --- | --- |
| `default` | Replaces default slot content (built-in SVGs or the `btn` fallback). Use for custom artwork or label inside the control. |

## TypeScript

Authoring types live in `types/webcomponent.type.d.ts`:

```ts
type IProvider = "facebook" | "google" | "gitlab" | "github" | "authentik";

export type Component = {
  id?: string;
  style?: string;
  social_auth_server_url?: string;
  auth_cookie_name?: string;
  redirectonlogin?: string;
  provider:
    | {
        url?: string;
        name: IProvider;
        params?: {
          redirect_url: string;
          client_id: string;
          scope: string;
          auth_server_url?: string;
        };
      }
    | undefined;
};

export type Events = {
  oauthFlowSuccess: { token: string };
  oauthFlowInit: {
    token?: string;
    provider: IProvider;
    tmpCode?: string;
    redirect_uri?: string;
  };
  oauthFlowRedirectStart: { provider: IProvider };
  oauthFlowCustom: { provider: IProvider };
};
```

## Minimal example

```html
<hb-auth-social-login-button
  provider='{"name":"google","url":"https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT&redirect_uri=YOUR_REDIRECT&response_type=token&scope=openid%20email"}'
  social_auth_server_url="https://your-auth.example"
  auth_cookie_name="hb_session"
></hb-auth-social-login-button>
```

Using composed params instead of `url` requires at least `name`, `params.client_id`, `params.redirect_url`, and `params.scope` so the component can build the authorize URL before redirecting.

---

<a id="wc-banner"></a>

# `hb-banner` — integrator guide

**Category:** content · **Tags:** content, marketing · **Package:** `@htmlbricks/hb-banner`

## Summary

`hb-banner` is a marketing-style hero strip: optional logo image beside a heading and supporting line. Content is rendered inside the shadow root using Bulma layout (`container`, `columns`), typography (`.title`, `.subtitle`), and visibility helpers so **wide** and **narrow** viewports get different alignment (side-by-side vs stacked, centered).

## Behavior

- **Two responsive variants** are rendered in the markup; Bulma visibility classes show exactly one at a time:
  - **Wide (not “touch”):** fluid container, vertically centered columns, logo column **right-aligned** (`is-one-third-desktop`), text column **two-thirds** (`is-two-thirds-desktop`).
  - **Narrow (“touch” and below desktop):** stacked, **centered** columns (`is-mobile`, `is-multiline`, `has-text-centered`), logo on its own row then title/subtitle.
- **`logouri`:** passed to `<img src="…">` in both layouts. If omitted or empty, the image still renders with an empty `src` (invalid image); omit the attribute only when you accept a broken image or handle it in your host page.
- **`title` / `description`:** rendered as Bulma `.title.is-4` and `.subtitle.is-5` respectively. Either can be omitted; empty headings still produce elements with no visible text.
- **Internationalization:** none in this component (`extra/docs.ts` lists no `i18n` entries).
- **Custom events:** none in `types/webcomponent.type.d.ts` (`Events` is `{}`).

## Layout and visual design

- **Host:** `display: block` (`styles/webcomponent.scss`).
- **Logo box:** `.hb-banner-figure` width `9.375rem` (max `100%`); `.hb-banner-logo` is `width: 100%`, `height: auto`, `max-height: 9.375rem`, `object-fit: contain`.
- **Theme:** Bulma 1.x Sass is forwarded on `:host` (container, columns, title, image, typography/spacing helpers, visibility). Additional Bulma CSS variables from the bundled theme apply; documented public variables are listed below.

## Logic (implementation reference)

1. Props are read from the custom element API (`logouri`, `title`, `description`; plus optional `id` per typings).
2. The same inner structure is duplicated: first block uses `is-hidden-touch`, second uses `is-hidden-desktop`, matching Bulma’s breakpoint helpers to the two layouts described above.

## Custom element tag

```html
<hb-banner …></hb-banner>
```

## Attributes (HTML / reflected props)

Web component attributes are **strings** (snake_case names). Logical optional fields from authoring types:

| Attribute | Role |
|-----------|------|
| `id` | Optional element id (per `Component` typings; standard host attribute). |
| `title` | Main heading text. |
| `description` | Subtitle / supporting line under the heading. |
| `logouri` | URL of the logo image for `<img src>`. |

## Events

No custom events are declared for this component (`Events` is an empty object in `types/webcomponent.type.d.ts`). Use standard DOM events on the host if needed.

## CSS custom properties, parts, and slots

**Documented theme variables** (from `extra/docs.ts` / `styleSetup`):

| Variable | Default | Notes |
|----------|---------|--------|
| `--bulma-primary` | `#00d1b2` | Accent / link tones from Bulma theme setup. |
| `--bulma-text` | `#363636` | Body / general text tone. |
| `--bulma-title-color` | `#363636` | Bulma `.title` color. |

Other `--bulma-*` tokens from the bundled Bulma theme may apply on `:host`; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

- **`::part`:** none (`cssParts` is empty).
- **Slots:** none (`htmlSlots` is empty); all markup is internal to the shadow root.

## TypeScript typings (authoring)

From `types/webcomponent.type.d.ts`:

```ts
export type Component = {
  id?: string;
  title?: string;
  description?: string;
  logouri?: string;
};

export type Events = {};
```

## Minimal HTML

```html
<hb-banner
  title="Welcome"
  description="Build faster with our component library."
  logouri="https://example.com/logo.svg"
></hb-banner>
```

Text-only (no logo URL):

```html
<hb-banner
  title="Welcome"
  description="Short supporting line under the heading."
></hb-banner>
```

Only set `logouri` when you have a valid image URL; the template always includes an `<img>` bound to `logouri`.

---

<a id="wc-calendar-appointments"></a>

# `hb-calendar-appointments`

**Category:** calendar · **Tags:** calendar, appointments

## Summary

A **month agenda** web component: it shows **only events in the visible month**, grouped **by calendar day**, with **per-day headings** and **clickable rows** for each appointment. An optional **header** switches the month and exposes slots for labels and navigation chrome.

## What it does

- Filters `events` to the month implied by `date` (month + year).
- Sorts events by `date` when `events` is parsed from a string.
- Renders one block per day that has at least one event: weekday (long, locale from `navigator.languages[0]` or `en`), day-of-month number, then rows ordered as in the sorted list.
- Each row shows a Bootstrap Icons dot (`bi-dot`), time (`HH:mm`), and `label`; optional per-event `color` overrides the icon color via inline style.
- Month navigation (`disable_header` off) updates `date` and emits **`changeCalendarDate`**.
- Row click emits **`calendarEventClick`** with the event `id`.

The `date-holidays` package is imported and an `IT` instance is constructed in script, but **that object is not used** anywhere in the template or derived data (no holiday-based labels or filtering in the current implementation).

## UI / layout

| Region | Behavior |
|--------|------------|
| **Header** (optional) | Flex row: default title (month name + year via `Intl` + `dayjs`), prev/next controls. Wrapped in `part="calendar-header"`. Inner title span uses `part="calendar-current-time-header"`. Entire header hidden when `disable_header` is enabled (see logic below). |
| **Slots in header** | `header` wraps title + nav; `calendar_month` replaces default month/year text; `header_month_icon_prev` / `header_month_icon_next` replace default buttons (still wired to `changeMonth(-1)` / `changeMonth(1)` via outer `onclick`). |
| **Agenda list** | Under `#appointments_container`: for each day bucket, a bold day line (`events_day`) then `event_row` blocks (focusable `role="button"`). If the month has no events, the list area is empty (no placeholder). |

Icons are loaded for the shadow tree via `styles/webcomponent.scss` (Bootstrap Icons font). A `<svelte:head>` stylesheet link is also present but does not apply inside shadow DOM by itself.

## Logic

- **Visible month:** `month` / `year` derived from `date` (`dayjs`).
- **Filtering:** `monthsEvent` keeps events whose month and year match `date`.
- **Grouping:** `eventsOfThisMonthByDay` buckets by day-of-month (`D`); first occurrence of a day creates the bucket, further events append to that bucket’s array.
- **`disable_header`:** In `$effect`, string values are normalized: `true` / `yes` / `""` (empty) → header hidden; otherwise shown.
- **`events`:** If a string, `JSON.parse`; then sorted ascending by `new Date(a.date)` vs `new Date(b.date)`.
- **`selected`:** If a string, parsed with `dayjs(selected).toDate()` for in-component use. There is a **`selectDay`** helper that sets `selected` and would emit **`changeSelectedDate`**, but **nothing in the default markup calls `selectDay`**, so that event is **not** produced by the shipped UI (only the typed API / future wiring).

## Custom element

```text
hb-calendar-appointments
```

## Attributes / properties

HTML attributes are strings. Align with your bridge; the component’s `$effect` coerces some values.

| Name | Typing (`Component`) | Notes |
|------|----------------------|--------|
| `id` | `string` (optional) | Passed through like other props; host `id` if set as attribute. |
| `date` | `Date` (optional) | Default: start of current month (`dayjs().startOf("month")`). Drives which month’s events are shown. From HTML, use an ISO-like string your runtime parses to `Date` / or set the property in JS. |
| `events` | `IEvent[]` (optional) | JSON array string from attributes; each item: `date`, `label`, `id` required for useful rows; `link`, `icon`, `color` optional (`link` / `icon` are **not read** by the template). |
| `selected` | `Date` (optional) | Parsed from string in `$effect`. No built-in control updates selection or emits `changeSelectedDate`. |
| `disable_header` | `boolean` (optional) | Default `false`. For attributes, this codebase often uses **`yes`** / **`no`**; the effect also treats string `"true"`, `"yes"`, and `""` as hide header. |

### `IEvent` (from typings)

| Field | Type | Used in UI |
|-------|------|------------|
| `date` | `Date` | Filter, sort, time column, weekday/day grouping |
| `label` | `string` | Row text (`aria-label`, visible label) |
| `id` | `string` | `calendarEventClick` payload |
| `link` | optional `string` | No |
| `icon` | optional `string` | No |
| `color` | optional `string` | Icon color (inline `style`) |

## Events (`CustomEvent`)

| Name | `detail` | When emitted (current implementation) |
|------|-----------|----------------------------------------|
| `calendarEventClick` | `{ eventId: string }` | User activates an `.event_row` (click path in template). |
| `changeCalendarDate` | `{ date: Date }` | Prev/next month navigation runs `changeMonth`. |
| `changeSelectedDate` | `{ selectedDate: Date }` | Only if something called `selectDay` — **not wired** in the default Svelte markup. |

## Styling

### CSS custom properties

Documented in `extra/docs.ts`; defaults from code (`styles/webcomponent.scss` + descriptions in docs):

| Variable | Kind | Default / source | Role |
|----------|------|-------------------|------|
| `--hb-calendar-event-button-color` | color | `var(--bulma-link, #485fc7)` on `:host` | Dot icon color when `event.color` is not set. |
| `--bulma-radius` | size | theme / `0.25rem` fallback in SCSS | `border-radius` on `.event_row`. |
| `--bulma-border` | color | theme / `hsl(0deg 0% 86%)` fallback | `outline` on `.event_row:hover`. |

### CSS parts (`::part`)

| Part | Description |
|------|-------------|
| `calendar-header` | Outer header strip (month navigation + title area). |
| `calendar-current-time-header` | Inner title span (month slot + default month/year text). |

## Slots

| Slot | Description |
|------|-------------|
| `header_month_icon_prev` | Replace default “previous month” control (e.g. icon button). |
| `header_month_icon_next` | Replace default “next month” control. |
| `header` | Wraps the whole header row (title + nav); default fills month + controls. |
| `calendar_month` | Replace/wrap visible month/year label in the header row. |

## Typings

Authoring types live in `types/webcomponent.type.d.ts`:

- **`Component`** — `id`, `date`, `events`, `selected`, `disable_header`
- **`IEvent`** — event record shape
- **`Events`** — `calendarEventClick`, `changeCalendarDate`, `changeSelectedDate`

Built outputs (`types/html-elements.d.ts`, `types/svelte-elements.d.ts`) are regenerated with `npm run build:wc`.

## Example HTML

```html
<hb-calendar-appointments
  id="agenda-1"
  disable_header="no"
  date="2026-04-01T00:00:00.000Z"
  events='[
    {"id":"a1","label":"Stand-up","date":"2026-04-17T09:00:00.000Z"},
    {"id":"a2","label":"Review","date":"2026-04-17T15:30:00.000Z","color":"#b86bff"}
  ]'
></hb-calendar-appointments>
```

With hidden header:

```html
<hb-calendar-appointments
  disable_header="yes"
  events='[{"id":"1","label":"Meeting","date":"2026-03-15T10:00:00.000Z"}]'
></hb-calendar-appointments>
```

Listen in JavaScript, for example:

```js
document.querySelector("hb-calendar-appointments")?.addEventListener("calendarEventClick", (e) => {
  console.log(e.detail.eventId);
});
document.querySelector("hb-calendar-appointments")?.addEventListener("changeCalendarDate", (e) => {
  console.log(e.detail.date);
});
```

---

<a id="wc-calendar-events"></a>

# `hb-calendar-events` (calendar-events)

**Category:** calendar · **Tags:** calendar · **Package:** `@htmlbricks/hb-calendar-events`

## Summary

`hb-calendar-events` is a Bulma-styled **month calendar** rendered as a full-width table: a **localized weekday header** (short names, order **Monday → Sunday**), a **variable number of week rows**, and **per-day cells** that can show **muted “padding” days** from the previous/next month. Users can **change the visible month**, **select a day** (current month or padding cells), and **click small event chips** mapped from a JSON `events` list. The component dispatches **`changeCalendarDate`**, **`changeSelectedDate`**, and **`calendarEventClick`** on the host for integration with host apps or sibling calendars.

---

## Behavior

- **Visible month** is anchored by the `date` prop: internally, `month`/`year` are derived from that value. The default is **the first day of the current month** (`dayjs().startOf("month")`).
- **Header** (month title + prev/next controls) is shown unless `disable_header` is enabled. Prev/next call `changeMonth(±1)`, update `date`, and dispatch **`changeCalendarDate`** with `{ date }` (a `Date` for the new month anchor).
- **Weekday labels** use `Intl.DateTimeFormat` with `weekday: "short"` and the browser’s primary language (`navigator.languages[0]`, falling back to `"en"`). **Column order** is fixed in markup as Mon–Sat from `startOf("week") + 1..6` days and **Sunday** in the last column (`startOf("week")`).
- **Grid size**: `rows = ceil((daysInMonth + dayOfWeekOfMonthStart) / 7)` where `dayOfWeek` follows **dayjs**’s `day()` (0 = Sunday … 6 = Saturday). The first row mixes **previous-month** padding cells and **current-month** days; the last row mixes **current-month** days and **next-month** padding cells; middle rows are **only** current month.
- **Day selection**: clicking a `<td>` (cell) sets `selected` to the logical calendar date for that cell and dispatches **`changeSelectedDate`** with `{ selectedDate }`. **Padding cells** select dates in the adjacent month (previous or next), not only the visible month.
- **Events**: optional `events` array is **filtered per month** for the visible month, previous month, and next month so padding cells can still show chips for events that fall on those dates. For each day, chips match events whose **calendar day** (`DD`) equals the cell’s day number in the **month that cell represents** (current, previous, or next). Each chip is a `<button>`; its click handler dispatches **`calendarEventClick`** with `{ eventId }` (the event’s `id` string). Chip **`onclick` does not stop propagation**; hosts that also listen on the cell should account for possible bubbling if they attach listeners outside the shadow root.
- **`IEvent.link` and `IEvent.icon`** exist in typings but are **not used** in the current template (no link or icon rendering).
- **`date-holidays`**: the module is imported and `new Holidays("IT")` is constructed, but **no holiday data is read or displayed** in the current markup—do not rely on Italian public holidays in the UI until that logic is wired.

### `disable_header` coercion

Inside `$effect`, if `disable_header` is a string, it is treated as **true** when (case-insensitive) `"true"` or `"yes"`, or when the string is **empty** `""`; otherwise **false**. From HTML attributes, expect the usual **string** forms your custom-element layer maps to this prop.

### `events` and `selected` coercion

- If `events` is a **string**, it is **`JSON.parse`d** into an array (invalid JSON will throw at runtime).
- If `selected` is a **string**, it is parsed with **`dayjs(selected).toDate()`** (pass a value dayjs understands, e.g. ISO 8601).

---

## Graphics and layout

- **Structure**: Optional header `div` (`part="calendar-header"`) plus a **single `<table class="table is-fullwidth hb-calendar-table">`** with `<thead>` (weekdays) and `<tbody>` (day rows).
- **Table**: `width: 100%`, `height: 100%`, `table-layout: fixed`, `min-height: 34.375rem`, `border-collapse: collapse`. Each **day cell** has `vertical-align: baseline`, a **1px** border using `--hb-calendar-cell-border`, and **row height** split evenly via inline `style="height: {100/rows}%;"` on `<td>`.
- **Header** (when enabled): flex row with spacing (`is-flex`, `is-justify-content-space-between`, etc.), default **month + year** text in a title-sized span, and default **small light buttons** `˂` / `˃` for prev/next unless slots override.
- **Cells**: day number in a `.cell-date` div; **padding** days use **`.cell-date-muted`**. **Today** adds **`.cell-today`** (accent color + bold). **Selected** adds **`.cell-selected`** (background). **Hover** on any `<td>` uses `--hb-calendar-hover`.
- **Event chips**: `.cell-event` buttons, full width of the cell, stacked; default background `--hb-calendar-event-button-color` and text `--bulma-white-bis`. If `event.color` is set, inline `background-color` (and transparent border) override the default chip background.

---

## Logic (implementation notes)

| Concern | Implementation |
|--------|------------------|
| Month navigation | `changeMonth` mutates `date`, then `dispatch("changeCalendarDate", { date })`. |
| Event lists | `$derived` filters: `monthsEvent`, `previousMonthEvents`, `nextMonthEvents` by `M` + `YYYY` of `f.date`. |
| Cell click vs chip | Cell `onclick` → `selectDay(...)`; chip `onclick` → `calendarEventClick({ eventId })`. |
| `id` prop | Normalized in `$effect` to `""` when falsy; used as a conventional element id hook if your layer sets it. |

**Note:** The `$effect` block contains `if (typeof date === "string") dayjs(date).startOf("month").toDate();` **without assigning** the result to `date`. Prefer supplying **`date` as a `Date`** (or rely on your framework’s property deserialization) for a predictable visible month.

---

## Custom element tag

```text
hb-calendar-events
```

---

## Properties / attributes (snake_case)

From **`types/webcomponent.type.d.ts`**. In plain HTML, **attributes are strings**; booleans are typically **`yes` / `no`** in this project’s web-component conventions—**and** this component’s `$effect` accepts string forms for `disable_header` as described above. **`events`** should be a **JSON string** representing an array of event objects.

| Name | Type (authoring) | Role |
|------|------------------|------|
| `id` | `string` (optional) | Optional identifier; coerced to `""` when missing in `$effect`. |
| `date` | `Date` (optional) | Month anchor; default start of **current** month. Prefer a real `Date` instance. |
| `events` | `IEvent[]` or JSON **string** | Events to show as chips; optional. Parsed from string when needed. |
| `selected` | `Date` or parseable **string** (optional) | Highlights the matching day (including padding months). |
| `disable_header` | `boolean` or string (optional) | When true, the entire header block (nav + slots) is omitted. |

### `IEvent` shape (each item in `events`)

| Field | Required | Notes |
|-------|----------|--------|
| `date` | yes | Event occurrence; matched by month/year/day for chip placement. |
| `label` | yes | Chip button text. |
| `id` | yes | Passed back as `eventId` in **`calendarEventClick`**. |
| `link` | no | Typing only; not rendered. |
| `icon` | no | Typing only; not rendered. |
| `color` | no | If set, applied as inline `background-color` on the chip. |

---

## Events (host `CustomEvent`)

All dispatched on the **custom element host** via `new CustomEvent(name, { detail })`, so by default they **do not bubble** and are **not composed** through the shadow boundary—listen on the **`hb-calendar-events` element** itself.

| Event name | `detail` (TypeScript) | When |
|------------|------------------------|------|
| `calendarEventClick` | `{ eventId: string }` | User activates an event chip (`id` → `eventId`). |
| `changeCalendarDate` | `{ date: Date }` | Visible month changes (prev/next). |
| `changeSelectedDate` | `{ selectedDate: Date }` | User selects a day cell (any row, including padding). |

---

## CSS custom properties

Documented in **`extra/docs.ts`** (`styleSetup.vars`); defaults in **`styles/webcomponent.scss`** fall back to Bulma tokens.

| Variable | Role |
|----------|------|
| `--hb-calendar-event-button-color` | Default background for event chips (default: `--bulma-link`). |
| `--hb-calendar-cell-border` | 1px cell border color (default: `--bulma-border`). |
| `--hb-calendar-hover` | `<td>` hover background (default: `--bulma-background-lighter`). |
| `--hb-calendar-selected` | Selected cell background (default: `--bulma-info`). |
| `--hb-calendar-today` | “Today” day number color (default: `--bulma-primary`). |
| `--bulma-radius` | Chip border radius (docs default `0.375rem`; SCSS fallback `0.25rem`). |
| `--bulma-white-bis` | Chip text on default chip background. |
| `--bulma-text-weak` | Muted weekday header tone. |

---

## CSS parts (`::part`)

| Part | Target |
|------|--------|
| `calendar-header` | Outer header strip (month navigation area). |
| `calendar-current-time-header` | Inner span wrapping the default month/year line (and `calendar_month` slot). |
| `cell` | Each day `<td>` in the grid (current, padding, selected, today). |

---

## Slots

Names and descriptions match **`extra/docs.ts`** (`htmlSlots`).

| Slot | Purpose |
|------|---------|
| `header` | Wraps default header content: replace entire header row layout while keeping the outer `calendar-header` part on the parent when `disable_header` is false. |
| `calendar_month` | Replace the default **month name + year** text inside the title span. |
| `header_month_icon_prev` | Replace default **previous month** control (default: small `˂` button). |
| `header_month_icon_next` | Replace default **next month** control (default: small `˃` button). |

---

## Typings

Authoring types for consumers and wrappers live in:

`src/wc/calendar-events/types/webcomponent.type.d.ts`

- **`IEvent`** — one calendar entry (date, label, id, optional link/icon/color).
- **`Component`** — props: `id`, `date`, `events`, `selected`, `disable_header`.
- **`Events`** — maps custom event names to their `detail` shapes: `calendarEventClick`, `changeCalendarDate`, `changeSelectedDate`.

After a full web-component build, generated DOM / Svelte element typings may also list this tag under `types/html-elements.d.ts` and `types/svelte-elements.d.ts` in the package output.

---

## Minimal example

```html
<hb-calendar-events
  events='[{"id":"launch","label":"Launch","date":"2026-03-15T10:00:00.000Z"}]'
></hb-calendar-events>
```

With **explicit month anchor**, **selection**, **no header**, and a listener (vanilla JS):

```html
<hb-calendar-events
  id="cal-1"
  disable_header="yes"
  date="2026-03-01T00:00:00.000Z"
  selected="2026-03-15T00:00:00.000Z"
  events='[{"id":"a","label":"Ship","date":"2026-03-15T12:00:00.000Z","color":"#2574fc"}]'
></hb-calendar-events>

<script>
  const el = document.getElementById("cal-1");
  el.addEventListener("changeSelectedDate", (e) => {
    console.log("selected", e.detail.selectedDate);
  });
  el.addEventListener("calendarEventClick", (e) => {
    console.log("event", e.detail.eventId);
  });
</script>
```

Adjust attribute serialization (`yes`/`no`, JSON escaping) to match how your **custom element** layer maps host attributes to component props.

---

<a id="wc-captcha-google-recaptcha-v2-invisible"></a>

# `hb-captcha-google-recaptcha-v2-invisible`

**Category:** utilities · **Tags:** utilities, security

## Summary

Custom element that loads Google’s reCAPTCHA v2 **explicit** API, mounts an **invisible** widget in the document **light DOM**, runs execution when the SDK is ready, and surfaces the verification token and render lifecycle through **custom events**.

## What it does

- Injects `https://www.google.com/recaptcha/api.js?render=explicit` (async/defer) into `document.head` with a stable script id (`recaptchav2-sdk`), if not already present.
- Ensures a host div exists on `document.body` with id `recaptchav2-element`, `data-size="invisible"`, then calls `grecaptcha.render()` with your site key (`api_key`) and a callback that dispatches the response event.
- After a successful render, dispatches **`googleRecaptchaRendered`**, then from `onMount`’s load path calls **`grecaptcha.execute()`** once the widget is ready.
- Polls (starting after 1s, then 2s retries) until `window.grecaptcha` is available **and** `api_key` is set before rendering.
- On teardown, removes that script node and body div, clears the polling timer, and assigns `window.grecaptcha` to `undefined`.

## How it renders

- **Shadow root:** Forwards Bulma + component SCSS (`styles/bulma.scss`, `styles/webcomponent.scss`); there is **no** visible captcha UI inside the shadow tree.
- **Light DOM / Google widget:** The actual reCAPTCHA widget is attached via the **`recaptchav2-element`** div appended to **`document.body`** (not inside the custom element). Integrators should assume global script injection and a body-level placeholder, not a child of `<hb-captcha-google-recaptcha-v2-invisible>`.

## Logic: site key, token, and `get`

- **Site key:** The `api_key` prop is passed to `grecaptcha.render(..., { sitekey: api_key, callback })`.
- **Token:** When Google invokes the callback, the component dispatches **`googleRecaptchaV2Response`** with **`detail.response`** (string). The implementation also toggles internal state so a later programmatic run can **`reset()`** then **`execute()`** if a response was already produced (`execCaptcha()`).
- **`get`:** An `$effect` runs when reactive dependencies change (including `get`). After the widget has rendered and an internal “ready” latch is set, if **`get !== null`** it calls `execCaptcha()` again (execute, or reset+execute if a response was already received). So **`null`** suppresses that path; **`undefined`** and other non-`null` values still satisfy `get !== null` once the latch is set—align testing with that condition, not only “attribute changed”.

## Attributes / props (`Component`, snake_case)

| Name | Type (authoring) | Role |
| --- | --- | --- |
| `api_key` | `string` (optional) | reCAPTCHA **site key** passed to `grecaptcha.render` as `sitekey`. |
| `get` | optional (typed `any`) | Participates in the post-render `$effect` / `execCaptcha()` flow; see above (`get !== null`). |

HTML consumers should follow your platform’s rules for string attributes (e.g. booleans as `yes` / `no` where applicable).

## Events (`CustomEvent`)

| Event | `detail` (TypeScript) |
| --- | --- |
| `googleRecaptchaV2Response` | `{ response: string }` — verification token from the callback. |
| `googleRecaptchaRendered` | `{ render: true }` — fired once after `grecaptcha.render` completes in this component. |

## Styling

- Bulma theme tokens are forwarded onto **`:host`** even though the captcha UI is not in the shadow tree (`extra/docs.ts`).
- Documented CSS custom properties for catalog/style setup: **`--bulma-link`**, **`--bulma-text`** (colors; defaults follow the forwarded Bulma theme).

## Parts and slots

- **`::part`:** none (`cssParts` is empty).
- **Slots:** none (`htmlSlots` is empty).

## Typings

Authoring types live in `types/webcomponent.type.d.ts`:

- **`Component`:** `api_key?`, `get?`
- **`Events`:** `googleRecaptchaV2Response` → `{ response: string }`; `googleRecaptchaRendered` → `{ render: true }`

## Example

Google’s public **test** site key for v2 (always passes in test mode); replace with your production key.

```html
<hb-captcha-google-recaptcha-v2-invisible api_key="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"></hb-captcha-google-recaptcha-v2-invisible>
```

```js
const el = document.querySelector("hb-captcha-google-recaptcha-v2-invisible");

el.addEventListener("googleRecaptchaRendered", (e) => {
  console.log("rendered", e.detail); // { render: true }
});

el.addEventListener("googleRecaptchaV2Response", (e) => {
  console.log("token", e.detail.response);
});
```

**Integrator note:** CSP, domain allowlisting, and Google’s key/domain configuration apply outside this component.

---

<a id="wc-card-video"></a>

## `hb-card-video` — card-video

**Category:** media | **Tags:** media, video

### Summary

A Bulma **card** with a fixed **16:9** media area at the top, optional title and description, optional timestamp in the footer, and optional “share + action” row when a page URL is supplied. The media area is either an **HTML5 `<video>`** (MP4 `source`) or a **YouTube iframe embed**, depending on `provider`.

### Behavior: native video vs YouTube

- **`provider` is `"youtube"`** — Renders an `<iframe>` inside Bulma’s `image is-16by9` figure. Set **`videosrc`** to the **full embed URL** (for example `https://www.youtube.com/embed/VIDEO_ID`). The iframe uses `allowfullscreen` and a static `title` of `YouTube video` for accessibility.
- **Any other `provider` (including omitted or empty)** — Renders a native **`<video controls>`** with a single **`<source src={videosrc} type="video/mp4">`**, plus an empty captions `<track>` placeholder. The figure uses **`has-background-black`** behind the letterboxed frame.

Browsers that do not support `<video>` see the fallback text inside the video element.

### Layout

Structure follows Bulma’s **card** pattern:

1. **`card-image`** — 16:9 figure (`image is-16by9`) containing either the iframe or the video.
2. **`card-content`** — Optional `title` (`title is-5`), optional description block, then (only if **`pageuri`** is set) Facebook “like” placeholder markup, a Twitter intent link styled as `twitter-share-button`, a line break, and optionally a primary **button** when **`linklabel`** is non-empty.
3. **`card-footer`** — Rendered **only when `time` is set**. It wraps a **named slot** (see Slots) whose default content is a small grey line with a clock icon and the formatted date/time.

The root inner wrapper uses **`hb-card-video-root`** with flex column layout so the card can stretch vertically (`card-content` grows with `flex: 1`).

### Logic

- **Description** — If `description` is longer than **100 characters**, the UI shows only the **first 100 characters** (no ellipsis added in markup).
- **`time` + `dateformat`** — When `time` is provided, the footer shows **Day.js**–formatted text. Default **`dateformat`** is `dddd DD MMMM YYYY HH:mm`. The formatter uses **`navigator.language`** as the Day.js locale, then applies a small transform that **uppercases the first letter of each word** in the formatted string.
- **`pageuri`** — When set, the content area includes **Facebook** (`fb-like` div with `data-href`, etc.) and **Twitter** (`twitter-share-button` / intent URL) **placeholder markup**. Those widgets only become interactive if the **host page** loads the corresponding third-party scripts and initializes them; this component does not bundle those SDKs.
- **`linklabel`** — Defaults to **`read more`**. The primary **`<a class="button is-primary">`** is rendered only when **`pageuri`** is set **and** `linklabel` is truthy with **non-zero** length (so an empty string hides the button even if `pageuri` is set).

### Custom element tag

`hb-card-video`

### Attributes (names as in typings; string values from HTML)

From HTML / `setAttribute`, values are **strings** (dates as parseable strings, for example ISO 8601). Use the same attribute names as in the table below (aligned with this package’s typings and custom-element props).

| Attribute | Required | Description |
| --- | --- | --- |
| `videosrc` | **Yes** | URL for the MP4 `source` (native video) or the full **YouTube embed** URL (`provider="youtube"`). |
| `provider` | No | Set to **`youtube`** for iframe mode; omit or use an empty string for native `<video>`. |
| `title` | No | Card heading (`title is-5`). |
| `description` | No | Body paragraph; truncated to **100** characters in the UI when longer. |
| `pageuri` | No | Canonical URL passed into share placeholders and the optional primary button `href`. |
| `linklabel` | No | Label for the primary button when `pageuri` is set; implementation default **`read more`**. |
| `time` | No | When set, shows the footer with formatted clock line (or slot content). Use a string the runtime can treat as a `Date` (for example `2026-03-01T12:00:00.000Z`). |
| `dateformat` | No | Day.js format string; default **`dddd DD MMMM YYYY HH:mm`**. |
| `id` | No | Passed through for the host / styling hooks. |

### Events

None. The public **`Events`** type is `{}` — no custom `CustomEvent` payloads are declared for this component.

### Styling

The shadow tree bundles **Bulma 1.x** pieces needed for the card, image ratio, typography, button, and content spacing. **Bootstrap Icons** are loaded for the footer clock icon (see `styles/webcomponent.scss`).

#### CSS custom properties

| Variable | Role |
| --- | --- |
| `--bulma-black` | Background behind the 16:9 **native** video frame (`has-background-black`). |
| `--bulma-primary` | Primary **button** (`is-primary`) for the `pageuri` action. |
| `--bulma-text` | Title and body copy. |
| `--bulma-link` | Link-toned controls where Bulma applies link styling. |

Other **`--bulma-*`** variables from the bundled Bulma setup may apply on `:host`; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

#### CSS parts (`::part`)

None exposed (`cssParts` is empty in `extra/docs.ts`).

#### HTML slots

| Slot | Description |
| --- | --- |
| `card-footer` | **Named slot** inside **`card-footer`**, only when **`time`** is set. **Default** slot content is the formatted timestamp row (clock icon + Day.js output). If you supply **slotted** content for `card-footer`, it **replaces** that default (standard slot behavior), while the outer `<footer class="card-footer">` wrapper remains. |

### Typings (`types/webcomponent.type.d.ts`)

```ts
export type Component = {
  id?: string;
  title?: string;
  description?: string;
  videosrc: string;
  provider?: "youtube" | "";
  pageuri?: string;
  linklabel?: string;
  time?: Date;
  dateformat?: string;
};

export type Events = {};
```

Use this shape for TypeScript wrappers, Storybook args, or casts when querying the host element. Remember that **DOM attributes are still strings** at the boundary even when typings use `Date` for `time`.

### Example

**Native MP4**

```html
<hb-card-video
  videosrc="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  title="Product tour"
  description="A short walkthrough of the dashboard."
  time="2026-03-01T12:00:00.000Z"
  dateformat="MMM D, YYYY"
  pageuri="https://example.com/posts/product-tour"
  linklabel="Open post"
></hb-card-video>
```

**YouTube embed**

```html
<hb-card-video
  videosrc="https://www.youtube.com/embed/dQw4w9WgXcQ"
  provider="youtube"
  title="Sample clip"
  description="Embedded iframe player."
  time="2024-01-01T12:00:00.000Z"
></hb-card-video>
```

---

<a id="wc-chartjs"></a>

# hb-chartjs

**Category:** data · **Tags:** data, chart

## Summary

`hb-chartjs` is a shadow-DOM web component that renders **Chart.js** (project dependency **^4.5.1**) on a `<canvas>`. You supply a single JSON **`data`** payload shaped like Chart.js’s constructor config (`type`, `data`, optional `options`). The component **destroys and recreates** the chart when that config changes and when the chart container’s size changes, so sizing stays in sync with the host layout.

## What it does

- Registers a fixed set of Chart.js **controllers**, **scales**, and **plugins** inside the component bundle (line, bar, bubble, doughnut, pie, polar area, radar, scatter; category/linear/log/radial linear/time/time-series scales; decimation, filler, legend, title, tooltip, subtitle).
- Builds a `Chart` instance against the shadow-root canvas, merging your `options` with internal defaults: **`responsive: true`**, **`maintainAspectRatio: true`**, and an **`aspectRatio`** derived from the container’s width/height when both are positive.
- Listens for **canvas clicks** and dispatches a **`chartClick`** custom event whose `detail` is an array of hit points (label/value pairs) from Chart.js’s nearest-element hit test.

## Appearance and layout

- **`:host`** (`styles/webcomponent.scss`): `width: 100%`; `height: auto`.
- **Chart wrapper** (`part="container"`, `#chart-container`): `position: relative`; fills width/height of the host (`width: 100%`, `height: 100%`); `overflow: hidden`; centered with `margin: auto`.
- **Canvas**: `display: block`; `max-width: 100%`; `max-height: 100%`; `width: 100%`; `height: auto`; centered.

Give the host a concrete height (or an `aspect-ratio`) if you need a predictable chart box; the internal layout passes the container box into Chart.js via `aspectRatio` when it can measure both dimensions.

## Logic (Chart.js lifecycle and props)

1. **Mount:** Marks the component as mounted, attaches a **`ResizeObserver`** on `#chart-container`, adds a **debounced** `window` `resize` listener (100 ms), then calls **`updateChart()`**.
2. **`updateChart()`:** If mounted, `data` is set, and the canvas exists in the shadow root, any existing **`Chart` is destroyed**, then a new `Chart` is created with `{ type: data.type, data: data.data, options: { ...data.options, ...defaultOptions } }`.
3. **Reactive `data`:** When `data` is a **string**, it is **`JSON.parse`d** and **`updateChart()`** runs. Treat HTML usage as **always passing a JSON string** on the `data` attribute (see types).
4. **Resize:** ResizeObserver callbacks and debounced window resize both call **`updateChart()`**, which **recreates** the chart (not an in-place `update()`).
5. **Unmount:** Removes listeners, disconnects the observer, and **destroys** the chart instance.

**Click path:** The canvas uses a debounced handler that calls `chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, true)`. For each element, `detail` gets `{ label, value }` from `chart.data.labels[index]` and `chart.data.datasets[datasetIndex].data[index]`. If nothing is hit, **`detail` is an empty array**.

## Custom element

`hb-chartjs`

## Attributes (snake_case; string values in HTML)

| Attribute | Required | Description |
|-----------|----------|-------------|
| `data` | no (recommended) | JSON string or object: Chart.js config with **`type`**, **`data`**, and optional **`options`**. When omitted initially, the chart updates once `data` is set. |
| `id` | no | Passed through as the component’s optional `id` (string). |

Authoritative prop names for integrators: **`src/wc/chartjs/types/webcomponent.type.d.ts`** (`Component`).

## Events

Listen with `addEventListener` (or framework equivalents).

| Event | `event.detail` (TypeScript) |
|-------|-----------------------------|
| `chartClick` | `Array<{ label?: string; value?: any }>` — one entry per hit element; empty array if no point was hit. |

Defined alongside `Component` in **`src/wc/chartjs/types/webcomponent.type.d.ts`** (`Events`).

## CSS custom properties, parts, and slots

**Variables** (theme on `:host` via forwarded Bulma host theme; see `extra/docs.ts` / `styles/bulma.scss`):

| Variable | Role |
|----------|------|
| `--bulma-text` | Text token on `:host` (e.g. captions around the chart). |
| `--bulma-background` | Surface / background token on `:host`. |

Chart colors normally come from the Chart.js JSON (`datasets`, `options`), not from these variables.

**Parts:**

| Part | Role |
|------|------|
| `container` | Wrapper around the `<canvas>` — size the chart from the light DOM with `::part(container)` (width, height, margins, `aspect-ratio`, etc.). |

**Slots:** none (`htmlSlots` is empty in `extra/docs.ts`).

## Typings path

- Consumer-oriented **`Component`** / **`Events`**: `src/wc/chartjs/types/webcomponent.type.d.ts`
- Metadata, CSS tokens, and Storybook-style examples: `src/wc/chartjs/extra/docs.ts`

## Minimal HTML and JavaScript example

```html
<hb-chartjs
  id="sales-chart"
  data='{"type":"bar","data":{"labels":["Q1","Q2"],"datasets":[{"label":"Revenue","data":[12,19]}]},"options":{}}'
></hb-chartjs>

<script>
  const chart = document.getElementById("sales-chart");
  chart.addEventListener("chartClick", (event) => {
    /** @type {{ label?: string; value?: any }[]} */
    const points = event.detail;
    console.log(points);
  });

  chart.setAttribute(
    "data",
    JSON.stringify({
      type: "line",
      data: {
        labels: ["Jan", "Feb"],
        datasets: [{ label: "Series A", data: [1, 2] }],
      },
      options: {},
    })
  );
</script>
```

Use **`setAttribute("data", …)`** with **`JSON.stringify`** when updating from vanilla JS so the internal string handling and chart rebuild stay aligned with attribute-based usage.

---

<a id="wc-checkout"></a>

## `hb-checkout` — checkout

**Category:** commerce · **Tags:** commerce, checkout · **Package:** `@htmlbricks/hb-checkout`

### Summary

`hb-checkout` is a **multi-step commerce checkout surface** hosted as a single custom element. It combines:

- A **delivery / identity** block (read-only profile, a fixed “display-only” profile mode, or an embedded **`hb-form`** for address capture).
- An optional **shipment method** block (built from your `shipments` array into another **`hb-form`** radio group, plus read-only summaries with **edit** affordances).
- A **payment** block that can render **PayPal** (`hb-payment-paypal`) and/or **Google Pay** (`<google-pay-button>` from `@google-pay/button-element`), driven by a **`gateways`** list and a shared **`payment`** descriptor.
- A **terms / notice** region (default copy + slot), and a **post-payment** confirmation region when checkout is marked complete.

Structured inputs (`shipments`, `user`, `gateways`, `payment`) are **JSON-serialized strings** when used from HTML attributes; the component **parses** them in an effect and normalizes defaults (for example **`payment.type`** defaults to **`buy`** if omitted, and **Google Pay** gateways without `cardNetworks` get **`["VISA","MASTERCARD"]`**).

---

### Behavior

#### Data ingestion and normalization

- **`payment`**, **`shipments`**, **`gateways`**, and **`user`** may arrive as **strings** (JSON). The component parses them inside `$effect` and replaces the bound values with parsed objects where applicable.
- If **`payment.type`** is missing after parse, it is set to **`buy`**.
- For each **Google** gateway after parsing **`gateways`**, if **`cardNetworks`** is missing or empty, it is set to **`["VISA","MASTERCARD"]`**.
- **`user`** (when a string) is merged into the internal **`formUserSchema`** field **`value`** entries so the embedded profile form reflects prefilled data (including nested **`row`** / **`columns`** ids when present in the schema).

#### Shipment radio schema

- The shipment **`hb-form`** uses a single required **radio** field with id **`shipmentsolution`**. Its options are rebuilt from **`shipments`**: each option’s **`label`** is **`${label} ${price}${currency}`** and **`value`** is the shipment **`id`**.
- If any shipment has **`selected: true`** or **`standard: true`**, that shipment’s **`id`** becomes the radio **`value`** in the schema (so the UI starts on a preselected / standard line when you pass those flags).

#### Local UI state (not separate custom events)

- **`editUser`** and **`editShipping`** toggle between **summary rows** (with ghost **edit** buttons) and the **`hb-form`** editors.
- Submitting the profile or shipment form sets internal **`formUserSchemaSubmitted`** / **`formShipmentSchemaSubmitted`** to **`yes`** briefly (with a short timeout back to **`no`**) so **`hb-form`** can run its submit path while **`hide_submit="yes"`** hides the form’s own submit control; the visible **Continue** button triggers that pulse.
- **`completed`** is a **host prop** (`"yes"` | `"no"`). When a wallet flow finishes successfully, the code sets **`completed = "yes"`** locally and fires **`paymentCompleted`**. It does **not** currently dispatch a separate **`completed`** custom event (see [Events](#events-customevent)).

#### Wallet rendering rules

- **PayPal**: rendered for every gateway with **`id === "paypal"`**, passing **`paypalid`**, **`total`** as a string from **`payment.total`**, and listening for **`hb-payment-paypal`** **`paymentCompleted`** to call the shared **`paymentCompleted("paypal")`** handler.
- **Google Pay**: rendered only when **`id === "google"`** **and** **`payment.countryCode`** is truthy. The `<google-pay-button>` is configured with **`environment="TEST"`** (hardcoded in the template), **`button-type`** from **`payment.type`** lowercased, **`button-size-mode="fill"`**, and a **`paymentRequest`** built from **`payment`** plus gateway fields (`gatewayId`, `gatewayMerchantId`, optional `merchantId` on the gateway, `merchantName` on `payment`, `allowedCardNetworks` from the gateway). **`loadpaymentdata`** calls **`paymentCompleted("google")`**.
- **`visibleWalletCount`** counts gateways that will actually render (**PayPal** always; **Google** only with **`countryCode`**). The wallet row adds a **single-column** layout class when that count is **`1`**, and **empty** PayPal/Google containers are hidden in the single-column case so a non-rendered Google row does not leave a blank grid cell.

#### Card form

- A **credit-card** path exists in **`libs/formSchemes.ts`** (`formCreditCardSchema`) and helper **`cardChange`**, but the **card UI block is commented out** in `component.wc.svelte`. **Consumers should not expect manual card entry** in the current build—only PayPal and Google Pay surfaces are active.

#### Dependencies loaded at runtime

- **`addComponent`** registers **`@htmlbricks/hb-form`** and **`@htmlbricks/hb-payment-paypal`** at the same version as the builder **`package.json`** so nested custom elements resolve consistently.

---

### UI layout (top to bottom)

1. **`title` slot** — Default content is a centered **`<h1 class="title">`** with text **Checkout** and **`part="title"`** (bottom border uses **`--hb-checkout-border`**).
2. **`#border_top`** — Main “Delivery” column:
   - **Subtitle** “Delivery” with send icon; the heading exposes `part="subtitle"`.
   - **Profile branch** (exact branch is described under [Checkout logic](#checkout-logic--visibility)):
     - Fixed user mode, read-only filled user, or **`hb-form`** + **Continue**.
   - **`#shipment_separator`** (only if **`shipments.length > 0`**): shipment summary or **`hb-form`** + **Continue**, depending on user completeness, edit flags, and whether a **selected/standard** shipment exists.
3. **`.payment_title`** — Separated from the block above by **`--hb-checkout-border`** on the top:
   - If **`completed !== "yes"`**: “Payment Method” subtitle (wallet icon), optional **wallet grid**, then **`.footer_note`** with **`part="payment_terms_note"`** wrapping the **`payment_terms`** slot.
   - If **`completed === "yes"`**: **`payment_completed`** slot (default centered “payment completed”, total line, text **invoice** button—**no click handler** is wired in the default fragment).

Icons use **Bootstrap Icons** classes; **`webcomponent.scss`** imports the icon font into the **shadow tree** (a `<svelte:head>` stylesheet link **does not** apply inside shadow DOM, so the Sass **`@import`** is the reliable source).

---

### Checkout logic & visibility

#### Profile (delivery identity)

| Condition | What you see |
| --- | --- |
| **`user?.fixed`** and **not** **`editUser`** | Minimal **read-only** area: default **`userinfo`** slot content only renders **`fullName`** when set (you can replace the slot for richer fixed-profile layouts). |
| **`user.fullName`** and **`user.addressWithNumber`** and **not** **`editUser`** | Read-only **Name** and **Address** rows (address concatenates street, city, zip, nationality) plus an **edit** button → **`editUser`** mode. |
| Otherwise | **“Address”** subtitle and **`hb-form`** (user schema) + primary **Continue**. On successful submit → **`saveUser`** event, internal **`user`** update, **`editUser`** cleared. |

#### Shipment block

Rendered only when **`shipments`** is a non-empty array.

- If **((full profile)** **`fullName` + `addressWithNumber`)** **or** **`user.fixed`**) **and** **not** **`editUser`**:
  - With **not** **`editShipping`** and a **selected** or **standard** shipment → **read-only** fee + arrival rows (arrival formatted with **dayjs** in this branch) and **edit** → **`editShipping`**.
  - Else → shipment **`hb-form`** + **Continue** → **`saveShipment`** selects that id, clears **`editShipping`**.
- Else if **not** **`editShipping`** and a **selected** or **standard** shipment exists → **read-only** shipment summary **without** requiring the full address branch first (second row shows **“Shipping Time”** and the raw **`arriveDate`** value—**not** passed through **dayjs** in this branch; consider keeping ISO strings for consistent display).

#### Payment block

Wallet buttons render only when **all** of the following hold:

- **`completed !== "yes"`**
- **Not** in **`editUser`** and **not** in **`editShipping`**
- Either **no shipments** **or** there is a **selected** or **standard** shipment (or the list is treated as satisfied via **`find`** on **`shipments`**)

Otherwise the payment wallet area is **suppressed** until the flow is in a “stable” profile + shipment state.

---

### Custom element tag

```html
<hb-checkout></hb-checkout>
```

---

### Attributes (snake_case; HTML values are strings)

From HTML or **`setAttribute`**, pass **objects and arrays as JSON strings**, **numbers as their decimal string**, and **booleans as `yes` / `no`** where applicable (see project conventions).

| Attribute | Required | Description |
| --- | --- | --- |
| **`shipments`** | Yes | JSON array of **`IShipment`** (see [Typings](#typings)). Each item needs at least **`price`**, **`arriveDate`** (ISO string recommended), **`available`**, **`id`**, **`label`**, **`currency`**. Optional **`selected`**, **`standard`** preselect a line. |
| **`gateways`** | Yes | JSON array of **`IGateway`** (see [Typings](#typings)). Supports **`id`**: **`paypal`** \| **`google`**. PayPal rows need a **`paypalid`**. Google rows need gateway configuration fields used in the **`paymentRequest`** (`gatewayId`, `gatewayMerchantId`, optional `merchantId`, etc.—see types). |
| **`payment`** | Yes | JSON **`IPayment`**: **`merchantName`**, **`total`**, **`currencyCode`**, **`countryCode`**, optional **`type`** (**`IPaymentType`**; defaults to **`buy`** if omitted), optional **`shipmentFee`**. **`countryCode`** controls whether **Google Pay** can render. |
| **`user`** | No | JSON **`IUser`** for prefilled / read-only display. Optional **`fixed`**: when **`true`** (boolean inside the JSON object), the component uses the **minimal fixed** profile branch. |
| **`completed`** | No | **`yes`** \| **`no`** (strings). **`no`** default. When **`yes`**, the component shows the **`payment_completed`** slot instead of wallet UI (you can set this from the host after your own backend confirmation, or it flips to **`yes`** after **`paymentCompleted`** handling in code). |
| **`id`** | No | Host element id string. |

---

### Events (`CustomEvent`)

Listeners use the **exact event type string** (camelCase), e.g. **`addEventListener("paymentCompleted", …)`** or in Svelte **`onpaymentCompleted={…}`**.

| Event | `detail` shape | When it fires |
| --- | --- | --- |
| **`saveUser`** | **`IUser`** | After the address **`hb-form`** submits valid data: **`fullName`**, **`addressWithNumber`**, **`city`**, **`zip`**, **`nationality`**. Internal schema values and **`user`** are updated; **`editUser`** becomes false. |
| **`saveShipment`** | **`IShipment`** | After the shipment radio form submits a valid **`shipmentsolution`** id that exists in **`shipments`**: that shipment is marked **`selected: true`**, others **`selected: false`**, and **`editShipping`** becomes false. |
| **`paymentCompleted`** | **`{ total: number; method: string; completed: true }`** | After **PayPal** or **Google Pay** signals success via their respective handlers. **`method`** is **`"paypal"`** or **`"google"`**. The host **`completed`** prop is set to **`yes`** (not a separate DOM event). |

---

### Styling

#### Bulma and host baseline

`styles/bulma.scss` forwards Bulma **1.x** utilities and **`button`**, **`title`**, plus helpers (**spacing**, **flexbox**, **gap**, **typography**), then applies **`light`** theme + **`setup`** on **`:host`**. Prefer **Bulma CSS variables** documented by Bulma and the variables below.

`styles/webcomponent.scss` pulls in **`host-baseline`** shared styles, sets **`:host`** display and typography tokens (**`--bulma-title-size`**, **`--bulma-subtitle-size`**, base **`font-size`** from **`--bulma-size-medium`**), and defines layout for titles, edit buttons, data rows, the wallet grid, and the terms panel.

#### CSS custom properties (documented for consumers)

| Variable | Role |
| --- | --- |
| **`--bulma-primary`** | Primary **Continue** buttons. |
| **`--bulma-link`** | Ghost **terms** link color; feeds **`--edit-color`** when unset. |
| **`--bulma-text`** | Default foreground for body and headings inside the flow. |
| **`--bulma-border`** | Dividers and default border color. |
| **`--bulma-border-weak`** | Softer border on the terms notice panel. |
| **`--bulma-background`** | Mixed into the terms notice **background** (`color-mix` with border). |
| **`--bulma-text-weak`** | Muted default terms copy. |
| **`--bulma-title-size`**, **`--bulma-subtitle-size`**, **`--bulma-subtitle-weight`** | Title / subtitle scale and weight on **` :host`**. |
| **`--bulma-size-medium`**, **`--bulma-size-small`** | Base host font size and smaller terms text. |
| **`--bulma-block-spacing`** | Line height spacing for definition-style rows. |
| **`--bulma-weight-bold`**, **`--bulma-weight-semibold`** | Edit controls and terms link weight. |
| **`--bulma-radius`** | Radius for the terms panel and compact links. |
| **`--edit-color`** | Ghost **edit** buttons (defaults to **`--bulma-link`**). |
| **`--hb-checkout-border`** | Border under the main title and above the payment section (default **`1px solid`** using **`--bulma-border`**). |
| **`--paypal-button-color`** | Reserved for PayPal chrome overrides (optional). |

#### Layout classes

- **`#payment_btn_container.hb-payment-wallet-row`** — two-column **CSS grid** for wallet buttons with gap; collapses to one column with **`.hb-payment-wallet-row--single`** when only one wallet is visible.

---

### CSS parts (`::part`)

Style from outside the shadow tree with **`hb-checkout::part(...)`**.

| Part | Exposed on | Purpose |
| --- | --- | --- |
| **`title`** | Default heading in **`title` slot** | Centered main title; bottom rule uses **`--hb-checkout-border`**. |
| **`subtitle`** | Multiple **`h3` / `h4` subtitles** | Section labels (Delivery, Address, Shipment Service, Payment Method, etc.). |
| **`payment_terms_note`** | Wrapper **`.footer_note`** around **`payment_terms`** | Panel chrome (border, radius, background mix) for legal / policy copy. |

---

### Slots

| Slot | Default | Purpose |
| --- | --- | --- |
| **`title`** | `<h1 part="title">Checkout</h1>` | Replace or wrap the main title region. |
| **`userinfo`** | Conditional name row in **fixed** user mode | Extra markup in the billing / shipping profile area around **`hb-form`** / read-only rows when you need custom layout (especially with **`user.fixed`**). |
| **`payment_terms`** | Short default sentence + ghost **terms and conditions** button (no navigation wired) | Replace with binding legal text, links, or checkboxes. Still wrapped by **`payment_terms_note`** styling unless you override via **`::part`**. |
| **`payment_completed`** | Centered confirmation title, total, **invoice** text button | Replace the post-payment panel when **`completed="yes"`**. |

---

### Typings

Authoring types live in **`types/webcomponent.type.d.ts`**. Shapes below mirror that file.

```ts
export type IShipment = {
  price: number;
  selected?: boolean;
  standard?: boolean;
  arriveDate: Date;
  available: boolean;
  id: string;
  label: string;
  currency: string;
};

export type IUser = {
  fullName: string;
  addressWithNumber?: string;
  city?: string;
  zip?: string;
  nationality?: string;
  fixed?: boolean;
};

export type IGateway = {
  id: "google" | "paypal";
  label: string;
  fixedPrice?: number;
  currency?: "€" | "$";
  percentagePrice?: number;
  paypalid?: string;
  cardNetworks?: string[];
  gatewayId?: string;
  gatewayMerchantId?: string;
  merchantId?: string;
};

export type IPaymentType =
  | "book"
  | "buy"
  | "checkout"
  | "donate"
  | "order"
  | "pay"
  | "plain"
  | "subscribe";

export type IPayment = {
  merchantName: string;
  total: number;
  currencyCode: string;
  countryCode: string;
  type?: IPaymentType;
  shipmentFee?: number;
};

export type Component = {
  id?: string;
  shipments: IShipment[];
  user?: IUser;
  gateways: IGateway[];
  payment: IPayment;
  completed?: "yes" | "no";
};

export type Events = {
  paymentCompleted: { total: number; method: string; completed: true };
  saveUser: IUser;
  saveShipment: IShipment;
};
```

**Runtime vs authoring:** At runtime, JSON parsing yields **plain objects**; date fields may be **strings** until you convert them—**dayjs** is used only in one shipment summary branch.

---

### Examples

#### PayPal only (minimal host page)

```html
<hb-checkout
  completed="no"
  payment='{"merchantName":"Acme","total":"49.99","currencyCode":"EUR","countryCode":"IT","type":"buy"}'
  shipments='[{"id":"std","label":"Standard","price":5,"arriveDate":"2026-04-01T00:00:00.000Z","available":true,"currency":"€","standard":true}]'
  gateways='[{"id":"paypal","label":"PayPal","paypalid":"YOUR_CLIENT_OR_SANDBOX_ID"}]'
></hb-checkout>
```

#### PayPal + Google Pay (Google requires `countryCode` and gateway fields)

```html
<hb-checkout
  completed="no"
  payment='{"merchantName":"Acme","total":"99.00","currencyCode":"EUR","countryCode":"IT","type":"buy"}'
  shipments='[{"id":"express","label":"Express","price":"9","arriveDate":"2026-04-18T12:00:00.000Z","available":true,"currency":"€"}]'
  gateways='[
    {"id":"paypal","label":"PayPal","paypalid":"YOUR_PAYPAL_ID"},
    {
      "id":"google",
      "label":"Google Pay",
      "gatewayId":"example",
      "gatewayMerchantId":"exampleGatewayMerchantId",
      "merchantId":"01234567890123456789",
      "cardNetworks":["VISA","MASTERCARD"]
    }
  ]'
></hb-checkout>
```

#### Prefilled profile (read-only until **edit**)

```html
<hb-checkout
  completed="no"
  payment='{"merchantName":"Acme","total":"45","currencyCode":"EUR","countryCode":"IT"}'
  shipments='[{"id":"s1","label":"Ground","price":10,"arriveDate":"2026-05-01T08:00:00.000Z","available":true,"currency":"€","selected":true}]'
  gateways='[{"id":"paypal","label":"PayPal","paypalid":"sandbox"}]'
  user='{"fullName":"Jane Example","addressWithNumber":"Via Roma 1","city":"Milan","zip":"20100","nationality":"IT"}'
></hb-checkout>
```

#### Completed state (confirmation slot default)

```html
<hb-checkout
  completed="yes"
  payment='{"merchantName":"Acme","total":"45","currencyCode":"EUR","countryCode":"IT"}'
  shipments='[]'
  gateways='[]'
></hb-checkout>
```

#### Listening from JavaScript

```js
const el = document.querySelector("hb-checkout");
el.addEventListener("saveUser", (e) => console.log("user", e.detail));
el.addEventListener("saveShipment", (e) => console.log("shipment", e.detail));
el.addEventListener("paymentCompleted", (e) =>
  console.log(e.detail.method, e.detail.total, e.detail.completed),
);
```

---

### Operational notes

- **Google Pay** is wired with **`environment="TEST"`** in the template; change the source before production if you need **PRODUCTION**.
- Nested **`hb-form`** and **`hb-payment-paypal`** carry **their own** styles and events—consult their READMEs for schema knobs and PayPal props.
- This component declares **no i18n language list** in **`extra/docs.ts`**; UI strings are **English** in the template.
- Bundle size: pulling **`hb-checkout`** also implies **`hb-form`** (large input surface) and **`hb-payment-paypal`** per **`componentSetup.dependencies`** in **`extra/docs.ts`**.

---

<a id="wc-checkout-shopping-cart"></a>

# `hb-checkout-shopping-cart`

**Category:** commerce · **Tags:** commerce, cart

Shopping-cart **order summary** for checkout flows. It aggregates line items from a `payment` payload, shows **subtotal**, **tax**, optional **shipping**, and **grand total**, and renders the line list with **`hb-table`** (pagination disabled). Styling uses **Bulma** tokens on `:host` and **Bootstrap Icons** for the cart title icon.

---

## Custom element

```html
<hb-checkout-shopping-cart></hb-checkout-shopping-cart>
```

Tag name: **`hb-checkout-shopping-cart`**

---

## Runtime dependency

The component registers **`hb-table`** via the shared component loader (`addComponent` for `@htmlbricks/hb-table` at the same version as the bundle). Your page or bundle must load **`hb-table`** so the inner `<hb-table>` custom element is defined. See `extra/docs.ts` → `componentSetup.dependencies` for the full nested dependency graph used by the table package.

---

## Attributes and properties (HTML integrators)

Web component attributes are **strings**. Complex data is passed as **JSON strings**. Booleans use **`yes`** / **`no`** where applicable.

| Name | Required | Description |
| --- | --- | --- |
| `payment` | Yes (for a meaningful cart) | JSON string describing the cart and pricing context. See [Payment payload](#payment-payload) below. |
| `completed` | No | `"yes"` or `"no"` (default **`no`**). Reserved in the public typings; the current implementation does **not** change layout or copy when set to `"yes"`. |
| `id` | No | Present in typings for API consistency; not applied to the visible root in the current markup (inner layout uses a fixed `id="page"`). |

From HTML, set `payment` as a **single-quoted** attribute whose value is JSON, or assign the property from JavaScript (`element.payment = { ... }`).

---

## Payment payload

TypeScript shape (authoring / wrappers):

```ts
interface IShopItem {
  id: string;
  unit?: string;
  name: string;
  unitaryPrice: number;
  taxPercentage: number;
  quantity?: number;
}

interface IShoppingPayment {
  countryCode: "IT" | "US" | "EU";
  currencySymbol?: "€" | "$";
  shipmentFee?: number;
  items: IShopItem[];
}
```

### Field notes

- **`countryCode`**: Drives default **`currencySymbol`** when it is omitted: `IT` and `EU` → `€`, `US` → `$`.
- **`currencySymbol`**: Shown next to monetary amounts. If missing after parse, it is filled from `countryCode` as above.
- **`shipmentFee`**: Optional number added to **grand total**. If absent, the shipping row shows **`-`** (no fee).
- **`items`**: Each line needs **`id`**, **`name`**, **`unitaryPrice`**, and **`taxPercentage`**. **`quantity`** defaults to **1** if omitted. **`unit`** is optional; if missing, the per-line price suffix uses the Italian word **unità** in the table’s price column.

If `payment` is missing or invalid, the component falls back to `{ countryCode: "IT", currencySymbol: "€", items: [] }`.

When `payment` arrives as a **string** (typical from `setAttribute`), it is **`JSON.parse`d**; missing `countryCode` defaults to **`IT`**, and `currencySymbol` is inferred from `countryCode` if still missing.

---

## How totals are calculated

- **Subtotal**: Sum of `unitaryPrice * (quantity ?? 1)` over all items.
- **Tax total**: Sum of per-line `unitaryPrice * (quantity ?? 1) * (taxPercentage / 100)`, then the **sum** is rounded to two decimal places (not each line before summing).
- **Grand total**: `subtotal + taxTotal + (shipmentFee ?? 0)`.

Per-row **line total** in the table uses **tax rounded per line** to two decimals before adding to the pre-tax line amount, which can differ slightly from the aggregate tax total above.

---

## Inner table

`<hb-table>` is used with:

- `disablepagination="yes"`
- `rows` and `headers` passed as **JSON strings** built from `payment.items`.

Column headers (labels are **Italian** in the current source): name (**nome**), quantity (**n.**), line total (**totale**). Summary rows above the table are also Italian (**SubTotale**, **Tasse**, **Spedizione**, **Totale**). There is **no i18n** package hook for this component in `extra/docs.ts`.

---

## CSS custom properties

Theme via [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and the host-specific token below.

| Variable | Type | Default | Role |
| --- | --- | --- | --- |
| `--bulma-text` | color | `#363636` | Title and summary text. |
| `--bulma-border` | color | `#dbdbdb` | Base color for the divider; feeds `--hb-checkout-border` if you do not override it. |
| `--hb-checkout-border` | border shorthand | `1px solid` + `var(--bulma-border, …)` | Top border of the block above the line-items table (`.cart_divider`). |

### `::part` and slots

- **CSS parts:** none (`styleSetup.parts` is empty).
- **Slots:** none.

---

## Events

`Events` in `types/webcomponent.type.d.ts` is **empty**: the component does **not** declare custom events in the public typings.

---

## Examples

### Minimal cart (HTML)

```html
<hb-checkout-shopping-cart
  payment='{"countryCode":"IT","currencySymbol":"€","items":[{"id":"sku-1","name":"T-shirt","unitaryPrice":19.99,"taxPercentage":22,"quantity":2}]}'
  completed="no"
></hb-checkout-shopping-cart>
```

### With shipping and explicit quantity

```html
<hb-checkout-shopping-cart
  payment='{"countryCode":"EU","currencySymbol":"€","shipmentFee":5.5,"items":[{"id":"a","name":"Poster","unitaryPrice":12,"taxPercentage":10,"quantity":2}]}'
></hb-checkout-shopping-cart>
```

### From JavaScript (object property)

```js
const el = document.querySelector("hb-checkout-shopping-cart");
el.payment = {
  countryCode: "US",
  items: [
    { id: "x", name: "Item", unitaryPrice: 10, taxPercentage: 8, quantity: 1 },
  ],
};
```

---

## Summary for integrators

- Pass **`payment`** as JSON (string from HTML, object from JS); align with **`IShoppingPayment`** / **`IShopItem`**.
- Expect **Italian** labels in the UI; no built-in language switching.
- Style with **`--bulma-*`** and **`--hb-checkout-border`** on the host.
- Ensure **`hb-table`** (and its dependencies) are loaded with your bundle.
- **`completed`**: accepted but **no visible “order placed” mode** in the current `component.wc.svelte` implementation—treat as forward-compatible only unless you fork or extend the component.

---

<a id="wc-contact-card"></a>

# `hb-contact-card`

**Category:** content · **Tags:** content, contact

The **contact card** web component renders a Bulma-styled card with a header (avatar or placeholder, name, optional title, collapse control, and optional actions menu) and a body of collapsible sections: company, contact details (email, phone, website), addresses, social links, and notes. Data is supplied as a single JSON object. Optional **Bootstrap Icons** are loaded for affordances.

**Peer dependency (runtime):** when `actions_list` is non-empty, the component registers and embeds **`hb-dropdown-simple`** for the overflow menu.

---

## Custom element

```html
<hb-contact-card></hb-contact-card>
```

---

## HTML attributes (snake_case, strings)

From HTML, complex values must be **JSON strings** on attributes (objects and arrays are not passed as live JS references).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `data` | **Yes** | JSON string of the contact payload (see **Contact data** below). Parsed in the component; invalid JSON is logged and leaves internal data empty until fixed. |
| `id` | No | String passed through on custom event `detail.id`. |
| `style` | No | Inline style string applied on the root card wrapper. |
| `actions_list` | No | JSON string array of menu items for `hb-dropdown-simple` (`key`, `label`, optional `badge`, `group`, `linkHref`). If empty or omitted, the menu is not shown. |
| `i18nlang` | No | Language for built-in labels (e.g. section titles). Supported in authoring metadata: **`en`**, **`it`**. |
| `show_header_collapse_button` | No | **`yes`** (default) or **`no`**: show or hide the header button that collapses the entire card body. |
| `start_collapsed` | No | **`no`** (default) or **`yes`**: render the card body collapsed on first paint. |

**Boolean strings:** only the attributes above use `yes` / `no`. Inside the JSON `data` object, use normal JSON booleans for fields like `clickable` (`true` / `false`).

---

## Contact data (`data` JSON)

The inner object is the **contact record**. Display name is computed as `fullName`, or `firstName` + `lastName`, or the literal `"Unknown Contact"`.

| Field | Type | Description |
|-------|------|-------------|
| `firstName`, `lastName` | string (optional) | Used for display name when `fullName` is absent. |
| `fullName` | string (optional) | Preferred single-field name. |
| `title` | string (optional) | Subtitle under the name in the header. |
| `emails` | array (optional) | Items: `address`, `type` (`home` \| `work` \| `personal` \| `other`), optional `label`. Rendered as `mailto:` links. |
| `phones` | array (optional) | Items: `number`, `type` (`home` \| `work` \| `mobile` \| `fax` \| `other`), optional `label`. Rendered as `tel:` links. |
| `website` | string (optional) | Opened in a new tab (`rel="noopener noreferrer"`). |
| `addresses` | array (optional) | Items: `type` (`home` \| `work` \| `billing` \| `shipping` \| `other`), optional `label`, `street`, `city`, `state`, `postalCode`, `country`. Rows are keyboard-activatable and dispatch `addressClick`. |
| `company`, `department`, `jobTitle` | string (optional) | Shown in the **Company** section if any is present. |
| `notes` | string (optional) | **Notes** section. |
| `avatar` | string (optional) | Image URL; if missing or blank, a gradient placeholder with a person icon is shown. |
| `latitude`, `longitude` | number (optional) | Accepted in typings; not rendered by the current template. |
| `socialMedia` | array (optional) | Items: `platform`, `url`, optional `username`. Platforms include `linkedin`, `twitter`, `facebook`, `instagram`, `github`, `youtube`, `tiktok`, `signal`, `whatsapp`, `telegram`, `other`. If `url` is empty but `username` is set, the component **fills `url`** using known per-platform patterns (e.g. `wa.me` for WhatsApp, `t.me` for Telegram). |
| `clickable` | boolean (optional) | When `true`, a full-card hit target dispatches **`contactClick`** on activation and the card gets hover / focus-visible affordances. |

---

## Actions menu (`actions_list`)

Each entry is passed through to **`hb-dropdown-simple`** as its `list` prop (stringified JSON). Typical fields:

| Field | Description |
|-------|-------------|
| `key` | Stable id for the action (consumer-defined). |
| `label` | Visible label. |
| `badge` | Optional number shown on the item. |
| `group` | Optional grouping label in the menu. |
| `linkHref` | Optional navigation target for the item. |

Selecting items is handled inside **`hb-dropdown-simple`**; this card does not map menu `key` values to `contactEdit` / `contactDelete` automatically—you can coordinate behavior in your host app if needed.

---

## Behavior

- **Card collapse:** The header chevron toggles the whole **body** (max-height / opacity animation). Toggling dispatches **`collapseChange`** with the new collapsed flag.
- **Section collapse:** Company, Contact, Address, Social, and Notes each have their own expand/collapse; these are **internal only** (no custom event).
- **Clickable card:** With `clickable: true`, a transparent button covers the card (`z-index` below header actions). Header controls, section toggles, links, and the actions dropdown sit above it and stop propagation where appropriate.
- **Links:** Email, phone, and website use normal anchors; click handlers still run to emit the corresponding events (in addition to default navigation where applicable).
- **Social:** Icon buttons link to `resolvedSocialMedia` URLs (built from `url` or from `username` + `platform`).

---

## Custom events

Listen with `addEventListener` on the element. All payloads include `id` from the element’s `id` attribute (string).

| Event | `detail` | When it fires |
|-------|----------|----------------|
| `contactClick` | `{ id, contact }` | `contact` is the **parsed inner contact object** (same shape as `data` without the outer attribute wrapper). Fires when `clickable` is true and the card hit area is activated. |
| `phoneClick` | `{ id, phone }` | Phone row link clicked. |
| `emailClick` | `{ id, email }` | Email row link clicked. |
| `websiteClick` | `{ id, website }` | Website link clicked. |
| `socialClick` | `{ id, social }` | Social icon link clicked. |
| `addressClick` | `{ id, address }` | Address row clicked or activated with Enter/Space. |
| `collapseChange` | `{ id, collapsed }` | Card body toggled via the header control. |
| `contactEdit` | `{ id, contact }` | Typings and dispatch helpers exist for an edit flow; the **default markup does not wire** this event to a control. |
| `contactDelete` | `{ id, contact }` | Same as `contactEdit` for delete. |

---

## Styling: CSS custom properties (`:host`)

Bulma-related tokens are documented in `extra/docs.ts` / `styleSetup`. The card and rows use:

| Variable | Role |
|----------|------|
| `--bulma-border` | Card border and separators. |
| `--bulma-radius` | Card corners, rows, section chrome. |
| `--bulma-shadow` | Default elevation. |
| `--bulma-card-background-color` | Card surface (falls back to `--bulma-body-background`). |
| `--bulma-card-shadow` | Stronger shadow on hover when the card is clickable. |
| `--bulma-body-background` | Fallback surface. |
| `--bulma-link` | Links, focus rings, accents. |
| `--bulma-link-invert` | Icon/text on social pills when hovered (solid link background). |
| `--bulma-info` | Second stop for avatar placeholder gradient. |
| `--bulma-scheme-main-bis` | Header strip and default social pill background. |
| `--bulma-white` | Placeholder icon color. |
| `--bulma-text`, `--bulma-text-strong`, `--bulma-text-weak` | Body, headings, muted labels. |
| `--bulma-background-lighter` | Hover on section headers and address rows. |
| `--bulma-radius-rounded` | Circular avatar and social pills. |

Theme setup follows Bulma 1.x CSS variables on `:host` (see project `styles/bulma.scss`).

---

## Styling: `::part` names

These names are published in component metadata (`styleSetup.parts`) for host-level styling:

| Part | Intended target |
|------|-----------------|
| `card` | Root card container. |
| `header` | Header strip (avatar, title, controls). |
| `body` | Scrollable / collapsible main content. |
| `avatar` | Avatar image or placeholder. |
| `actions` | Actions dropdown region. |

---

## Slots

None.

---

## Example: minimal host page

```html
<hb-contact-card
  id="c1"
  i18nlang="en"
  data='{"fullName":"Alex Doe","title":"Engineer","emails":[{"address":"alex@example.com","type":"work"}],"phones":[{"number":"+15551234567","type":"mobile"}],"clickable":true}'
  actions_list='[{"key":"edit","label":"Edit","group":"Actions"},{"key":"email","label":"Send email","group":"Actions"}]'
></hb-contact-card>

<script type="module">
  const el = document.querySelector("hb-contact-card");
  el.addEventListener("contactClick", (e) => {
    console.log("contact", e.detail.contact);
  });
  el.addEventListener("collapseChange", (e) => {
    console.log("collapsed", e.detail.collapsed);
  });
</script>
```

---

## Example: collapsed by default, no menu

```html
<hb-contact-card
  id="c2"
  start_collapsed="yes"
  show_header_collapse_button="yes"
  data='{"fullName":"Jamie Lee","company":"Acme","jobTitle":"CEO","clickable":false}'
></hb-contact-card>
```

---

## TypeScript authoring

For props and nested types (`PhoneNumber`, `EmailAddress`, `Address`, `SocialMedia`, `ContactData`, `Component`, `Events`), see `types/webcomponent.type.d.ts` next to this package.

---

## Build note (monorepo)

From the `builder` folder, a single package build:

```bash
npm run build:wc -- contact-card
```

---

<a id="wc-contact-item"></a>

## `hb-contact-item` — contact-item

**Category:** content | **Tags:** content, contact

### What it does

Renders a single contact row: one of **phone**, **postal address**, **email**, **website**, or **social** profile. Each variant shows a Bootstrap Icon (when enabled) and optional label text. Behavior is controlled by a JSON **`config`** object: show or hide the icon column, show or hide text, and choose whether a click should **`window.open`** a URL or dispatch a **`contactClick`** custom event with the active mode and payload.

Evaluation order is fixed: if multiple inputs are present, **phone** wins, then **social**, **address**, **email**, and **site**. In practice you should pass only one contact type per instance.

### Custom element

`hb-contact-item`

### Attributes / props (snake_case)

Web component attributes are **strings**. Objects must be passed as **JSON** (single-line attribute values are typical). Property names on the element use **snake_case**; keys **inside** the JSON payloads follow the TypeScript shapes below (camelCase where noted).

| Attribute | Type | Description |
| --- | --- | --- |
| `id` | string (optional) | Host-controlled identifier; forwarded like any HTML id when set by the runtime. |
| `style` | string (optional) | Inline style string on the host element. |
| `phone` | JSON (optional) | **`IPhone`**: `number` (required), `callOnClick` (optional boolean). The value is included in `contactClick` detail; the component does not open `tel:` URLs by itself—handle dialing or navigation in your listener if needed. |
| `address` | JSON (optional) | **`IAddress`**: `address` (required), `shortAddress` (optional), `mapUri` (optional), `latLang` (optional number array `[lat, lng]`). If `mapUri` or `latLang` is set, a click opens a map URL (`latLang` becomes a Google Maps query URL; if both `latLang` and `mapUri` exist, **`mapUri` wins**). |
| `email` | JSON (optional) | **`IEmail`**: `address` (required), `mailLink` (optional boolean). If `mailLink` is true, a click calls `window.open` with **`address` as the URL string**—use a full URL (for example `mailto:support@example.com`) so the browser can open the mail client. |
| `site` | JSON (optional) | **`ISite`**: `uri` (optional), `label` (optional), `open` (optional boolean). Display text is `label` if set, otherwise `uri`. If `open` is true and `uri` is set, a click opens `uri` in a new window. |
| `social` | JSON (optional) | **`ISocial`**: `name` (required), `label` (optional), `pageUri` (optional). The icon class is `bi bi-{name}` (Bootstrap Icons suffix only, without the `bi-` prefix). If `pageUri` is set, a click opens that URL. |
| `config` | JSON (optional) | **`IConfig`**: `icon` (optional object with optional `fill` boolean), `text` (optional boolean), `dispatcher` (optional boolean). Defaults applied in logic: `icon.fill` defaults to false if `icon` exists; `text` defaults to true unless explicitly `false`; `dispatcher` defaults to true unless explicitly `false`. The **`fill`** flag is accepted for forward compatibility but **does not** currently switch icon classes in markup. |

### Click behavior (summary)

1. If the row is configured to open a window (**social** `pageUri`, **address** map link, **email** with `mailLink`, or **site** with `open` + `uri`), the first click runs **`window.open`** with the resolved URL. **`contactClick` is not fired** in that branch.
2. Otherwise, if **`config.dispatcher`** is not disabled, a click dispatches **`contactClick`** with detail **`{ action, options }`**:
   - **`action`**: `"phone"` | `"social"` | `"address"` | `"email"` | `"site"` (whichever branch is active).
   - **`options`**: the parsed object for that branch (same shape as the corresponding prop).

The root span gets a clickable affordance (Bulma `is-clickable`) when a window URL will be used on click.

### CSS custom properties

None are declared for this component in `styleSetup`. The shadow tree loads Bulma helpers for layout and Bootstrap Icons for glyphs. Surrounding page typography and colors affect only the host, not inner defaults beyond Bulma utilities.

### CSS parts (`::part`)

| Part | Description |
| --- | --- |
| `iconcell` | Wrapper around the leading icon column (when `config.icon` is truthy). |
| `prop` | Text span for the visible value (when `config.text` is enabled). |

### HTML slots

None.

### Events (`CustomEvent`)

| Event | `detail` | When it fires |
| --- | --- | --- |
| **`contactClick`** | `{ action: string; options: ... }` — `options` matches the active **`phone` / `address` / `email` / `site` / `social`** object | On click when **no** `window.open` URL is resolved for that row and **`config.dispatcher`** is true (default). |

### Usage notes

- Prefer **one** of `phone`, `address`, `email`, `site`, or `social` per element to avoid surprising precedence.
- Escape JSON correctly in HTML attributes when needed (for example use single quotes around the attribute value and double quotes inside the JSON string).
- Social **`name`** must be a valid Bootstrap Icons glyph name fragment (for example `facebook`, `linkedin`, `twitter-x`).
- Bootstrap Icons CSS is injected from the component (`svelte:head` / shadow stylesheet); no extra icon font link is required on the host page for the shadow tree.
- There is **no** built-in i18n catalog for this package; labels come entirely from your JSON props.

### Minimal HTML examples

**Email row (click opens mail client via `mailto:`):**

```html
<hb-contact-item
  email='{"address":"mailto:hello@example.com","mailLink":true}'
  config='{"text":true,"dispatcher":true}'
></hb-contact-item>
```

**Email row (no automatic navigation—handle `contactClick`):**

```html
<hb-contact-item
  email='{"address":"hello@example.com"}'
  config='{"dispatcher":true}'
></hb-contact-item>
```

**Phone row (integrator handles `contactClick`):**

```html
<hb-contact-item
  phone='{"number":"+39 02 1234567","callOnClick":true}'
></hb-contact-item>
```

**Address with external map link:**

```html
<hb-contact-item
  address='{"address":"Via Example 42, Milan, Italy","shortAddress":"Milan HQ","mapUri":"https://www.openstreetmap.org/search?query=Milan"}'
></hb-contact-item>
```

**Website with new-tab behavior:**

```html
<hb-contact-item
  site='{"label":"Company site","uri":"https://example.com","open":true}'
  config='{"text":true,"icon":{"fill":false}}'
></hb-contact-item>
```

### Listening from JavaScript

```javascript
const el = document.querySelector("hb-contact-item");
el.addEventListener("contactClick", (e) => {
  const { action, options } = e.detail;
  // action: "phone" | "address" | "email" | "site" | "social"
  // options: payload object for that action
});
```

---

<a id="wc-cookie-law-banner"></a>

# `hb-cookie-law-banner`

**Category:** layout · **Tags:** layout, compliance

Fixed bottom cookie/consent bar built with Bulma (`notification is-dark`, columns, buttons). It stays visible until the visitor chooses **Accept** or (optionally) **Decline**, then hides and persists the choice in **`localStorage`** so repeat visits do not show the bar again.

---

## Behavior and logic

1. **Persistence** — On **Accept** or **Decline**, the component writes `localStorage.setItem("cookielaw", "yes" | "no")` (`"yes"` for accept, `"no"` for decline). On load, if that key exists, the banner is treated as dismissed and does not show the visible state.

2. **Visibility** — The outer bar uses CSS (see [Styling](#styling)) to sit fixed at the bottom of the viewport. When a stored choice exists, the `is-visible` class is omitted so the bar animates to hidden (opacity / translate). When there is no stored choice, `is-visible` is applied so the bar appears (with a short transition delay).

3. **`allowdecline`** — Only the string `"yes"` enables the decline button. Any other value (including missing) is normalized to **`"no"`** in an effect: accept-only flow with a single primary button.

4. **`cookielawuri4more`** — The “Learn more” anchor is rendered when this attribute is **not** the literal string `"no"`. If the URL is empty, the link still appears and points to `https://wikipedia.org/wiki/HTTP_cookie`. Set `cookielawuri4more="no"` to hide the link entirely.

5. **`capabilities`** — If passed as a JSON string (typical from HTML attributes), the component parses it in an effect. The current markup **does not render** group/item toggles or lists from `capabilities`; the shape exists in typings and metadata for integrators who may read the prop or extend the host app. The visible UI remains title, body, link, and buttons.

6. **Internationalization** — `i18nlang` selects entries from the built-in dictionary (`"en"`, `"it"`). Strings include title, body, “Learn more”, “Accept”, and “Decline”. Override copy with slots when you need full control. (As shipped, the default body string under `en` may still be Italian; use the `text` slot for guaranteed locale-specific copy.)

7. **Host logging** — Choosing an option logs an informational message to the console (`accept cookie law`).

---

## Layout

- **Shell:** Bulma `notification is-dark is-radiusless mb-0` with `role="alert"`.
- **Grid:** `columns is-mobile is-vcentered is-multiline` — on mobile the text column is full width (`is-12-mobile`); from tablet up, copy uses `is-8-tablet` and actions `is-4-tablet`.
- **Actions column:** Flex row with wrap, end justification, centered cross-axis, and gap between the optional link and buttons.
- **Fixed positioning:** Implemented in `styles/webcomponent.scss` (bottom edge, full width, `z-index: 40`), with horizontal column margins reset so the bar does not widen the document scroll width.

---

## Custom element

`hb-cookie-law-banner`

---

## Attributes / properties (snake_case, string-oriented)

Web component attributes are strings. Booleans use **`yes`** / **`no`**. Complex values such as `capabilities` are JSON strings.

| Attribute / prop | Type (logical) | Description |
| --- | --- | --- |
| `id` | `string` (optional) | Optional element id (standard host usage). |
| `style` | `string` (optional) | Inline styles on the host (standard). |
| `allowdecline` | `"yes"` \| `"no"` (optional, default `"no"`) | `"yes"` shows **Decline**; anything else behaves as `"no"`. |
| `i18nlang` | `string` (optional) | Language key, e.g. `en`, `it`. |
| `cookielawuri4more` | `string` (optional) | Policy URL for “Learn more”. Omit or leave empty for the Wikipedia cookie fallback. Use the literal **`no`** to hide the link. |
| `capabilities` | `ICapabilities` as JSON string (optional) | Structured groups/items for cookie metadata; parsed when provided as a string. Not used by the default UI (see [Behavior](#behavior-and-logic)). |

---

## Events

| Event | `detail` | When |
| --- | --- | --- |
| `acceptCookieLaw` | `{ accepted: boolean }` | After the user clicks **Accept** (`true`) or **Decline** (`false`, only if decline is enabled). The choice is written to `localStorage` before the event is dispatched. |

---

## Slots

| Slot | Purpose |
| --- | --- |
| `title` | Replaces the default translated heading inside `title is-5`. |
| `text` | Replaces the default translated paragraph inside `content is-size-6`. |

Default slot content comes from the i18n dictionary for the active `i18nlang`.

---

## Styling

### Bulma and host tokens

The component forwards Bulma Sass with theme variables on `:host`. Documented CSS custom properties (from metadata / `extra/docs.ts`):

| Variable | Role |
| --- | --- |
| `--bulma-scheme-main` | Scheme / surface for the dark notification bar. |
| `--bulma-text` | Primary text (title and body). |
| `--bulma-link` | “Learn more” link color on the inverted bar. |
| `--bulma-border` | Useful if you extend the layout with extra separators. |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for the full token set available where Bulma is forwarded.

### Component SCSS

`styles/webcomponent.scss` pins the bar to the bottom (`position: fixed`, `inset-inline: 0`, `bottom: 0`), hides it by default (`opacity: 0`, `visibility: hidden`, `translateY(100%)`), and reveals it with `.hb-cookie-law.is-visible` including transition timing.

### CSS parts

None (`::part` not exposed).

---

## TypeScript typings

Authoring types live in `types/webcomponent.type.d.ts`.

**`ICapabilities`** — Cookie catalog shape:

- **`groups`:** `{ _id, label, type }[]`
- **`items`:** entries with `groupId`, `title`, `scope`, `_id`, `cookieName`, and optional fields such as `description`, `descriptionUrl`, `thirdCompanyName`, `isMandatory`, `isTechnical`, `isMarketing`, `isThirdPartyOwned`, `isSessionCookie`, `cookieExpirationInSeconds`

**`Component`** (props):

- `id?`, `style?`, `allowdecline?`, `i18nlang?`, `cookielawuri4more?`, `capabilities?` (typed as `ICapabilities` in TS; from HTML pass JSON string)

**`Events`:**

- `acceptCookieLaw`: `{ accepted: boolean }`

---

## Example

Minimal host page with English copy, optional policy URL, and decline enabled:

```html
<hb-cookie-law-banner
  allowdecline="yes"
  i18nlang="en"
  cookielawuri4more="https://example.com/cookies"
></hb-cookie-law-banner>

<script>
  document.querySelector("hb-cookie-law-banner").addEventListener("acceptCookieLaw", (e) => {
    console.log(e.detail.accepted); // true = accept, false = decline
  });
</script>
```

Accept-only (no decline button):

```html
<hb-cookie-law-banner allowdecline="no"></hb-cookie-law-banner>
```

Hide “Learn more”:

```html
<hb-cookie-law-banner cookielawuri4more="no"></hb-cookie-law-banner>
```

Custom title and body via slots (shadow DOM slot API depends on how you mount the bundle; with declarative shadow DOM or frameworks, use the same slot names):

```html
<hb-cookie-law-banner allowdecline="yes">
  <span slot="title">Cookies on this site</span>
  <span slot="text">We use cookies to run the site and measure usage. See our policy for details.</span>
</hb-cookie-law-banner>
```

Optional `capabilities` JSON string (for hosts that store or forward metadata; default UI unchanged):

```html
<hb-cookie-law-banner
  allowdecline="yes"
  capabilities='{"groups":[{"_id":"essential","label":"Essential","type":"technical"}],"items":[{"groupId":"essential","title":"Session","scope":"strictly_necessary","_id":"1","cookieName":"sid","isTechnical":true,"isMandatory":true}]}'
></hb-cookie-law-banner>
```

---

<a id="wc-dashboard-card1"></a>

# `hb-dashboard-card1`

**Package:** `@htmlbricks/hb-dashboard-card1`  
**Category:** data · **Tags:** data, dashboard

## Purpose

`hb-dashboard-card1` is a compact **Bulma card** layout for dashboards and admin shells. You configure the **header** (optional Bootstrap Icon, title, optional right-aligned tag) and **body** (optional removal of inner padding) via **JSON strings** on attributes. Use the **`header_content`** and **`content`** slots when you need full control over markup inside the card header and body regions.

The component applies Bulma’s **light theme** tokens on `:host` and loads **Bootstrap Icons** for the optional header icon (`bi-*` classes).

---

## Registration and usage

Load your built script (or bundle) so the custom element is defined, then use the tag in HTML or in any framework that renders real DOM. Attribute names are **`snake_case`**. From HTML, **structured props are JSON strings** (see [Attributes](#attributes)).

---

## Custom element

| Tag |
| --- |
| `hb-dashboard-card1` |

---

## Attributes

All reflected / declarative inputs are **strings** at the DOM boundary (HTML `attributes` or `setAttribute`). Pass **objects as JSON**.

| Attribute | Required | Description |
| --- | --- | --- |
| `id` | No | Passed through for identification / testing (host element `id`). |
| `header` | No | JSON object describing the header row. If omitted, invalid, or empty after parse, the default header chrome still renders but **title and icon stay empty** unless you use the `header_content` slot. |
| `body` | No | JSON object for body options. If omitted or invalid, defaults to `{ "noborder": false }`. |

### `header` JSON shape

| Field | Type | Description |
| --- | --- | --- |
| `label` | string | Main title text (truncates with ellipsis when space is tight). |
| `icon` | string | Optional Bootstrap Icons **glyph name only** (no `bi-` prefix), e.g. `"graph-up"`, `"bell"`. |
| `badge` | string | Optional text for a Bulma **`tag`** on the right (status, delta, count). |

Example:

```json
{ "label": "Revenue", "icon": "graph-up", "badge": "+12%" }
```

### `body` JSON shape

| Field | Type | Description |
| --- | --- | --- |
| `noborder` | boolean | When `true`, the main **`card-content`** area uses **no padding** (`p-0`), useful for charts or tables that should bleed to the card edges. |

Example:

```json
{ "noborder": true }
```

### Parsing behavior

- **`header` and `body`** accept either a **string** (parsed with `JSON.parse`) or an object when set from JavaScript on the custom element’s property layer.
- **Invalid JSON** or non-object values for `body` fall back to **`{ noborder: false }`**.
- **Invalid or empty `header` JSON** yields **no parsed header**; the component does not throw.

### `style` (TypeScript only)

The authoring type `Component` includes an optional **`style`** field for wrappers and tooling. The current Svelte implementation does **not** destructure or apply that prop inside the component; use normal **host** `style` / CSS if you need outer layout tweaks.

---

## Slots

Shadow slots let you replace the default fragments Bulma would otherwise own.

| Slot | Default | When you use it |
| --- | --- | --- |
| `header_content` | Built-in **`card-header-title`** row: optional icon, `label`, optional `badge`. | Supplying **children with `slot="header_content"` replaces the entire default title row**. Use this for fully custom header markup. |
| `content` | Empty (reserved for your body markup). | Main **`card-content`** area: charts, tables, metrics, etc. |

---

## Custom events

None. The `Events` contract for this component is **empty**—there are no documented `CustomEvent` outputs from the host.

---

## Styling

### Bulma CSS variables (`:host`)

Set **`--bulma-*`** variables on the host or an ancestor; they flow into the shadow tree with this component’s Bulma setup.

| Variable | Role |
| --- | --- |
| `--bulma-card-background-color` | Card panel background. |
| `--bulma-card-shadow` | Elevation / shadow around the card. |
| `--bulma-card-header-color` | Header title text color. |
| `--bulma-text-strong` | Strong text context used with header styling. |

Defaults and longer descriptions are listed in `extra/docs.ts` (`styleSetup.vars`) for catalog and design tools.

### CSS parts

| Part | Element | Use |
| --- | --- | --- |
| `card` | Root `.card` container | Host-level tweaks to the whole widget (radius, border, background overrides complementary to variables). |

### Icons

- **In-component icon:** `header.icon` uses classes `bi bi-{icon}`; names match [Bootstrap Icons](https://icons.getbootstrap.com/).
- **Stylesheets:** Icons are loaded from a CDN in the component (`svelte:head`) and the shared stylesheet also references Bootstrap Icons for consistent font loading—integrators typically do not need to add another icon link unless they strip shadow assets.

---

## Markup structure (Bulma)

The implementation uses Bulma **`card`**, **`card-header`**, **`card-header-title`**, **`card-content`**, and **`tag`**. Header layout uses flex helpers so long **labels truncate** and the **badge stays right-aligned**.

---

## Internationalization

No built-in i18n strings; all visible text comes from your **`header`** JSON, slot content, or slotted children.

---

## Examples

### Minimal header with icon and badge

```html
<hb-dashboard-card1
  header='{"label":"Revenue","icon":"graph-up","badge":"+12%"}'
></hb-dashboard-card1>
```

### Title only, padded body

```html
<hb-dashboard-card1 header='{"label":"Active users"}'></hb-dashboard-card1>
```

### Tight body for a chart or table

```html
<hb-dashboard-card1
  header='{"label":"Metrics","icon":"graph-up","badge":"live"}'
  body='{"noborder":true}'
>
  <div slot="content" style="height: 200px; background: linear-gradient(...)"></div>
</hb-dashboard-card1>
```

### Custom header via slot

Replacing the default row removes the built-in icon/label/badge layout; style slotted nodes from **your** stylesheet (shadow Bulma rules do not target slotted light DOM unless the component exposes `::slotted(...)` rules).

```html
<hb-dashboard-card1 body='{"noborder":true}'>
  <div slot="header_content" style="display:flex;align-items:center;gap:0.5rem;width:100%;">
    <strong>Custom</strong>
    <span style="margin-left:auto;">meta</span>
  </div>
  <p slot="content">Body from slot.</p>
</hb-dashboard-card1>
```

### JavaScript: set properties

```js
const el = document.querySelector("hb-dashboard-card1");
el.header = { label: "Alerts", icon: "bell", badge: "3" };
el.body = { noborder: false };
```

---

## Files in this package (for contributors)

| File | Role |
| --- | --- |
| `component.wc.svelte` | Custom element implementation |
| `types/webcomponent.type.d.ts` | `Component` / `Events` authoring types |
| `extra/docs.ts` | Catalog metadata, CSS vars, parts, slots, Storybook args |
| `styles/bulma.scss` | Bulma modules + theme on `:host` |
| `styles/webcomponent.scss` | Layout tweaks, icon import |

---

<a id="wc-dashboard-counter-lines"></a>

# `hb-dashboard-counter-lines`

**Category:** data · **Tags:** data, dashboard

## Overview

`hb-dashboard-counter-lines` renders a vertical list of dashboard-style metrics. Each row can show an optional Bootstrap Icon, a primary label, and a compact counter value in a Bulma light, rounded tag. Rows may be static or clickable depending on an optional `link` object.

The custom element is registered as **`hb-dashboard-counter-lines`**.

## When to use it

Use this component when you need a simple, scannable summary of several numeric or textual KPIs (counts, rates, totals) with optional navigation or a custom click action per row.

## Data model (`lines`)

The `lines` prop is a **`Line[]`** array. From HTML, pass it as a **JSON string** (serialized array).

Each line object supports:

| Field | Type | Description |
| --- | --- | --- |
| `text` | string | Primary label shown on the row (truncated with an ellipsis when space is tight). |
| `value` | string | Counter text rendered inside the Bulma `tag is-light is-rounded` pill. |
| `icon` | string (optional) | Bootstrap Icons **glyph name only** (without the `bi-` prefix), e.g. `"people"`, `"clock"`. Renders as `class="bi bi-{icon}"`. |
| `link` | object (optional) | When present and valid, the whole row becomes clickable. See [Link behavior](#link-behavior) below. |
| `index` | number (optional) | Reserved in the type for ordering or metadata; the current markup does not display it. |

### Parsing rules

- If `lines` is omitted, `null`, or an empty string after trim, nothing is rendered.
- If `lines` is a string, the component **`JSON.parse`s** it once (synchronously) before paint. Invalid JSON logs an error to the console and results in an empty list.
- Non-array JSON or an empty array also yields no visible content.

## Attributes and properties (snake_case)

Web component attributes are strings. Complex values must be JSON strings.

| Name | Required | Description |
| --- | --- | --- |
| `id` | No | Optional; present on the authoring `Component` type for integrations that set it from JavaScript. |
| `style` | No | Optional on the authoring `Component` type; the implementation does not forward a `style` prop into the shadow tree—use Bulma variables, `::part`, or the native HTML `style` attribute on the host element if you need one-off host styling. |
| `lines` | No | JSON string representing `Line[]`, e.g. `[{"text":"Users","value":"42","icon":"people"}]`. |

## Link behavior

When `link` is set and includes both `type` and `uri`, the row is interactive (`cursor`/click styling from your theme and `::part(item)`).

| `link.type` | Effect |
| --- | --- |
| `"tab"` | Opens `link.uri` in a new browser tab (`window.open(..., "_blank")`). |
| `"page"` | Navigates the current window to `link.uri` (`window.location.href`). |
| `"event"` | Dispatches a **`counterClick`** custom event with **`detail.key`** equal to `link.uri` (use `uri` as your logical event key). |

If `link` is missing, incomplete, or `uri` is empty, the row is **not** clickable.

## Events

| Event name | `detail` shape | When it fires |
| --- | --- | --- |
| `counterClick` | `{ key: string }` | When the user activates a row whose `link.type` is `"event"`. `key` is the line’s `link.uri`. |

Listen with `addEventListener("counterClick", ...)` or the equivalent in your framework.

## Layout and styling

- **Host:** `:host` is a column flex container with spacing between rows, full width, `min-width: 0` so flex children can shrink inside tight layouts.
- **Row:** Label cluster (icon + text) grows/shrinks; the value tag stays on the right and does not shrink.
- **Typography:** Long labels use a single line with ellipsis overflow.

Theming uses **Bulma CSS variables** inside the shadow root. You can also target exposed **CSS parts** from the light DOM.

### CSS custom properties (`--bulma-*`)

| Variable | Default | Role |
| --- | --- | --- |
| `--bulma-primary` | `#00d1b2` | Primary brand color (e.g. tag accents when using Bulma color modifiers). |
| `--bulma-text` | `#363636` | Main label text color. |
| `--bulma-text-weak` | `#7a7a7a` | Muted supporting text where applicable. |
| `--bulma-radius` | `0.375rem` | Base radius for components that depend on Bulma radius tokens. |
| `--bulma-radius-rounded` | `9999px` | Pill radius for `.tag.is-rounded`. |

Set `--bulma-*` on `html`, `body`, or on the element / its ancestors so values inherit into the shadow tree. See the [Bulma CSS variables documentation](https://bulma.io/documentation/features/css-variables/).

### CSS parts (`::part(...)`)

| Part | Description |
| --- | --- |
| `item` | Outer row container; meaningful for hover, cursor, padding, or background when the row has a `link`. |
| `icon` | Bootstrap Icon `<i>` element for that row. |
| `text` | Primary label `<span>`. |
| `value` | Bulma tag showing the counter value (`tag is-light is-rounded`). |

### Slots

None.

## Dependencies (runtime)

- **Bootstrap Icons** font (loaded for icon classes used in the shadow tree).
- **Bulma** (Sass modules forwarded in the component styles).

## Examples

### Several static metrics

```html
<hb-dashboard-counter-lines
  lines='[{"text":"Active users","value":"1.2k","icon":"people"},{"text":"Errors","value":"3","icon":"exclamation-triangle"},{"text":"Latency p95","value":"120ms","icon":"speedometer2"}]'
></hb-dashboard-counter-lines>
```

### Row that opens a URL in a new tab

```html
<hb-dashboard-counter-lines
  lines='[{"text":"Open reports","value":"12","icon":"graph-up-arrow","link":{"type":"tab","uri":"https://example.com/reports"}}]'
></hb-dashboard-counter-lines>
```

### Row that emits `counterClick`

```html
<hb-dashboard-counter-lines
  id="kpi-block"
  lines='[{"text":"Drill down","value":"99+","link":{"type":"event","uri":"open-details"}}]'
></hb-dashboard-counter-lines>

<script>
  document.getElementById("kpi-block").addEventListener("counterClick", (e) => {
    console.log("key:", e.detail.key); // "open-details"
  });
</script>
```

## Internationalization

No built-in i18n strings; pass already-translated `text` and `value` values in `lines`.

---

<a id="wc-dashboard-indicator"></a>

# `hb-dashboard-indicator` — Dashboard indicator

**Package folder:** `dashboard-indicator`  
**Category:** data  
**Tags:** data, dashboard  
**NPM scope (distribution):** `@htmlbricks/hb-dashboard-indicator`

---

## Summary

`hb-dashboard-indicator` is a compact KPI tile for dashboards: a colored **header** shows a large **metric** (`number`), an optional **caption** (`text`), and a **Bootstrap Icon**; an optional **footer** appears when `link_label` is set and acts as a single **click target** that emits a **`dashboardIndicatorClick`** custom event carrying the element’s `id`.

Accent color (“karma”) is either a **Bulma semantic palette** on `:host` (`success`, `danger`, `warning`, `primary`, `secondary`, `info`) or **`none`** to drive header background and footer text color from **`--hb--dashboard-indicator-background`** on the host.

---

## Behavior

### Rendering

- **`number`:** Shown as the main value (Bulma `title is-3`, with tightened line-height for a compact header). In markup, pass it as a **string** (web component attributes are always strings); the component coerces to a number internally (default **`0`** when omitted).
- **`text`:** Optional subtitle under the number (Bulma `subtitle is-7`). Empty string hides the subtitle line container content but the layout still reserves flow; empty caption is visually minimal.
- **`icon`:** Bootstrap Icons **glyph name only** (no `bi-` prefix). Rendered as `<i class="bi bi-{icon}">`. Implementation default if unset is **`hypnotize`**; the authoring type marks `icon` as optional to mirror HTML usage.
- **`link_label`:** If **non-empty**, a **footer** strip is rendered with the label and a right-pointing circle arrow icon. If empty or unset, **no footer** and **no** `dashboardIndicatorClick` from this control (there is nothing to click).
- **`id`:** Optional string forwarded in **`dashboardIndicatorClick`** detail as `{ id: string }`. If unset, detail is `{ id: "" }`.

### `karma` (accent)

Allowed values: `success`, `danger`, `warning`, `primary`, `secondary`, `info`, `none`.

- **`secondary`:** Not a separate Bulma palette in this component; it is **mapped to Bulma `dark`** HSL tokens (`--bulma-dark-h` / `-s` / `-l` with defaults), same as the SCSS comment in the repo.
- **Semantic values (`success` … `info` except `none`):** Header **background** and footer **text color** use `hsl(var(--bulma-<token>-h), …)` resolved from `:host` (Bulma 1.x CSS variables). **`--hb--dashboard-indicator-background` is ignored** for these values.
- **`none`:** Header background and footer link/icon color use **`var(--hb--dashboard-indicator-background, <fallback>)`** where fallback is Bulma **success** semantic HSL. Set the variable on **`hb-dashboard-indicator`** (inline `style`, stylesheet, or design token layer).

If `karma` is **not** one of the allowed literals (e.g. typo), the component **normalizes to `success`** (`karmaResolved`).

### Interaction

- **Click:** Only the **footer** (`link_label` visible) calls `dispatch("dashboardIndicatorClick", { id })`. The header is **not** clickable for this event.
- **Implementation note:** The footer uses a click handler without an equivalent keyboard handler in source; for strict accessibility, integrators may wrap the tile or add host-level keyboard handling if needed.

### Assets and shadow DOM

- **Bootstrap Icons** are loaded for the shadow tree (stylesheet link in the component head **and** a font import in `styles/webcomponent.scss` in the build). Integrators should use **valid icon names** compatible with the Bootstrap Icons version pinned by the bundle.
- **Bulma** card, title, and helper utilities are included via `styles/bulma.scss` with `:host` variable forwarding so **`--bulma-*`** tokens on the custom element affect the tile.

---

## Layout

- **Root:** Bulma **`card`** with class `hb-dashboard-indicator` and `data-karma={resolved}` for styling hooks.
- **Header (`card-header`):** Single **`card-header-title`** row (`hb-dashboard-indicator-header-inner`) as a flex row: **`nowrap`**, **space-between**, vertically centered.
  - **Left:** Icon wrapper (`hb-dashboard-indicator-icon`) — large icon (~`4rem`), white glyph on semantic header background.
  - **Right:** Flex column aligned **end** (`has-text-right`): **number** then **text**, white text, `min-width: 0` to avoid overflow blowout in tight grids.
- **Header padding:** Vertical `0.5rem`; horizontal **`var(--bulma-block-spacing, 1rem)`**. Inner title padding is reset to avoid double padding with Bulma’s defaults.
- **Footer (`card-footer`):** Only when `link_label` is set. Flex **space-between**, **clickable** (`is-clickable`), vertical padding `py-2`. Surface color from **`--bulma-background`** with scheme fallbacks; top border matches surface for a flush edge. First/last children get horizontal margins from **`--bulma-block-spacing`**.

---

## Logic (implementation reference)

| Concern | Rule |
| --- | --- |
| Valid `karma` set | `success`, `danger`, `warning`, `primary`, `secondary`, `info`, `none` |
| Invalid `karma` | Treated as **`success`** |
| Event emission | `new CustomEvent("dashboardIndicatorClick", { detail: { id } })` on `$host()` |
| Footer visibility | `{#if link_label}` — truthy string required |

The `style` field exists on the **TypeScript `Component`** type for authoring, but the Svelte props block does **not** destructure `style`; host styling still works by setting the standard **`style`** attribute on `<hb-dashboard-indicator>` (especially for CSS variables).

---

## Custom element tag

```html
<hb-dashboard-indicator …></hb-dashboard-indicator>
```

---

## Attributes / properties (snake_case, string values in HTML)

Web components reflect **string** attributes. Use JSON only where a future API requires it (not used here). Numbers and enums are plain strings.

| Attribute | Required | Default (behavior) | Description |
| --- | --- | --- | --- |
| `id` | No | `""` | Passed through in `dashboardIndicatorClick` detail. |
| `number` | No | `0` | Main KPI; pass as string, e.g. `"42"`. |
| `text` | No | `""` | Caption under the number. |
| `icon` | No | `"hypnotize"` in implementation | Bootstrap Icons name without `bi-` prefix. |
| `link_label` | No | `""` | If set, shows footer and enables click + event. |
| `karma` | No | `"success"` | Accent mode; see Behavior. |
| `style` | No | — | Standard HTML: useful to set `--hb--dashboard-indicator-background` and other variables on the host. |

---

## Events

Listen with `addEventListener` or framework equivalents.

| Event name | `event.detail` | When it fires |
| --- | --- | --- |
| **`dashboardIndicatorClick`** | `{ id: string }` | User **clicks** the **footer** while `link_label` is set. `id` matches the element’s `id` attribute / property (empty string if unset). |

TypeScript (consumer-side):

```ts
el.addEventListener("dashboardIndicatorClick", (e: CustomEvent<{ id: string }>) => {
  console.log(e.detail.id);
});
```

---

## CSS custom properties

Documented in `extra/docs.ts` / `styleSetup.vars`. Set on **`hb-dashboard-indicator`** (`:host`) unless your bundler wraps the element.

| Variable | Role |
| --- | --- |
| **`--hb--dashboard-indicator-background`** | When **`karma="none"`**: header **background** and footer **foreground** (label + arrow). If unset, falls back to Bulma **success** semantic color. **Ignored** when `karma` is any Bulma semantic value. |
| **`--bulma-block-spacing`** | Horizontal padding in header and footer row (default `1rem` in CSS). |
| **`--bulma-background`** | Footer strip **surface** color; when unset, SCSS uses scheme HSL fallbacks (`--bulma-scheme-h`, `--bulma-scheme-s`, `--bulma-background-l`, etc.). |

Additional **`--bulma-*-h` / `-s` / `-l`** (and defaults `--bulma-hb-def-*`) on `:host` control semantic karma colors per [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

### `::part` names

**None** — `cssParts` is empty.

### Slots

**None** — `htmlSlots` is empty; all content is props-driven.

---

## Typings (`types/webcomponent.type.d.ts`)

### `Karma`

```ts
export type Karma =
  | "success"
  | "danger"
  | "warning"
  | "primary"
  | "secondary"
  | "info"
  /** Use `--hb--dashboard-indicator-background` on the host instead of Bulma `karma` tokens. */
  | "none";
```

### `Component`

```ts
export type Component = {
  id?: string;
  style?: string;
  number?: number;
  text?: string;
  icon?: string;
  link_label?: string;
  karma?: Karma;
};
```

### `Events`

```ts
export type Events = {
  dashboardIndicatorClick: { id: string };
};
```

Generated builds also emit **`types/html-elements.d.ts`** and **`types/svelte-elements.d.ts`** under the package output for DOM and Svelte typings.

---

## Examples

### Minimal tile with footer and Bulma success accent

```html
<hb-dashboard-indicator
  id="kpi-sales"
  number="128"
  text="Orders today"
  icon="cart-check"
  link_label="View details"
  karma="success"
></hb-dashboard-indicator>
```

### Danger accent (queue / errors)

```html
<hb-dashboard-indicator
  id="kpi-failures"
  number="3"
  text="Failed jobs"
  icon="exclamation-triangle"
  link_label="Open queue"
  karma="danger"
></hb-dashboard-indicator>
```

### Custom brand color (`karma="none"`)

```html
<hb-dashboard-indicator
  id="kpi-custom"
  karma="none"
  style="--hb--dashboard-indicator-background: #6366f1"
  number="12"
  text="Custom metric"
  icon="stars"
  link_label="Details"
></hb-dashboard-indicator>
```

### No footer (display-only KPI)

```html
<hb-dashboard-indicator
  number="11"
  text="Active sessions"
  icon="window"
  karma="primary"
></hb-dashboard-indicator>
```

### Vanilla JS: handle navigation by `id`

```html
<hb-dashboard-indicator
  id="orders"
  number="54"
  text="Open orders"
  icon="inbox"
  link_label="Go to inbox"
  karma="warning"
></hb-dashboard-indicator>
<script>
  document.querySelector("hb-dashboard-indicator").addEventListener("dashboardIndicatorClick", (e) => {
    if (e.detail.id === "orders") {
      location.assign("/inbox");
    }
  });
</script>
```

---

## i18n

None configured in `extra/docs.ts` (`i18nLanguages` is empty). All visible strings are supplied by the host via attributes.

---

<a id="wc-dialog"></a>

# `hb-dialog`

Modal dialog built with Bulma’s `modal` and `modal-card` patterns inside a shadow root. Visibility is controlled with the string attribute **`show`**: `"yes"` opens the overlay, `"no"` keeps it unmounted.

While open, the component sets **`document.body.style.overflow` to `"hidden"`** so the page behind the modal does not scroll, and restores the previous value when the dialog closes.

**Category:** overlays · **Tags:** overlays, modal  
**Package:** `@htmlbricks/hb-dialog` · **Custom element:** `<hb-dialog>`

---

## Behavior

- **Open / close:** The modal tree is rendered only when `show="yes"`. Closing sets `show` to `"no"` (or removes the open state) depending on the interaction path.
- **Transitions:** The outer overlay uses a **fade** transition; the card uses **fly** (vertical motion). When the enter transition finishes, **`modalShow`** fires with `show: true`; when the exit transition finishes, **`modalShow`** fires with `show: false`.
- **Backdrop:** With the default **`backdrop`** behavior, a click on the dimmed **`.modal-background`** (the target that received the click) closes the dialog. To turn that off, set the **`backdrop` property** to `false` in JavaScript (a string attribute such as `backdrop="no"` is still truthy and will not disable dismissal—see the attributes section below). When **`backdrop`** is false, the background layer is still rendered but clicks on it do not dismiss because the handler checks **`backdrop`** before closing.
- **Keyboard:** When **`keyboard`** is enabled (default), an **`Escape`** listener is attached to `document` while the modal is open; **`Escape`** closes the dialog. When `keyboard` is disabled, that listener is not registered.
- **Header delete control:** The small Bulma **delete** control in the default header only sets `show` to `"no"`; it does **not** dispatch **`modalConfirm`**.

---

## Attributes and properties

Names are **snake_case**. For **`disable_confirm`**, **`hide_close`**, and **`hide_confirm`**, you can use the string values **`"yes"`** / **`"no"`** (or **`"true"`** / **`"false"`**) and they are normalized inside the component. **`show`** uses **`"yes"`** / **`"no"`** only. For **`backdrop`** and **`keyboard`**, use real booleans on the element **property** when you need `false` (see the note after the table).

| Name | Type | Description |
| --- | --- | --- |
| `id` | string (optional) | Identifier echoed on custom events as `detail.id`. |
| `show` | `"yes"` \| `"no"` (optional, default `"no"`) | Whether the modal is visible. |
| `title` | string (optional) | Default title when the `title` slot is not used. |
| `content` | string (optional) | Default body text when the `body-content` slot is not used. |
| `closelabel` | string (optional) | Default label for the footer close button (slot `close-button-label` overrides). |
| `confirmlabel` | string (optional) | Default label for the confirm button (slot `confirm-button-label` overrides). |
| `backdrop` | boolean (optional, default `true`) | If truthy, a backdrop click can dismiss the modal (when the click target is the background layer). |
| `keyboard` | boolean (optional, default `true`) | If truthy, `Escape` closes the modal while it is open. |
| `describedby` | string (optional) | Value for `aria-describedby` on the dialog root. |
| `labelledby` | string (optional) | Value for `aria-labelledby` on the dialog root. |
| `confirm_btn_class` | string (optional, default `"primary"`) | Visual style token for the confirm button; mapped to Bulma `is-*` modifiers (see below). |
| `close_btn_class` | string (optional, default `"secondary"`) | Visual style token for the footer close button; same mapping rules. |
| `disable_confirm` | boolean (optional) | If truthy, the confirm button is `disabled`. String `"yes"` / `"true"` is coerced to a disabled button. |
| `hide_close` | boolean (optional) | If truthy, hides the header delete control and the footer close button. Coerced from `"yes"` / `"true"`. |
| `hide_confirm` | boolean (optional) | If truthy, hides the confirm button. Coerced from `"yes"` / `"true"`. |
| `style` | string (optional) | Declared in typings; the Svelte implementation does not currently destructure or forward this as a component prop. |

For **`backdrop`** and **`keyboard`**, the implementation uses ordinary JavaScript truthiness (unlike `disable_confirm` / `hide_close` / `hide_confirm`, which normalize `"yes"` / `"true"`). A string attribute like `keyboard="no"` is still **truthy** in JS. For a reliable “off”, set the **property** to `false` in JavaScript (or omit the attribute and use defaults as appropriate).

### Button style tokens (`confirm_btn_class` / `close_btn_class`)

Values are compared case-insensitively, optional `btn-` prefix is stripped, then mapped to Bulma modifiers:

| Token | Bulma modifier |
| --- | --- |
| `primary` | `is-primary` |
| `secondary` | `is-light` |
| `success` | `is-success` |
| `danger` | `is-danger` |
| `warning` | `is-warning` |
| `info` | `is-info` |
| `light` | `is-light` |
| `dark` | `is-dark` |
| `link` | `is-ghost` |
| *(unknown)* | `is-light` |

---

## Slots

| Slot | Purpose |
| --- | --- |
| `header` | Replaces the entire modal head (title row and optional delete control). The default head includes the `title` slot and the delete button when `hide_close` is not set. |
| `title` | Heading inside the default header when `header` is not overridden. |
| `body-content` | Main body; falls back to the `content` prop (or a short default string). |
| `modal-footer` | Wraps the footer region; default content wraps the `footer` slot. |
| `footer` | Default footer: a `.buttons` group with close and confirm actions (respecting `hide_close` / `hide_confirm`). |
| `close-button-label` | Inner text of the default footer close button (defaults to `closelabel` or `"Close"`). |
| `confirm-button-label` | Inner text of the default confirm button (defaults to `confirmlabel` or `"Save changes"`). |

---

## Custom events

Listen with `addEventListener` or framework bindings on the host element.

| Event | `detail` | When it fires |
| --- | --- | --- |
| **`modalShow`** | `{ id: string; show: boolean }` | After the overlay **enter** transition ends (`show: true`) or after the **leave** transition ends (`show: false`). |
| **`modalConfirm`** | `{ id: string; confirm: boolean }` | **`confirm: true`** when the user activates the confirm button (then the dialog closes). **`confirm: false`** when the user activates the default footer **close** button (then the dialog closes). Not emitted for header delete, backdrop dismiss, or `Escape`. |

---

## Styling: CSS custom properties

These are documented for `:host` (Bulma modal layout tokens; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

| Variable | Role |
| --- | --- |
| `--hb-modal-max-width` | Maximum width of the modal card (`.modal-card`). Default `40rem`. |
| `--bulma-modal-card-head-padding` | Padding for `.modal-card-head`. Default `1rem 1.25rem`. |
| `--bulma-modal-card-body-padding` | Padding for `.modal-card-body`. Default `1rem 1.25rem`. |
| `--bulma-modal-card-title-size` | Title font size. Default `var(--bulma-size-5)`. |
| `--bulma-modal-card-title-line-height` | Title line height. Default `1.25`. |
| `--bulma-modal-card-spacing` | Spacing between card regions. Default `1.5rem`. |

Additional **`--bulma-*`** variables from Bulma’s setup may apply on `:host` depending on your theme.

### CSS parts

| Part | Target | Use |
| --- | --- | --- |
| `modal-dialog` | `.modal-card` | Size, border radius, shadows, or layout of the dialog surface. |

---

## HTML example

```html
<hb-dialog
  id="confirm-delete"
  show="yes"
  title="Delete item?"
  content="This cannot be undone."
  confirmlabel="Delete"
  closelabel="Cancel"
></hb-dialog>
```

```js
const el = document.querySelector("hb-dialog");
el.addEventListener("modalConfirm", (e) => {
  if (e.detail.confirm) {
    // user chose Delete
  } else {
    // user chose Cancel
  }
});
el.addEventListener("modalShow", (e) => {
  console.log("dialog visibility:", e.detail.show);
});
```

To open or close from script, set the reflected property or attribute, for example `el.setAttribute("show", "yes")` / `el.setAttribute("show", "no")`.

---

## Accessibility notes

- The root node uses **`role="dialog"`**, **`aria-modal="true"`**, and optional **`aria-labelledby`** / **`aria-describedby`** when you pass `labelledby` and `describedby` (typically IDs of elements in the light DOM).
- Focus management is not fully delegated outside the shadow tree; validate focus order and screen reader behavior in your host page when using slots or dynamic content.

---

## Internationalization

No built-in `i18n` language list is shipped for this component; use props and slots for copy.

---

<a id="wc-dialog-loader"></a>

## `hb-dialog-loader` — dialog-loader

**Category:** overlays | **Tags:** overlays, modal, loading

### What it does

`hb-dialog-loader` is a thin wrapper around **`hb-dialog`**. While **`percentage`** is non-zero, the inner dialog is shown (`show="yes"` on `hb-dialog`); when **`percentage`** is zero, the dialog is hidden (`show="no"`). The body contains a Bulma **`<progress class="progress is-primary">`** whose value matches the rounded percentage (0–100) and a centered label with the same value. The component re-dispatches **`modalShow`** from the inner dialog so the host can track open/close state. The dialog title comes from the **`title`** prop or the **`title`** slot.

**Dependency:** `hb-dialog` (registered by the component build).

### Custom element

`hb-dialog-loader`

### Attributes / props (snake_case)

Web component attributes are strings. Use numeric strings for **`percentage`** (for example `"45"`). Booleans elsewhere in the stack follow your host conventions; this component only drives inner visibility from **`percentage`**.

| Property | Type | Notes |
| --- | --- | --- |
| `id` | string (optional) | Present in the public `Component` typings; not forwarded to `hb-dialog` in the current implementation. |
| `style` | string (optional) | Present in typings; the implementation does not currently wire this to the inner dialog (see `component.wc.svelte`). |
| `title` | string (optional) | Default dialog title when the `title` slot is empty. Default text in code: `loading`. |
| `percentage` | number / numeric string (required) | Progress 0–100. **`0`** (or equivalent) hides the loader; any other value shows the dialog and fills the bar. String values are parsed with `parseInt` in an effect. |

### CSS custom properties

Documented in `extra/docs.ts` **`styleSetup.vars`** (Bulma on `:host`), plus spacing used in `styles/webcomponent.scss`:

| Variable | Description |
| --- | --- |
| `--bulma-primary` | Progress value color (Bar uses `is-primary`). |
| `--bulma-border-weak` | Progress track background. |
| `--bulma-text` | Percentage label text color. |
| `--bulma-block-spacing` | Used for spacing above the percentage label (`margin-top: calc(var(--bulma-block-spacing) * 0.5)`). |

Additional **`--bulma-*`** tokens from Bulma’s progress and typography setup apply on `:host` (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

### CSS parts

| Part | Description |
| --- | --- |
| *(none)* | No `::part` surface is exposed here; the inner `hb-dialog` owns its own parts (for example `modal-dialog` on `hb-dialog`). |

### Slots

| Slot | Description |
| --- | --- |
| `title` | Content for the dialog title area (inner `hb-dialog` title slot). If empty, the **`title`** prop is used. |

### Events (`CustomEvent` names)

- **`modalShow`** — `{ id: string; show: boolean }` — Forwarded from the inner **`hb-dialog`** when it opens or closes.

### Usage notes

- Drive **`percentage`** from your async work (upload, batch job, etc.): keep it updated until the task finishes, then set it to **`0`** to dismiss.
- The progress element exposes **`aria-valuenow`**, **`aria-valuemin`**, **`aria-valuemax`**, and a **`title`** attribute with the rounded percentage for a simple native tooltip.
- Overlay behavior (backdrop, body scroll, focus) follows **`hb-dialog`**; test in real browsers if you nest or stack modals.
- There is no i18n block in `extra/docs.ts` for this package.

### Minimal HTML example

```html
<hb-dialog-loader title="Uploading…" percentage="45"></hb-dialog-loader>
```

### Closing the loader

```html
<hb-dialog-loader percentage="0"></hb-dialog-loader>
```

When **`percentage`** is zero, the wrapper sets the inner dialog’s **`show`** to **`no`**, so the loader is not visible.

---

<a id="wc-dialogform"></a>

# `hb-dialogform`

**Category:** forms · **Tags:** forms, overlays

Bulma-style modal that composes **`hb-dialog`** with an embedded **`hb-form`**. You drive the fields with the same **`schema`** contract as `hb-form` (JSON array of field definitions). The inner form’s submit control is hidden; the dialog’s **Confirm** acts as submit when the form is valid.

---

## What it does

- Opens and closes like a normal `hb-dialog` via the `show` attribute (`"yes"` / `"no"`).
- Renders `hb-form` with `hide_submit="yes"` so only the dialog footer buttons apply.
- Forwards **`updateForm`** events from the inner form with the same **`detail`** shape as `hb-form`’s **`update`** event (live values and **`_valid`**).
- **Confirm:** if the last form update was valid, dispatches **`modalFormConfirm`** with a payload derived from that update (field values plus metadata; see [Events](#events)).
- **Cancel / invalid confirm:** dispatches **`modalFormCancel`** with `{ id, error? }` (see [Events](#events)).
- Re-dispatches **`modalShow`** when the inner dialog reports open/close (`{ id, show }`).
- **Confirm** is disabled on the inner dialog until the form reports valid (`disable_confirm`).

Nested **`hb-dialog`** and **`hb-form`** are registered at runtime (`addComponent`) using the same package version as the host.

---

## Custom element

`hb-dialogform`

---

## Attributes and properties

Web component **attributes** are strings. Use **`yes`** / **`no`** for boolean-like flags where noted; the implementation also accepts `"true"` for **`backdrop`** and **`keyboard`** when they arrive as strings.

| Name | Required | Type / values | Description |
| --- | --- | --- | --- |
| `schema` | no | JSON string (array) | Same **`schema`** shape as **`hb-form`**: array of field objects (`type`, `id`, `label`, `value`, …). From HTML, pass a **JSON string**. If omitted or the array is empty, no **`hb-form`** block is rendered. |
| `show` | no | `"yes"` \| `"no"` | Controls modal visibility. Empty string is treated like **`yes`**. Default **`no`**. |
| `id` | no | string | Host id; forwarded into the inner dialog id suffix (`id + "_modalform"` or a generated fallback). Included on **`modalFormCancel`** / merged into confirm payload as **`_id`**. |
| `title` | no | string | Dialog title (inner **`hb-dialog`**). |
| `content` | no | string | Supplemental content prop on inner **`hb-dialog`**. |
| `closelabel` | no | string | Close button label. |
| `confirmlabel` | no | string | Confirm button label. |
| `backdrop` | no | boolean or string | Coerced in the host script for string attributes (`"true"` / **`yes`** → true). **Not** passed to **`hb-dialog`** in the current markup, so the inner dialog keeps its own defaults. |
| `keyboard` | no | boolean or string | Same coercion as **`backdrop`**. **Not** passed to **`hb-dialog`** in the current markup. |
| `describedby` | no | string | **Not** passed to **`hb-dialog`** in the current markup. |
| `labelledby` | no | string | **Not** passed to **`hb-dialog`** in the current markup. |
| `dialogclasses` | no | string | Present in typings; **not** applied to the dialog in the current template. |
| `style` | no | string | Present in typings; **not** used by the current implementation (commented in source). |

For field definitions and validation, follow **`hb-form`** documentation and the form schema types under `builder/src/wc/form/types/`.

---

## Slots

| Slot | Role |
| --- | --- |
| `form-header` | Optional content **above** **`hb-form`** inside the modal body. |
| `form-footer` | Optional content **below** **`hb-form`** inside the modal body. |
| `header` | If set, forwarded to **`hb-dialog`** **`header`** (replaces default modal head). |
| `modal-footer` | If set, forwarded to **`hb-dialog`** **`modal-footer`** (replaces entire footer region). |
| `footer` | If set, forwarded to **`hb-dialog`** **`footer`** (default footer slot / button row). |
| `close-button-label` | Forwarded to **`hb-dialog`** for the close control label. |
| `confirm-button-label` | Forwarded to **`hb-dialog`** for the confirm control label. |

---

## CSS custom properties

Set these on **`hb-dialogform`** (or ensure they inherit) to tune layout; they are intended to align with nested **`hb-dialog`** / **`hb-form`** theming.

| Variable | Default (from metadata) | Description |
| --- | --- | --- |
| `--hb-modal-max-width` | `40rem` | Max width of the inner dialog card. |
| `--bulma-modal-card-head-padding` | `1rem 1.25rem` | Modal header padding (inner dialog). |
| `--bulma-modal-card-body-padding` | `1rem 1.25rem` | Modal body padding around the form area. |
| `--bulma-column-gap` | `0.75rem` | Column gap used by nested **`hb-form`** field layout. |

Additional **`--bulma-*`** variables from Bulma / **`hb-dialog`** / **`hb-form`** may apply depending on your theme setup.

### CSS parts

None exposed on this host (`styleSetup.parts` is empty).

---

## Events

All events are **`CustomEvent`** instances dispatched from the **`hb-dialogform`** host.

| Event | `detail` shape | When |
| --- | --- | --- |
| `updateForm` | Same as **`hb-form`** **`update`**: `{ _valid: boolean; _id: string; …values }` | On every inner form update (validity and field values). |
| `modalShow` | `{ id: string; show: boolean }` | When the inner modal opens or closes (re-dispatched from **`hb-dialog`**). |
| `modalFormConfirm` | Object: last valid **`update`**-like map plus host **`_id`** | User confirms and the form was valid (`_valid` true on the stored payload). |
| `modalFormCancel` | `{ id: string; error?: string }` | User cancels confirm, or confirm while invalid / missing values (`error` may be `"invalid form"`). |

**Confirm payload:** The implementation stores the latest **`update`** detail, clears **`_id`** on the copy, then assigns **`_id`** to the host’s **`id`** before dispatching **`modalFormConfirm`**.

---

## Usage notes

- **`schema`** from HTML must be a **single JSON string** representing an array (escape quotes as needed).
- Track **`updateForm`** to reflect validity in your own UI if the dialog is not the only feedback channel.
- Prefer **`yes`** / **`no`** for boolean attributes on custom elements for consistency with this codebase; inner dialogs may still default **`backdrop`** / **`keyboard`** until host props are wired through.
- This package does not define **`i18n`** language entries in metadata; labels come from attributes and slots.

---

## HTML example

```html
<hb-dialogform
  id="profile-editor"
  show="yes"
  title="Edit profile"
  schema='[{"type":"text","id":"name","label":"Name","required":true,"value":""}]'
  confirmlabel="Save"
  closelabel="Cancel"
></hb-dialogform>
```

```html
<script>
  const el = document.querySelector("hb-dialogform");
  el.addEventListener("updateForm", (e) => {
    console.log("valid:", e.detail._valid, "values:", e.detail);
  });
  el.addEventListener("modalFormConfirm", (e) => {
    console.log("confirmed:", e.detail);
  });
  el.addEventListener("modalFormCancel", (e) => {
    console.log("cancelled:", e.detail);
  });
  el.addEventListener("modalShow", (e) => {
    console.log("modal:", e.detail.show);
  });
</script>
```

---

## Related components

- **`hb-dialog`** — modal chrome, footer buttons, **`modalShow`** / **`modalConfirm`**.
- **`hb-form`** — **`schema`**, **`update`**, validation, and field types.

Package name in metadata: **`@htmlbricks/hb-dialogform`**.

---

<a id="wc-downloader"></a>

# `hb-downloader`

**Category:** utilities  
**Tags:** utilities, files

Web component that opens an inner **`hb-dialog`** while a download URL is active, fetches the resource with **`XMLHttpRequest`** as a **blob**, shows **Bulma** progress feedback, then triggers a **browser save** (temporary object URL + programmatic `<a download>` click). It forwards **`modalShow`** from the dialog, and emits **`downloadComplete`** or **`downloadError`** when the transfer finishes or fails. Closing the dialog clears the request URL, aborts any in-flight XHR, and resets error state.

## Dependencies

- **`hb-dialog`** — used for the modal shell, title slot, and open/close lifecycle that drives the download.

## Custom element

`hb-downloader`

## Attributes and properties

In HTML, attributes are **snake_case** and values are **strings** (see project conventions: booleans as `yes` / `no`, objects as JSON strings, numbers as string digits). The dialog’s visibility is driven internally from **`uri`**: a non-empty `uri` turns the inner dialog on; a successful download or closing the dialog clears `uri` and related state.

| Name | Required | Description |
| --- | --- | --- |
| `uri` | Yes (to start a download) | URL of the resource to fetch with `GET`. If empty, no dialog is shown and no request runs. |
| `downloadid` | No | String passed through to the inner dialog’s `id` and used in event `detail` where applicable. |
| `targetfilename` | No | Suggested file name for the save dialog. If omitted and `uri` is set, a default is derived from the last path segment of `uri` (query string stripped). The server may still override this via **`Content-Disposition`** when that header is readable (see CORS below). |
| `headers` | No | Optional request headers. From an HTML attribute, pass a **JSON object** as a string, e.g. `'{"Accept":"application/json"}'`. The component parses JSON strings into a plain object before opening the request. |
| `id` | No | Optional identifier for the host element (typing only; not wired to download logic in the current markup). |
| `style` | No | Present in typings for parity with other components; not applied in the current implementation. |

## Slots

| Slot | Description |
| --- | --- |
| `title` | Content for the dialog title area. Default text: **Downloading**. |

## Events

All events are **DOM `CustomEvent`** instances on `hb-downloader`.

| Event | `detail` shape | When it fires |
| --- | --- | --- |
| `modalShow` | `{ id: string; show: boolean }` | Propagated from the inner **`hb-dialog`** whenever its modal visibility changes (including when this component opens or closes the download UI). |
| `downloadComplete` | `{ downloaded: boolean; id: string }` | After a **successful** HTTP response (status 2xx), blob URL creation, and save trigger; internal request state is cleared. |
| `downloadError` | `{ downloaded: boolean; id: string; error: unknown }` | On network/XHR errors, non-2xx status, or thrown errors during setup. `downloaded` reflects whether a full save had completed. `error` may be a string (e.g. non-2xx status), an `Error`, or another thrown value. |

Listen in HTML with the usual `oneventname` form (e.g. `ondownloadcomplete`) or `addEventListener('downloadComplete', ...)`.

## CSS custom properties

These variables theme the **Bulma** `<progress>` bar and the optional percentage label (when the response exposes a known total length).

| Variable | Role |
| --- | --- |
| `--bulma-primary` | Filled portion of the progress bar. |
| `--bulma-border-weak` | Track / unfilled background. |
| `--bulma-text` | Color of the centered percentage text (when total size is known). |
| `--bulma-block-spacing` | Vertical spacing above the percentage line (the label uses half of this value as top margin). |

## CSS `::part`

None on this component (`styleSetup.parts` is empty). Dialog chrome and parts belong to **`hb-dialog`**.

## Behavior and integration notes

1. **Method** — Single **`GET`** request with `responseType: "blob"`. Progress uses `xhr.onprogress` (`loaded` / `total`). While **`total`** is unknown (`0`), the UI shows an **indeterminate** progress bar; when `total` is known, a **determinate** bar and percentage appear.
2. **File name** — If the response includes **`Content-Disposition`** with a `filename="..."` value and the browser can read that header, that name is preferred for `download`. If the header is missing or not exposed, the implementation falls back to **`targetfilename`** / URI inference. For cross-origin responses, the server may need to expose **`Content-Disposition`** (e.g. `Access-Control-Expose-Headers`) for filename detection to work.
3. **CORS** — The origin must be allowed to read the resource and relevant response headers; otherwise the request or filename detection may fail.
4. **Cleanup** — When the dialog closes (`show` becomes false), **`uri`** and **`downloadid`** are cleared, **`XMLHttpRequest.abort()`** is called if needed, and **`errorMessage`** is cleared. Errors are shown as plain text in the dialog body when present.
5. **Headers** — Only set headers your endpoint and CORS policy allow; invalid JSON in the `headers` attribute is caught and ignored (headers then unset).

## Examples

### Minimal: URL only

File name is inferred from the path when possible.

```html
<hb-downloader
  uri="https://www.w3.org/History/19921103-hypertext/hypertext/WWW/TheProject.html"
></hb-downloader>
```

### Explicit file name and correlation id

```html
<hb-downloader
  uri="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  targetfilename="dummy.pdf"
  downloadid="demo-pdf"
></hb-downloader>
```

### Custom headers (JSON string on the attribute)

Using single quotes around the attribute value avoids escaping inner double quotes.

```html
<hb-downloader
  uri="https://httpbin.org/json"
  downloadid="json-sample"
  headers='{"Accept":"application/json"}'
></hb-downloader>
```

### Custom title slot

```html
<hb-downloader uri="https://example.com/report.pdf" targetfilename="report.pdf">
  <span slot="title">Saving report…</span>
</hb-downloader>
```

### Script listener (vanilla)

```js
const el = document.querySelector('hb-downloader');
el.addEventListener('downloadComplete', (e) => {
  console.log('saved', e.detail.downloaded, e.detail.id);
});
el.addEventListener('downloadError', (e) => {
  console.error('failed', e.detail.id, e.detail.error);
});
el.addEventListener('modalShow', (e) => {
  console.log('dialog', e.detail.show, e.detail.id);
});
```

## TypeScript (authoring)

For local typings used when wrapping or typing the element, see `types/webcomponent.type.d.ts`:

- **`Component`** — `uri`, optional `headers` as `IHeader` (string keys and string values), optional `targetfilename`, `downloadid`, `id`, `style`.
- **`Events`** — `downloadError`, `downloadComplete`, `modalShow` with the `detail` shapes listed above.

Generated consumer typings also appear under `types/html-elements.d.ts` and `types/svelte-elements.d.ts` after a web component build.

---

<a id="wc-dropdown-notifications"></a>

## `hb-dropdown-notifications` — dropdown-notifications

**Package:** `@htmlbricks/hb-dropdown-notifications`  
**Category:** overlays  
**Tags:** overlays, menu, notifications

### What it does

`hb-dropdown-notifications` is a notification menu built as a Bulma **dropdown**: a bell trigger toggles the `is-active` state on the root dropdown. The panel includes a fixed **“Notifications”** header, an optional **“Clear all”** link when `clearurl` is non-empty, a **default slot** for your notification rows, and an optional **“View all”** footer when `viewurl` is non-empty. Set `align` to `right` to add Bulma’s `is-right` class so the menu aligns toward the end of the bar (typical for navbar-end placement).

Behavior notes for integrators:

- Open state is toggled **only** by clicking the bell button (`aria-expanded` updates accordingly). There is **no** built-in document click-outside handler; closing after navigation or custom UX is up to the host page or a wrapper.
- **“Clear all”** and **“View all”** are plain `<a href="…">` links; they do not emit custom events (the public `Events` type is empty).

### Custom element

`hb-dropdown-notifications`

### Attributes (snake_case; string values in HTML)

Web component attributes are strings. Align with your HTML Bricks / build conventions for optional empty strings.

| Attribute | Required | Description |
| --- | --- | --- |
| `id` | No | Optional element identifier (see your generated typings for host binding). |
| `style` | No | Optional inline style string for host-level styling (declared on the `Component` type). |
| `clearurl` | No | URL for **Clear all**. If empty or omitted, the link is not rendered. |
| `viewurl` | No | URL for **View all**. If empty or omitted, the footer block (divider + link) is not rendered. |
| `align` | No | `left` or `right` (default `left`). `right` applies `is-right` on the dropdown. |

In HTML, pass attributes as strings; use `""` for URLs you want omitted.

### Events

None. The component does not declare custom events on its public `Events` type.

### Accessibility

The bell control is a `<button type="button">` with:

- `aria-label="Notifications menu"`
- `aria-haspopup="true"`
- `aria-expanded` bound to open state
- `aria-controls="dropdown-notifications-menu"` pointing at the menu container (`role="menu"`)

Ensure slotted items are keyboard-focusable and labeled appropriately if they are interactive.

### Styling (CSS custom properties)

Theme the panel and chrome with Bulma variables on `:host`. Optional helpers support muted text inside **slotted** markup when you use `rgba(var(--x-muted-rgb), var(--x-text-opacity))` patterns.

| Variable | Role |
| --- | --- |
| `--bulma-scheme-main` | Background of the dropdown panel (`dropdown-content`). |
| `--bulma-border` | Dividers and menu borders. |
| `--bulma-text` | Header label and default body text in the panel. |
| `--bulma-link` | “Clear all” / “View all” link color. |
| `--x-muted-rgb` | Comma-separated RGB triplet (e.g. `115, 130, 140`) for muted slotted copy. |
| `--x-text-opacity` | Opacity multiplier (typically `0`–`1`) paired with `--x-muted-rgb`. |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for the full theme map.

### CSS `::part`

None. Style via host custom properties, Bulma classes on slotted nodes, and global Bulma utilities as needed.

### Slots

| Slot | Description |
| --- | --- |
| *(default)* | Content between the fixed **Notifications** header and the optional **View all** footer. Place each notification as a Bulma `dropdown-item` (or equivalent) for correct spacing. The title row and footer links are **not** slotted. |

### Integration patterns

1. **Rows in the default slot** — wrap each entry in `dropdown-item` (and optional `is-active` / media / text utilities) so spacing matches Bulma’s dropdown content.
2. **Footer off** — omit meaningful URLs by setting `clearurl` and/or `viewurl` to empty strings so those sections disappear.
3. **Navbar end** — use `align="right"` so the menu does not overflow off the right edge of the viewport.

### Minimal HTML example

```html
<hb-dropdown-notifications
  clearurl=""
  viewurl="/notifications"
  align="right"
>
  <div class="dropdown-item">Sample notification</div>
</hb-dropdown-notifications>
```

### Example with both footer actions

```html
<hb-dropdown-notifications
  clearurl="/api/notifications/clear"
  viewurl="/notifications"
  align="left"
>
  <a href="/notifications/1" class="dropdown-item">Order #1024 shipped</a>
  <a href="/notifications/2" class="dropdown-item">New message from support</a>
</hb-dropdown-notifications>
```

---

<a id="wc-dropdown-simple"></a>

# `hb-dropdown-simple` (dropdown-simple)

**Category:** overlays  
**Tags:** overlays, menu

## Overview

`hb-dropdown-simple` is a Bulma-styled dropdown: a clickable trigger opens a floating menu built from a declarative `list`. Plain rows dispatch a selection event with the item `key` and close the menu; rows with `linkHref` render as normal links instead. The component also reports open/close toggles so you can sync state with the host page.

## Custom element

```html
<hb-dropdown-simple></hb-dropdown-simple>
```

Tag name: `hb-dropdown-simple` (see `component.wc.svelte`).

## Public API (props / attributes)

Web component attributes are **strings** in HTML. Use **snake_case** names where your tooling maps props to attributes (same logical names as the TypeScript `Component` type).

| Prop | Type (authoring) | HTML / notes |
| --- | --- | --- |
| `id` | `string` (optional) | Passed through on `dropdownSwitch` so multiple instances can be distinguished. |
| `style` | `string` (optional) | Standard host styling; the implementation also reads `float` on the host to choose a default `position` when `position` is omitted. |
| `position` | `"left"` \| `"right"` (optional) | Aligns the menu (`is-right` when `"right"`). If unset, `position` defaults to `"right"` when the host element’s **inline** `style.float` is `"right"`, otherwise `"left"`. |
| `list` | `IDropDownMenuListItem[]` | **JSON string** in HTML. Each item should include at least `key` and `label`. Optional: `badge`, `group`, `linkHref`. The component parses JSON when the runtime value is not already a usable array shape. |
| `open` | `boolean` or `"yes"` \| `"no"` | Controls the open state (`is-active`). String `"yes"` and boolean `true` open; `"no"` and `false` close. **Note:** an empty string for `open` is treated like open (`true`) in the current implementation. |

### List item shape (`IDropDownMenuListItem`)

- **`key`** (string) — Identifier emitted with `dropDownClick` for non-link rows.
- **`label`** (string) — Visible text for the row.
- **`badge`** (number, optional) — Present in the public type for richer menus; the default markup does not render it.
- **`group`** (string, optional) — Same as `badge`: typed for integrations; not rendered by the default template.
- **`linkHref`** (string, optional) — If set, the row is an `<a class="dropdown-item">` to that URL instead of a clickable div that emits `dropDownClick`.

## Slots

| Slot | Purpose |
| --- | --- |
| `dropdownbutton` | Content of the trigger. Default fallback text is `btn`. The host wraps this slot in a clickable region that toggles the menu. |

## Events

Listen with `addEventListener` using the **exact** event names below (they are case-sensitive).

| Event | `detail` | When |
| --- | --- | --- |
| `dropdownSwitch` | `{ open: boolean; id: string }` | Whenever the user toggles the menu via the trigger (open becomes the new state). |
| `dropDownClick` | `{ key: string }` | When the user activates a **non-link** menu row (`linkHref` not set). The implementation then toggles the menu closed. |

Rows with **`linkHref`** are rendered as links only; they do not dispatch `dropDownClick` from this component.

## Styling

Theming uses **Bulma CSS variables** on `:host`. Common tokens referenced for this component:

| Variable | Role |
| --- | --- |
| `--bulma-scheme-main` | Background of the floating menu panel (`dropdown-content`). |
| `--bulma-border` | Border color for the menu and separators between items. |
| `--bulma-text` | Default text color for menu rows and the trigger area. |
| `--bulma-link` | Accent for link rows (`linkHref`) and interactive hover states. |

See the [Bulma CSS variables documentation](https://bulma.io/documentation/features/css-variables/) for the full variable set.

### CSS `::part` names

| Part | Targets | Typical overrides |
| --- | --- | --- |
| `dropdown` | Bulma `dropdown` root (active/right modifiers) | Layout, margins, `z-index` for stacking. |
| `dropdownbutton` | Wrapper around the `dropdownbutton` slot | Padding, min-width, typography on the control. |
| `dropdowncontent` | Floating `dropdown-menu` panel | Width, max-height, shadow for the opened list. |

## Behavior summary

1. Clicking the trigger toggles open/closed and emits `dropdownSwitch`.
2. Clicking a row **without** `linkHref` runs `dropDownClick` with that row’s `key`, then closes the menu.
3. Rows **with** `linkHref` behave like normal links; closing the menu is left to navigation or host logic.

## Minimal HTML example

```html
<hb-dropdown-simple
  list='[{"key":"a","label":"Option A"},{"key":"b","label":"Option B"}]'
  position="left"
></hb-dropdown-simple>
```

Example with a link row and controlled openness (strings `yes` / `no`):

```html
<hb-dropdown-simple
  id="actions-1"
  open="yes"
  list='[{"key":"edit","label":"Edit"},{"key":"docs","label":"Docs","linkHref":"https://example.com/docs"}]'
></hb-dropdown-simple>
```

## Package metadata (from docs)

- **npm:** `@htmlbricks/hb-dropdown-simple`
- **IIFE bundle:** `main.iife.js` (see your build / CDN layout for this package).

---

<a id="wc-editor-video"></a>

# `hb-editor-video` (editor-video)

**Package:** `@htmlbricks/hb-editor-video`  
**Category:** media  
**Tags:** media, video, editor

## Overview

`hb-editor-video` is a **video trimmer** web component: it plays an MP4 from `src`, shows a **16:9** Bulma `card` with the video, and keeps an **`hb-range-slider`** in sync with playback so the user can set **in** and **out** times in **seconds** (`track.minValue` / `track.maxValue`). Numeric **h / m / s** fields and small **cue** buttons snap the trim window to the current playhead. An optional **`hb-form`** (driven by a JSON **schema** string on `form`) can **gate** the primary **Send** action until metadata is valid. The footer shows the selected **duration** and a **Send** control.

The component registers **`hb-range-slider`** and **`hb-form`** at runtime (same bundle version as the host package). Icons use **Bootstrap Icons** (stylesheet injected in the shadow tree).

## Custom element

```html
<hb-editor-video></hb-editor-video>
```

## Attributes (HTML)

Web component attributes are **strings**. Use **JSON strings** for structured props where noted (`track`, `form`). Names follow **snake_case** where applicable.

| Attribute | Required | Description |
| --- | --- | --- |
| `src` | **Yes** | URL of the video source (MP4; passed to `<video><source>`. |
| `track` | No | JSON string for the trim window in **seconds**, e.g. `{"minValue":0,"maxValue":120}`. Parsed on the client; if omitted, defaults apply and values are **clamped** to the loaded media duration on `loadedmetadata`. |
| `form` | No | JSON **string** describing the `hb-form` **schema** (same shape the form component expects). When set, the embedded form is shown with **submit hidden**; **Send** triggers validation and, when valid, a `dispatchTrack` event with **track fields merged** into the submitted form payload. |
| `id` | No | Optional element id. |

The TypeScript `Component` type also includes optional `style`; it is not wired in the current Svelte implementation—prefer styling the host with CSS or documented **CSS variables** below.

## Behavior

- **Playback:** Play/pause toggles the `<video>` element. During `timeupdate`, the current time is **clamped** inside `[track.minValue, track.maxValue]` (pauses and seeks when crossing bounds).
- **Slider:** `hb-range-slider` receives `min` / `max` from the asset duration, `minval` / `maxval` from `track`, and `position_value` from the playhead. Moving the slider updates `track`, seeks the video, and emits **`changeTrackValues`**.
- **Time inputs:** Editing h/m/s recomputes `track.minValue` / `track.maxValue` in seconds and emits **`changeTrackValues`**.
- **Cue buttons:** Set **in** from current time (▼/▲ pair on the left) or **out** from current time (right), then emit **`changeTrackValues`**.
- **No `form`:** **Send** dispatches **`dispatchTrack`** with the current **`ITrack`** (`minValue`, `maxValue` in seconds).
- **With `form`:** **Send** is only enabled when the form is valid (`hb-form` **update** events). Clicking **Send** submits the form path; on success, **`dispatchTrack`** fires with **`Object.assign(track, formDetails)`** (validated form fields merged onto the track object).

## Events

Listen with `addEventListener` or framework equivalents; `event.detail` shapes:

| Event | `detail` |
| --- | --- |
| `changeTrackValues` | `{ minVaule: number; maxValue: number }` — fired when the trim range or slider changes. **Note:** the first property is spelled **`minVaule`** in the implementation and typings (typo preserved for compatibility). |
| `dispatchTrack` | **`ITrack`** (seconds) as the event `detail` when **`form` is unset**. When **`form` is set**, the same object shape with validated **`hb-form`** fields merged in (`Object.assign` onto the track). |

## CSS custom properties

Theme and layout follow **Bulma 1.x** tokens. Relevant variables (see `extra/docs.ts` / `styleSetup`):

| Variable | Role |
| --- | --- |
| `--hb-slider-background-color` | Accent for the nested **`hb-range-slider`** (trim bar). The markup maps it from **`--bulma-primary`** by default; set it on **`hb-editor-video`** to retint the slider independently. |
| `--bulma-primary` | Primary **Send** button and default slider accent when mapped in markup. |
| `--bulma-control-height` | Line box for the duration row and width of small time inputs. |
| `--bulma-link` | Link-styled Bulma tokens where they apply. |

## CSS `::part` names

None.

## Slots

None.

## Minimal examples

**Trim only (no form):**

```html
<hb-editor-video
  src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  track='{"minValue":0,"maxValue":120}'
></hb-editor-video>
```

**With a small metadata form** (schema is a JSON string; this matches the Storybook-style example in `extra/docs.ts`):

```html
<hb-editor-video
  id="my-editor"
  src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  track='{"minValue":0,"maxValue":5}'
  form='[{"type":"text","id":"clipTitle","label":"Clip title","required":false,"value":"","placeholder":"Optional label"}]'
></hb-editor-video>
```

**Listening from JavaScript:**

```js
const el = document.querySelector("hb-editor-video");

el.addEventListener("changeTrackValues", (e) => {
  console.log(e.detail.minVaule, e.detail.maxValue);
});

el.addEventListener("dispatchTrack", (e) => {
  console.log("final selection", e.detail);
});
```

## TypeScript (authoring)

From `types/webcomponent.type.d.ts`:

```ts
export interface ITrack {
  minValue: number;
  maxValue: number;
}

export type Component = {
  id?: string;
  style?: string;
  src: string;
  form?: string;
  track?: ITrack;
};

export type Events = {
  changeTrackValues: { minVaule: number; maxValue: number };
  dispatchTrack: ITrack & Record<string, unknown>;
};
```

## License

Package metadata in `extra/docs.ts` lists **Apache-2.0** (`LICENSE.md`). The Svelte source file header may still cite MIT for historical attribution; treat the **package license** as authoritative for distribution.

---

<a id="wc-faq-component"></a>

# hb-faq-component

**Category:** content · **Tags:** content

## Summary

`hb-faq-component` shows topic chips (Bootstrap Icons + labels) and an accordion FAQ list. Topics can narrow entries by `topic` key; a chip with `catchAll` clears the filter. Answer bodies use `{@html}` — only trusted HTML.

## What it does

1. **Parse:** In `$effect`, string `info` / `topics` are `JSON.parse`d into arrays; invalid JSON is logged.
2. **Topics:** Renders a flex wrap of light buttons; `catchAll` sets `filter` to `undefined`, else sets `filter` to the chip `key`.
3. **List:** For each `info` item (optionally filtered by `filter`), a checkbox + label accordion row shows `title` and HTML `text`.
4. **Icons:** Loads Bootstrap Icons CSS in `<svelte:head>` for `bi-{icon}` classes.

## Custom element

`hb-faq-component`

## Attributes

Attributes are **snake_case**. From HTML, structured props are **JSON strings**.

| Attribute | Type (logical) | HTML / notes |
| --- | --- | --- |
| `id` | `string` (optional) | Host id. |
| `style` | `string` (optional) | In typings; not read in the current script. |
| `info` | `string` \| `FaqInfoItem[]` | FAQ rows: `title`, `text` (HTML), optional `topic` for filtering. |
| `topics` | `string` \| `FaqTopicChip[]` | Chips: `key`, `label`, `icon` (Bootstrap icon name without `bi-`), optional `catchAll`, optional `index` (filled when parsing JSON). |
| `filter` | `string` (optional) | Active topic key; chips update this; catch-all clears it. |

## Custom events

**None** — `Events` is `{}` in `types/webcomponent.type.d.ts`.

## Styling

### CSS custom properties (`extra/docs.ts`)

| Variable | Role |
| --- | --- |
| `--bulma-column-gap` | Gap between topic buttons. |
| `--bulma-block-spacing` | Vertical padding per FAQ row. |
| `--bulma-border` | Divider between accordion items. |
| `--bulma-text` | Expand “+” color. |
| `--bulma-size-4` | Question label size. |
| `--bulma-size-5` | Answer body size. |

### CSS parts

**None.**

### Slots

**None** — see `extra/docs.ts` (`htmlSlots`).

## TypeScript

`types/webcomponent.type.d.ts` — `Component`, `Events`, `FaqInfoItem`, `FaqTopicChip`.

## Behavior notes

- Accordion `id` / `for` use `title` with spaces removed — duplicate titles in the same list can collide.
- Bootstrap Icons font is loaded from jsDelivr in the document head.

## Minimal HTML example

```html
<hb-faq-component
  topics='[{"key":"general","label":"General","icon":"question-circle"}]'
  info='[{"topic":"general","title":"What is this?","text":"<p>Short <strong>HTML</strong> answer.</p>"}]'
></hb-faq-component>
```

## Security

Only pass HTML you trust in `info[].text`, or sanitize before serializing JSON.

---

<a id="wc-footer"></a>

# hb-footer

**Category:** layout · **Tags:** layout, navigation

## Summary

`hb-footer` is a site footer with **small**, **regular**, and a **large** placeholder layout. The regular layout uses Bulma `container`, columns, `hb-contact-item` for contacts/socials/cells, optional link columns, policy row, and slots for header/content/bottom. **Bootstrap Icons** load for small-layout expand controls.

## What it does

1. **Coercion:** `disable_expanding_small` accepts `yes` / `true` as strings; JSON props (`company`, `columns`, …) are parsed in `$effect`; missing column/cell `_id` and policy keys are normalized.
2. **Small mode:** Compact bar with optional expand to the full regular grid unless `disable_expanding_small` is true.
3. **Regular mode:** Company block, optional contacts, socials (preset networks), dynamic columns with label buttons (`footerClick` + cell `_id`) or nested contact payloads, policy anchors vs `footerClick` with `key`.
4. **Large mode:** Renders the text `large` only — not ready for production.

## Custom element

`hb-footer`

## Attributes

Strings from HTML; objects/arrays as **JSON strings**; booleans **`yes`** / **`no`** (or `true` / `false` for `disable_expanding_small`).

| Attribute | Type (logical) | HTML / notes |
| --- | --- | --- |
| `company` | `ICompany` (JSON) | **Required** for meaningful UI: `logoUri`, `siteName`, `companyName`, `description`, optional `vatNumber`, `fiscalCode`, `since`, `registration`. |
| `type` | `"small"` \| `"regular"` \| `"large"` | Default `regular`; invalid values become `regular`. |
| `id` | `string` (optional) | Host id. |
| `style` | `string` (optional) | In typings; not read in the current script. |
| `columns` | `IColumn[]` (JSON) | Optional `_id`, `title`, `cells` with optional `_id`, `label`, or `phone` / `address` / `email` / `site`. |
| `contacts` | `IContacts` (JSON) | Phones, addresses, emails, sites. |
| `socials` | `ISocials` (JSON) | Keys: `facebook`, `gmail`, `twitter`, `github`, `youtube`, `twitch`, `discord` — presence shows a row; `github` uses the value as link URI. |
| `policies` | `IPolicies[]` (JSON) | `label`, `key`, optional `link` (anchor vs button). |
| `brandandcontacts` | `IBrandAndContacts` (JSON) | Parsed if present; **not** rendered in the default template. |
| `disable_expanding_small` | `boolean` / string | Disables expand for `type="small"`. |

## Custom events

| Event name | `detail` shape |
| --- | --- |
| `footerClick` | `{ elClick: string }` — column cell `_id` or policy `key` when no `link`. |

## Styling

### CSS custom properties (`extra/docs.ts`)

| Variable | Role |
| --- | --- |
| `--hb-footer-sections` | Equal-width column count (set inline on `.hb-footer-columns`). |
| `--bulma-section-padding-desktop` | Top padding of `:host` footer. |
| `--bulma-block-spacing` | Spacing between contact rows. |
| `--bulma-size-7` | Bottom line typography. |

### CSS parts (`extra/docs.ts`)

| Part | Role |
| --- | --- |
| `column-cell-button-content` | Ghost button for column cells with `label`. |

### Slots (`extra/docs.ts`)

| Slot | Role |
| --- | --- |
| `footer_small` | Compact bar when `type="small"` and not expanded. |
| `footerheader` | Region above the main grid. |
| `footercontent` | Wraps the main columns grid. |
| `footerpolicy` | Policy row. |
| `footerbottom` | Bottom copyright / registration line. |

## TypeScript

`types/webcomponent.type.d.ts` — `Component`, `Events`, `ICompany`, `IColumn`, `IContacts`, `ISocials`, `IPolicies`, etc.

## Dependencies

`hb-contact-item` — phones, addresses, emails, sites, social rows, mixed column cells.

## Minimal HTML example

```html
<hb-footer
  type="regular"
  company='{"logoUri":"","siteName":"Acme","companyName":"Acme S.r.l.","description":"Short tagline."}'
></hb-footer>
```

```js
document.querySelector("hb-footer")?.addEventListener("footerClick", (e) => {
  console.log(e.detail.elClick);
});
```

---

<a id="wc-form"></a>

# hb-form

**Category:** forms · **Tags:** forms · **Package:** `@htmlbricks/hb-form`

`hb-form` is a **JSON `schema`-driven** form engine for HTML Bricks: each schema row describes one logical field (or a layout row). The host maps `type` to the correct `hb-input-*` custom element, wires **validation state**, **conditional visibility** from `dependencies`, and exposes a **submit** / **update** event contract for backends, SPAs, or low-code flows.

You integrate by (1) loading this component plus its **input** dependencies, (2) passing a **`schema`** (JSON string from HTML, or an object when using a JS framework), and (3) listening for **`update`**, **`submit`**, **`submitinvalid`**, and optionally **`getValues`**.

---

## What it does

- Renders a **Bulma**-styled form (`columns` / `field` / `label`) inside the shadow root.
- Parses **`schema`** when it arrives as a **string** (typical HTML attribute), merges live values, and avoids redundant resets when the **same** schema string is re-applied.
- Passes each control a **`schemaentry`** attribute: JSON serialization of the schema entry merged with the current value (`allValues[id] ?? entry.value`).
- Forwards **`show_validation`** (`"yes"` / `"no"`) to every nested input so validation UI can be shown or suppressed consistently.
- For **`hb-input-file`** and **`hb-input-coords`**, forwards **`i18nlang`** to the child.
- Aggregates per-field validity in **`valids`**; the whole form is considered invalid until visible fields have reported validity (see [Validation and `_valid`](#validation-and-_valid)).
- Supports **`dependencies`**: fields (and nested columns) can show or hide based on other fields’ values.
- **`submitted="yes"`** triggers **`onSubmit()`** on a microtask after render (programmatic submit from the host).
- Several text-like inputs call **`onclickEnter`** to run the same submit handler as the primary button.
- Dispatches **`update`** on value/validity changes with a **300 ms** debounce (only `update` is debounced; other events fire immediately).

---

## Runtime prerequisites

`hb-form` **dynamically registers** child packages via `addComponent` (see `component.wc.svelte`). Your page or bundle must actually load the corresponding `hb-input-*` scripts/styles so those custom elements are defined.

Declared dependency graph (from `extra/docs.ts` metadata) includes at least:

- `hb-input-text`, `hb-input-area`, `hb-input-number`, `hb-input-email`
- `hb-input-select`, `hb-input-radio`, `hb-input-checkbox`
- `hb-input-date`, `hb-input-datetime`, `hb-input-color`, `hb-input-range`
- `hb-input-file`, `hb-input-coords`, `hb-input-array-objects`, `hb-input-array-tags`

Nested packages (e.g. `hb-input-array-objects` → `hb-form`, `hb-table`, dialogs, paginate, etc.) apply when you use those field types—plan bundle size accordingly.

---

## Custom element

| Tag       | Notes                                      |
| --------- | ------------------------------------------ |
| `hb-form` | Shadow root; forwards Bulma + local SCSS. |

---

## Attributes and properties (snake_case, string-friendly)

HTML and `setAttribute` only carry **strings**. Booleans and numbers must follow your host conventions (this codebase generally uses **`yes` / `no`** for booleans on attributes where documented). TypeScript `Component` (see `types/webcomponent.type.d.ts`) describes the logical shape.

| Name                 | Required | Type / values (logical)                    | Role |
| -------------------- | -------- | ------------------------------------------ | ---- |
| `schema`             | **yes**  | `FormSchema` (array) or JSON **string**    | Field definitions. **Recommended:** pass **`schema` as a JSON string** from HTML or `setAttribute`: the parse **`$effect`** runs `JSON.parse`, resets `valids` / `allValues` / `visibility`, and calls **`setVisibility`**. Identical consecutive strings are skipped to avoid churn. If you bind a **live object** without going through that string path, **`setVisibility` may never run** in the reference implementation—controls can stay invisible until you adjust the integration. |
| `id`                 | no       | `string`                                   | Passed through to `update` detail as **`_id`** (see events). Default `""`. |
| `style`              | no       | `string`                                   | Present on `Component` typings; not consumed by current script logic (commented in source)—safe for host/CSS use if your toolchain forwards it. |
| `values`             | no       | `FormValues`                               | Declared on `Component` for typing / tooling; **not** read in `component.wc.svelte`—values come from schema defaults, user input, and `allValues`. |
| `isInvalid`          | no       | `boolean`                                  | Declared on `Component` typings; **not** read in `component.wc.svelte` (internal state is the derived **`is_invalid`** variable). Treat as reserved / wrapper metadata unless your build maps it. |
| `submitted`          | no       | `"yes"` \| `"no"` \| `null`                | **`"yes"`** queues **`onSubmit()`** on a microtask. Inside submit, the implementation sets **`show_validation = "yes"`** and **`submitted = "no"`** before validating. |
| `getvals`            | no       | `"yes"` \| `"no"` \| `null`                | Typed API for **`getValues`**. The source defines **`getVals()`** but **does not call it from a reactive effect**; toggling this attribute alone may not emit **`getValues`** until your bundle wires it. Prefer **`update`** / **`submit`** for snapshots unless you confirm behavior in your build. |
| `show_validation`    | no       | `"yes"` \| `"no"` (default **`"no"`**)     | Forwarded to all child inputs. Forced to **`"yes"`** when submit runs. |
| `hide_submit`        | no       | `boolean` (coerced)                        | If **truthy** (`true`, `"yes"`, `"true"`), the entire submit area (including slots) is **not** rendered. |
| `buttons_outlined`   | no       | `"yes"` \| `"no"` (default **`"no"`**)      | When **`"yes"`** (or coerced **`"true"`** / boolean **`true`** from JS), the **default** submit `<button>` gets Bulma **`is-outlined`** in addition to **`is-primary`**. Does not affect fully custom **`submit_button`** slot markup. |
| `i18nlang`           | no       | `string` (e.g. **`en`**, **`it`**)         | Passed to **`hb-input-file`** and **`hb-input-coords`**. |

---

## `schema` — `FormSchema` and each entry

`FormSchema` is **`FormSchemaEntry[]`**. Shared fields (`FormSchemaEntryShared`) and the full entry type are defined in `types/webcomponent.type.d.ts`.

### Common fields (every entry, including inside `row` columns)

| Field               | Type                         | Purpose |
| ------------------- | ---------------------------- | ------- |
| `id`                | `string`                     | Stable key for DOM `for=`, internal maps, and event payloads. **Required** for non-row entries you expect to submit. |
| `type`              | `string`                     | Discriminator mapped in `registeredComponents` (see table below). On standalone `schemaentry` payloads to inputs, `type` may be implied by the host tag. |
| `label`             | `string`                     | Rendered above the control unless `labelIsHandledByComponent` applies (checkbox). |
| `value`             | `unknown`                    | Default / initial value when nothing is in `allValues` yet. |
| `dependencies`      | `FormSchemaDependency[]`     | Conditional visibility (see below). |
| `readonly`          | `boolean`                    | Carried in `schemaentry` for inputs that support it. |
| `disabled`          | `boolean`                    | Same. |
| `required`          | `boolean`                    | Shown as `*` on label; enforced by child inputs + validation. |
| `validationRegex`   | `string`                     | Carried to inputs that honor regex validation. |
| `validationTip`     | `string`                     | Tip text for invalid state in children. |
| `placeholder`       | `string`                     | Passed through `schemaentry`. |
| `params`            | `Record<string, unknown>`    | Type-specific options (e.g. `min`/`max` for number, `options` for select/radio, nested `schema` for `arrayobjects`). |

### `FormSchemaDependency`

```ts
{ id: string; values?: (string | number | boolean)[] }
```

- **`id`**: id of the controlling field.
- **`values`**: optional whitelist. If omitted, the dependent is eligible when the controller has **any** non-empty value (not `undefined` / `null` / `""`). If present, the controller’s current value must satisfy **`dep.values.includes(depVal)`** (strict equality with `string | number | boolean`).

Dependency resolution uses **`valueForDependency`**: live **`allValues[depId]`** first, otherwise the **`value`** on the schema entry for that id.

---

## Schema `type` → nested custom element

Mapping is defined in **`registeredComponents`** in `component.wc.svelte`. Unknown **`type`** values cause **`setVisibility` / `getControls` to `throw`** at runtime—there is no silent skip.

| Schema `type`   | Child element                 | Notes |
| --------------- | ----------------------------- | ----- |
| `row`           | *(none — layout only)*       | Renders Bulma **`columns is-multiline`**. Nested entries live in **`params.columns`**. `registeredComponents.row` uses **`options.row: true`** and **`component: undefined`**. |
| `text`          | `hb-input-text`              | Enter key submits. |
| `textarea`      | `hb-input-area`              | Enter key submits (handler on component). |
| `number`        | `hb-input-number`            | Enter key submits. |
| `email`         | `hb-input-email`             | Enter key submits. |
| `select`        | `hb-input-select`            | |
| `radio`         | `hb-input-radio`             | |
| `checkbox`      | `hb-input-checkbox`          | **`labelIsHandledByComponent: true`** — outer `hb-form` label is **not** rendered (the checkbox handles labeling). Column cells get a slightly taller **`min-height`** for alignment. |
| `date`          | `hb-input-date`              | Enter key submits. |
| `datetime`      | `hb-input-datetime`          | Enter key submits. |
| `color`         | `hb-input-color`             | |
| `file`          | `hb-input-file`              | Receives **`i18nlang`**. |
| `range`         | `hb-input-range`             | |
| `coords`        | `hb-input-coords`            | Receives **`i18nlang`**. |
| `arrayobjects`  | `hb-input-array-objects`     | Nested schema under **`params.schema`**. |
| `arraytags`     | `hb-input-array-tags`        | Optional schema field **`array_style`**: `"pills"` or `"area"` (string). When **omitted**, **`hb-form`** passes **`array_style="area"`** on the host (textarea-like chips). Set **`array_style: "pills"`** for the classic tag row + add control. |

---

## Layout: `row` and columns

- Top-level (or nested) entry with **`type: "row"`** must provide **`params.columns`** as another array of **`FormSchemaEntry`** objects.
- The row’s own **`id`** participates in **`visibility`** like any other entry.
- Column cells are **`column is-flex is-align-items-center`**; checkboxes get inline **`min-height: 3.25rem`** on the column for vertical alignment.

---

## Conditional visibility

1. On string schema parse, **`setVisibility`** walks the tree: for each entry, **`visibility[id] = visibility[id] || !dependencies?.length`**. So fields **with** dependencies start **hidden** (`false`) unless already true; fields **without** dependencies start **visible**.
2. **`dependencyMap`** groups entries by **every** dependency id they reference (via `groupMultipleBy`).
3. When a control fires **`onsetVal`** with `{ id, value, valid }`, **`setValByMessage`** updates **`allValues`**, **`valids`**, and for the changed id runs **`handleVisibility`** on dependents—cascading **`show`** / **`hide`**.
4. **`values`** (derived object used for submit) includes only entries where **`visibility[id]`** is true, using **`allValues[id] ?? entry.value`**.

Hidden branch values are therefore omitted from **`submit`** / **`getValues`** payloads (matching legacy “visible only” shape).

---

## Validation and `_valid`

- Each child emits **`onsetVal`**; the form stores **`valids[fieldId]`**.
- **`is_invalid`** is **`true`** when there are **no** `valids` entries yet, or when **any visible** field has **`valids[id] === false`**.
- **`onSubmit`** always sets **`show_validation = "yes"`** so users see errors after an attempt.
- If invalid: dispatches **`submitinvalid`** with **`{}`** and **does not** dispatch **`submit`**.
- If valid: dispatches **`submit`** with **`{ _valid: true, ...values }`** where **`values`** is the flat map of visible field ids → current values (same shape as `FormSubmitLikeDetail` in typings: **`_valid` + `FormValues`**).

---

## Events (`types/webcomponent.type.d.ts`)

All events are **native `CustomEvent`** on the host element (`bubbles` / `composed` follow your Svelte CE compile settings; detail shapes below match the implementation).

| Event            | `detail` type (logical) | When |
| ---------------- | ------------------------- | ---- |
| **`update`**     | **`{ _valid: boolean; _id: string } & FormValues`** | Value or validity changed; **debounced 300 ms** and coalesced. **`_id`** is the form’s **`id`** prop (string). |
| **`submit`**     | **`FormSubmitLikeDetail`** = **`{ _valid: boolean } & FormValues`** | Successful validation after submit (button or **`submitted="yes"`**). **`_valid`** is `true` when dispatched. |
| **`submitinvalid`** | **`Record<string, never>`** (`{}`) | Submit attempted while the form is invalid. |
| **`getValues`**  | **`FormSubmitLikeDetail`** | Same merge as **`submit`** when **`getVals()`** runs with **`getvals === "yes"`** (see attribute notes). |

**Correction vs older docs:** **`submit`** and **`getValues`** details are a **flat** object **`{ _valid, ...fieldValues }`**, not a nested `{ values: { ... } }` object. **`update`** is also **flat** field keys plus **`_valid`** and **`_id`**, not a nested `values` property.

---

## Slots (`::part` host children)

Declared in `extra/docs.ts` / rendered near the bottom of `component.wc.svelte`.

| Slot              | Description |
| ----------------- | ----------- |
| **`submit_button`** | Replaces the **entire** default submit control (default content is the primary Bulma button + inner label slot). Still wrapped in the same clickable **`span`** that calls **`onSubmit()`**. |
| **`submit_label`**  | Default text inside the primary button (**`Submit`**). Ignored if **`submit_button`** fully replaces the button. |
| **`other_buttons`** | Extra controls rendered **after** the submit slot, still inside **`button_container`**. |

If **`hide_submit`** is true, **no** submit region (and no slots) is rendered.

---

## CSS parts

| Part                 | Description |
| -------------------- | ----------- |
| **`button_container`** | Flex wrapper around submit + **`other_buttons`**. Element id **`button_container`**. |
| **`main_button`**      | Default **`<button type="button" class="button is-primary">`** when using the stock **`submit_button`** slot. |

Style the host with **`::part(button_container)`** / **`::part(main_button)`** from the light DOM (where supported).

---

## CSS custom properties

From `styleSetup` / Bulma integration (`extra/docs.ts`):

| Variable               | Meaning |
| ---------------------- | ------- |
| **`--bulma-column-gap`** | Horizontal padding / column gap on **`:host`**; tuned so Bulma **`.columns`** negative margins fit. |

Additional **`--bulma-*`** variables come from forwarded Bulma modules in `styles/bulma.scss` (form, grid, button, etc.). See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

---

## Internationalization

`i18nlang` is forwarded to file and coords inputs. Supported languages listed in `extra/docs.ts` include **English** and **Italian**; extend via package metadata if more locales are registered upstream.

---

## Integration patterns

### 1. Declarative HTML (minimal)

Pass **`schema`** as a **single JSON string** (escape quotes for HTML).

```html
<hb-form
  id="signup"
  schema="[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;name&quot;,&quot;label&quot;:&quot;Name&quot;,&quot;required&quot;:true}]"
  show_validation="no"
  submitted="no"
  i18nlang="en"
></hb-form>
```

### 2. Vanilla JS — programmatic submit and listening

```js
const form = document.querySelector("hb-form");

form.addEventListener("update", (e) => {
  const { _valid, _id, ...rest } = e.detail;
  console.log("valid?", _valid, "form id:", _id, "values:", rest);
});

form.addEventListener("submit", (e) => {
  if (e.detail._valid) {
    console.log("posted", e.detail);
  }
});

form.addEventListener("submitinvalid", () => {
  console.warn("fix validation errors");
});

// Trigger the same path as clicking submit (after schema is parsed):
form.setAttribute("submitted", "yes");
```

### 3. Building `schema` in JavaScript

```js
const schema = [
  {
    id: "profile",
    type: "row",
    params: {
      columns: [
        { id: "firstName", type: "text", label: "First name", required: true },
        { id: "lastName", type: "text", label: "Last name", required: true },
      ],
    },
  },
  {
    id: "age",
    type: "number",
    label: "Age",
    required: true,
    params: { min: 0, max: 120 },
    validationTip: "Enter a realistic age.",
  },
];

form.setAttribute("schema", JSON.stringify(schema));
```

### 4. Conditional field (dependencies)

```js
const schema = [
  { id: "code", type: "text", label: "Access code", required: true },
  {
    id: "secret",
    type: "text",
    label: "Secret phrase",
    dependencies: [{ id: "code", values: ["VIP"] }],
  },
];
```

The **`secret`** field stays hidden until **`code`**’s live value is exactly **`"VIP"`** (or the number/boolean you list in **`values`**).

---

## Troubleshooting

| Symptom | Likely cause |
| ------- | ------------- |
| Console error **`unknown component type`** | **`type`** string not in the mapping table—fix the schema or extend the registry in source. |
| Submit always invalid at first | **`valids`** empty until children emit **`onsetVal`**—ensure inputs mount and fire validation for visible fields. |
| Schema changes ignored | String compare: **identical** `schema` string to the previous one is intentionally skipped; mutate or append a cache-busting suffix if you truly need a no-op reparse. |
| Dependents never appear | Controller value empty or not in **`values`** whitelist; dependency controller may still be hidden. |

---

## TypeScript

Authoring types: `types/webcomponent.type.d.ts` — `Component` (including `schema` as **`string | FormSchema`**), **`Events`**, **`FormSchemaEntry`**, **`FormSubmitLikeDetail`**, **`FormValues`**, **`IComponentName`**, etc.

---

## See also

- **`extra/docs.ts`** — `styleSetup`, slots, Storybook args, dependency list, and packaged examples (`schema1`, conditional schemas, file + array-objects samples).
- **`component.wc.svelte`** — `registeredComponents`, visibility, submit pipeline, and slot markup.

---

<a id="wc-form-composer"></a>

# hb-form-composer

**Category:** forms · **Tags:** forms, builder

## Summary

Visual builder for **`hb-form`** schemas: internal **`hb-table`** lists fields, **`hb-dialogform`** edits a row via nested **`hb-form`**, optional debug preview. **`done`** emits the accumulated **`output_schema`**.

## What it does

1. **Parse:** `output_schema` JSON string → array; `debug` string `yes` / `true` / `""` → boolean.
2. **Table:** Rows mirror `output_schema`; delete disabled when row has `value`.
3. **Add:** `hb-dialogform` confirms into `output_schema` (replaces same `label`).
4. **Done:** Primary outlined button dispatches **`done`** with `{ schema, id }`.

## Custom element

`hb-form-composer`

## Attributes

| Attribute | Type (logical) | HTML / notes |
| --- | --- | --- |
| `id` | `string` (optional) | Echoed on **`done`** as `detail.id`. |
| `style` | `string` (optional) | In typings; not read in script. |
| `debug` | `boolean` \| `string` | `yes` / `true` / empty string → true. |
| `output_schema` | `string` \| `FormSchema` | Initial rows; JSON string from HTML. |

Field types in the dialog selector include text, textarea, email, number, select, radio, checkbox, file, date, datetime, color, range (see `component.wc.svelte`).

## Custom events

| Event name | `detail` shape |
| --- | --- |
| `done` | `{ schema: FormSchema; id: string }` — current `output_schema`; fires only on explicit **done** click. |

## Styling

No host-only vars or parts in `extra/docs.ts`; theming comes from nested **`hb-form`**, **`hb-table`**, **`hb-dialogform`**.

### Slots

**None** on the host.

## Dependencies

See `extra/docs.ts` — **`hb-form`**, **`hb-table`**, **`hb-dialogform`** and transitive inputs, table, dialogs, etc.

## TypeScript

`types/webcomponent.type.d.ts` — `Component`, `Events` (`FormSchema` from `../../form/types/webcomponent.type` relative to this folder; same module as `hb-form`).

## Minimal HTML example

```html
<hb-form-composer id="my-composer" debug="no"></hb-form-composer>
```

```js
document.querySelector("hb-form-composer")?.addEventListener("done", (e) => {
  console.log(e.detail.id, e.detail.schema);
});
```

---

<a id="wc-form-contact"></a>

# hb-form-contact

**Category:** forms · **Tags:** forms, contact

## Summary

Opinionated contact form: JSON **`informations`** toggles fields; the component builds an **`hb-form`** `schema` at runtime. Optional **`privacy_policy`** checkbox and **`captcha`** (reCAPTCHA v2 invisible) before dispatching **`formContactSubmit`** or **`formContactSubmitWithCaptcha`**.

## What it does

1. **Parse:** `informations`, `privacy_policy`, `captcha` as JSON strings in `$effect`.
2. **Schema:** Derived `schema` array — name, email, phone, subject, message in order when keys exist; privacy row uses id `policy`.
3. **Submit:** On valid **`hb-form`** submit, if `captcha.siteKey` → mount captcha child; else **`formContactSubmit`**. After v2 response → **`formContactSubmitWithCaptcha`** with `response` and `type: "recaptcha-v2-invisible"`.

## Custom element

`hb-form-contact`

## Attributes

| Attribute | Type (logical) | HTML / notes |
| --- | --- | --- |
| `id` | `string` (optional) | Host id. |
| `style` | `string` (optional) | In typings; not read in script. |
| `informations` | `string` \| object | Which fields exist and `required` / `label` / `placeholder` / `value`. Non-object → no form. |
| `privacy_policy` | `string` \| object | Needs `input` for label; optional `link`, `policyId`, `required`. |
| `captcha` | `string` \| object | `siteKey` + `type` (`googlev_recaptchav2_invisible` \| `googlev_recaptchav3`); v3 type is typed but UI uses v2 invisible when `siteKey` is set. |

## Custom events

| Event name | `detail` shape |
| --- | --- |
| `formContactSubmit` | `{ _valid: boolean; values: Record<string, string \| number \| boolean> }` — no captcha path. |
| `formContactSubmitWithCaptcha` | Same as above plus **`response: string`** and optional **`type`** (`"recaptcha-v2-invisible"`). |

## Styling

### CSS custom properties (`extra/docs.ts`)

| Variable | Role |
| --- | --- |
| `--bulma-block-spacing` | Vertical rhythm around the form (default `1.5rem` in metadata). |

### CSS parts / slots

**None.**

## TypeScript

`types/webcomponent.type.d.ts` — `Component`, `Events`.

## Dependencies

`hb-form`, `hb-captcha-google-recaptcha-v2-invisible` (when captcha used).

## Minimal HTML example

```html
<hb-form-contact
  informations='{"email":{"required":true},"message":{}}'
  privacy_policy='{"input":"I agree to the privacy policy","required":true}'
></hb-form-contact>
```

---

<a id="wc-funnel"></a>

# hb-funnel

**Category:** forms · **Tags:** forms, multistep

## Summary

Multi-step funnel over **`hb-form`**: pass an array of schemas (one per step). Renders one inner form for the active step; valid submit merges values into the step schema and advances or fires final **`submit`**.

## What it does

1. **Parse:** `schemes` as JSON string → `parsedSchemes` (`{ schema, valid }[]`); then clears `schemes` to `[]` in memory.
2. **Form snapshot:** `schema` passed to inner `hb-form` as JSON string; refreshed when schemes load or **`step`** changes, not on every **`update`** (stable typing).
3. **Navigation:** Italian labels — **Indietro**, **Avanti**, **Salva**; **`submitstep`** toggles `yes` to trigger inner submit/validation.
4. **Events:** **`update`** on inner form updates (debounced from child) updates current step `valid` only; **`submit`** when last step validates.

## Custom element

`hb-funnel`

## Attributes

| Attribute | Type (logical) | HTML / notes |
| --- | --- | --- |
| `schemes` | `string` \| `FormSchema[]` | JSON: array of **schemas** (each schema is a `FormSchema` / array of entries). |
| `step` | `number` \| `string` | 1-based index (default `1`). |
| `steps` | `number` \| `string` | Fallback count before schemes parsed. |
| `submitstep` | `"yes"` \| `"no"` | Drives inner `submitted`; non-`yes` normalized to `no`. |
| `id` | `string` (optional) | Passed to inner `hb-form` id pattern. |
| `style` | `string` (optional) | In typings. |

## Custom events

| Event name | `detail` shape |
| --- | --- |
| `update` | `{ step: number; scheme: { schema: FormSchema; valid: boolean }; valid: boolean }` |
| `submit` | `{ schemes: { schema: FormSchema; valid: boolean }[]; steps: number; step: number }` |

## Styling

No dedicated host vars or parts in `extra/docs.ts`; Bulma **`--bulma-*`** via nested **`hb-form`**.

### Slots

**None.**

## TypeScript

`types/webcomponent.type.d.ts` — `Component`, `Events`, `FunnelStepSchemes`. Shared **`FormSchema`**: `builder/src/wc/form/types/webcomponent.type.d.ts`.

## Minimal HTML example

```html
<hb-funnel
  schemes='[[{"type":"text","id":"name","label":"Name","required":true}]]'
></hb-funnel>
```

---

<a id="wc-gallery-video"></a>

# hb-gallery-video

**Category:** media · **Tags:** media, video, gallery

## Summary

Paginated **`hb-card-video`** grid from JSON **`cards`**, with **`hb-paginate`**, optional text and date filters, and internal vs **external** filter modes.

## What it does

1. **Cards:** Parse JSON string; assign `_id`; parse **`time`** with Day.js when present; sort/filter/slice per mode.
2. **Pagination:** **`pageChange`** from paginator updates `page` unless **`externalfilter`** is set.
3. **Filters:** Text + date toolbar when all cards have **`time`** (otherwise date search disabled with console warning).

## Custom element

`hb-gallery-video`

## Attributes

Snake_case in HTML; **`link_label`** maps to prop `linkLabel`. Numbers as strings; **`disabletextfilter`**: `yes` hides text search.

| Attribute | Type (logical) | Notes |
| --- | --- | --- |
| `cards` | `string` \| `ICard[]` | **Required** content; invalid JSON → empty list. |
| `id` | `string` (optional) | |
| `style` | `string` (optional) | Typings only. |
| `size` | `number` \| `string` | Items per page (default **12**). |
| `page` | `number` \| `string` | 0-based; when **0**, date format forced to `dddd DD MMMM YYYY HH:mm`. |
| `pages` | `number` \| `string` | With **`externalfilter`**, total pages from host. |
| `link_label` | `string` | Prop `linkLabel`. |
| `dateformat` | `string` | Day.js format when `page` ≠ 0. |
| `primarycolor` | `string` | Passed to **`hb-paginate`**. |
| `filter` | `string` | Text filter. |
| `externalfilter` | `string` | Non-empty → host-driven filtering; see README behavior in source. |
| `disabletextfilter` | `string` | `yes` hides text field. |
| `initialdate` / `lastdate` | `Date` \| `string` | Fallback bounds. |

## Custom events

| Event name | `detail` shape |
| --- | --- |
| `pageChange` | `{ page: number; cards: ICard[] }` — slice vs full list per **`externalfilter`**. |
| `textFilterVideos` | `{ filter?: string }` — when **`externalfilter`** and `filter` defined. |
| `dateFilterVideos` | `{ start?: Date; end?: Date; dateKey: "start" \| "end" }` |

## Styling

### CSS custom properties (`extra/docs.ts`)

| Variable | Role |
| --- | --- |
| `--hb-gallery-video-date-input-max-width` | Date inputs max width. |
| `--bulma-column-gap` | Toolbar / grid gutters. |

### CSS parts (`extra/docs.ts`)

| Part | Role |
| --- | --- |
| `container` | Root `container is-fluid`. |

### Slots

**None.**

## TypeScript

`types/webcomponent.type.d.ts` — `Component`, `Events`, `ICard`.

## Minimal HTML example

```html
<hb-gallery-video
  cards='[{"title":"Clip","videoSrc":"https://example.com/video.mp4","time":"2021-08-06T22:46:30.565Z"}]'
  size="12"
  page="0"
></hb-gallery-video>
```

---

<a id="wc-gauge"></a>

# hb-gauge

**Category:** data · **Tags:** data, chart

## Summary

**JustGage** SVG gauge inside the shadow root: pass **`options`** as JSON (or object from JS). Created on mount, destroyed on teardown, recreated on window **resize** (200 ms debounce) and when **`options`** is reparsed from a string.

## Custom element

`hb-gauge`

## Attributes

| Attribute | Type (logical) | HTML / notes |
| --- | --- | --- |
| `options` | `string` \| `GaugeOptions` | **Required** for a useful chart. Merged with `{ element: #gauge }` before JustGage. Minimum: **`value`**, **`min`**, **`max`**; extra keys follow JustGage. |
| `id` | `string` (optional) | |
| `style` | `string` (optional) | Typings only; host `style` still applies to `<hb-gauge>`. |

## Custom events

**None** — `Events` is `{}`.

## Styling

### CSS custom properties (`extra/docs.ts`)

| Variable | Role |
| --- | --- |
| `--hb-gauge-min-height` | Reserved vertical space on `:host` before paint. |

### CSS parts (`extra/docs.ts`)

| Part | Role |
| --- | --- |
| `gauge` | Mount node `#gauge` (`width: 100%`). |

### Slots

**None.**

Bulma is bundled for consistency; the visible dial is SVG from JustGage.

## TypeScript

`types/webcomponent.type.d.ts` — `Component`, `IGauge`, `GaugeOptions`, `Events`.

## Minimal HTML example

```html
<hb-gauge options='{"value":50,"min":0,"max":100}'></hb-gauge>
```

---

<a id="wc-input-area"></a>

# hb-input-area

**Category:** inputs · **Tags:** inputs · **Package:** `@htmlbricks/hb-input-area`

## Summary

Bulma **textarea** in the shadow root, driven by **`schemaentry`** (same shape as **`hb-form`** rows). Emits **`setVal`** and **`clickEnter`** (Enter without Shift).

Typings: `InputAreaParams`, `FormSchemaEntry`, `Component`, `Events` in `types/webcomponent.type.d.ts`.

## Behavior

- **Rendering:** If `schemaentry` is missing, invalid JSON, or otherwise unparsable, the component renders nothing until a valid object is provided.
- **Value sync:** When the fingerprint of `schemaentry` changes, the textarea value is reset from `schemaentry.value` when that property is present; otherwise the value becomes an empty string.
- **Live updates:** The component dispatches **`setVal`** whenever `value`, `valid`, or the field `id` changes (and redundant identical payloads are skipped). The field must have an **`id`** for `setVal` to be emitted.
- **Enter key:** **Enter** without **Shift** prevents the default newline and dispatches **`clickEnter`**. **Shift+Enter** keeps default behavior so users can insert line breaks.
- **Optional `label`:** The shared schema type allows `label`, but this web component’s markup does not render a separate label element; use your host page or a wrapping layout for captions if you need a visible title.

## Validation rules

Validation affects the `valid` flag in events and, when **`show_validation="yes"`** and the field is **`required`**, the `is-success` / `is-danger` classes on the textarea and the optional **`invalid-feedback`** part.

| Mode | `valid` |
| --- | --- |
| No parsed field | `false` |
| **`required: true`** and empty value | `false` |
| **`required: true`** with non-empty value | `true` only if `validationRegex` matches (when set), length ≥ `params.min` (when set), and length ≤ `params.max` (when set) |
| **`required`** not set or **`required: false`** | Always `true` (regex and `min` / `max` are not applied in this branch) |

`params.min` and `params.max` are **inclusive** character counts on the current string value.

## Styling

The shadow tree loads Bulma 1.x form modules (including textarea-related pieces) via `styles/bulma.scss`. Theme the control with **`--bulma-*`** custom properties on **`:host`** (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

### CSS custom properties

| Variable | Role |
| --- | --- |
| `--bulma-primary` | Focus ring and valid-state accents |
| `--bulma-danger` | Invalid border and danger help text |
| `--bulma-border` | Default textarea border |
| `--bulma-text` | Text color inside the control |
| `--bulma-radius` | Corner radius |

### CSS parts

| Part | Target |
| --- | --- |
| `input` | The `<textarea>` element |
| `invalid-feedback` | The `help is-danger` line shown when validation feedback is enabled and the value is invalid |

### Slots

This component does not declare any slots.

## Custom element

```text
hb-input-area
```

## Attributes (snake_case)

Web component attributes are **strings**. Booleans use **`yes`** / **`no`**. Complex objects must be **JSON strings** (escape quotes correctly in HTML).

| Attribute | Required | Description |
| --- | --- | --- |
| `schemaentry` | Yes | JSON string (HTML) or object (JS); see below. |
| `show_validation` | No | `"yes"` or `"no"` (default **`no`**). When `"yes"` and the field is required, invalid state shows danger styling and `validationTip` under the textarea when invalid. |
| `id` | No | Optional host id string (component props typing). |
| `style` | No | Optional inline style string (component props typing). |

### `schemaentry` JSON shape

The payload follows **`FormSchemaEntryShared`** (from `hb-form` typings) with `params` narrowed to **`InputAreaParams`** for this control.

**Commonly used keys**

| Key | Description |
| --- | --- |
| `id` | **Required.** Field identifier; also used in event payloads. |
| `type` | Optional discriminator for form rows; may be omitted when the tag already implies a textarea. |
| `label` | Optional; not rendered by this component’s template. |
| `value` | Optional initial content (coerced with `String(...)`). |
| `placeholder` | Optional placeholder text. |
| `readonly` | Optional boolean in JSON. |
| `disabled` | Optional boolean in JSON. |
| `required` | Optional boolean in JSON. |
| `validationRegex` | Optional string; compiled with `new RegExp(...)`. |
| `validationTip` | Optional message shown in the danger help line when invalid and `show_validation` is enabled. |
| `dependencies` | Optional; carried in the shared schema type for form use cases. |
| `params` | Optional **`InputAreaParams`**: `min` / `max` inclusive length bounds (numbers in JSON). |

## Events

Listen with `addEventListener` or framework equivalents on the host element.

| Event | `detail` |
| --- | --- |
| `setVal` | `{ value: string; valid: boolean; id: string }` |
| `clickEnter` | `{ value: string; valid: boolean; id?: string }` |

## Usage examples

### Minimal host markup

```html
<hb-input-area
  schemaentry="{&quot;id&quot;:&quot;notes&quot;,&quot;label&quot;:&quot;Notes&quot;,&quot;placeholder&quot;:&quot;Type here…&quot;}"
  show_validation="no"
></hb-input-area>
```

### Required field with visible validation

```html
<hb-input-area
  schemaentry="{&quot;id&quot;:&quot;notes&quot;,&quot;required&quot;:true,&quot;placeholder&quot;:&quot;Notes (required)&quot;,&quot;validationTip&quot;:&quot;Please enter at least a few characters.&quot;}"
  show_validation="yes"
></hb-input-area>
```

### Length bounds (required field)

```html
<hb-input-area
  schemaentry="{&quot;id&quot;:&quot;bio&quot;,&quot;required&quot;:true,&quot;params&quot;:{&quot;min&quot;:10,&quot;max&quot;:500},&quot;validationTip&quot;:&quot;10–500 characters.&quot;}"
  show_validation="yes"
></hb-input-area>
```

### Listening from JavaScript

```js
const el = document.querySelector("hb-input-area");

el.addEventListener("setVal", (e) => {
  const { value, valid, id } = e.detail;
  console.log(id, valid, value);
});

el.addEventListener("clickEnter", (e) => {
  const { value, valid, id } = e.detail;
  console.log("enter", id, valid, value);
});
```

## TypeScript

`types/webcomponent.type.d.ts` — `schemaentry` is typed as `string | FormSchemaEntry | undefined` for HTML vs JS consumers.

## Integration with `hb-form`

`schemaentry` matches the shared form row shape so the same JSON you would use in a form schema (for an area / textarea row) can be passed into this standalone control. Coordinate `id` with your surrounding form state and use `setVal` to keep parent models in sync.

---

<a id="wc-input-array-objects"></a>

# hb-input-array-objects

**Category:** inputs · **Tags:** inputs

## Summary

`hb-input-array-objects` is a repeating **object list** editor for structured records. It renders existing rows with **`hb-table`** (with row delete actions) and uses **`hb-form`** driven by **`schemaentry.params.schema`** to compose the next row. Submitting the nested form appends a new object to the value array and updates the table. When the page (or host) uses a **dark** Bulma theme (`prefers-color-scheme: dark`, `data-theme="dark"`, or `.theme-dark` on `html`/`body`/host), the slotted **Add** control is **`is-primary is-outlined`** for clearer contrast on dark surfaces.

The package registers **`hb-form`** and **`hb-table`** as dependencies (same bundle version as this component).

## Behavior

- **Schema:** `params.schema` is a **`hb-form`-compatible** array of field definitions (`type`, `id`, `label`, `params`, …). Nested **`type: "row"`** entries with **`params.columns`** are flattened when building table headers and cell keys: each column field’s **`id`** becomes a property on each stored object.
- **Value:** The live value is an array of plain objects. Each row should include a stable **`_objId`** string (used for table row identity and delete matching). If a row is missing **`_objId`**, the component falls back to a generated id when syncing the table.
- **Add row:** The add block is hidden when **`schemaentry.disabled`** is **`true`**. Table actions are empty in that case, so rows cannot be deleted.
- **Table:** Pagination is turned off (`disablepagination="yes"`). The only configured row action is **delete** (trash/remove style icon).
- **`setVal`:** Whenever the internal value or validity changes, the component dispatches **`setVal`** with **`{ value, valid, id }`**, where **`id`** is **`schemaentry.id`**. Duplicates are suppressed when the payload is unchanged.

### Validation

- If **`schemaentry.required`** is **`true`**, **`valid`** is **`true`** only when **`value`** is a non-empty array; otherwise **`valid`** is **`false`**.
- If **`required`** is not set or is **`false`**, **`valid`** is **`true`**.
- When **`show_validation`** is **`yes`**, **`schemaentry.validationTip`** is shown as Bulma **`help is-danger`** if the field is invalid.

## Styling (Bulma)

Bulma **1.x** is included in the shadow root. Theme the host (or ancestors) with **`--bulma-*`** variables so the table, buttons, and help text match the rest of the app. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

Host-level tokens documented for this component (see **`extra/docs.ts`** / **`styleSetup.vars`**):

| Variable | Role |
|----------|------|
| `--bulma-text` | Table labels, form labels, and help text |
| `--bulma-border` | Outer border around the table + add form block |
| `--bulma-danger` | Invalid validation message color |
| `--bulma-link` | Primary actions (for example the add-row control styled with Bulma’s info tone) |
| `--bulma-scheme-main` | Surface behind nested controls |
| `--bulma-radius` | Corner radius for buttons and table chrome |

## Custom element

`hb-input-array-objects`

## TypeScript

`types/webcomponent.type.d.ts` — `schemaentry` as `string | FormSchemaEntry | undefined`.

## Attributes (snake_case)

Web component attributes are **strings**. Complex data must be **JSON serialized** on the attribute (or set as a property from JavaScript).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes | JSON string describing the field (see below). Parsed with the shared **`parseSchemaentryProp`** helper: accepts a JSON string or, from JS, an object. |
| `show_validation` | No | **`yes`** or **`no`**. Default in the implementation is **`no`**. When **`yes`**, invalid state shows **`validationTip`**. |
| `id` | No | Optional host element id. |
| `style` | No | Optional inline styles on the host. |

### `schemaentry` JSON shape

The serialized object matches **`FormSchemaEntry`** in **`types/webcomponent.type.d.ts`**, with **`params`** typed as **`InputArrayObjectsParams`**.

**Required**

- **`id`** — string; forwarded on **`setVal`** as **`detail.id`**.

**`params` (`InputArrayObjectsParams`)**

| Key | Description |
|-----|-------------|
| **`schema`** | Array of nested field definitions (same logical shape as rows passed to **`hb-form`**). This drives both the **add** form and the **table** columns. |
| **`columns`** | Optional mirror used like row-style layouts; rarely needed (see typings). |
| **`addPropertyLabel`** | String label for the add button when the **`add-object-label`** slot is not used. Default button text is **`Add item`**. |

**Optional top-level fields** (shared with other form entries; see **`FormSchemaEntryShared`** in **`hb-form`** typings)

Examples: **`value`** (initial array of rows), **`required`**, **`disabled`**, **`validationTip`**, **`placeholder`**, **`label`**, **`readonly`**, **`validationRegex`**, **`dependencies`**, etc.

**`value`** (when present) must be an array of objects. Each object should include **`_objId`** plus one property per flattened field **`id`** from **`params.schema`**.

## Events

| Event | `detail` |
|-------|----------|
| **`setVal`** | **`{ value: { _objId: string; [key: string]: unknown }[]; valid: boolean; id: string }`** — current rows, overall validity for this control, and the field **`id`**. |

Listen in HTML with **`addEventListener("setVal", …)`** or the equivalent in your framework.

## Slots

| Slot | Description |
|------|-------------|
| **`add-object-label`** | Optional **light DOM** content for the add-row button label. When present, it overrides **`params.addPropertyLabel`**. |

## CSS `::part` selectors

| Part | Description |
|------|-------------|
| **`properties-container`** | Wrapper around the repeating table and the add form (layout / border overrides from the light DOM). |
| **`invalid-feedback`** | The **`p.help.is-danger`** node used when **`show_validation="yes"`** and the value is invalid. |

## TypeScript

Authoring types for this package live in **`types/webcomponent.type.d.ts`**:

- **`Component`** — host props
- **`FormSchemaEntry`** / **`InputArrayObjectsParams`** — `schemaentry` shape
- **`Events`** — custom event payloads (e.g. **`setVal`**)

## Examples

### Minimal host markup

Use a single-line JSON attribute (or build the string in JavaScript).

```html
<hb-input-array-objects
  schemaentry='{"id":"people","params":{"schema":[{"type":"text","id":"name","label":"Name"}]}}'
  show_validation="no"
></hb-input-array-objects>
```

### Pre-filled rows

Each row needs **`_objId`** and keys matching field ids (here, **`firstName`** / **`lastName`** inside a **`row`**):

```html
<hb-input-array-objects
  schemaentry='{"id":"people","params":{"schema":[{"type":"row","id":"name-row","params":{"columns":[{"type":"text","id":"firstName","label":"First name"},{"type":"text","id":"lastName","label":"Last name"}]}}]},"value":[{"_objId":"1","firstName":"Ada","lastName":"Lovelace"}]}'
></hb-input-array-objects>
```

### Required list with visible validation

```html
<hb-input-array-objects
  schemaentry='{"id":"items","required":true,"validationTip":"Add at least one row.","params":{"schema":[{"type":"text","id":"title","label":"Title"}]}}'
  show_validation="yes"
></hb-input-array-objects>
```

---

<a id="wc-input-array-tags"></a>

# hb-input-array-tags

**Category:** inputs · **Tags:** inputs · **Package:** `@htmlbricks/hb-input-array-tags`

## Summary

Bulma **tag list** input: value is **`Tag[]`** (`id`, `label`, optional metadata). Add via **preset** `availableTags`, **free text** (`freeTag`), or both; optional remove; **`setVal`** sync. **`schemaentry.params`** is optional in typings but you need **`availableTags`** and/or **`freeTag`** for the add UI.

## Custom element

`hb-input-array-tags`

## When to use it

Use this component when you need a **multi-value string list** with a stable **`id` per chip** (not only display text): skills, labels, filters, roles, or any set of tokens you want to serialize as JSON and validate as **required** or optional.

---

## Value shape (`Tag[]`)

Each entry is a **`Tag`**:

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | `string` | Yes | Stable key; duplicates (same `id`) are not added twice. |
| `label` | `string` | Yes | Text shown on the chip. |
| `type` | `string` | No | Reserved for your own semantics (not used by the default UI). |
| `colorVarName` | `string` | No | Same rules as **`params.colorVarName`**: Bulma semantic name, alias **`secondary`**, or **`--`…** CSS variable. Overrides field-level default when set. |
| `icon` | `string` | No | Reserved for your own semantics (not used by the default UI). |

Initial selection is supplied as **`schemaentry.value`** (array of `Tag`). The component emits the current array on every meaningful change via **`setVal`**.

---

## Custom element API

### Attributes (host)

Web component **attributes are strings**. Pass structured data as a **JSON string** on `schemaentry`. Booleans in HTML attributes for this host use **`yes`** / **`no`** where noted.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes | JSON object describing the field (see below). If missing or invalid JSON, nothing is rendered. |
| `show_validation` | No | `"yes"` or `"no"` (default-like behavior when omitted is `"no"` in typings). When `"yes"` and the field is invalid, **`validationTip`** is shown. |
| `array_style` | No | `"pills"` (default when omitted or any other value) or `"area"`. **`area`**: Bulma **`textarea`‑like** region with **squared tokens** (trailing **×** when `allowRemove`) and an inline draft field; presets are filtered as you type into a **custom suggestion panel** (not Bulma’s `dropdown` markup), with **“Add …”** when **`freeTag`** is enabled. **`pills`**: original row of tags plus **+** / select / small input. |
| `id` | No | Optional id on the host element. |
| `style` | No | Optional inline styles on the host element. |

### `schemaentry` object

The JSON object follows **`FormSchemaEntry`** for this control: shared form keys plus optional **`params`** as **`InputArrayTagsParams`**.

**Required**

- **`id`** — string; forwarded on **`setVal`** as `detail.id`.

**Strongly recommended**

- **`params`** — see next section (needs `availableTags` and/or `freeTag` for add UI).

**Commonly used optional keys**

- **`value`** — `Tag[]`; initial tags.
- **`required`** — boolean; when `true`, **`valid`** is `false` until at least one tag exists.
- **`disabled`** — boolean; disables add/remove and controls.
- **`validationTip`** — string; shown when `show_validation` is `"yes"` and the field is invalid.

Other keys from the shared form schema (`label`, `placeholder`, `readonly`, `dependencies`, etc.) may be present for consistency with **`hb-form`** or your app metadata; this component’s template focuses on tags, add controls, and validation help.

### `schemaentry.params` (`InputArrayTagsParams`)

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `availableTags` | `Tag[]` | — | When **non-empty**, choosing “add” opens a **`<select>`** of presets (by `id` / `label`). |
| `freeTag` | `boolean` | — | When **true**, users can add tags by **typing** (dedicated input or **“New Tag”** path from the select). |
| `colorVarName` | `string` | — | Chip styling: **Bulma semantic** names get **`tag is-<name>`** (`primary`, `link`, `info`, `success`, `warning`, `danger`, `dark`, `light`, `text`, `white`, `black`), plus alias **`secondary`** → **`link`**. If the string **starts with `--`**, it is treated as a **CSS custom property** for **`background-color: var(<token>)`** with light text (legacy / custom themes). Per-tag `colorVarName` overrides the field default for that chip. |
| `allowRemove` | `boolean` | — | When **true**, each chip gets a **remove** control (**`pills`**: Bulma **`delete`**; **`area`**: **×** on the squared token). |
| `addTagLabel` | `string` | — | Label for the add chip; default **`+`**. Overridden by the **`add-tag-label`** slot when slotted. |

**Add UI rules**

- The add affordance is shown only if **`params.availableTags`** has length **or** **`params.freeTag`** is true.
- **Presets only:** `availableTags` set, `freeTag` false → add opens the select; choosing an option adds that tag.
- **Free text only:** no `availableTags` (or empty), `freeTag` true → add opens a small text field; **Enter** commits; **Escape** cancels.
- **Both:** select lists presets; if **`freeTag`** is true, an extra option **“New Tag”** (`_custom`) switches to the text input.
- **Duplicates:** a tag whose **`id`** already exists in `value` is not added again.

**Commit vs blur (free-text input)**

- **Enter** commits the trimmed input as a new tag (`id` and `label` both set to that string).
- A **click outside** the host (window listener, `composedPath` shadow-DOM aware) also commits trimmed text when the inline input is open.
- **Blur** on the inline input closes and clears without committing (use Enter or click-outside to save).

### `array_style="area"` (textarea-like)

- **Squared tokens** (not Bulma pill `tag`s) plus an **inline text field** sit inside a Bulma **`textarea`** shell; each token shows a trailing **×** when **`allowRemove`** is on. **`schemaentry.placeholder`** is used on the draft field.
- **Suggestions** list: unselected **`availableTags`** filtered by the current draft (`id` / `label` substring, case-insensitive). With **`freeTag`**, an extra row shows **Add** plus the draft in quotes when the draft is non-empty and not already selected. The list is a **custom panel** (`hb-input-array-tags-suggest-*`) with **`position: fixed`**: it anchors to the **end of the draft text** in the viewport (caret position does not move the panel), **to the right** when there is room (otherwise flipped to the left), and overlays the field so it stays clickable. Panel **width** follows the widest suggestion row (`max-content`), capped at the viewport. Layout updates on **scroll** (capture), **resize**, and typing. Styled with **`--bulma-*`** tokens so it survives Svelte WC CSS compilation (no reliance on Bulma `dropdown-*` class names in markup).
- **Keyboard:** **Arrow Up/Down** move the active row; **Enter** applies the active row, or the only match, or commits free text when **`freeTag`**; **comma** commits free text when **`freeTag`** and the draft is non-empty; **Backspace** on an empty draft removes the last chip; **Escape** clears the draft and closes the list.
- **Mouse:** choose a suggestion (**`mousedown`** uses **`preventDefault()`** so the field keeps focus). **Click outside** the host still commits trimmed free text (same window listener as pills) when **`freeTag`** is on.
- **`add-tag-label`** slot applies to the pills **+** control only, not the area field.

---

## Events

| Event | `detail` | When |
|-------|----------|------|
| `setVal` | `{ value: Tag[]; valid: boolean; id: string }` | Whenever `value`, `valid`, or field `id` changes in a way that affects the payload (internally deduplicated so identical payloads are not re-fired). |

Listen in plain DOM:

```js
el.addEventListener("setVal", (e) => {
  const { value, valid, id } = e.detail;
});
```

---

## Slots

| Slot | Description |
|------|-------------|
| `add-tag-label` | Light DOM content for the add control. When provided, replaces **`params.addTagLabel`** and the default **`+`**. |

---

## Theming and `::part`

**Bulma CSS variables** on **`:host`** control chrome and validation (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)). The catalog-style list maintained for tooling lives in **`extra/docs.ts`** (`styleSetup.vars`), including:

- **`--bulma-text`**, **`--bulma-text-weak`** — text and muted hints.
- **`--bulma-border`** — input/select borders.
- **`--bulma-danger`** — invalid help text.
- **`--bulma-link`** — interactive accents / focus alignment.
- **`--bulma-primary`** — default chip background when no `colorVarName` is set (Bulma **`is-light`**-style chips otherwise).

| CSS part | Description |
|----------|-------------|
| `invalid-feedback` | The **`p.help.is-danger`** node when **`show_validation`** is **`yes`** and the field is invalid. |

---

## TypeScript types

Authoring types for apps and wrappers live in **`types/webcomponent.type.d.ts`**:

- **`Tag`**, **`InputArrayTagsParams`**, **`FormSchemaEntry`**, **`Component`**, **`Events`**

---

## Examples

### Minimal free-text tags (HTML)

```html
<hb-input-array-tags
  schemaentry='{"id":"skills","params":{"freeTag":true,"allowRemove":true}}'
  show_validation="yes"
></hb-input-array-tags>
```

### Presets + optional custom tag

```html
<hb-input-array-tags
  schemaentry='{"id":"roles","required":true,"validationTip":"Pick at least one role.","params":{"availableTags":[{"id":"admin","label":"Admin"},{"id":"user","label":"User"}],"freeTag":true,"allowRemove":true,"addTagLabel":"Add"}}'
  show_validation="yes"
></hb-input-array-tags>
```

### Initial value and themed chips

```html
<hb-input-array-tags
  schemaentry='{"id":"labels","value":[{"id":"bug","label":"bug"},{"id":"feature","label":"feature"}],"params":{"freeTag":true,"allowRemove":true,"colorVarName":"--bulma-primary"}}'
></hb-input-array-tags>
```

### Programmatic updates (JavaScript)

Assign a **new JSON string** to `schemaentry` when your field definition or initial `value` changes; the component syncs from the serialized schema when its fingerprint changes.

```js
const el = document.querySelector("hb-input-array-tags");

el.setAttribute(
  "schemaentry",
  JSON.stringify({
    id: "tags",
    value: [{ id: "a", label: "Alpha" }],
    params: { freeTag: true, allowRemove: true },
  }),
);
```

Remember: from HTML, use attribute-safe JSON (single-quoted attribute, double quotes inside, or generate the string in code).

---

## TypeScript

`types/webcomponent.type.d.ts` — `schemaentry` as `string | FormSchemaEntry | undefined`; `params` optional on `FormSchemaEntry`.

## Build note (monorepo)

From the **`builder`** package root, a single-package web component build can be run with:

`npm run build:wc -- input-array-tags`

Use the full workspace pipeline when you need all packages or regenerated global element typings.

---

<a id="wc-input-checkbox"></a>

# hb-input-checkbox

**Category:** inputs · **Tags:** inputs

## Summary

Labeled **checkbox** or Bulma **switch** (`params.type === "switch"`) from **`schemaentry`**. Emits **`setVal`** on checked/validity changes. Missing or invalid **`schemaentry`** → nothing rendered.

`schemaentry` is typed as optional `string | FormSchemaEntry` for HTML vs JS; the control appears once a valid field is parsed.

## Custom element

```html
<hb-input-checkbox></hb-input-checkbox>
```

Tag name: **`hb-input-checkbox`**

## Attributes (host)

Web component attributes use **snake_case**. Values exposed through HTML attributes are **strings** (including JSON for structured data and **`yes`** / **`no`** for booleans where noted).

| Attribute | Required | Description |
|-----------|----------|-------------|
| **`schemaentry`** | Yes (for visible UI) | JSON string (HTML) or object (JS). Must include a string **`id`** when parsed. See [Schema (`schemaentry`)](#schema-schemaentry) below. |
| **`show_validation`** | No | **`yes`** or **`no`** (default **`no`**). When **`yes`**, a required but unchecked field shows **`validationTip`** inside the shadow tree and the host gets an invalid outline. |
| **`id`** | No | Optional id on the custom element host. |
| **`style`** | No | Optional inline styles on the host element. |

## Schema (`schemaentry`)

The TypeScript shape is built from shared form row fields plus checkbox-specific **`params`**. At minimum, provide **`id`**. Other keys are optional unless you need labels, defaults, or validation UX.

| Key | Description |
|-----|-------------|
| **`id`** | Stable field identifier. Used in **`setVal`** payloads and as the native control **`id`**. |
| **`label`** | Text beside the control. A **`*`** is appended when **`required`** is true. |
| **`required`** | When true, **`valid`** is false until the control is checked. |
| **`value`** | Initial / synced value: boolean, number, or string (coerced to boolean with JavaScript truthiness when applied). |
| **`validationTip`** | Message shown when **`show_validation`** is **`yes`**, **`required`** is true, and the box is unchecked. |
| **`disabled`** | When true, the input is disabled and not interactive. |
| **`readonly`** | When true, the input is disabled for interaction (same net effect as **`disabled`** for the native control). |
| **`placeholder`** | Allowed by the shared schema; this checkbox implementation does not render a separate placeholder control. |
| **`params`** | See [Switch mode (`params.type`)](#switch-mode-paramstype). |

### Switch mode (`params.type`)

Optional **`params`** object:

| Key | Values | Description |
|-----|--------|-------------|
| **`type`** | **`"switch"`** (or omit / other for default) | When **`"switch"`**, the native checkbox is styled as a Bulma-colored switch (track and knob). Otherwise a standard checkbox appearance is used. |

## TypeScript

`types/webcomponent.type.d.ts` — **`InputCheckboxParams`**, **`FormSchemaEntry`**, **`Component`**, **`Events`**.

## Validation and events

**Validity:** If **`required`** is true, **`valid`** is true only when the control is checked. If **`required`** is false or omitted, **`valid`** is always true.

**`setVal` custom event:** Fired when **`value`**, **`valid`**, or the field **`id`** changes, after internal de-duplication of identical payloads.

| Property | Type | Description |
|----------|------|-------------|
| **`detail.value`** | `boolean` | Current checked state. |
| **`detail.valid`** | `boolean` | Whether the field satisfies **`required`**. |
| **`detail.id`** | `string` | The **`schemaentry.id`**. |

Listen in HTML or script:

```html
<hb-input-checkbox id="terms"></hb-input-checkbox>
<script>
  document.getElementById("terms").addEventListener("setVal", (e) => {
    console.log(e.detail); // { value, valid, id }
  });
</script>
```

## Styling

Styles use **Bulma 1.x** Sass inside the shadow root. The bundle forwards Bulma form layers **`form/shared`**, **`form/input-textarea`**, **`form/checkbox-radio`**, and **`form/tools`**, then applies the light theme on **`:host`** (see **`styles/bulma.scss`**). Component-specific layout and switch visuals are in **`styles/webcomponent.scss`**.

Theme the host with **Bulma CSS variables** (see [Bulma: CSS variables](https://bulma.io/documentation/features/css-variables/)).

Documented **`--bulma-*`** tokens for this package (from **`extra/docs.ts`** / **`styleSetup.vars`**):

| Variable | Role |
|----------|------|
| **`--bulma-text`** | Label text next to the checkbox or switch. |
| **`--bulma-border`** | Checkbox border and switch track border. |
| **`--bulma-link`** | Switch “on” fill and border when **`params.type`** is **`"switch"`**. |
| **`--bulma-danger`** | Invalid outline and danger help text when validation is shown. |
| **`--bulma-radius`** | Corner radius for the switch track area. |
| **`--bulma-radius-rounded`** | Pill radius for the circular switch knob. |

Switch styling also reads optional greys / scheme variables (for example **`--bulma-grey-*`**, **`--bulma-scheme-*`**, **`--bulma-white`**, **`--bulma-input-*`**) as fallbacks in **`styles/webcomponent.scss`**.

## CSS parts (`::part`)

| Part | Description |
|------|-------------|
| **`input`** | The native checkbox input (standard mode) or the same element styled as the switch track and knob (**`switch`** mode). |
| **`invalid-feedback`** | The **`p.help.is-danger`** paragraph when **`show_validation`** is **`yes`** and the required field is invalid. |

## Slots

None.

## Examples

**Required agreement checkbox**

```html
<hb-input-checkbox
  schemaentry='{"id":"accept","label":"I agree to the terms","required":true,"validationTip":"You must accept to continue."}'
  show_validation="yes"
></hb-input-checkbox>
```

**Pre-checked optional subscription**

```html
<hb-input-checkbox
  schemaentry='{"id":"newsletter","label":"Email me updates","required":false,"value":true}'
></hb-input-checkbox>
```

**Switch UI**

```html
<hb-input-checkbox
  schemaentry='{"id":"notifications","label":"Enable notifications","params":{"type":"switch"},"value":false}'
></hb-input-checkbox>
```

**Disabled / locked state**

```html
<hb-input-checkbox
  schemaentry='{"id":"locked","label":"Terms accepted","value":true,"disabled":true}'
></hb-input-checkbox>
```

## Package metadata

- **npm scope / repo name:** `@htmlbricks/hb-input-checkbox` (see **`extra/docs.ts`**)
- **License:** Apache-2.0 (see **`extra/docs.ts`** → **`licenses`**)

---

<a id="wc-input-color"></a>

# `hb-input-color`

**Category:** inputs · **Tags:** inputs

## Overview

`hb-input-color` is a form-oriented color field built on the native HTML **`type="color"`** control inside a Bulma-styled shadow tree. It is driven by a single **`schemaentry`** payload (JSON). The component normalizes initial **`value`** strings—named HTML colors and `rgb(...)` / comma-separated RGB forms—into a leading-**`#`** hex string suitable for the native picker, then keeps internal state in sync with a short **debounced** pass when the user changes the swatch.

Nothing is rendered until **`schemaentry`** parses successfully into an object with a usable shape; invalid JSON or a missing parseable object leaves the host empty.

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-color` | `<hb-input-color …></hb-input-color>` |

## Attributes (web component)

Attributes use **snake_case**. In plain HTML, attribute values are **strings**. Pass **`schemaentry`** as a **JSON string** (escape quotes as required by your templating layer).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes (for UI) | JSON string describing the field (see below). Invalid or missing JSON yields no rendered field. |
| `show_validation` | No | `"yes"` or `"no"`. When `"yes"`, invalid required fields show **`validationTip`** in a danger help line. Default in implementation: `"no"`. |
| `id` | No | Optional id on the host element (component prop; not the same as the field `id` inside `schemaentry`). |
| `style` | No | Optional inline styles on the host element. |

Boolean-like flags inside **`schemaentry`** follow the usual object/JSON rules (`true` / `false` in JSON—not the string `"yes"` / `"no"` on inner keys unless your serializer does that).

## `schemaentry` shape

The TypeScript type is **`FormSchemaEntry`** in `types/webcomponent.type.d.ts`: shared form fields from `FormSchemaEntryShared`, with **`params`** narrowed to **`InputColorParams`** (`Record<string, never>`). **`params`** is **not read** by this component today; omit it or use `{}` so future options can be added without breaking authors.

Fields **read by this component** include:

| Field | Role |
|-------|------|
| `id` | **`string`**, required for **`setVal`** dispatch (the effect bails out if `id` is missing). Also set as the **`id`** attribute on the native `<input type="color">`. |
| `value` | Optional initial color; coerced with `String(...)`, then normalized (named colors → hex, RGB → hex) when applied from schema. |
| `required` | When truthy, **`valid`** is false until the normalized string **`value`** has length **greater than 1** (so a lone `"#"` is invalid). When falsy, the field is always considered valid. |
| `validationTip` | Message shown when **`show_validation`** is `"yes"` and the field is invalid. |
| `placeholder` | Forwarded to the native input (many browsers ignore placeholder for `type="color"`). |
| `readonly` | Forwarded to the native input. |
| `disabled` | When truthy, the native input is disabled. |

Other shared keys (for example **`label`**, **`type`**, **`dependencies`**, **`validationRegex`**) exist on the shared type for consistency with the form system; **`label`** is **not** rendered as visible markup in this component—only the color control and optional validation help are shown. **`validationRegex`** is **not** used in the current **`valid`** derivation.

## Validation and `setVal`

**`valid`** is derived as: optional fields are always valid; required fields are valid when the current string **`value`** has **`length > 1`** (after normalization from schema updates).

The component dispatches a **`setVal`** **`CustomEvent`** when the field is active, **`id`** is set, and the payload **`{ value, valid, id }`** changes compared to the last dispatch (duplicate payloads are suppressed).

| Property | Type | Meaning |
|----------|------|---------|
| `value` | `string` | Current normalized value string (hex-oriented after boot from schema). |
| `valid` | `boolean` | Whether the field satisfies the rule above. |
| `id` | `string` | Same as **`schemaentry.id`**. |

Listen with `addEventListener("setVal", ...)` or your framework’s DOM event binding.

## Behavior notes

- **Normalization on load from schema:** Named HTML colors are resolved via the internal color map; `rgb(...)` and similar comma forms are converted to hex (`#` prefix) where parsing succeeds.
- **User input:** The visible control is bound to **`colorVal`**; on **`input`**, a **200 ms** debounced handler resets the canonical **`value`** from **`colorVal`**, so rapid changes do not thrash derived state.
- **Schema sync:** Updates from the parent **`schemaentry`** string are applied when the schema **fingerprint** changes (see `../lib/schemaentry.ts`), avoiding feedback loops with local edits.

## Styling

Bulma-derived styling is loaded from the component’s SCSS entry (`styles/bulma.scss` forwards Bulma modules and theme setup on `:host`). Theme tokens are standard **`--bulma-*`** variables documented in Bulma’s [CSS variables](https://bulma.io/documentation/features/css-variables/) guide.

### CSS custom properties (`:host`)

| Variable | Role |
|----------|------|
| `--bulma-text` | Label and helper text color. |
| `--bulma-border` | Border around the native color swatch. |
| `--bulma-danger` | Invalid outline and danger help emphasis. |
| `--bulma-radius` | Corner radius of the color control. |
| `--bulma-scheme-main` | Surface tone where Bulma maps scheme colors onto inputs. |

The authoritative list for catalog metadata is mirrored in **`extra/docs.ts`** (`styleSetup.vars`).

### CSS `::part` API

| Part | Description |
|------|-------------|
| `input` | The native **`type="color"`** element (class `hb-input-color-native`). |
| `invalid-feedback` | The **`p.help.is-danger`** node when **`show_validation`** is `"yes"` and the value is invalid. |

## Slots

None (`htmlSlots` is empty in **`extra/docs.ts`**).

## TypeScript

Authoring types live in **`types/webcomponent.type.d.ts`**: **`InputColorParams`**, **`FormSchemaEntry`**, **`Component`**, **`Events`**.

## Examples

Minimal required color with initial hex value:

```html
<hb-input-color
  schemaentry='{"id":"accent","required":true,"value":"#07689f"}'
  show_validation="no"
></hb-input-color>
```

Optional field (always valid regardless of empty value):

```html
<hb-input-color
  schemaentry='{"id":"bg","required":false,"label":"Background"}'
></hb-input-color>
```

Show validation message when required and invalid:

```html
<hb-input-color
  schemaentry='{"id":"brand","required":true,"validationTip":"Pick a color to continue."}'
  show_validation="yes"
></hb-input-color>
```

Disabled preset:

```html
<hb-input-color
  schemaentry='{"id":"locked","value":"#ff5733","disabled":true,"label":"Theme (locked)"}'
></hb-input-color>
```

In JavaScript you can set **`schemaentry`** as an object where the custom-element layer maps props to attributes, but from **HTML** always pass the serialized JSON string on the attribute.

## Package metadata

Display name and npm-style package key used in the builder catalog: **`hb-input-color`** / **`@htmlbricks/hb-input-color`**. See **`extra/docs.ts`** (`componentSetup`) for Storybook args, extended examples, and **`styleSetup`**.

---

<a id="wc-input-coords"></a>

# `hb-input-coords`

**Category:** inputs · **Tags:** inputs, maps

Latitude and longitude editor for forms and standalone pages. It embeds **`hb-map`** for picking a point on the map and two **`hb-input-number`** fields for numeric latitude and longitude. The host passes a single JSON **`schemaentry`** describing the field; the component dispatches **`setVal`** whenever the effective value or validity changes.

This package registers **`@htmlbricks/hb-map`** and **`@htmlbricks/hb-input-number`** as dependencies (same bundle version).

---

## Features

- **Map + inputs:** Click the map to set coordinates; edit lat/lon in the number fields; both stay in sync.
- **Responsive layout:** Map and fields sit side by side from the tablet breakpoint up; on smaller screens the map stacks above the fields.
- **Validation:** Optional inline help when **`show_validation="yes"`**. For **`required`** fields, coordinates **`(0, 0)`** are treated as “empty” (invalid), so you can distinguish “not set” from a real position at the origin only when the field is not required.
- **Disabled state:** With **`disabled: true`** on the schema entry, the map is non-interactive (reduced opacity, no pointer events) and both number inputs are disabled.
- **Labels:** Built-in English strings can be overridden per field via **`schemaentry.params`** or by passing **`i18nlang`** for the bundled dictionary.

---

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-coords` | `<hb-input-coords …></hb-input-coords>` |

---

## Attributes

Web component attributes are **strings**. Use **`yes`** / **`no`** for booleans where noted. Object-shaped data (the whole schema entry) must be **JSON serialized** into the attribute value.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes | JSON string describing the field (see below). If missing or invalid JSON, nothing is rendered. |
| `show_validation` | No | `"yes"` or `"no"` (default `"no"`). When `"yes"` and the field is invalid, **`validationTip`** is shown in the danger help area (see **`::part`**). |
| `i18nlang` | No | Locale hint for built-in words (e.g. latitude / longitude labels and placeholders). |
| `id` | No | Optional id on the host element. |
| `style` | No | Optional inline styles on the host element. |

### `schemaentry` JSON shape

The payload follows the shared form schema entry shape (see **`../form/types/webcomponent.type.d.ts`** — **`FormSchemaEntryShared`**) plus this component’s **`value`** and **`params`**:

| Property | Description |
|----------|-------------|
| `id` | **Required.** Stable field id; included on every **`setVal`** detail. |
| `label` | Optional main label context (child inputs use lat/lon-specific labels from params or i18n). |
| `required` | If true, **`(lat === 0 && lon === 0)`** fails validation. |
| `disabled` | If true, map and inputs are non-interactive. |
| `validationTip` | Message shown when **`show_validation`** is **`yes`** and the value is invalid. |
| `value` | Optional **`{ "lat": number, "lon": number }`** — initial or controlled coordinates. |
| `params` | Optional **`InputCoordsParams`** (see types file) — map and UI tuning. |

### `params` (`InputCoordsParams`)

| Key | Description |
|-----|-------------|
| `zoom` | Default map zoom when the entry is applied from schema (fallback **14** if unset). |
| `center` | **`[lng, lat]`** (or map center tuple) typed for the embedded map (**`InputCoordsParams`** in **`types/webcomponent.type.d.ts`**). |
| `source` | **`InputCoordsMapSource`**: **`{ "type": string, "url"?: string }`** passed to **`hb-map`** (defaults to **`{ "type": "osm" }`** when omitted). |
| `options` | **`InputCoordsMapOptions`**, e.g. **`{ "centerFromGeometries": true }`** (default when omitted). |
| `latitudeLabel` / `longitudeLabel` | Override labels on the two number fields. |
| `latitudePlaceholder` / `longitudePlaceholder` | Override placeholders. |

---

## Events

Listen for **`setVal`** on the host. The detail matches **`Events`** in **`types/webcomponent.type.d.ts`**:

| Event | `detail` |
|-------|----------|
| `setVal` | **`{ value: { lat: number; lon: number } \| undefined; valid: boolean; id: string }`** |

The component avoids redundant dispatches when the payload is unchanged. **`valid`** reflects the current rules (including the **`(0,0)`** rule for required fields).

---

## Styling

Bulma is loaded in the shadow root with **`--bulma-*`** tokens for text, borders, danger help, and accents used by nested controls. Component-specific sizing is controlled on **`:host`**:

| CSS variable | Purpose |
|--------------|---------|
| `--hb-input-coords-height` | Optional height of the whole block (e.g. **`400px`**, **`60vh`**). Map and coordinate columns stretch to match. When unset, height follows the coordinates column on larger breakpoints (map matches that row height). |
| `--hb-input-coords-map-min-height` | Optional minimum height of the map column on tablet+ when the coordinate block is shorter. |
| `--hb-input-coords-map-stacked-min-height` | Minimum map height when columns stack (mobile). Default chain includes **`--hb-input-coords-map-height`** then **`280px`**. |
| `--hb-input-coords-map-height` | Legacy fallback for stacked map minimum height when **`--hb-input-coords-map-stacked-min-height`** is unset (default **`280px`**). |
| `--bulma-text`, `--bulma-border`, `--bulma-danger`, `--bulma-link` | Bulma theme tokens (see **`extra/docs.ts`** / **`styleSetup`**). |

### `::part` names

| Part | Description |
|------|-------------|
| `invalid-feedback` | The **`p.help.is-danger`** node when **`show_validation="yes"`** and the coordinates are invalid. |

There are **no** light-DOM slots for this component.

---

## TypeScript

Authoring types live in **`types/webcomponent.type.d.ts`**:

- **`Component`** — host props
- **`Events`** — custom event payloads
- **`FormSchemaEntry`**, **`InputCoordsParams`**, **`InputCoordsMapSource`**, **`InputCoordsMapOptions`**

Built packages also emit **`types/html-elements.d.ts`** and **`types/svelte-elements.d.ts`** for DOM and Svelte consumers.

---

## HTML example

Pass **`schemaentry`** as a single JSON string. Using single quotes on the attribute keeps the inner JSON readable:

```html
<hb-input-coords
  schemaentry='{"id":"location","required":true,"label":"Pickup point","validationTip":"Choose a valid point on the map.","value":{"lat":45.4642,"lon":9.19},"params":{"zoom":12,"source":{"type":"osm"},"options":{"centerFromGeometries":true}}}'
  show_validation="yes"
  i18nlang="en"
></hb-input-coords>
```

Minimal required-only example:

```html
<hb-input-coords
  schemaentry='{"id":"coords","required":true}'
  show_validation="no"
></hb-input-coords>
```

```js
document.querySelector("hb-input-coords").addEventListener("setVal", (e) => {
  console.log(e.detail.value, e.detail.valid, e.detail.id);
});
```

---

## Related metadata

Storybook knobs and catalog-style metadata (description, **`styleSetup`**, examples) are maintained in **`extra/docs.ts`** (**`componentSetup`**).

---

<a id="wc-input-date"></a>

# `hb-input-date`

**Category:** inputs · **Tags:** inputs

## Description

`hb-input-date` is a native HTML **`type="date"`** control wrapped in Bulma **`field`** / **`control`** markup. It reads its configuration from a serialized **`schemaentry`** object (same shape as form rows in `hb-form`), keeps the chosen value in sync with that schema, and optionally shows Bulma validation styling (**`is-success`** / **`is-danger`**) plus **`help is-danger`** feedback.

Optional inclusive bounds come from **`schemaentry.params`**: **`min`** and **`max`** are parsed with **`new Date(...)`** for validation. When those dates are finite, the same bounds are written to the native **`min`** / **`max`** attributes using the UTC calendar day from **`Date#toISOString()`** (first 10 characters, `YYYY-MM-DD`).

The component dispatches **`setVal`** whenever the current value or validity changes (for fields with an **`id`**), and **`clickEnter`** when the user presses **Enter** inside the input.

## Styling (Bulma)

Bulma **1.x** Sass is bundled in the shadow root: **`form/shared`**, **`form/input-textarea`**, and **`form/tools`**, with the light theme applied on **`:host`**. Set **`--bulma-*`** on the host (or on ancestors so they inherit) — see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/). Public tokens are listed in **`extra/docs.ts`** (`styleSetup.vars`).

| CSS variable (`:host`) | Role |
|------------------------|------|
| `--bulma-text` | Label, native date value, and help text. |
| `--bulma-border` | Input border before success/danger modifiers. |
| `--bulma-danger` | Invalid state border and **`help is-danger`** emphasis. |
| `--bulma-success` | Valid state (**`is-success`**) when validation styling is enabled. |
| `--bulma-scheme-main` | Input surface background inside the shadow root. |

The host is **`display: inline-block`** with flush field margins (see **`styles/webcomponent.scss`**).

## CSS `::part` names

| Part | Description |
|------|-------------|
| `invalid-feedback` | The **`p.help.is-danger`** node shown when **`show_validation="yes"`**, the field is invalid, and **`validationTip`** is set on **`schemaentry`**. |

## HTML slots

None.

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-date` | `<hb-input-date …></hb-input-date>` |

## Attributes (snake_case; string values in HTML)

From **`types/webcomponent.type.d.ts`**, all reflected host attributes are strings.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes | JSON string describing the field (see below). May be updated over time; when its fingerprint changes, the internal value is reset from **`schemaentry.value`**. |
| `show_validation` | No | **`"yes"`** or **`"no"`** (default **`"no"`**). When **`"yes"`** and the field is **`required`**, the input gets **`is-success`** / **`is-danger`** and an optional danger **`help`** line. |
| `id` | No | Optional id on the host element. |
| `style` | No | Optional inline styles on the host element. |

### `schemaentry` JSON shape

The payload is a **`FormSchemaEntry`** for this host (see **`types/webcomponent.type.d.ts`**), based on **`FormSchemaEntryShared`** from **`form/types/webcomponent.type.d.ts`**, with **`params`** narrowed to **`InputDateParams`**.

| Property | Required | Description |
|----------|----------|-------------|
| `id` | **Yes** (for events) | Passed to the native **`input`** as **`id`**. **`setVal`** is only emitted when **`id`** is present. |
| `label` | No | Not rendered by this component; useful for forms and documentation. |
| `value` | No | Initial / controlled value, stringified when applied. Use an ISO-like date string (e.g. **`"1984-02-27"`**); the control uses **`type="date"`**. |
| `required` | No | When **`true`**, an empty value fails validation, and **`min`** / **`max`** (if set) are enforced inclusively. When **`false`** or omitted, the derived validity is always **`true`**. |
| `validationTip` | No | Message shown inside **`::part(invalid-feedback)`** when **`show_validation="yes"`** and the field is invalid. |
| `placeholder` | No | Forwarded to the native **`placeholder`** (browser support for placeholders on date inputs varies). |
| `readonly` | No | Forwarded to the native **`readonly`** attribute. |
| `disabled` | No | When **`true`**, the input is disabled. |
| `params` | No | **`InputDateParams`**: optional **`min`** / **`max`**, each a **`string`** or **`number`** understood by **`new Date(...)`**. Used for inclusive range checks and, when valid, for native **`min`** / **`max`**. |

Other keys from the shared form schema type (**`type`**, **`dependencies`**, **`validationRegex`**, etc.) may appear in JSON for compatibility with **`hb-form`** but are not used by this component’s template.

### Validation rules

- **`show_validation="yes"`** and **`required`**: the input is **`is-success`** when valid and **`is-danger`** when invalid.
- **`required`**: the user must pick a date (**non-empty** value).
- **`params.min`** / **`params.max`**: if present and parse to a real date, the chosen date’s timestamp must be **≥ `min`** and **≤ `max`** (inclusive). If **`required`** is false, these bounds are not applied by the derived **`valid`** flag (it stays **`true`**).

## Events

| Event | `detail` | When |
|-------|----------|------|
| `setVal` | `{ value: string \| undefined; valid: boolean; id: string }` | After **`schemaentry`** is applied and whenever **`value`** or **`valid`** changes; skipped if **`schemaentry.id`** is missing. |
| `clickEnter` | `{ value: string \| undefined; valid: boolean; id?: string }` | **`keypress`** with **`Enter`** on the input (**`preventDefault`**). |

Listen with **`addEventListener`** in the same way as for other **`hb-*`** elements.

## Usage notes

- **`schemaentry`** must be valid JSON when provided as a string; invalid JSON yields no parsed field and nothing is rendered inside the shadow root.
- Booleans in HTML attributes use this library’s convention: **`show_validation="yes"`** / **`"no"`** (not bare boolean attributes).
- **`setVal`** deduplication uses the same payload shape internally; consumers should not rely on duplicate suppression details.
- **Timezone:** bounds use **`Date`** arithmetic; the attributes **`min`** / **`max`** on the native control use the UTC date from **`toISOString()`**, which can differ from local calendar expectations for edge timestamps — prefer plain calendar dates (e.g. midnight UTC strings) when possible.

## TypeScript

Authoring types live in **`types/webcomponent.type.d.ts`**:

- **`InputDateParams`** — `{ min?: string | number; max?: string | number }`
- **`FormSchemaEntry`** — shared form row fields plus optional **`value`** and **`params`**
- **`Component`** — host props: **`id?`**, **`style?`**, **`show_validation?`**, **`schemaentry`**
- **`Events`** — **`setVal`** and **`clickEnter`** payloads

Built packages also emit **`types/html-elements.d.ts`** and **`types/svelte-elements.d.ts`** after **`npm run build:wc`**.

## Minimal HTML example

```html
<hb-input-date
  schemaentry="{&quot;id&quot;:&quot;birth_date&quot;,&quot;label&quot;:&quot;Birth date&quot;,&quot;required&quot;:true,&quot;validationTip&quot;:&quot;Please choose a valid date.&quot;}"
  show_validation="yes"
></hb-input-date>
```

With optional bounds (values must be JSON-escaped in HTML):

```html
<hb-input-date
  schemaentry="{&quot;id&quot;:&quot;appointment&quot;,&quot;required&quot;:true,&quot;params&quot;:{&quot;min&quot;:&quot;2024-01-01&quot;,&quot;max&quot;:&quot;2024-12-31&quot;}}"
  show_validation="no"
></hb-input-date>
```

---

<a id="wc-input-datetime"></a>

# `hb-input-datetime`

**Category:** inputs · **Tags:** inputs

## Overview

`hb-input-datetime` is a custom element that composes **`hb-input-date`** and several **`hb-input-number`** instances into one Bulma **`field is-grouped is-grouped-multiline`** row: a calendar date, then hours, minutes, and (by default) seconds, separated by colons. The host derives a single **ISO 8601** string (via `Date#toISOString()`) from the combined local date and time and reports it through the **`setVal`** custom event together with a **`valid`** flag.

The implementation registers the child packages **`@htmlbricks/hb-input-date`** and **`@htmlbricks/hb-input-number`** (same version as the bundle) so nested elements resolve correctly.

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-datetime` | `<hb-input-datetime …></hb-input-datetime>` |

## Host attributes and properties

Web component **attributes are strings**. Pass complex data as a **JSON string** (or set the `schemaentry` property to an object from JavaScript). Boolean-like host flags use **`yes`** / **`no`** where noted.

| Name | Required | Description |
|------|----------|-------------|
| `id` | No | Optional id on the host element. |
| `style` | No | Optional inline styles on the host. |
| `schemaentry` | Yes* | JSON (string or object) describing the field; see [Schema](#schema). If parsing fails, nothing is rendered. |
| `show_validation` | No | **`yes`** or **`no`** (default **`no`**). When **`yes`**, and the combined value is invalid while `validationTip` is set, a Bulma **`help is-danger`** line is shown (exposed as a CSS `::part`). |

\*Consumers should always supply a valid `schemaentry` the parser can read; invalid JSON yields an empty host.

## Schema

`schemaentry` follows the shared form row shape (`FormSchemaEntryShared`) with `params` narrowed to **`InputDatetimeParams`**. Keys that **this component reads** are:

| Key | Effect |
|-----|--------|
| `id` | **Required.** Included on every **`setVal`** `detail`. |
| `value` | Optional initial value. Coerced with `new Date(...)`. When the schema fingerprint changes and `value` is present, the host splits it into date (`YYYY-MM-DD`), hours, minutes, and seconds for the sub-controls. Very small or very large numeric timestamps are ignored (internal guard on `valueOf()`). |
| `required` | When **truthy**, `valid` is **`false`** until a full ISO datetime can be built **and** it lies within optional `params.min` / `params.max` (inclusive, compared with `Date#valueOf()`). When **falsy**, `valid` stays **`true`** (including empty input). |
| `disabled` | When **truthy**, inner `hb-input-date` / `hb-input-number` entries are built with `disabled: true`. |
| `validationTip` | Message shown in **`help is-danger`** when `show_validation` is **`yes`** and `valid` is **`false`**. |
| `params` | See [Datetime params](#datetime-params-inputdatetimeparams). |

Other keys allowed by the shared type (for example `label`, `placeholder`, `type`) may be present for larger forms or tooling; **this component does not render a label or placeholder** from `schemaentry`—only the grouped controls and optional validation help.

### Datetime params (`InputDatetimeParams`)

| Key | Type | Description |
|-----|------|-------------|
| `min` | `string \| number` | Passed to `new Date(...)`. With **`required`**, the composed instant must be **≥** this bound. |
| `max` | `string \| number` | Passed to `new Date(...)`. With **`required`**, the composed instant must be **≤** this bound. |
| `removeSeconds` | `boolean` | When truthy, the seconds **`hb-input-number`** is **not** rendered. Seconds still default to **0** when the user edits hours or minutes so the ISO string can be formed. |

## Value and validation behavior

- The derived **`value`** is **undefined** until **date**, **hours**, **minutes**, and **seconds** are all set (seconds may be **0**). The implementation builds a local datetime string `YYYY-MM-DD HH:mm:ss` and converts it with `new Date(...).toISOString()`.
- Changing the hour control initializes **minutes** and **seconds** to **0** if they were unset; changing **minutes** initializes **seconds** to **0** if unset.
- **`setVal`** is emitted when **`value`**, **`valid`**, or **`id`** changes, with deduplication so identical payloads are not fired twice in a row.

## Events

| Event | `detail` | Notes |
|-------|----------|--------|
| `setVal` | `{ value: string \| undefined; valid: boolean; id: string }` | Fired whenever the composed value or validity changes (after deduplication). `value` is **undefined** until date, hours, minutes, and seconds are all set (seconds may be `0`). |

There is no **`clickEnter`** event on this component (composed child elements handle their own keyboards); rely on **`setVal`** for live value and validity updates.

## Styling

### Bulma in the shadow root

`styles/bulma.scss` forwards Bulma form modules **`form/shared`**, **`form/input-textarea`**, and **`form/tools`**, with the light theme applied on **`:host`**. Nested **`hb-input-date`** / **`hb-input-number`** elements ship their own shadows and input styling.

### Theme variables (`:host`)

These tokens are documented for consumers in `extra/docs.ts` (`styleSetup.vars`):

| CSS variable | Role |
|--------------|------|
| `--bulma-text` | Grouped labels context and validation help text. |
| `--bulma-border` | Borders on the composed date/time controls (via children). |
| `--bulma-danger` | Invalid outline and **`help is-danger`**. |
| `--bulma-success` | Success styling on children when validation display is enabled. |
| `--bulma-scheme-main` | Surface behind the nested web components. |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for global Bulma tuning.

### CSS parts

| Part | Description |
|------|-------------|
| `invalid-feedback` | The **`p.help.is-danger`** node shown when `show_validation="yes"` and the combined value is invalid. |

## Typings

Authoring types live in **`types/webcomponent.type.d.ts`**: **`InputDatetimeParams`**, **`FormSchemaEntry`**, **`Component`**, **`Events`**.

## Examples

### From JavaScript (recommended)

Setting `schemaentry` as an object avoids heavy HTML escaping.

```html
<hb-input-datetime id="event_start"></hb-input-datetime>
<script>
  const el = document.getElementById("event_start");
  el.addEventListener("setVal", (e) => {
    console.log(e.detail.value, e.detail.valid, e.detail.id);
  });
  el.schemaentry = JSON.stringify({
    id: "starts_at",
    label: "Start",
    required: true,
    validationTip: "Choose a complete date and time within the allowed range.",
    params: {
      removeSeconds: true,
      min: "2024-01-01T00:00:00.000Z",
      max: "2024-12-31T23:59:59.999Z",
    },
  });
</script>
```

### From HTML (JSON attribute)

```html
<hb-input-datetime
  schemaentry='{"id":"starts_at","required":true,"params":{"removeSeconds":true}}'
  show_validation="yes"
></hb-input-datetime>
```

## Peer dependencies (nested packages)

Declared in `extra/docs.ts` for bundling and documentation:

- `hb-input-date`
- `hb-input-number`

---

<a id="wc-input-email"></a>

# `hb-input-email`

**Category:** inputs · **Tags:** inputs · **Package:** `@htmlbricks/hb-input-email`

## Overview

`hb-input-email` is a Bulma-styled email field built as a custom element with a shadow root. It binds to a single **`schemaentry`** object (serialized as JSON when used from HTML), runs lightweight email-shape checks and optional **`validationRegex`** / length **`params`**, and emits **`setVal`** whenever the value or validity changes. Pressing **Enter** in the input emits **`clickEnter`**.

The markup is the native control only (Bulma **`input`** inside **`control`**). There is no built-in `<label>` element; add a label in the host page if you need one.

## Validation rules

Behavior depends on **`required`** in `schemaentry`:

- **Required (`required: true`)**  
  The value is **invalid** until all of the following hold:

  - Non-empty string.
  - Contains **`@`** and **`.`**, with reasonable placement: no leading/trailing **`@`** or **`.`** on the local or domain side, no **`.@`** or **`@.`**, no spaces.
  - If **`validationRegex`** is present and parses as a valid `RegExp`, the whole value must match it.
  - If **`params.min`** / **`params.max`** are set, the string length must be **≥ min** and **≤ max** (inclusive), evaluated after the checks above.

  If any step fails, the field is invalid.

- **Optional (`required: false` or omitted)**  
  The component treats the field as **always valid** (`valid: true` in events). Optional fields do **not** receive Bulma **`is-success`** / **`is-danger`** classes, even when **`show_validation="yes"`**.

Invalid **`validationRegex`** strings are ignored (no extra regex constraint).

## Visual feedback (`show_validation`)

Set **`show_validation`** to **`yes`** or **`no`** (string booleans for HTML attributes).

When **`show_validation="yes"`** and the field is **required** and **invalid**, the input gets Bulma’s **`is-danger`** class. When valid under the same conditions, it gets **`is-success`**.

If **`validationTip`** is set and validation is shown and the field is invalid, a **`p.help.is-danger`** line is rendered. You can style it via the **`invalid-feedback`** CSS part (see below).

## Styling

The component forwards shared Bulma form styles into the shadow root. Theme it with Bulma’s **`--bulma-*`** custom properties on **`body`**, **`:root`**, or any ancestor so they inherit onto **`:host`**. See the [Bulma CSS variables documentation](https://bulma.io/documentation/features/css-variables/).

Variables documented for this package are listed in **`extra/docs.ts`** under **`styleSetup.vars`** (for example **`--bulma-text`**, **`--bulma-border`**, **`--bulma-danger`**, **`--bulma-success`**, **`--bulma-scheme-main`**).

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-email` | `<hb-input-email …></hb-input-email>` |

## Attributes (snake_case; string values from HTML)

Web component attributes are always strings. Pass **`schemaentry`** as a **JSON string**. Use **`yes`** / **`no`** for boolean-like flags.

| Attribute           | Required | Description |
|---------------------|----------|-------------|
| **`schemaentry`**   | Yes      | JSON object describing the field (see next section). |
| **`show_validation`** | No     | **`yes`** or **`no`**. Default in the implementation is **`no`**. |
| **`id`**            | No       | Optional id on the host element. |
| **`style`**         | No       | Optional inline styles on the host element. |

If **`schemaentry`** cannot be parsed (invalid JSON string), nothing is rendered.

## `schemaentry` JSON

Typical keys (aligned with shared form schema types; see **`types/webcomponent.type.d.ts`**):

| Key                 | Role |
|---------------------|------|
| **`id`**            | Field identifier. **Should be set**: the **`setVal`** effect only runs when **`id`** is present, so consumers usually rely on this id in event payloads. |
| **`label`**         | Optional; useful for metadata or parent forms. **Not rendered** as a DOM label by this component. |
| **`value`**         | Optional initial value (coerced with `String(...)` when applied from the schema). |
| **`placeholder`**   | Optional placeholder text. |
| **`required`**      | **`true`** / **`false`** — drives validation and Bulma state classes as described above. |
| **`readonly`**      | Optional; passed to the input. |
| **`disabled`**      | Optional; disables the input when truthy. |
| **`validationRegex`** | Optional string compiled with `new RegExp(...)`. |
| **`validationTip`** | Message shown in the danger help line when validation UI is active and the field is invalid. |
| **`params`**        | Optional **`InputEmailParams`** object (see below). |

## `params` (`InputEmailParams`)

| Key    | Type   | Description |
|--------|--------|-------------|
| **`min`** | number | Minimum **string length** (inclusive), after basic email checks. |
| **`max`** | number | Maximum **string length** (inclusive). |

## Events

Listen with **`addEventListener`** or framework bindings on the custom element.

| Event            | `detail` |
|------------------|----------|
| **`setVal`**     | `{ value: string; valid: boolean; id: string }` — emitted when the value or validity changes, **only if** `schemaentry.id` is defined. |
| **`clickEnter`** | `{ value: string; valid: boolean; id?: string }` — emitted when the user presses **Enter** in the input (default prevented). |

## CSS `::part` names

| Part                   | Description |
|------------------------|-------------|
| **`invalid-feedback`** | The **`p.help.is-danger`** element when **`show_validation="yes"`**, **`validationTip`** is set, and the field is invalid. |

## TypeScript

Authoring types for props and events live in **`types/webcomponent.type.d.ts`**:

- **`InputEmailParams`**
- **`FormSchemaEntry`**
- **`Component`**
- **`Events`**

## Examples

### Minimal required email (HTML)

```html
<hb-input-email
  schemaentry="{&quot;id&quot;:&quot;email&quot;,&quot;required&quot;:true,&quot;placeholder&quot;:&quot;you@example.com&quot;}"
></hb-input-email>
```

### Show validation styling and tip

```html
<hb-input-email
  schemaentry="{&quot;id&quot;:&quot;work_email&quot;,&quot;required&quot;:true,&quot;placeholder&quot;:&quot;name@company.com&quot;,&quot;validationTip&quot;:&quot;Enter a valid work email address.&quot;}"
  show_validation="yes"
></hb-input-email>
```

### Optional secondary email

```html
<hb-input-email
  schemaentry="{&quot;id&quot;:&quot;backup_email&quot;,&quot;required&quot;:false,&quot;placeholder&quot;:&quot;optional@example.com&quot;}"
></hb-input-email>
```

### Length bounds (`params`)

```html
<hb-input-email
  schemaentry="{&quot;id&quot;:&quot;corp_email&quot;,&quot;required&quot;:true,&quot;params&quot;:{&quot;min&quot;:6,&quot;max&quot;:254},&quot;validationTip&quot;:&quot;Adjust length to fit your policy.&quot;}"
  show_validation="yes"
></hb-input-email>
```

### `validationRegex` and JSON escaping

`validationRegex` is a string passed to `new RegExp(...)`. When you embed it in a **`schemaentry`** JSON attribute, follow normal JSON escaping (for example a literal dot in the pattern is often written as **`\\.`** inside the JSON string value). Building `schemaentry` with `JSON.stringify({...})` in JavaScript avoids manual escaping mistakes.

## Listening for changes (vanilla JS)

```javascript
const el = document.querySelector("hb-input-email");

el.addEventListener("setVal", (e) => {
  const { value, valid, id } = e.detail;
  console.log(id, value, valid);
});

el.addEventListener("clickEnter", (e) => {
  const { value, valid, id } = e.detail;
  // handle submit-on-enter
});
```

---

<a id="wc-input-file"></a>

# `hb-input-file`

**Category:** inputs · **Tags:** inputs

`hb-input-file` is a file-upload control for HTML Bricks forms and standalone pages. It wraps a native `<input type="file">` in a Bulma-styled field, supports restricting file types with `accept`, optional **image-placeholder** mode, built-in **English** and **Italian** UI strings, and reports its value with a **`setVal`** custom event.

---

## Overview

- **Standard layout** — Bulma `file has-name is-fullwidth`: “choose file” call-to-action, optional **clear** control (does not reopen the file dialog; click is stopped on the button), and a **filename** strip with a **Bootstrap Icons** glyph inferred from MIME type or file extension.
- **Placeholder image layout** — When `schemaentry.params.placeHolderImage.src` is set, the host shows a clickable image and translated “select image” text. The real file input stays in the shadow tree (visually hidden) but remains focusable and programmatically usable.
- **Preview** — After a file is chosen, the component can show an image preview (object URL) for common image types, or a MIME-style icon row for other types.
- **Overlay preview** — If `accept` looks image-oriented (the implementation treats it as such when `/image/i` matches `accept`) **and** the picked file is JPEG, PNG, GIF, or SVG, a compact overlay preview is used with filename and clear control.
- **Validation** — `required` means a file must be selected. Toggle **`show_validation`** to surface `validationTip` in a Bulma `help is-danger` block when the field is invalid.

Icons use the Bootstrap Icons font; the component loads the stylesheet from jsDelivr inside the shadow scope.

---

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-file` | `<hb-input-file …></hb-input-file>` |

Published package name (npm): `@htmlbricks/hb-input-file`.

---

## Attributes (snake_case)

Web component attributes are **strings**. Pass booleans as **`yes`** / **`no`**, numbers as strings where applicable, and structured data as **JSON strings** (for `schemaentry`).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes | JSON string describing the field (see below). Invalid or unparsable JSON yields no rendered field. |
| `show_validation` | No | `yes` or `no` (default `no`). When `yes`, shows `validationTip` if the field is required and no valid file is selected. |
| `i18nlang` | No | BCP-47 style language code for built-in strings. Supported in this component: `en` (default behavior), `it`. Parent `hb-form` can forward its locale. |
| `id` | No | Optional id on the host element. |
| `style` | No | Optional inline styles on the host element. |

---

## `schemaentry` (JSON)

`schemaentry` follows the shared form row shape (`FormSchemaEntryShared`) with file-specific `params`. At minimum you should supply **`id`** (string). Typical keys:

| Key | Type | Description |
|-----|------|-------------|
| `id` | string | Field id; forwarded on `setVal` and applied to the native file input. |
| `label` | string | Optional; used by surrounding forms for labeling (this component’s markup focuses on the control). |
| `required` | boolean | If `true`, a file must be chosen for `valid` to be `true`. |
| `validationTip` | string | Message shown when `show_validation` is `yes` and the field is invalid. |
| `placeholder` | string | Passed to the native input as `placeholder` where relevant. |
| `readonly` | boolean | Forwarded to the native input. |
| `disabled` | boolean | Disables the input and interactive controls. |
| `params` | object | Optional **`InputFileParams`** (see next table). |

### `params` (`InputFileParams`)

| Key | Type | Description |
|-----|------|-------------|
| `accept` | string | Native `accept` attribute: comma-separated MIME types, extensions (e.g. `.pdf`), or a mix. If omitted, the component uses `*` (any file). |
| `placeHolderImage` | object | Enables placeholder-image mode. **`src`** (string, required) is the placeholder image URL. Optional **`width`** and **`height`** (numbers) constrain preview / placeholder dimensions (defaults used in CSS if omitted). |

---

## Events

| Event | `detail` | When |
|-------|----------|------|
| `setVal` | `{ value: string \| undefined; valid: boolean; id: string }` | Whenever `value`, `valid`, or `id` changes; duplicate payloads are suppressed internally. |

Listen with `addEventListener("setVal", …)` or the equivalent in your framework.

---

## Internationalization

Built-in UI strings (e.g. “Choose file”, “No file selected”, clear button labels, placeholder mode copy) come from the component dictionary. Set **`i18nlang`** to switch language; **`it`** enables Italian strings, **`en`** (or omitting the attribute) keeps English.

---

## Styling

The shadow root uses Bulma form patterns and shared tokens. Prefer **Bulma CSS variables** on `:host` — see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| CSS variable | Role |
|--------------|------|
| `--bulma-text` | Label, filename strip, and CTA text. |
| `--bulma-border` | File control border (`file` / `has-name`). |
| `--bulma-danger` | Required-empty state and danger help text. |
| `--bulma-link` | Primary “choose file” background in standard mode. |
| `--bulma-scheme-main` | Filename strip and placeholder surfaces. |

Additional layout and icon rules live in `styles/webcomponent.scss`.

---

## CSS `::part` API

| Part | Description |
|------|-------------|
| `input` | Native `<input type="file">` (visually hidden in placeholder-image mode; still part-targetable). |
| `file-name` | Filename text, or the “no file selected” hint in standard mode; in placeholder modes, the name strip / overlay filename. |
| `clear` | Clear control in **standard** mode (between CTA and filename). |
| `invalid-feedback` | The `p.help.is-danger` node when validation messaging is visible. |

---

## Slots

None (`htmlSlots` is empty for this package).

---

## TypeScript

Authoring types for consumers and wrappers live in **`types/webcomponent.type.d.ts`**:

- `InputFilePlaceholderImage`, `InputFileParams`
- `FormSchemaEntry` (file row + `params`)
- `Component` (element props)
- `Events` (`setVal` detail)

---

## Examples

### Required PDF upload

```html
<hb-input-file
  schemaentry="{&quot;id&quot;:&quot;contract&quot;,&quot;label&quot;:&quot;Contract (PDF)&quot;,&quot;required&quot;:true,&quot;validationTip&quot;:&quot;Please upload a PDF.&quot;,&quot;params&quot;:{&quot;accept&quot;:&quot;application/pdf,.pdf&quot;}}"
  show_validation="yes"
  i18nlang="en"
></hb-input-file>
```

### Image placeholder (`accept` + preview dimensions)

```html
<hb-input-file
  schemaentry="{&quot;id&quot;:&quot;avatar&quot;,&quot;label&quot;:&quot;Profile photo&quot;,&quot;required&quot;:true,&quot;validationTip&quot;:&quot;Select an image.&quot;,&quot;params&quot;:{&quot;accept&quot;:&quot;image/*&quot;,&quot;placeHolderImage&quot;:{&quot;src&quot;:&quot;https://example.com/placeholder.png&quot;,&quot;width&quot;:300,&quot;height&quot;:300}}}"
  i18nlang="en"
></hb-input-file>
```

### Optional attachment (not required)

```html
<hb-input-file
  schemaentry="{&quot;id&quot;:&quot;attachment&quot;,&quot;label&quot;:&quot;Attachment (optional)&quot;,&quot;required&quot;:false}"
></hb-input-file>
```

### Italian UI

```html
<hb-input-file
  schemaentry="{&quot;id&quot;:&quot;allegato&quot;,&quot;label&quot;:&quot;Allegato&quot;,&quot;required&quot;:true,&quot;validationTip&quot;:&quot;Seleziona un file.&quot;}"
  i18nlang="it"
></hb-input-file>
```

### Listen for `setVal`

```html
<script>
  const el = document.querySelector("hb-input-file");
  el.addEventListener("setVal", (e) => {
    console.log(e.detail.id, e.detail.valid, e.detail.value);
  });
</script>
```

---

## Behavior notes

- **Clear** resets the native input value, revokes any object URL used for preview, and dispatches `setVal` with updated `value` / `valid`.
- **MIME / extension** — If the browser does not report a MIME type, the component guesses from the file extension against a built-in map for icon selection.
- **`hb-form`** — When embedded in `hb-form`, align `i18nlang` and `show_validation` with the form host for consistent UX.

---

<a id="wc-input-number"></a>

# `hb-input-number`

Integrator guide for the **`hb-input-number`** custom element: a Bulma-styled native **`<input type="number">`** driven by a single **`schemaentry`** object (JSON on the host). The control reports its value with **`setVal`** and optionally handles **Enter** with **`clickEnter`** when no native **`min`** / **`max`** constraints are configured.

---

## What it does

- Renders **nothing** until **`schemaentry`** parses to a valid object; then it shows a Bulma **`field`** / **`control`** / **`input.input`**.
- Keeps an internal numeric **`value`** in sync with **`schemaentry`** when the schema fingerprint changes (local typing does not overwrite the schema on every keystroke unless the parent updates **`schemaentry`**).
- Computes **`valid`**: if the field is **not** **`required`**, **`valid`** is always **`true`**. If **`required`**, **`valid`** is **`false`** when **`value`** is **`undefined`** or **`null`**; when **`value`** is set, optional **`params.min`** / **`params.max`** (only if the corresponding key exists) are applied as lower/upper bounds (see [Bounds (`params`)](#bounds-params)).
- When **`show_validation`** is **`"yes"`** and the field is **`required`**, the input gets Bulma **`is-success`** / **`is-danger`** and an optional **`validationTip`** is shown as **`help is-danger`** (exposed as a CSS **`::part`**).
- **`is_small="yes"`** maps to **`input.is-small`**. For **`type="number"`** + **`is-small`**, spin buttons are hidden in shadow styles so the control height aligns with compact selects (values remain editable via keyboard).

---

## Custom element

| Name | Tag |
|------|-----|
| `hb-input-number` | `<hb-input-number …></hb-input-number>` |

---

## Host API (attributes / properties)

Web component **reflected** surface is **string-oriented** in HTML: pass booleans as **`"yes"`** / **`"no"`**, and pass **`schemaentry`** as a **JSON string**. In Svelte or other frameworks you may also assign the **`schemaentry`** property as an **object** (the parser accepts both; see `parseSchemaentryProp` in `../lib/schemaentry`).

| Name | Type (logical) | HTML / notes |
|------|----------------|--------------|
| **`schemaentry`** | `FormSchemaEntry \| undefined` | **JSON string** on the attribute. Required for UI; invalid JSON yields no field. |
| **`show_validation`** | `"yes" \| "no"` | Default **`"no"`**. When **`"yes"`** and **`schemaentry.required`**, shows success/danger styling and invalid **`validationTip`**. |
| **`is_small`** | `"yes" \| "no"` | Default **`"no"`**. When **`"yes"`**, applies **`input.is-small`**. |
| **`id`** | `string` | Optional host id (component typings). |
| **`style`** | `string` | Optional inline host style (component typings). |

---

## `schemaentry` shape

Authoritative TypeScript lives in **`types/webcomponent.type.d.ts`** (`FormSchemaEntry`, **`InputNumberParams`**, **`Component`**, **`Events`**). The entry extends the shared form row shape (see **`../../form/types/webcomponent.type`**) with number-specific pieces:

### Shared fields (subset used here)

| Field | Purpose |
|-------|---------|
| **`id`** | Field id; used in event payloads and on the **`<input>`** element. |
| **`label`** | Optional; not rendered as separate markup in this component (schema is mainly for id, validation, placeholders). |
| **`value`** | Optional **number** initial / synced value from schema. |
| **`required`** | When truthy, **`valid`** is false if **`value`** is **`undefined`** / **`null`**, and range rules apply when **`params`** keys exist. |
| **`placeholder`** | Passed to the input. |
| **`readonly`** | Passed to the input. |
| **`disabled`** | Disables the input when truthy. |
| **`validationTip`** | Shown as **`p.help.is-danger`** when **`show_validation="yes"`** and the value is invalid. |

Other **`FormSchemaEntryShared`** fields (e.g. **`type`**, **`dependencies`**, **`validationRegex`**) exist on the shared type; this component focuses on number input behavior above.

### Bounds (`params`)

**`InputNumberParams`** (optional **`schemaentry.params`**):

```ts
type InputNumberParams = {
  min?: number;
  max?: number;
};
```

**Important:** a native DOM **`min`** or **`max`** attribute is set **only if** that key **exists** on **`params`** (checked with **`Object.prototype.hasOwnProperty.call`**). Omit a key entirely if you do not want that attribute.

- When **`min`** / **`max`** keys are present, they participate in validation for **`required`** fields and are reflected on the input.
- **Enter key:** **`clickEnter`** is wired **only** when **both** **`min`** and **`max`** keys are absent from **`params`** (no native min/max mode). Otherwise the handler is not attached.

---

## Events

Listen with **`addEventListener`** or framework equivalents. Payloads match **`types/webcomponent.type.d.ts`** (`Events`).

| Event | `detail` | When |
|-------|----------|------|
| **`setVal`** | `{ value: number; valid: boolean; id: string }` | Emitted when **`schemaentry.id`** is set and **`value`**, **`valid`**, or **`id`** changes compared to the last payload (deduplicated). **`value`** may be **`undefined`** when empty. |
| **`clickEnter`** | `{ value: string; valid: boolean; id?: string }` | **Enter** inside the input, **only** if **`params`** defines **neither** **`min`** nor **`max`**. **`preventDefault`** is called on the key event. |

---

## Styling

Bulma **1.x** form styles are bundled in the shadow root; theme tokens are driven by **`--bulma-*`** on **`:host`** (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

### CSS custom properties (`styleSetup`)

| Variable | Role |
|----------|------|
| **`--bulma-text`** | Label, input value, and help text. |
| **`--bulma-border`** | Default input outline before success/danger. |
| **`--bulma-danger`** | Invalid (**`is-danger`**) border and feedback. |
| **`--bulma-success`** | Valid (**`is-success`**) accents when validation feedback is shown. |
| **`--bulma-scheme-main`** | Control surface / scheme background where Bulma maps scheme to controls. |

### CSS parts

| Part | Role |
|------|------|
| **`invalid-feedback`** | The **`p.help.is-danger`** node for **`validationTip`** when **`show_validation="yes"`** and the value is invalid. Style from the light DOM with **`::part(invalid-feedback)`**. |

### Slots

None. Configuration is entirely through **`schemaentry`** JSON.

---

## HTML example (vanilla)

```html
<hb-input-number
  schemaentry='{"id":"quantity","label":"Quantity","required":true,"validationTip":"Enter a number between 3 and 10.","placeholder":"0","params":{"min":3,"max":10},"value":4}'
  show_validation="yes"
></hb-input-number>

<script>
  const el = document.querySelector("hb-input-number");
  el.addEventListener("setVal", (e) => {
    console.log(e.detail); // { value, valid, id }
  });
</script>
```

**Compact height** (e.g. next to pagination or dense toolbars):

```html
<hb-input-number
  is_small="yes"
  schemaentry='{"id":"pageSize","label":"Rows","value":10}'
></hb-input-number>
```

**Enter to submit** (no **`min`** / **`max`** keys on **`params`**):

```html
<hb-input-number
  schemaentry='{"id":"code","required":true,"placeholder":"Numeric code"}'
></hb-input-number>
<script>
  document.querySelector("hb-input-number").addEventListener("clickEnter", (e) => {
    console.log("Enter pressed", e.detail);
  });
</script>
```

---

## TypeScript imports

For authoring against the published shape in this package:

```ts
import type {
  Component,
  Events,
  FormSchemaEntry,
  InputNumberParams,
} from "./types/webcomponent.type";
```

Use **`Component`** for props and **`Events`** for **`CustomEvent`** detail typing.

---

## Metadata and Storybook

**`extra/docs.ts`** defines **`styleSetup`** (vars, parts), **`componentSetup.description`**, **`storybookArgs`**, and **`examples`** (required, min/max combinations, zero values, disabled). Use those examples as additional configuration references alongside this file.

---

<a id="wc-input-radio"></a>

# `hb-input-radio`

**Category:** inputs · **Tags:** inputs

`hb-input-radio` is a Bulma-styled **radio group** web component. It reads its configuration from a single **`schemaentry`** payload (JSON in HTML, or an object when set from JavaScript), builds one native `<input type="radio">` per option under a shared **`name`** equal to the field **`id`**, and reports the current choice with a **`setVal`** custom event.

The group’s accessible name comes from **`label`**, then **`id`**, then the fallback `"Options"` (`aria-label` on the `radiogroup`).

---

## When the component renders

- If **`schemaentry`** is missing, not parseable as JSON, or not an object, **nothing** is rendered.
- If **`params.options`** is missing or empty, the **field shell** still renders but **no radios** appear until you supply at least one option.

---

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-radio` | `<hb-input-radio …></hb-input-radio>` |

---

## Attributes (snake_case)

Web component attributes are **strings**. For booleans exposed as attributes, use **`yes`** or **`no`** (see `show_validation`).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes* | Serialized field definition (JSON string from HTML). Must include a stable **`id`** and, for choices, **`params.options`**. Can be set to `undefined` / omitted in typed wrappers; the host then renders nothing. |
| `show_validation` | No | `"yes"` or `"no"` (default **`no`**). When **`yes`**, a required group with no selection shows **`validationTip`** in a Bulma danger help line (if `validationTip` is set). |
| `id` | No | Optional id on the host element. |
| `style` | No | Optional inline styles on the host element. |

\*Required for a usable field in markup; the TypeScript `Component` type allows `undefined` for programmatic cases.

### Passing `schemaentry` from HTML

Use a **single-quoted** attribute value so the JSON can use double quotes:

```html
<hb-input-radio
  schemaentry='{"id":"plan","required":true,"label":"Plan","validationTip":"Select a plan.","params":{"options":[{"label":"Starter","value":"starter"},{"label":"Pro","value":"pro"}]}}'
  show_validation="yes"
></hb-input-radio>
```

From JavaScript you may assign **`element.schemaentry`** as either a **JSON string** or a **plain object**; the internal parser accepts both without mutating the prop.

---

## `schemaentry` shape

The host expects a **`FormSchemaEntry`** compatible object (see `types/webcomponent.type.d.ts` and shared `FormSchemaEntryShared` from `hb-form`). Fields that matter for this component:

| Field | Description |
|-------|-------------|
| `id` | **Required.** Becomes the native `name` for all radios in the group and is echoed on events as `detail.id`. |
| `label` | Optional visible context; used for `aria-label` on the radiogroup. |
| `required` | If true, **`valid`** is false until an option is selected. |
| `value` | Optional initial selection; coerced with `String(...)` when the schema sync runs. |
| `validationTip` | Message shown when **`show_validation="yes"`**, the field is invalid, and this string is set. |
| `disabled` | If true, all radios are disabled. |
| `readonly` | If true, all radios are disabled (same effect as `disabled` in the template). |
| `placeholder` | Allowed by the shared schema type; **not** rendered as a separate control by this component. |
| `params` | **`InputRadioParams`** — see below. |

### `params` (`InputRadioParams`)

| Key | Type | Description |
|-----|------|-------------|
| `options` | `InputRadioOption[]` | List of choices. Each item needs a **`value`** (string). **`label`** is optional; the UI falls back to **`value`**. |

---

## Validation behavior

- **Required (`required: true`)**: `valid` is **false** until the user selects an option (`value` is a non-empty choice).
- **Optional (`required: false` or omitted)**: `valid` is **true** even when nothing is selected (`value` may be `undefined`).
- **Inline message**: Shown only when **`show_validation`** is **`yes`**, **`validationTip`** is set, and the current state is invalid.

---

## Events

| Event | `detail` | When |
|-------|----------|------|
| `setVal` | `{ value: string \| undefined; valid: boolean; id: string }` | Whenever **`value`**, **`valid`**, or **`id`** changes in a way that affects the payload. Duplicate payloads are suppressed. |

Listen in plain JavaScript:

```js
const el = document.querySelector("hb-input-radio");
el.addEventListener("setVal", (e) => {
  const { value, valid, id } = e.detail;
  console.log(id, value, valid);
});
```

---

## Styling

- Bulma **form** patterns: `field`, `control`, `radios`, `radio`, and `help is-danger` for invalid feedback.
- Theme the host with Bulma **CSS variables** on `:host` — see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/). Public tokens used by this bundle are listed in **`extra/docs.ts`** (`styleSetup.vars`).

### CSS custom properties (high level)

| Variable | Role |
|----------|------|
| `--bulma-text` | Labels and legend-related text. |
| `--bulma-border` | Radio outline; group border when invalid. |
| `--bulma-link` | Checked accent where the browser supports tinting. |
| `--bulma-danger` | Invalid outline and danger help. |
| `--bulma-scheme-main` | Background inside the shadow root. |

### `::part` selectors

| Part | Targets |
|------|---------|
| `input` | Each native `<input type="radio">` (same part name repeated per option). |
| `invalid-feedback` | The `p.help.is-danger` node when validation messaging is visible. |

---

## TypeScript

Authoring types for this package live in **`types/webcomponent.type.d.ts`**:

- **`InputRadioOption`**, **`InputRadioParams`**
- **`FormSchemaEntry`** (radio-specific entry)
- **`Component`** (host props)
- **`Events`** (`setVal` detail)

After a full web component build, generated element maps may also include this tag under **`types/html-elements.d.ts`** / **`types/svelte-elements.d.ts`**.

---

## Examples

### Required group with validation

```html
<hb-input-radio
  schemaentry='{"id":"tier","required":true,"label":"Tier","validationTip":"Choose a tier.","params":{"options":[{"label":"Free","value":"free"},{"label":"Paid","value":"paid"}]}}'
  show_validation="yes"
></hb-input-radio>
```

### Preselected value

```html
<hb-input-radio
  schemaentry='{"id":"region","required":true,"label":"Region","value":"eu","params":{"options":[{"label":"US","value":"us"},{"label":"EU","value":"eu"}]}}'
></hb-input-radio>
```

### Disabled group

```html
<hb-input-radio
  schemaentry='{"id":"locked","label":"Plan","value":"pro","disabled":true,"params":{"options":[{"label":"Starter","value":"starter"},{"label":"Pro","value":"pro"}]}}'
></hb-input-radio>
```

### Updating options from JavaScript

```js
const radio = document.querySelector("hb-input-radio");
radio.schemaentry = JSON.stringify({
  id: "gift",
  required: false,
  label: "Gift wrap",
  params: {
    options: [
      { label: "No", value: "no" },
      { label: "Yes", value: "yes" },
    ],
  },
});
```

---

## Related metadata

Storybook knobs, CSS variables, `::part` names, and packaged examples are defined in **`extra/docs.ts`** (`componentSetup`, `styleSetup`, `examples`).

---

<a id="wc-input-range"></a>

# `hb-input-range`

**Category:** inputs · **Tags:** inputs

## Overview

`hb-input-range` is a Bulma-styled web component that wraps a native HTML range control (`type="range"`). You drive it with a single JSON `schemaentry` attribute: bounds come from optional `params.min` / `params.max`, and the component validates against those bounds when the field is required. It emits a `setVal` custom event whenever the effective value or validity changes (after deduplication).

Use it when you need a string-attribute-friendly range slider that matches the same `schemaentry` pattern as other `hb-input-*` fields in forms.

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-range` | `<hb-input-range …></hb-input-range>` |

## Host attributes (HTML)

Web component attributes are **strings**. Pass booleans as **`yes`** / **`no`**, numbers inside JSON as JSON numbers, and structured data as a **JSON string** on `schemaentry`.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `schemaentry` | Yes | JSON describing the field (see below). If the attribute is missing, JSON parsing fails, or the value is `null`, nothing is rendered. A parsed object without `id` still shows the range, but **`setVal` is not emitted**. |
| `show_validation` | No | `yes` or `no` (default in code: `no`). When `yes`, and `schemaentry.validationTip` is set, an invalid value shows Bulma `help is-danger` with that tip. |
| `id` | No | Optional id on the host element (component typings). |
| `style` | No | Optional inline styles on the host. |

## `schemaentry` JSON

The attribute value is a serialized object aligned with **`FormSchemaEntry`** in `types/webcomponent.type.d.ts` (shared keys come from `../../form/types/webcomponent.type`).

| Property | Description |
|----------|-------------|
| `id` | **Required** for `setVal` dispatches (the effect bails out without it). Also sets the native input `id`. |
| `value` | Optional initial numeric value. Non-numeric or missing values become `undefined` internally. |
| `required` | When `true`, the value must be set and satisfy any declared `min` / `max` (see validation). |
| `disabled` | When `true`, the slider is non-interactive. |
| `readonly` | Passed through to the native `readonly` attribute. |
| `placeholder` | Passed through to the native `placeholder` attribute. |
| `validationTip` | Message shown when `show_validation` is `yes` and the field is invalid. |
| `params` | Optional **`InputRangeParams`**: slider bounds and validation limits. |

**Bounds (`params`)** — type **`InputRangeParams`**: optional `min` and/or `max`, each a **number** or **numeric string**. The implementation uses `Object.prototype.hasOwnProperty.call(params, "min")` (and similarly for `"max"`): a key must **exist** on `params` for the native `min` / `max` attributes and validation to use it. If a key is missing, that side is unconstrained.

The shared schema also allows other keys (for example `label`); this component **does not render a label** in its template—it renders the range input and optional invalid feedback only.

## Validation

- **`required` is false or omitted:** the component treats the field as **valid** (`valid: true` in `setVal`).
- **`required` is true:** the value must be a defined number, and:
  - if `params` **has** a `min` key, `value >= min` (after coercion to number);
  - if `params` **has** a `max` key, `value <= max`.

Invalid state uses **`--bulma-danger`** for the danger help text. The native control’s **`accent-color`** follows Bulma tokens (see Styling).

## Events

| Event | `detail` |
|-------|----------|
| `setVal` | `{ value: number \| undefined; valid: boolean; id: string }` |

`setVal` is dispatched when `id` is present and the payload changes (value, validity, or id). Listen with `addEventListener("setVal", …)` in the host page or framework.

## Styling

### Bulma CSS variables (`:host`)

Documented in `extra/docs.ts` (`styleSetup.vars`):

| Variable | Role |
|----------|------|
| `--bulma-text` | Label and current value readout color. |
| `--bulma-border` | Track baseline color where Bulma maps borders to the range. |
| `--bulma-link` | Native range `accent-color` (thumb / filled portion). In `styles/webcomponent.scss`, `accent-color` is set to `var(--bulma-link, …)`. |
| `--bulma-danger` | Invalid state and `help is-danger` when out of `min` / `max` or empty. |
| `--bulma-success` | Valid thumb accent when `show_validation` marks the field as valid. |

Sass entry: `styles/bulma.scss` forwards Bulma **`form/shared`**, **`form/input-textarea`**, and **`form/tools`**, then applies the light theme on `:host`.

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for how to set them on the host or ancestor.

### CSS parts

| Part | Description |
|------|-------------|
| `input` | The native `type="range"` element (class `hb-input-range-native`). |
| `invalid-feedback` | The `p.help.is-danger` node when validation feedback is shown. |

The control is **`display: inline-block`** on `:host`, full width inside the host, with fixed height for the native range (`styles/webcomponent.scss`).

## TypeScript

Authoring types: **`types/webcomponent.type.d.ts`** — `InputRangeParams`, `FormSchemaEntry`, `Component`, `Events`.

After `npm run build:wc`, generated DOM typings include `hb-input-range` in `types/html-elements.d.ts` and Svelte element typings in `types/svelte-elements.d.ts`.

## Examples

### Minimal range (0–100)

```html
<hb-input-range
  schemaentry='{"id":"volume","required":true,"params":{"min":0,"max":100},"value":50}'
  show_validation="no"
></hb-input-range>
```

### Required with validation message

```html
<hb-input-range
  schemaentry='{"id":"level","required":true,"params":{"min":1,"max":10},"value":1,"validationTip":"Pick a level between 1 and 10."}'
  show_validation="yes"
></hb-input-range>
```

### Listen for changes (vanilla JS)

```javascript
const el = document.querySelector("hb-input-range");
el.addEventListener("setVal", (e) => {
  const { value, valid, id } = e.detail;
  console.log(id, value, valid);
});
```

### Disabled slider

```html
<hb-input-range
  schemaentry='{"id":"volume_locked","label":"Volume","params":{"min":0,"max":100},"value":65,"disabled":true}'
></hb-input-range>
```

## Package metadata

Component name in catalog setup: **`hb-input-range`** (`extra/docs.ts` → `componentSetup`). Description and Storybook-oriented examples also live in `extra/docs.ts`.

---

<a id="wc-input-select"></a>

# `hb-input-select`

**Category:** inputs · **Tags:** inputs

## Description

`hb-input-select` renders a native `<select>` inside the shadow DOM, driven entirely by the **`schemaentry`** prop (serialized JSON on the host). Options come from **`params.options`**: each entry is an **`InputSelectOption`** with a stable **`id`**, a string **`value`**, and an optional **`label`** (the visible text defaults to **`value`** when **`label`** is omitted).

The control respects **`required`**, **`disabled`**, and **`readonly`** from the schema. Because HTML selects do not support `readonly`, **`readonly`** is applied the same way as **`disabled`** (non-interactive select).

With **`show_validation="yes"`** and **`required`**, Bulma modifiers **`select.is-success`** / **`select.is-danger`** reflect validity, and **`validationTip`** is shown as **`p.help.is-danger`** when the value is invalid. **`is_small="yes"`** adds Bulma **`select.is-small`** for a compact control (for example next to **`hb-paginate`**).

On every meaningful change of **`value`**, **`valid`**, or field **`id`**, the element dispatches a bubbling **`setVal`** custom event. When **`schemaentry`** is updated later (for example from a parent form) and the component realigns the internal value from JSON, it avoids emitting a duplicate **`setVal`** if the payload matches what was already reported—so consumers are not treated as if the user changed the field again.

If **`schemaentry`** is missing or cannot be parsed into a field, nothing is rendered.

## Custom element

| Name | Tag |
| --- | --- |
| `hb-input-select` | `<hb-input-select …></hb-input-select>` |

## Attributes (snake_case; string values in HTML)

Web component attributes are always strings. Pass booleans as **`yes`** or **`no`**. Pass the schema as a **JSON string** (for example via **`setAttribute("schemaentry", JSON.stringify(...))`** in JavaScript).

| Attribute | Required | Description |
| --- | --- | --- |
| `schemaentry` | Yes | JSON string describing the field (see below). |
| `show_validation` | No | **`yes`** or **`no`**. Component default is **`no`** when the prop is unset. Toggles success/danger styling and the validation tip. |
| `is_small` | No | **`yes`** or **`no`**. Component default is **`no`** when unset. When **`yes`**, applies Bulma **`select.is-small`**. |

Optional host passthroughs supported by the typings (when your setup forwards them):

| Attribute | Required | Description |
| --- | --- | --- |
| `id` | No | Host element id. |
| `style` | No | Inline styles on the host. |

## `schemaentry` JSON shape

The serialized object extends the shared form schema entry (see **`FormSchemaEntryShared`** in the form typings) with select-specific **`params`**. At minimum you should supply **`id`** and, for a visible control, **`params.options`**.

Typical keys:

| Key | Description |
| --- | --- |
| `type` | Use **`"select"`** for clarity when generating JSON; the host already implies a select. |
| `id` | Stable field id; echoed in **`setVal`** **`detail.id`**. |
| `label` | Optional; useful for surrounding forms even though this component only renders the control and help text. |
| `placeholder` | Optional; reserved for higher-level form UIs. |
| `value` | Optional initial or controlled value (string, number, or boolean in JSON; coerced to string internally). |
| `required` | When **`true`**, an empty selection is invalid. |
| `readonly` / `disabled` | When set, the `<select>` is disabled. |
| `validationTip` | Message shown when invalid and **`show_validation`** is on. |
| `params` | **`InputSelectParams`** (see next table). |

### `params` (`InputSelectParams`)

| Key | Type | Description |
| --- | --- | --- |
| `options` | **`InputSelectOption[]`** | List of choices. Each item needs **`id`** and **`value`**; **`label`** is optional. Use **`value: ""`** with a blank label for a placeholder row on optional fields. |

## Events

| Event | `detail` |
| --- | --- |
| **`setVal`** | **`{ value: string; valid: boolean; id: string }`** — current option value (often **`""`** when a placeholder option is chosen), whether the field satisfies **`required`**, and the field **`id`**. |

Listen with **`addEventListener("setVal", ...)`** or your framework’s equivalent.

## Styling (Bulma)

The shadow tree bundles Bulma **1.x** Sass modules **`form/shared`**, **`form/select`**, and **`form/tools`**, with theme setup on **`:host`** (`--bulma-hb-def-*`). Override public **`--bulma-*`** on **`body`** or **`:root`** so they inherit onto the component host. See the [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) documentation.

### CSS custom properties (common overrides)

| Variable | Role |
| --- | --- |
| `--bulma-text` | Selected option text and help message color. |
| `--bulma-border` | Select wrapper border before success/danger states. |
| `--bulma-danger` | Invalid **`select.is-danger`** and **`help.is-danger`**. |
| `--bulma-success` | Valid **`select.is-success`** when validation feedback is shown. |
| `--bulma-link` | Dropdown arrow / focus accents on the Bulma **`select`** control. |
| `--bulma-scheme-main` | Native `<select>` surface inside the shadow root. |

### CSS parts

| Part | Role |
| --- | --- |
| **`invalid-feedback`** | The **`p.help.is-danger`** node when **`show_validation`** is **`yes`** and the required field is invalid. |

### HTML slots

None.

### Markup outline

- Outer **`div.field`** > **`div.control`** > **`div.select`** (Bulma wrapper) > native **`<select>`**.
- Optional **`p.help.is-danger`** with **`part="invalid-feedback"`** when validation is shown and the value fails **`required`**.

## Typings

**`types/webcomponent.type.d.ts`** exports **`InputSelectOption`**, **`InputSelectParams`**, **`FormSchemaEntry`**, **`Component`**, and **`Events`** for authors and wrappers.

## Examples

### JavaScript (recommended for `schemaentry`)

```js
const el = document.querySelector("hb-input-select");

el.setAttribute(
  "schemaentry",
  JSON.stringify({
    type: "select",
    id: "country",
    label: "Country",
    required: true,
    validationTip: "Please choose a country.",
    value: "it",
    params: {
      options: [
        { id: "it", label: "Italy", value: "it" },
        { id: "fr", label: "France", value: "fr" },
        { id: "de", label: "Germany", value: "de" },
      ],
    },
  }),
);

el.setAttribute("show_validation", "yes");
el.setAttribute("is_small", "no");

el.addEventListener("setVal", (e) => {
  console.log(e.detail); // { value, valid, id }
});
```

### Static HTML (JSON in a single-quoted attribute)

Use single quotes around the attribute value so the JSON can contain double quotes unescaped. If the JSON itself must contain apostrophes, escape those in JSON strings as **`\'`** or prefer the JavaScript example.

```html
<hb-input-select
  schemaentry='{"type":"select","id":"country","required":true,"value":"it","params":{"options":[{"id":"it","label":"Italy","value":"it"},{"id":"fr","label":"France","value":"fr"}]}}'
  show_validation="yes"
  is_small="no"
></hb-input-select>
```

---

<a id="wc-input-text"></a>

# `hb-input-text`

Single-line text input (`<input type="text">`) styled with Bulma inside the shadow root. The field is configured entirely through the **`schemaentry`** payload (JSON object or JSON string). The component reports value and validity with **`setVal`** and emits **`clickEnter`** when the user presses Enter.

## When to use it

Use **`hb-input-text`** for plain text capture in forms or standalone pages where you want Bulma field styling, optional length and regex checks (for **required** fields), and typed custom events instead of wiring `input` yourself.

## Attributes (web component)

HTML attributes and reflected properties are **strings**. Boolean-like flags use **`yes`** / **`no`** where noted.

| Attribute | Values | Description |
| --- | --- | --- |
| `schemaentry` | JSON string | Serialized **`FormSchemaEntry`** for this field (see below). Required for UI; invalid or missing JSON yields no rendered field. May be set as a property with an object in JavaScript frameworks that support non-string props. |
| `show_validation` | `yes` / `no` (default `no`) | When `yes`, applies Bulma **`is-success`** / **`is-danger`** on the input for **required** fields and shows **`validationTip`** in **`::part(invalid-feedback)`** while invalid. |
| `id` | string | Optional host id (separate from the field id inside `schemaentry`). |
| `style` | string | Optional inline style on the host element. |

Pass **`schemaentry`** as a **JSON string** from HTML, for example:

```html
<hb-input-text schemaentry='{"id":"username","label":"Username","placeholder":"Your name","required":true}'></hb-input-text>
```

Adjust attribute quoting for your templating system. In JavaScript you can assign **`schemaentry`** as an object if your integration passes object props to the custom element.

## `schemaentry` shape

Authoritative TypeScript types live in **`types/webcomponent.type.d.ts`** (`InputTextParams`, `FormSchemaEntry`, `Component`, `Events`). The JSON object extends the shared form row shape with a typed optional **`params`** object.

| Property | Type / notes | Description |
| --- | --- | --- |
| `id` | string | **Required.** Becomes the native `<input>` **`id`** and is included in event payloads. |
| `type` | string | Optional discriminator for **`hb-form`** rows; may be omitted when this element is used alone. |
| `label` | string | Optional. Present on the shared schema for form tooling; **this web component does not render a label**—place headings or `<label for="…">` in the light DOM if needed. |
| `value` | string | Optional initial / controlled value (coerced with **`String(...)`** when synced from schema). |
| `placeholder` | string | Optional placeholder text. |
| `readonly` | boolean | Optional; maps to the input **`readonly`** attribute. |
| `disabled` | boolean | Optional; disables the control. |
| `required` | boolean | When truthy, validation runs (see below) and visual validation classes apply if **`show_validation`** is **`yes`**. |
| `validationRegex` | string | Optional **string** form of a JavaScript regex (passed to **`new RegExp(...)`**). Evaluated only for **required** fields when the value is non-empty. |
| `validationTip` | string | Message shown in **`::part(invalid-feedback)`** when invalid and **`show_validation`** is **`yes`**. |
| `dependencies` | array | Optional; carried on the shared schema type for form systems—**not used** by this component’s template. |
| `params` | object | Optional **`InputTextParams`** (see next section). |

### `params` (`InputTextParams`)

| Key | Type | Description |
| --- | --- | --- |
| `min` | number | Inclusive **minimum string length**. Used only when **`required`** is truthy. If omitted, length is not enforced from below (implementation treats missing **`min`** as no minimum other than “non-empty” when required). |
| `max` | number | Inclusive **maximum string length**. Used only when **`required`** is truthy. If omitted, an internal upper bound is used so very long strings still pass unless you set **`max`** explicitly. |

## Validation behavior

- **`required`** is falsy: **`valid`** is always **`true`**. **`validationRegex`**, **`params.min`**, and **`params.max`** are **not** applied. **`setVal`** still fires as the user types with **`valid: true`** (once `schemaentry` is parsed and **`id`** is present).
- **`required`** is truthy:
  - Empty value ⇒ **`valid: false`**.
  - Non-empty value ⇒ if **`validationRegex`** is set, the whole value must match the regex.
  - Length: value length must be **≥ `params.min`** when **`min`** is set (including **`0`**), and **≤ `params.max`** when **`max`** is set.
- Success / danger Bulma classes and the danger help line only appear when **`show_validation="yes"`** **and** the field is **required**.

## Events

Listen with **`addEventListener`** or framework equivalents.

| Event | `detail` | When |
| --- | --- | --- |
| **`setVal`** | `{ value: string; valid: boolean; id: string }` | Whenever **`value`** or **`valid`** changes (deduplicated when unchanged). Requires a parsed **`schemaentry`** with **`id`**. |
| **`clickEnter`** | `{ value: string; valid: boolean; id?: string }` | User presses **Enter** in the input; default is prevented. **`id`** matches **`schemaentry.id`** when present. |

Authoring types for both events are in **`types/webcomponent.type.d.ts`** (`Events`).

## Styling

The shadow root bundles Bulma **form** modules (see **`styles/bulma.scss`**). Theme the control from a parent document by setting **`--bulma-*`** custom properties on **`hb-input-text`** (or inherited from **`body`** / **`:root`** depending on your setup).

### CSS custom properties

| Variable | Role |
| --- | --- |
| `--bulma-text` | Label-like copy, input text, and help text color. |
| `--bulma-border` | Neutral input border before success / danger modifiers. |
| `--bulma-danger` | Invalid (**`is-danger`**) border and feedback emphasis. |
| `--bulma-success` | Valid (**`is-success`**) accents when validation feedback is shown. |
| `--bulma-scheme-main` | Input surface / scheme-driven background where Bulma maps scheme to controls. |

### CSS parts

| Part | Role |
| --- | --- |
| **`invalid-feedback`** | The **`p.help.is-danger`** node that shows **`validationTip`** when **`show_validation`** is **`yes`** and the field is invalid. |

### Slots

None. Layout and copy come only from **`schemaentry`**.

## TypeScript

For authoring and wrappers, import or mirror the types in **`types/webcomponent.type.d.ts`**. After a full web component build, generated **`types/html-elements.d.ts`** and **`types/svelte-elements.d.ts`** under the package describe the custom element and DOM typings.

## Examples

**Minimal optional field** (always valid; no success/danger styling unless you treat it as required elsewhere):

```html
<hb-input-text schemaentry='{"id":"nickname","placeholder":"Optional nickname"}'></hb-input-text>
```

**Required field with validation message and Bulma states:**

```html
<hb-input-text
  show_validation="yes"
  schemaentry='{"id":"code","required":true,"placeholder":"2–6 chars","validationTip":"Enter between 2 and 6 characters.","params":{"min":2,"max":6}}'
></hb-input-text>
```

**Disabled with initial value:**

```html
<hb-input-text
  schemaentry='{"id":"email_display","label":"Email","value":"user@example.com","disabled":true,"placeholder":"Locked"}'
></hb-input-text>
```

```js
const el = document.querySelector("hb-input-text");
el.addEventListener("setVal", (e) => {
  console.log(e.detail.id, e.detail.value, e.detail.valid);
});
el.addEventListener("clickEnter", (e) => {
  if (e.detail.valid) submit(e.detail);
});
```

## Related files

| File | Purpose |
| --- | --- |
| `component.wc.svelte` | Implementation, events, and Bulma markup. |
| `types/webcomponent.type.d.ts` | **`Component`**, **`Events`**, **`FormSchemaEntry`**, **`InputTextParams`**. |
| `extra/docs.ts` | Storybook metadata, **`styleSetup`**, and example payloads. |
| `styles/bulma.scss` / `styles/webcomponent.scss` | Bulma forwarding and component-specific shadow styles. |

---

<a id="wc-json-viewer"></a>

# `hb-json-viewer`

**Category:** data · **Tags:** data, json · **Package:** `@htmlbricks/hb-json-viewer`

## Description

`hb-json-viewer` renders JSON as an expandable, syntax-highlighted tree inside a shadow root. The payload may be a JavaScript object (when you set the `json` property from code) or a JSON string (from the `json` attribute or property). When `json` is a string, the component tries `JSON.parse`; on failure it logs a `console.error` and keeps the unparsed string as `json`.

Optional **edit mode** (`edit="yes"`) adds inline controls to rename object keys, change primitive values, delete nodes, and append children. Each structural change dispatches an `update` event on the host whose `detail` includes **deep snapshots** `previous_json` and `json` so hosts can persist or diff the tree without re-reading the DOM.

## Initial expand mode (`status`)

The `status` attribute controls the **initial** collapsed state per depth. Users can still expand or collapse nodes afterward.

| Value | Behavior |
|-------|----------|
| `open` | All levels start expanded (default). |
| `closed` | All object/array nodes start collapsed. |
| `first` | Only the root level is expanded; nested containers start collapsed. |

## Edit mode and `strict_schema`

When `edit` is `no` (default), the tree is read-only.

When `edit` is `yes`, object keys show a pencil control for renaming, primitives show a pencil for values, non-primitive nodes can add children, and deletable nodes show a remove control. **Renaming** is ignored if the new key is already taken on the same parent. **Add child:** arrays receive a new `null` element; objects receive a new property `newKey` (or `newKey_1`, `newKey_2`, … if those names already exist) with value `null`.

`strict_schema` (default `yes`) controls how values are edited:

- **`strict_schema="yes"`** — Only **string**, **number**, and **boolean** leaves are editable. Strings use a single-line input or a textarea when the value is long or contains newlines. Numbers use a numeric input; booleans use a `true` / `false` select. **`null`**, **`undefined`**, and other primitive-like displays are not opened for value editing in this mode (structure can still be changed with add/delete where applicable).
- **`strict_schema="no"`** — Any primitive leaf uses one **textarea** prefilled with `JSON.stringify`-style text; confirming parses with `JSON.parse`, or keeps the raw string if parsing fails.

## Styling (Bulma and tree tokens)

The component forwards Bulma into the shadow root and themes the tree with **`--jn-*`** tokens and **`--bulma-*`** variables on `:host`. Defaults chain through Bulma’s variable system — see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

There are **no** `::part` hooks; customize appearance by setting CSS custom properties on `hb-json-viewer`.

Notable groups:

- **Tree chrome:** `--jn-bracket`, `--jn-close`, `--jn-key`, `--jn-sep`, `--jn-string`, `--jn-children-border`, `--jn-button-border`, `--jn-button-hover-bg`, `--jn-type-object`, `--jn-type-array`, `--jn-popover-*`.
- **Edit UI:** `--jn-edit-icon`, `--jn-edit-border`, `--jn-edit-focus-border`, `--jn-edit-bg`, `--jn-delete-icon`, `--jn-delete-icon-hover`.
- **Scheme:** `--bulma-text`, `--bulma-text-weak`, `--bulma-background` (and other Bulma tokens as needed).

Dark appearance follows **`prefers-color-scheme: dark`**, or **`data-theme="dark"`** / **`.theme-dark`** on `html` or `body`, or **`data-theme="dark"`** on the host element.

The authoritative list (types, defaults, and descriptions) lives in **`extra/docs.ts`** (`styleSetup.vars`).

## CSS parts

None.

## HTML slots

| Slot | Description |
|------|-------------|
| `default` | Optional light-DOM content rendered **below** the JSON tree (for example toolbars, help text, or footnotes). |

## Custom element

| Name | Tag |
| --- | --- |
| `hb-json-viewer` | `<hb-json-viewer …></hb-json-viewer>` |

## Attributes (snake_case; string encodings in HTML)

Web component **attributes** are strings. Use `"yes"` / `"no"` for booleans as reflected in HTML.

| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `id` | No | — | Optional element id. |
| `style` | No | — | Optional inline styles on the host. |
| `json` | No* | — | JSON document as a **string** in markup, a parsed **object** from JavaScript, or omitted / **`undefined`** for the empty state. If the assigned `json` is a string, the component parses it when possible. |
| `status` | No | `open` | `open` \| `closed` \| `first` — initial expand/collapse behavior (see above). |
| `edit` | No | `no` | `yes` enables editing and add/delete controls; `no` is read-only. |
| `strict_schema` | No | `yes` | `yes` uses type-specific editors for primitives; `no` uses a JSON textarea per leaf. |

\*If `json` is missing or **`undefined`**, the host shows a short “No data” message (see `component.wc.svelte`).

## Events

### `update`

Fired after every successful edit-mode mutation. **`detail`** matches `JsonViewerUpdateDetail` in the types below.

| `detail.type` | Meaning | Extra fields |
|---------------|---------|----------------|
| `editvalue` | Primitive value changed | `oldValue`, `newValue` |
| `editkey` | Object property renamed | `oldKey`, `newKey` |
| `deletenode` | Entry removed from object or array | `deletedValue` |
| `addnode` | New `null` entry added | — |

Always present: **`type`**, **`previous_json`**, **`json`** (deep clones of the tree before and after), and **`keyPath`** (`(string | number)[]` path to the affected node; for `editkey`, the path uses the **new** key as the last segment).

## Usage notes

- **Large payloads in HTML:** Prefer assigning `element.json = myObject` or `element.setAttribute('json', JSON.stringify(obj))` from script instead of embedding huge escaped strings in static HTML.
- **Key paths:** Indices in `keyPath` are numeric positions for array elements and string keys for object properties.
- **Snapshots:** `previous_json` and `json` are produced with `structuredClone` when available, otherwise `JSON.parse(JSON.stringify(...))` with a best-effort fallback.

## Types

```typescript
export type Component = {
  id?: string;
  json?: any;
  status?: "open" | "closed" | "first";
  edit?: "yes" | "no";
  strict_schema?: "yes" | "no";
};

/** Discriminant for `update` — which operation produced the new tree */
export type JsonViewerUpdateType =
  | "editvalue"
  | "editkey"
  | "deletenode"
  | "addnode";

export type JsonViewerUpdateDetail = {
  type: JsonViewerUpdateType;
  /** Full tree before this change (deep snapshot) */
  previous_json: any;
  /** Full tree after this change (deep snapshot) */
  json: any;
  keyPath: (string | number)[];
  oldValue?: any;
  newValue?: any;
  oldKey?: string;
  newKey?: string;
  deletedValue?: any;
};

export type Events = {
  update: JsonViewerUpdateDetail;
};
```

## Examples

### Static HTML

```html
<hb-json-viewer
  json='{"hello":"world","nested":{"a":1}}'
  status="first"
  edit="no"
></hb-json-viewer>
```

### JavaScript (property API and events)

```html
<hb-json-viewer id="viewer" status="open" edit="yes" strict_schema="yes"></hb-json-viewer>
<script type="module">
  const el = document.getElementById("viewer");
  const data = { title: "Demo", count: 3, items: ["a", "b"] };
  el.json = data;
  el.addEventListener("update", (e) => {
    const { type, json, keyPath } = e.detail;
    console.log(type, keyPath, json);
  });
</script>
```

Additional structured examples (orders, analytics-style payloads, strict vs flexible edit) are defined in **`extra/docs.ts`** (`examples`).

---

<a id="wc-layout"></a>

# `hb-layout`

**Category:** layout · **Tags:** layout, shell · **Package:** `@htmlbricks/hb-layout`

## Overview

`hb-layout` is a responsive **application shell** that picks one of two nested implementations based on the viewport width:

- **Viewport width &lt; 800px** — mounts **`hb-layout-mobile`** (mobile chrome, off-canvas patterns, compact navbar).
- **Viewport width ≥ 800px** — mounts **`hb-layout-desktop`** (sidebar + desktop navbar).

The host forwards configuration props, **named slots**, and many **custom events** from whichever child is active. Bulma-driven child packages (navbar, footer, sidebar, cookie banner, and so on) inherit theme tokens you set on `hb-layout` or its ancestors.

## Consumer rules (web components)

- **Attribute names** use **`snake_case`** (for example `page_title`, `cookielawuri4more`).
- From **HTML**, pass **objects and arrays as JSON strings** (for example `company='{"siteName":"Acme"}'`).
- Pass **booleans** as the strings **`yes`** or **`no`** where the platform expects stringly attributes (see individual props below; some nested booleans remain JSON booleans inside JSON payloads).
- For authoritative **TypeScript** shapes of nested objects (`company`, `contacts`, `navlinks`, …), see `types/webcomponent.type.d.ts` in this folder and the shared types re-exported from **`hb-footer`**, **`hb-navbar`**, and **`hb-sidenav-link`** (paths referenced in that file).

## Props (attributes / properties)

| Name | Purpose |
|------|---------|
| `i18nlang` | Locale hint passed into nested chrome (for example `en`, `it`). |
| `i18nlanguages` | Languages for the sidebar language UI: either a **JSON array** of `{ code, label }` or an equivalent in-memory array when using a framework binding. |
| `id` | Declared on the component type; use the **`id` attribute on the `<hb-layout>` element** in HTML when you need a stable host id. |
| `style` | Present in typings for host-level styling; the inner implementation focuses on nested layout tags—set styles on the custom element as needed. |
| `page_title` | Title string shown in layout chrome (navbar / header context). |
| `pagename` | Optional **current page key** label consumed by sidebar navigation state. |
| `company` | Company / brand payload (`ICompany`) — JSON from HTML. |
| `contacts` | Contact blocks for footer (`IContacts`). |
| `socials` | Social links payload (`ISocials`). |
| `usermenu` | Navbar user menu definition (`IUserMenu`). |
| `navlinks` | Sidebar / navigation entries (`INavLink[]`). Icons use **Bootstrap Icons** names **without** the `bi-` prefix (for example `"gear"`). |
| `sidebar` | Sidebar options: `title`, `logo`, `type`, `enablefooter`, `enablethemeswitch` (footer/theme toggles use `yes` / `no` / `false`-style string flags where applicable). |
| `footer` | Footer behaviour: `type` — `auto` \| `small` \| `regular` \| `large`; `disable_expanding_small` — boolean in the JSON object. |
| `policies` | Footer policy links (`IPolicies[]`). |
| `columns` | Extra footer column configuration (`IColumn[]`) when supported by `hb-footer`. |
| `cookielaw` | Enables or configures cookie-law UI together with the fields below (`yes` / `no`, with child layouts also accepting `true` / `false` in nested checks). |
| `cookielawuri4more` | “Learn more” URL for the cookie banner. |
| `cookielawlanguage` | Language passed into `hb-cookie-law-banner`. |
| `cookielawallowdecline` | Whether decline is offered (`yes` / `no`). |
| `single_screen` | When enabled, uses a **single-viewport** column layout with footer behaviour tuned for compact shells (see Storybook-style examples in `extra/docs.ts`). |
| `heders` | **(Spelling as in the API.)** Optional array of `{ name?, content, property? }` objects rendered as **`<meta>`** tags via `<svelte:head>`: either `name`+`content` or Open Graph–style `property`+`content`. |

## Events

The table lists events **`hb-layout` forwards or emits**. Listen with `addEventListener` or your framework’s `on*` bindings on **`hb-layout`**.

| Event | `detail` | Notes |
|-------|----------|--------|
| `offcanvasswitch` | `{ isOpen: boolean }` | Mobile / drawer state from the active child. |
| `pageChange` | `{ page: string }` | Sidebar or shell navigated to a new logical page key. |
| `navbarDropDownClick` | `{ key: string }` | **Desktop path only** at the `hb-layout` wrapper: the mobile branch in `component.wc.svelte` does not re-dispatch this handler, so you will not receive it while the mobile layout is mounted. |
| `footerClick` | `{ elClick: string }` | Footer interaction payload from `hb-footer`. |
| `navbarSlotClick` | `{ side: "left" \| "right" \| "center" }` | Navbar slot clicks bubbled from the nested navbar. |
| `themeChange` | `{ mode: "light" \| "dark" \| "auto" }` | Theme mode changes from sidebar chrome. |
| `languageChange` | `{ code: string }` | Language selection changes from sidebar chrome. |

## Slots

Slots are **declared on `hb-layout`** and forwarded into the active `hb-layout-*` child where the child exposes an equivalent surface.

| Slot | Role | Availability at the `hb-layout` bridge |
|------|------|------------------------------------------|
| `page` | Primary application view / routed content. | **Mobile and desktop** — always forwarded. |
| `nav-center-slot` | Center navbar region (tabs, titles, tools). | **Mobile and desktop**. |
| `nav-right-slot` | Right navbar actions / menus. | **Mobile and desktop**. |
| `nav-left-slot` | Left navbar region. | **Mobile only** — the desktop branch does not wrap this slot; desktop left chrome is driven by **`company` / `sidebar`** inside `hb-layout-desktop`. |
| `nav-header-slot` | Optional row above the main navbar. | **Mobile only** — not passed through in the desktop branch of `hb-layout`. |

## Theme and layout styling (Bulma)

Set **`--bulma-*`** CSS variables on **`hb-layout`** (or on an ancestor) so nested navbar, sidebar, footer, and page regions share one palette. Optional **chrome** overrides (inherit into nested shadow hosts):

| Variable | Role |
|----------|------|
| `--hb-layout-navbar-background` | Top **`hb-navbar`** strip only (overrides Bulma navbar token for that host). |
| `--hb-layout-sidebar-background` | **`hb-sidebar-desktop`** shell only (navbar unchanged). |
| `--bulma-text` | Primary text forwarded into navbar, sidebar, and page regions. |
| `--bulma-background` | Application background behind the shell. |
| `--bulma-link` | Interactive accents in forwarded navigation components. |
| `--bulma-border` | Dividers and low-contrast outlines. |

Per-component fallbacks: **`hb-navbar`** still resolves **`--hb-navbar-background-color`** then **`--bulma-navbar-background-color`**; **`hb-sidebar-desktop`** uses **`--hb-sidebar-background-color`** then **`--bulma-scheme-main-bis`** / **`--bulma-scheme-main-ter`** (see those packages’ READMEs).

See also [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

## CSS `::part` API

Expose these parts from the **mounted** child layout (same names as in `styleSetup` / `extra/docs.ts`):

| Part | Description |
|------|-------------|
| `container` | Outer shell wrapping navbar, main column, and footer stack. |
| `navbar` | Top navigation bar (`part="navbar"` on nested `hb-navbar`). |
| `page` | Main scrollable content column. |
| `footer` | Footer region from `hb-footer`. |

## Nested packages

Published bundles pull in **`hb-layout-desktop`** and **`hb-layout-mobile`**, each with its own dependency tree (`hb-navbar`, `hb-footer`, `hb-offcanvas`, `hb-sidebar-desktop`, `hb-sidenav-link`, `hb-dropdown-simple`, `hb-cookie-law-banner`, `hb-contact-item`, …). See `dependencies` in `extra/docs.ts` for the full graph used by documentation and catalog tooling.

## Examples and metadata

Storybook-oriented **`examples`** (default shell, cookie law, single-screen mode, minimal nav) live in **`extra/docs.ts`** alongside **`styleSetup`**, **`htmlSlots`**, and **`storybookArgs`**.

## Type reference (summary)

The authoring `Component` / `Events` types live in **`types/webcomponent.type.d.ts`**. They compose:

- `IContacts`, `ISocials`, `ICompany`, `IColumn`, `IPolicies` from the footer typings,
- `IUserMenu` from the navbar typings,
- `INavLink` from the sidenav link typings,

plus this host’s fields (`i18nlanguages`, `sidebar`, `footer`, `heders`, cookie-law props, `single_screen`, etc.). Open that file for exact optional fields and unions.

---

<a id="wc-layout-desktop"></a>

# `hb-layout-desktop`

**Category:** layout · **Tags:** layout, shell, responsive · **Package:** `@htmlbricks/hb-layout-desktop`

Desktop application shell: a top **`hb-navbar`**, an optional left **`hb-sidebar-desktop`** when **`navlinks`** has at least one item, a main **`page`** slot column, an optional **`hb-cookie-law-banner`**, and **`hb-footer`**. Child packages are registered at runtime (`hb-footer`, `hb-offcanvas`, `hb-navbar`, `hb-cookie-law-banner`, `hb-sidebar-desktop`).

## When the sidebar appears

- **`navlinks`** non-empty **and** the internal navbar menu state is open (`switchopen` / off-canvas flow): the shell renders the two-column grid (sidebar track + main column).
- **`navlinks`** empty **or** the menu has been closed from the navbar switch: the sidebar rail is omitted; content runs full width under the navbar (same cookie/footer rules as the two-column variant).

The navbar still receives **`navlinks`** context for branding and burger behavior where applicable.

## Viewport height and scrolling

The host is constrained to the viewport (**`min-height` / `height` / `max-height`**: **`100vh`** / **`100dvh`**) with **`overflow: hidden`** so tall slotted content cannot stretch the shell or the sidebar rail. For a non-viewport parent, prefer giving the host a definite height (for example **`height: 100%`**) instead of relying only on dynamic viewport units—see comments in `styles/webcomponent.scss`.

### Default mode (`single_screen` off / falsy)

The main column scrolls as a whole. The **`page`** slot sits in a flex column with the optional cookie banner and footer below it; short content still gets a stretched middle region so the footer stays at the bottom of the scrollable band.

### Single-screen mode (`single_screen` truthy)

The cookie banner and footer stay pinned under the **`page`** area inside the main column; **only** the **`page`** region scrolls. When **`footer.type`** is **`auto`**, the footer is forced to the compact **`small`** variant; in default mode, **`auto`** maps to **`regular`**.

From HTML/attributes, **`single_screen`** is normalized as a string: anything other than **`"no"`** is treated as enabled; **`"no"`** disables it.

## Cookie banner visibility

**`hb-cookie-law-banner`** is mounted when any of these hold: **`cookielaw`** is **`"yes"`** or **`"true"`**; **`cookielawallowdecline`** is **`"yes"`** or **`"no"`**; **`cookielawlanguage`** is non-empty; **`cookielawuri4more`** is non-empty. The banner receives **`language`** from **`cookielawlanguage`** or falls back to **`i18nlang`**, plus **`allowdecline`** and **`cookielawuri4more`**.

## Props (attributes)

Web component **attributes** are **strings** in **`snake_case`**. Pass structured data (**`company`**, **`contacts`**, **`navlinks`**, **`usermenu`**, **`socials`**, **`columns`**, **`policies`**, **`sidebar`**, **`footer`**, **`i18nlanguages`**) as **JSON strings** from HTML, or assign parsed objects in JavaScript. Booleans follow the project convention **`yes`** / **`no`** where noted.

| Name | Role |
|------|------|
| **`i18nlang`** | Active locale code forwarded to **`hb-sidebar-desktop`**. |
| **`i18nlanguages`** | Language picker entries: JSON array of **`{ code, label }`** (or equivalent string form). |
| **`id`**, **`style`** | Standard host passthrough. |
| **`company`** | Company block for footer (JSON); navbar left brand may use **`company.logoUri`** and **`company.siteName`**. |
| **`page_title`** | Preferred string for **`hb-navbar`** **`companybrandname`**; falls back to **`company.siteName`**. |
| **`navlinks`** | Sidebar navigation (**`INavLink[]`** as JSON). Empty array removes the sidebar column (see above). |
| **`pagename`** | Current page key forwarded as **`hb-sidebar-desktop`** **`navpage`**. |
| **`usermenu`** | Navbar user menu payload (JSON). |
| **`socials`**, **`contacts`**, **`columns`**, **`policies`** | Forwarded to **`hb-footer`** as JSON strings inside the shadow tree. |
| **`sidebar`** | JSON object: **`title`**, **`logo`**, **`type`**, plus **`enablefooter`** / **`enablethemeswitch`** (**`yes`** / **`no`**) for **`hb-sidebar-desktop`**. |
| **`footer`** | JSON object: **`type`** **`auto`** \| **`small`** \| **`regular`** \| **`large`**; **`disable_expanding_small`** boolean maps to **`yes`**/**`no`** on **`hb-footer`**. Parsed from a string in an effect if needed. |
| **`cookielaw`**, **`cookielawuri4more`**, **`cookielawallowdecline`**, **`cookielawlanguage`** | Cookie-law banner wiring (see visibility rules). |
| **`single_screen`** | String or boolean; controls pinned footer/cookie vs scrolling column (see above). |

The authoritative TypeScript shape is **`types/webcomponent.type.d.ts`** (including nested types from **`hb-footer`**, **`hb-navbar`**, **`hb-sidenav-link`**, **`hb-sidebar-desktop`**).

## Events

All events are **`CustomEvent`** instances on **`hb-layout-desktop`**.

| Event | `detail` |
|-------|----------|
| **`offcanvasswitch`** | **`{ isOpen: boolean }`** — forwarded from **`hb-navbar`** when the nav menu open state changes. |
| **`pageChange`** | **`{ page: string }`** — forwarded from **`hb-sidebar-desktop`**. |
| **`navbarDropDownClick`** | **`{ key: string }`** — forwarded from **`hb-navbar`**. |
| **`navbarSlotClick`** | **`{ side: "left" \| "right" \| "center" }`** — forwarded from **`hb-navbar`**. |
| **`footerClick`** | **`{ elClick: string }`** — forwarded from **`hb-footer`**. |
| **`themeChange`** | **`{ mode: "light" \| "dark" \| "auto" }`** — forwarded from **`hb-sidebar-desktop`**. |
| **`languageChange`** | **`{ code: string }`** — forwarded from **`hb-sidebar-desktop`**. |

## Slots

| Slot | Description |
|------|-------------|
| **`nav-center-slot`** | Projected into **`hb-navbar`** **`center-slot`** (titles or inline controls). |
| **`nav-right-slot`** | Projected into **`hb-navbar`** **`right-slot`** (actions, extras). |
| **`page`** | Main application view body in the desktop content column. |

## CSS custom properties

Theme and layout tokens (Bulma variables and host-specific sizing) are documented in **`extra/docs.ts`** under **`styleSetup.vars`**. Highlights:

| Variable | Purpose |
|----------|---------|
| **`--hb-layout-navbar-background`** | Optional top bar background for nested **`hb-navbar`** (set here, on **`hb-layout`**, or on an ancestor). |
| **`--hb-layout-sidebar-background`** | Optional **`hb-sidebar-desktop`** shell background (distinct from the navbar token). |
| **`--hb-layout-desktop-sidebar-width`** | First grid track width for the sidebar column (default **`240px`**). |
| **`--bulma-navbar-item-img-max-height`** | Caps the optional brand image in the navbar left area. |
| **`--bulma-text`**, **`--bulma-background`**, **`--bulma-border`** | Shell chrome aligned with [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/). |

## CSS `::part` names

| Part | Description |
|------|-------------|
| **`navbar`** | The top **`hb-navbar`** strip. |
| **`container`** | The main flex column under the navbar (grid row or full-width main). |
| **`page`** | The scrollable main content region beside the optional sidebar. |
| **`footer`** | The bottom **`hb-footer`** region. |

## Navigation icons

Optional **`icon`** fields on each **`navlinks`** entry use **Bootstrap Icons** glyph names only (for example **`house-door`**). See the **`hb-sidebar-desktop`** package README for conventions.

## Custom element

```html
<hb-layout-desktop
  navlinks='[{"label":"Home","key":"home","icon":"house-door"}]'
  pagename="home"
  page_title="My app"
  single_screen="no"
>
  <span slot="page">…</span>
</hb-layout-desktop>
```

Adjust attribute payloads to match your JSON schema for **`company`**, **`footer`**, **`usermenu`**, and other structured props.

## Types

Authoring types for **`Component`** and **`Events`** live in **`types/webcomponent.type.d.ts`**. Generated consumer typings (**`types/html-elements.d.ts`**, **`types/svelte-elements.d.ts`**) are produced on **`npm run build:wc`** from the builder package.

---

<a id="wc-layout-mobile"></a>

# `hb-layout-mobile`

**Category:** layout · **Tags:** layout, shell, responsive · **Package:** `@htmlbricks/hb-layout-mobile`

## Overview

`hb-layout-mobile` is a mobile-first application shell: optional offcanvas navigation (`hb-offcanvas` + `hb-sidebar-desktop`), a top `hb-navbar`, a primary `page` slot, an optional `hb-cookie-law-banner`, and `hb-footer`. It forwards navbar, footer, and navigation-related custom events to the host.

The host element is a fixed-height viewport column (`100vh` / `100dvh`, `overflow: hidden`). The inner `.hb-layout-mobile__shell` is a flex column that stacks navbar, scrollable page region, and—depending on mode—cookie banner and footer.

### Scroll modes

- **Default (`single_screen` off or `"no"`):** The main band (part `page`) scrolls vertically. Inside it, the slot sits in a flex column with the cookie banner and footer so the slot grows when content is short, keeping the footer at the bottom of the scroll area (see `.hb-layout-mobile__page--footer-inside` in styles). When `footer.type` is `"auto"`, the embedded footer uses the **`regular`** size.
- **`single_screen` on (any string except `"no"`, or boolean true):** Cookie banner and footer sit **outside** the scroll area at the bottom of the shell; **only** the page slot scrolls. When `footer.type` is `"auto"`, the footer uses the **`small`** size.

If you embed the host in a sized container instead of the full viewport, give ancestors a defined height and set `height: 100%` on `hb-layout-mobile` so the flex chain resolves.

## Dependencies

The bundle registers nested packages (see `extra/docs.ts`): `hb-footer` (with `hb-contact-item`), `hb-offcanvas` (with `hb-sidenav-link`, `hb-sidebar-desktop`), `hb-navbar` (with `hb-dropdown-simple`), `hb-cookie-law-banner`, and `hb-sidebar-desktop`. Nested components ship their own Bulma scopes; theme tokens on the document still apply where shared.

## Styling

### Host and layout (SCSS)

`styles/webcomponent.scss` pins `:host` to full dynamic viewport height, hides overflow, and uses flex for the shell. The page area uses `overflow-y: auto` where scrolling applies. Bootstrap Icons are loaded for nested UI. `styles/bulma.scss` forwards Bulma flex helpers and host-scoped theme tokens so the shell can align with the Bulma variable system.

### Theme variables (`--bulma-*`)

Set public Bulma CSS variables on `:root` / `body` (or any ancestor) so nested shadow roots that read them stay on-brand. Documented in `extra/docs.ts`:

| Variable | Role |
|----------|------|
| `--hb-layout-navbar-background` | Optional **`hb-navbar`** background (set on this host, **`hb-layout`**, or an ancestor). |
| `--hb-layout-sidebar-background` | Optional **`hb-sidebar-desktop`** background in offcanvas / shell. |
| `--bulma-scheme-main` | Default surfaces inside nested chrome. |
| `--bulma-text` | Primary text in navbar and footer. |
| `--bulma-navbar-background-color` | Bulma token consumed by **`hb-navbar`** (after optional layout / **`--hb-navbar-background-color`**). |
| `--bulma-scheme-main-bis`, `--bulma-scheme-main-ter` | Default **`hb-sidebar-desktop`** host background chain when layout sidebar override is unset. |
| `--bulma-navbar-item-color` | Navbar links and controls. |
| `--bulma-footer-background-color` | Footer background when `hb-footer` is used. |
| `--bulma-link` | Interactive accents (dropdowns, links). |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

### CSS parts

| Part | Description |
|------|-------------|
| `container` | Outer flex column stacking navbar, page, and (in single-screen mode) fixed cookie/footer. |
| `navbar` | Wrapper around `hb-navbar` for shell alignment. |
| `page` | Main column: scrollable viewport for content (and in default mode, cookie + footer inside the same scroll stack). |
| `footer` | Wrapper around the embedded `hb-footer`. |

### HTML slots

| Slot | Description |
|------|-------------|
| `nav-header-slot` | Rendered in `hb-offcanvas` header span (only when `navlinks` is non-empty). |
| `nav-left-slot` | Forwarded to `hb-navbar` `left-slot`. |
| `nav-center-slot` | Forwarded to `hb-navbar` `center-slot`. |
| `nav-right-slot` | Forwarded to `hb-navbar` `right-slot`. |
| `page` | Primary content between navbar and footer/cookie stack. |

## Custom element

`hb-layout-mobile`

## Attributes (HTML)

Web component attributes use **snake_case** and **string** values. Objects and arrays must be **JSON strings**. Booleans from HTML are typically `"yes"` / `"no"` (this repo’s convention); some props also accept `"true"` / `"false"` where noted.

| Attribute | Description |
|-----------|-------------|
| `id` | Optional host identifier (typed on `Component`). |
| `style` | Optional inline style string (typed on `Component`). |
| `company` | JSON: company / brand payload for navbar and footer (`ICompany`). |
| `contacts` | JSON: footer contacts (`IContacts`). |
| `socials` | JSON: footer social links (`ISocials`). |
| `navlinks` | JSON: `INavLink[]`. When the array is **non-empty**, `hb-offcanvas` is rendered and the navbar shows the menu control; when empty or omitted, offcanvas is omitted and `noburger` is set on the navbar. |
| `sidebar` | JSON: offcanvas chrome — `title`, `logo`, `type`, optional `enablefooter`, `enablethemeswitch` (`"yes"` / `"no"` / `"false"` / empty). Passed through to `hb-offcanvas` / sidebar where used. |
| `usermenu` | JSON: `IUserMenu` for the navbar avatar dropdown. |
| `pagename` | Active navigation key for offcanvas (`navpage` on `hb-offcanvas`). |
| `page_title` | Navbar brand text (overrides `company.siteName` when set). |
| `footer` | JSON: `{ type?: "auto" \| "small" \| "regular" \| "large"; disable_expanding_small?: boolean }`. Defaults to `{ type: "auto", disable_expanding_small: false }` when coerced from string. |
| `columns` | JSON: `IColumn[]` for `hb-footer`. |
| `policies` | JSON: `IPolicies[]` for `hb-footer`. |
| `cookielaw` | `"yes"` / `"true"` (and other typed literals) — part of cookie banner visibility. |
| `cookielawuri4more` | Policy link URI string for the cookie banner. |
| `cookielawlanguage` | Language string for the cookie banner. |
| `cookielawallowdecline` | `"yes"` / `"true"` / `"no"` / `"false"` — banner decline behavior. |
| `single_screen` | String `"no"` disables single-screen mode; any other non-empty string enables it (coerced to boolean). Controls fixed footer/cookie vs scrolling layout (see above). |
| `i18nlang` | Language code passed into nested widgets (e.g. `en`, `it`). |
| `i18nlanguages` | JSON array `{ code, label }[]` or string — language options for the offcanvas/sidebar path. |

### Cookie banner visibility

`hb-cookie-law-banner` is rendered when **any** of the following holds: `cookielaw` is `"yes"` or `"true"`, `cookielawallowdecline` is `"yes"` or `"no"`, `cookielawlanguage` is a non-empty string, or `cookielawuri4more` is a non-empty string. The banner receives `language` from `cookielawlanguage` or `i18nlang`, plus `allowdecline` and `cookielawuri4more`.

## Events

Custom events bubble from the host as declared in `types/webcomponent.type.d.ts`:

| Event | `detail` |
|-------|----------|
| `offcanvasswitch` | `{ isOpen: boolean }` — from offcanvas or navbar menu switch. |
| `pageChange` | `{ page: string }` — offcanvas page selection. |
| `navbarDropDownClick` | `{ key: string }` — navbar dropdown entry. |
| `footerClick` | `{ elClick: string }` — footer interaction key. |
| `navbarSlotClick` | `{ side: "left" \| "right" \| "center" }` — navbar slot area click. |
| `languageChange` | Same shape as `hb-sidebar-desktop` `languageChange` (e.g. `{ code: string }`). |
| `themeChange` | Same shape as `hb-sidebar-desktop` `themeChange` (theme preference). |

## Usage notes

- **Navigation icons:** `navlinks` entries use Bootstrap Icons names **without** the `bi-` prefix (e.g. `house-door`), consistent with other layout/nav components.
- **JSON props:** At runtime, `footer`, `sidebar`, and `company` may be parsed from JSON strings in effects when passed as strings from attributes.

## TypeScript (`types/webcomponent.type.d.ts`)

```typescript
import type {
  IContacts,
  ISocials,
  ICompany,
  IColumn,
  IPolicies,
} from "../../footer/types/webcomponent.type";
import type { IUserMenu } from "../../navbar/types/webcomponent.type";
import type { INavLink } from "../../sidenav-link/types/webcomponent.type";
import type { Events as SidebarDesktopEvents } from "../../sidebar-desktop/types/webcomponent.type";

export type I18nLanguageOption = { code: string; label: string };

export type Component = {
  id?: string;
  style?: string;
  socials?: ISocials;
  contacts?: IContacts;
  company?: ICompany;
  navlinks?: INavLink[];
  pagename?: string;
  page_title?: string;
  usermenu?: IUserMenu;
  cookielaw?: "yes" | "true" | "no" | "false" | null | "" | undefined;
  columns?: IColumn[];
  single_screen?: boolean;
  cookielawuri4more?: string;
  cookielawallowdecline?: "yes" | "true" | "no" | "false" | null | "" | undefined;
  cookielawlanguage?: string;
  sidebar?: {
    title?: string;
    logo?: string;
    type?: string;
    enablefooter?: "yes" | "no" | "false" | null | "" | undefined;
    enablethemeswitch?: "yes" | "no" | "false" | null | "" | undefined;
  };
  footer?: {
    type?: "auto" | "small" | "regular" | "large";
    disable_expanding_small?: boolean;
  };
  policies?: IPolicies[];
  i18nlang?: string;
  i18nlanguages?: I18nLanguageOption[] | string;
};

export type Events = {
  offcanvasswitch: { isOpen: boolean };
  pageChange: { page: string };
  navbarDropDownClick: { key: string };
  footerClick: { elClick: string };
  navbarSlotClick: { side: "left" | "right" | "center" };
  languageChange: SidebarDesktopEvents["languageChange"];
  themeChange: SidebarDesktopEvents["themeChange"];
};
```

## Minimal HTML example

```html
<hb-layout-mobile i18nlang="en"></hb-layout-mobile>
```

---

<a id="wc-map"></a>

# `hb-map`

**Category:** maps · **Tags:** maps · **Package:** `@htmlbricks/hb-map`

## Description

`hb-map` is an interactive map web component built with OpenLayers. You configure **center** and **zoom**, a **source** (OpenStreetMap raster, custom XYZ tiles, or CARTO vector basemaps), **options** (fit view to markers, label layout), and **data** for on-map markers (longitude/latitude, optional icon, HTML popup, and optional text label).

The component listens for **single clicks**: clicks on empty map emit coordinates and current view; clicks on markers open optional popup HTML and emit a marker event. Setting **`screenshot`** to `yes` captures the visible map to PNG and emits a **base64** payload when rendering completes.

## Basemap sources (`source`)

Set `source` as a JSON string (see [Attributes](#attributes-snake_case-string-values-in-html)).

| `source.type` | Behavior |
|---------------|----------|
| `osm` | OpenStreetMap raster tiles. |
| `xyz` | Raster tiles from `source.url` (XYZ template with `{z}`, `{x}`, `{y}`). |
| `carto_vector` | CARTO vector tiles (default tile URL if `url` is omitted). Styling uses built-in presets. |

### CARTO vector: `source.style`

For `carto_vector`, `source.style` may be `positron`, `dark_matter`, or `voyager` when the page uses a **light** color scheme.

When the component detects a **dark** scheme, the vector basemap **always** uses the **dark_matter** palette, regardless of `source.style`. Detection order:

1. `hb-map`, then `document.documentElement`, then `document.body`: `data-theme="dark"` or class `theme-dark` → dark; `data-theme="light"` or `theme-light` → light.
2. If neither is set explicitly, **`prefers-color-scheme: dark`** selects dark.

The host and document are observed for `data-theme` and `class` changes, and the media query is listened to, so the basemap updates when the theme toggles.

### CARTO vector: boundaries and labels

- **`boundaries_only`**: When `true`, only the `boundary` layer is drawn as lines (plus **country names** from the `place` layer where `class === "country"`). Land, roads, water fills, and other layers are not drawn.
- **`national_boundaries_only`**: When `true`, only international / country boundaries (OSM `admin_level` **2**) are shown as **solid** lines. When `false`, level 2 stays solid and other allowed levels use a **dashed** stroke.
- **`boundary_admin_levels`**: Optional JSON **array string** of numeric admin levels to keep, e.g. `"[2]"` for countries only, `"[2,4]"` for countries and other divisions present in the tiles. Empty or omitted means no extra filter (all boundary features at the current zoom still pass the national-only rule when that flag applies). With `national_boundaries_only`, the effective set is the **intersection** with level 2; if the list excludes `2`, no boundaries render.

Boolean fields in serialized `source` may use **`yes` / `no`** (or `true` / `false` / `1` / `0` for compatibility).

### OSM in dark mode

For `osm`, raster tiles are rendered with a **grayscale + invert** filter when the same dark detection applies, so the map stays readable on dark UIs.

## Marker data (`data`)

`data` is a JSON array of objects. Each object may include a **`marker`** field. The current implementation **renders only `marker` entries** (icons, labels, popups, and click handling). The TypeScript `Component` type also describes optional **`point`** and **`line`** shapes for consistency with the schema; those are **not** drawn by this version of the component.

### Marker object (`marker`)

| Field | Description |
|-------|-------------|
| `lngLat` | `[longitude, latitude]` in EPSG:4326 (same order as OpenLayers `fromLonLat`). |
| `icon` | Optional: `uri` (image URL), `scale`, `anchor` `[x, y]`, `opacity`, `color` (tint). |
| `id` | Optional string passed through on `markerClick`. |
| `popupHtml` | Optional HTML string shown in the map popup when the marker is clicked. |
| `text` | Optional label next to or around the icon (or alone if there is no `icon.uri`). |
| `text_position` | Per-marker override: `top`, `right`, `bottom`, `left` (default from `options.text_position` or `right`). |
| `text_offset` | Per-marker pixel gap for label placement; falls back to `options.text_offset`, then an internal default. |

If there is no custom icon and no `text`, a default pin icon is used.

## Options (`options`)

JSON string. Common fields:

| Field | Description |
|-------|-------------|
| `centerFromGeometries` | When truthy, the view is centered or **fitted** from marker positions (fit when there are multiple markers with valid extent). |
| `text_position` | Default label side relative to the icon: `top`, `right`, `bottom`, `left`. |
| `text_offset` | Default pixel gap between icon and label. |
| `text_scale` | Multiplier for label font size (default scale 1 → base ~13px). |

## Screenshots

Set the attribute **`screenshot="yes"`** to request a capture after the next render completes. The component dispatches **`screenshotTaken`** with `{ base64 }` (PNG data URL). Clear or change the attribute between captures if your host framework does not re-trigger on repeated `yes`.

## Styling (Bulma and host size)

The map canvas sits on a host whose background can follow Bulma’s scheme variable **`--bulma-scheme-main`**. Size is controlled by **`--hb-map-width`**, **`--hb-map-height`**, and fallback **`--hb-map-default-size`** (default `200px` when width/height are unset). See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and `extra/docs.ts` (`styleSetup.vars`).

| Variable | Purpose |
|----------|---------|
| `--bulma-scheme-main` | Background behind the map. |
| `--hb-map-default-size` | Fallback width and height when explicit size vars are empty. |
| `--hb-map-width` | Optional explicit host width. |
| `--hb-map-height` | Optional explicit host height. |

## CSS parts

None.

## HTML slots

None.

## Custom element

`hb-map`

## Attributes (snake_case; string values in HTML)

Web component attributes are strings. Pass objects and arrays as **JSON strings**. Use **`yes` / `no`** for boolean fields where the project encoding applies; nested `source` booleans also accept `true` / `false` as strings.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `id` | No | Optional element id. |
| `style` | No | Optional host inline style (type-level; use CSS variables for layout when possible). |
| `zoom` | Yes* | Zoom level as a string (e.g. `"11"`). Parsed to a number internally. |
| `center` | Yes* | JSON array string: **`[longitude, latitude]`** in EPSG:4326. |
| `data` | Yes* | JSON array of geometry entries; **markers** use the `marker` property (see [Marker data](#marker-data-data)). |
| `source` | Yes* | JSON object: at minimum `{ "type": "osm" \| "xyz" \| "carto_vector", ... }` with optional `url`, `style`, `boundaries_only`, `boundary_admin_levels`, `national_boundaries_only` for vector tiles. |
| `options` | Yes* | JSON object for view and label defaults (see [Options](#options-options)). |
| `screenshot` | No | Set to `yes` to capture the map and emit `screenshotTaken`. |

\*The TypeScript `Component` type marks these as required for a complete configuration. The implementation still applies **defaults** when attributes are missing: `zoom` `6`, `center` **`[37.5176038, 15.0819224]`** (same `[longitude, latitude]` order as elsewhere, passed to `fromLonLat`), `source` `{ type: "osm" }`, `options` `{}`, `data` `[]`.

On window **resize**, the map layout is updated (debounced).

## Events

Listen with `addEventListener` or your framework’s binding. All `detail` payloads are plain objects.

| Event | `detail` |
|-------|----------|
| `pointClickCoordinates` | `{ coordinates: { latitude, longitude }, zoom, center }` — `center` is `[lon, lat]` in EPSG:4326 when derived from the view. Emitted when the click does not hit a marker. |
| `markerClick` | `{ coordinates: { latitude, longitude }, id? }` — `id` is present when the feature carries `marker.id` in `data`. |
| `screenshotTaken` | `{ base64 }` — PNG data URL string. |

## TypeScript (`types/webcomponent.type.d.ts`)

```typescript
export type Component = {
  id?: string;
  style?: string;
  zoom: number;
  center: number[];
  data: {
    marker?: {
      lngLat: number[];
      icon?: {
        uri: string;
        scale?: number;
        anchor?: number[];
        opacity?: number;
        color?: string;
      };
      id?: string;
      popupHtml?: string;
      text?: string;
      text_position?: "top" | "right" | "bottom" | "left";
      text_offset?: number;
    };
    point?: {
      lngLat: number[];
      icon?: {
        uri: string;
        scale?: number;
        anchor?: number[];
        opacity?: number;
        color?: string;
      };
      id?: string;
      popupHtml?: string;
    };
    line?: {
      lngLat: number[];
      icon?: {
        uri: string;
        scale?: number;
        anchor?: number[];
        opacity?: number;
        color?: string;
      };
      id?: string;
      popupHtml?: string;
    }[];
  }[];
  source: {
    type: string;
    url?: string;
    /**
     * Basemap palette for light UI. In dark color scheme (`prefers-color-scheme: dark` or
     * `data-theme="dark"` / `.theme-dark` on host or document), `dark_matter` is always used.
     */
    style?: "positron" | "dark_matter" | "voyager";
    /** When `true`, only the vector `boundary` layer is drawn (no land, roads, water fill, etc.). */
    boundaries_only?: boolean;
    /**
     * Optional JSON array string of OSM `admin_level` values to keep for `boundary` features,
     * e.g. `"[2]"` for country borders, `"[2,4]"` for countries plus common internal divisions.
     * Omit or empty to show all boundaries present in the tiles at the current zoom.
     */
    boundary_admin_levels?: string;
    /**
     * When `true`, only international / country boundaries (`admin_level` 2) are drawn; internal
     * regional lines are hidden. Combines with `boundary_admin_levels` by intersection (e.g. if
     * the JSON list excludes `2`, no boundaries render). National lines use a solid stroke;
     * when `false`, level 2 stays solid and other levels stay dashed.
     */
    national_boundaries_only?: boolean;
  };
  options: {
    centerFromGeometries?: boolean;
    text_position?: "top" | "right" | "bottom" | "left";
    text_offset?: number;
    text_scale?: number;
  };
  screenshot?: string;
};

export type Events = {
  pointClickCoordinates: {
    coordinates: { latitude: number; longitude: number };
    zoom: number;
    center: number[];
  };
  markerClick: {
    coordinates: { latitude: number; longitude: number };
    id?: string;
  };
  screenshotTaken: {
    base64: string;
  };
};
```

## Minimal HTML example

```html
<hb-map
  zoom="9"
  center="[10,10]"
  source='{"type":"osm"}'
  options="{}"
  data="[]"
></hb-map>
```

## Example: CARTO Voyager with markers

```html
<hb-map
  zoom="11"
  center="[2.3522,48.8566]"
  source='{"type":"carto_vector","style":"voyager"}'
  options='{"centerFromGeometries":true}'
  data='[{"marker":{"lngLat":[2.36,48.86],"text":"Paris"}}]'
></hb-map>
```

Inside JSON strings, use standard JSON booleans (`true` / `false`). Truthy string values (e.g. `"yes"`) for `centerFromGeometries` are also treated as enabled when parsed into a plain object.

---

<a id="wc-markdown-viewer"></a>

# `hb-markdown-viewer`

**Category:** content · **Tags:** content, markdown · **Package:** `@htmlbricks/hb-markdown-viewer`

## Description

`hb-markdown-viewer` renders Markdown supplied through a `data` payload (typically `{ content: string }`). The implementation uses [marked](https://marked.js.org/) with **GitHub Flavored Markdown (GFM)** enabled, **line breaks** turned on (`breaks: true`), and **smart lists** enabled. Before parsing, the component **normalizes compact, single-line Markdown** (for example inline `* list` items and bold section headers after punctuation) so lists and headings read more reliably when `content` arrives as one long string.

The output is injected into a Bulma-based layout: a `section` with a fluid `container` and an inner `.markdown-body` region styled in `styles/webcomponent.scss` (headings, lists, links, blockquotes, code, tables, and horizontal rules).

## Security note

Rendered output is applied with `{@html ...}`. Treat `data.content` as **trusted** input, or sanitize it upstream. Untrusted Markdown can still carry HTML depending on your Markdown pipeline and options.

## Data model and attributes

Web component **attributes and reflected properties** are **strings**. Pass structured data as a **JSON string**.

| Concern | Details |
|--------|---------|
| `data` | **Required for meaningful output.** JSON string shaped like `{ "content": "<markdown string>" }`. If the host passes a string, the component attempts `JSON.parse`; parse failures are logged to the console. |
| `id` | Optional string identifier for the host element. |
| `style` | Optional in the authoring `Component` type (`types/webcomponent.type.d.ts`). The current `component.wc.svelte` implementation does **not** read a `style` prop; use host `style` / stylesheets or CSS variables for appearance. |

### `data.content`

- **Type:** string (Markdown source).
- **Updates:** Whenever `data` / `data.content` changes, the component re-parses JSON (when needed) and re-renders HTML.
- **Errors:** Markdown rendering errors are logged; on failure the component falls back to using the raw `content` string as the HTML source string (still applied via `{@html}`), so avoid relying on this for untrusted input.

## Markdown behavior

- **GFM:** tables, task lists, strikethrough, autolinks, and other GFM features supported by `marked` with `gfm: true`.
- **Line breaks:** single newlines in the source can produce line breaks in output (`breaks: true`).
- **Normalization:** helps when `content` has few newlines—for example inserts breaks before inline list markers and improves spacing before bold headings following `.` or `:`.

## Styling (CSS custom properties)

The host can theme the viewer using **Bulma CSS variables** (see [Bulma: CSS variables](https://bulma.io/documentation/features/css-variables/)) and the component-specific token below. Defaults and roles match `extra/docs.ts`.

| Variable | Type (conceptual) | Default | Role |
|----------|-------------------|---------|------|
| `--bulma-text` | color | `#363636` | Default body text in the markdown column. |
| `--bulma-text-strong` | color | `#363636` | Heading emphasis color. |
| `--bulma-link` | color | `#485fc7` | Link color inside fenced blocks and anchors. |
| `--bulma-border` | color | `#dbdbdb` | Horizontal rules, blockquote border, and code borders. |
| `--bulma-scheme-main-bis` | color | `#f5f5f5` | Blockquote background tint. |
| `--bulma-scheme-main-ter` | color | `#f0f0f0` | Inline and fenced code background. |
| `--bulma-line-height-main` | number | `1.6` | Body line height inside `.markdown-body`. |
| `--bulma-weight-semibold` | number | `600` | Heading font weight. |
| `--bulma-radius-small` | length | `0.25rem` | Code block corner radius. |
| `--bulma-column-gap` | string | `0.75rem` | Gap between Bulma columns wrapping the content (from `styleSetup`; use when aligning with Bulma column layouts). |
| `--hb-markdown-body-tab-size` | number | `4` | Tab size for preformatted / code display (`tab-size` on `.markdown-body`). |

## CSS parts

None (`styleSetup.parts` is empty).

## HTML slots

None.

## Custom element

```text
hb-markdown-viewer
```

## Events

No custom events are declared in the public `Events` typing for this package. The component does not document a stable custom event API for hosts.

## TypeScript (`Component` / `Events`)

Authoring types for props and events live in `types/webcomponent.type.d.ts`:

```typescript
export type Component = {
  id?: string;
  style?: string;
  data: {
    content: string;
  };
};

export type Events = {};
```

## Examples

### Minimal HTML

```html
<hb-markdown-viewer data='{"content":"# Hello\n\n**world**"}'></hb-markdown-viewer>
```

### Headings and lists

```html
<hb-markdown-viewer
  data='{"content":"## Release notes\n\n### Highlights\n\n- Faster startup\n- Smaller bundle\n\n### Steps\n\n1. Install dependencies\n2. Run the build\n3. Deploy"}'
></hb-markdown-viewer>
```

### Inline and fenced code

```html
<hb-markdown-viewer
  data='{"content":"Example `npm` command:\n\n```bash\nnpm run build:wc -- input-text\n```\n\nInline code like `Component` is supported too."}'
></hb-markdown-viewer>
```

When embedding JSON in HTML attributes, escape quotes as required by your templating system so the attribute value remains valid JSON.

## Package metadata (from `extra/docs.ts`)

- **Name:** `hb-markdown-viewer`
- **npm-style repo name:** `@htmlbricks/hb-markdown-viewer`
- **License:** Apache-2.0 (see package `LICENSE.md` where applicable)

---

<a id="wc-matrix-video"></a>

# `hb-matrix-video`

**Category:** media · **Tags:** media, video, grid · **Package:** `@htmlbricks/hb-matrix-video`

## Overview

`hb-matrix-video` is a responsive **video wall** custom element. It lays out a list of **tiles** (`items`) in a **gapless Bulma** `columns` / `column` grid, sizes the grid from the host viewport, and biases layouts toward a **16:9** cell aspect ratio. Each tile can embed a page (`iframe`), an MP4 stream (`video`), a **WHEP** live player (`hb-player-live` via `mediamtx-webrtc`), or a PTZ camera player (`hb-player-live-camera-ptz` via `mediamtx-webrtc-ptz`).

The component tracks which tile is under the pointer and emits **`hoverItem`** and **`clickItem`** custom events (see [Events](#events)).

## Custom element

`hb-matrix-video`

## Dependencies

The bundle registers **`hb-player-live`** and **`hb-player-live-camera-ptz`** (same package version as this component). PTZ tiles render **`hb-player-live-camera-ptz`** with `media_type="whep"` and `live_uri="{uri}/whep"`. Plain WebRTC tiles use **`hb-player-live`** with `media_type="whep"` and `mediauri="{uri}/whep"`.

## Attributes (snake_case; string values in HTML)

Web component attributes are **strings**. Complex data must be **JSON** on a single attribute (see [HTML Bricks development conventions](https://htmlbricks.dev/) for your bundle version).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `items` | **Yes** | JSON string: array of tile objects (see [Tile `items`](#tile-items)). |
| `id` | No | Optional element `id`. |
| `style` | No | Optional; present in authoring types for host styling when supported by your integration. |

### Tile `items`

Each entry is an object with:

| Field | Required | Description |
|-------|----------|-------------|
| `type` | Yes | `"iframe"` \| `"video"` \| `"mediamtx-webrtc"` \| `"mediamtx-webrtc-ptz"`. |
| `id` | Yes | Stable string id for the tile (used in events and DOM `id="select_{id}"`). |
| `uri` | Yes | **iframe:** `src` URL. **video:** MP4 source URL. **mediamtx-***: base URL; the component appends `/whep` for the player. |
| `title` | No | **iframe:** `title` attribute. **video:** passed to `<source>` (may be ignored by browsers for `source`). |

Example `items` value (escaped for HTML):

```json
[
  {"type":"iframe","id":"cam-1","uri":"https://example.com/embed","title":"Camera 1"},
  {"type":"mediamtx-webrtc","id":"live-1","uri":"https://mediamtx.example.com/mystream"}
]
```

## Layout behavior

- **Sizing:** On mount and on **window resize** (debounced), the inner `#matrix` container height is set from the window height minus its offset from the top; width uses the container client width. Rows and columns are recomputed from that box.
- **One tile:** Single row and column.
- **Narrow viewports:** If the container width is **below 450px**, tiles stack in **one column** (one tile per row).
- **Two tiles:** If the container is wider than 16:9, tiles sit **side by side**; if taller, they stack **vertically**.
- **Three or more:** The implementation searches row/column counts that keep the implied 16:9 grid within the container (different strategy for landscape vs portrait container aspect ratio).

Empty or invalid `items` JSON is logged to the console; the grid renders when `items` is a valid array.

## Events

Listen with `addEventListener` or your framework’s DOM bindings.

| Event | `detail` | When it fires |
|-------|----------|----------------|
| `hoverItem` | `{ id?: string; selected: boolean }` | Pointer **enters** a tile (`id` set, `selected: true`) or **leaves** the tile (`id` omitted, `selected: false`). |
| `clickItem` | `{ id: string }` | When a **selected** tile id exists and an **`iframe`** tile’s **same-origin** document receives a `click` (the implementation attaches a listener on `iframe` load). Other tile types do not currently wire this path in the source. |

## Styling (Bulma + host tokens)

The shadow root includes Bulma **columns** / **column** and spacing utilities. You can theme the wall with CSS variables; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and `extra/docs.ts` for defaults.

| Variable | Purpose |
|----------|---------|
| `--bulma-column-gap` | Horizontal gap between column cells (default `0.75rem` in metadata). |
| `--bulma-scheme-invert` | Default full-bleed background behind tiles when `--hb-matrix-video-bg` is unset. |
| `--hb-matrix-video-bg` | Optional explicit host background behind the grid (falls back to `--bulma-scheme-invert`). |

## CSS parts

None.

## Slots

None.

## TypeScript (authoring)

```typescript
export type Component = {
  id?: string;
  style?: string;
  items: {
    type: "video" | "iframe" | "mediamtx-webrtc" | "mediamtx-webrtc-ptz";
    id: string;
    uri: string;
    title?: string;
  }[];
};

export type Events = {
  hoverItem: { id?: string; selected: boolean };
  clickItem: { id: string };
};
```

## Minimal HTML example

```html
<hb-matrix-video
  id="wall-1"
  items='[{"type":"iframe","id":"1","uri":"https://example.com/embed","title":"Stream"}]'
></hb-matrix-video>
```

## Vanilla JavaScript example

```javascript
const el = document.querySelector("hb-matrix-video");

el.addEventListener("hoverItem", (e) => {
  console.log("hover", e.detail.id, e.detail.selected);
});

el.addEventListener("clickItem", (e) => {
  console.log("click", e.detail.id);
});

el.setAttribute(
  "items",
  JSON.stringify([
    { type: "video", id: "a", uri: "https://example.com/file.mp4" },
    { type: "iframe", id: "b", uri: "https://example.com/other" },
  ])
);
```

## Notes

- **Cross-origin iframes:** Browsers block access to `contentWindow` for cross-origin documents; in that case the `iframe` click bridge may not run and **`clickItem` may not fire**—`hoverItem` still reflects pointer hover.
- **Console:** The implementation may log layout debug information during development builds.

---

<a id="wc-messages-box"></a>

# `hb-messages-box`

**Category:** messaging · **Tags:** messaging, chat · **Package:** `@htmlbricks/hb-messages-box`

## Overview

`hb-messages-box` is a chat shell that composes two child custom elements:

- **`hb-messages-list`** — renders the thread from your `messages` and `authors` data, with optional `options` passed through unchanged.
- **`hb-messages-send`** — provides the composer; when the user sends content, this host re-dispatches the child’s `sendMessage` event on itself so your page can hook a single transport layer.

Optional **`message`** JSON can seed the draft text in the composer. The host does not append new rows to `messages` for you after send; you typically update `messages` from your own handler (or your backend push layer).

## Dependencies

The bundle registers **`hb-messages-list`** and **`hb-messages-send`** (same package version as this component). Styling and behavior details for the list and composer live in those packages’ documentation and `extra/docs.ts` files.

## Styling

This element’s shadow root mainly handles **layout** (flex column, scrollable list region, composer strip). List and composer each ship **Bulma** in their own shadow trees; theme them with **`--bulma-*`** and component-specific variables documented on **`hb-messages-list`** and **`hb-messages-send`**.

### Host CSS custom properties

| Variable | Default | Purpose |
|----------|---------|---------|
| `--hb-messages-box-min-height` | `100px` | Minimum height of the host (`:host`). |
| `--hb-messages-box-list-min-height` | `200px` | Minimum height of the list + composer column (`.hb-messages-box`). |

### CSS parts

| Part | Purpose |
|------|---------|
| `msglist` | Wrapper around `hb-messages-list` for host-level styling. |
| `msgsend` | Wrapper around `hb-messages-send` for host-level styling. |

### Slots

None.

## Custom element

`hb-messages-box`

## Attributes and properties (HTML)

Web component **attributes are strings**. Use **snake_case** names. Complex values must be **JSON strings** (e.g. `messages='[{"id":"1",...}]'`). Booleans elsewhere in the framework are often `yes` / `no` as strings; this component’s public payload is mostly JSON for `messages`, `authors`, `options`, and `message`.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `messages` | Yes | JSON string: array of message objects (`TMessage[]`). |
| `authors` | Yes | JSON string: array of participant objects (`TAuthor[]`). |
| `options` | No | JSON string: bag forwarded to `hb-messages-list` (may be `{}`). |
| `message` | No | JSON string: `{ "text"?: string }` (and shapes your tooling accepts) to seed the composer draft; parsed on the client when provided as a string. |
| `id` | No | Declared on the component type for optional host use. |

`timestamp` in JSON is typically an ISO date string after serialization; ensure your list implementation accepts the shape you pass.

## Events

Listen for **`sendMessage`** on `hb-messages-box`. The **`detail`** is the same object **`hb-messages-send`** emits (this host forwards it without changing fields): **`text`**, **`id`**, **`tags`** (string array), and **`files`** (staged rows with **`id`**, **`name`**, **`mimetype`**, **`fileSize`**, **`content`** (`File`), optional **`objectUrl`**). Your handler should perform validation, upload files if needed, and update `messages` (and clear or adjust the composer via `message` / child state) according to your app rules.

## Data shapes (TypeScript)

Authoring types for the **thread and participants** (`types/webcomponent.type.d.ts`):

```typescript
export type TMessage = {
  id: string;
  text: string;
  timestamp: Date;
  type: "text" | "image" | "video" | "audio" | "file";
  status?: "pending" | "sent" | "received" | "read";
  system?: boolean;
  reply?: boolean;
  quotedMessageId?: string;
  authorId?: string;
  uri?: string;
};

export type TAuthor = {
  id: string;
  name: string;
  avatar?: string;
  status: "online" | "offline" | "away" | "busy";
  me?: boolean;
};

export type TMessageSend = {
  text?: string;
};

export type Component = {
  id?: string;
  style?: string;
  messages: TMessage[];
  authors: TAuthor[];
  options?: {
    showTimestamp?: boolean;
    showAvatar?: boolean;
    showName?: boolean;
    bubbles?: boolean;
  };
  message?: TMessageSend;
};

export type Events = {
  sendMessage: {
    text?: string;
    id: string;
    tags: string[];
    files: {
      id: string;
      name: string;
      mimetype: string;
      fileSize: number;
      content: File;
      objectUrl?: string;
    }[];
  };
};
```

The **`sendMessage`** `detail` matches **`hb-messages-send`** (`types/webcomponent.type.d.ts` there defines **`MessageSendFileItem`** for each file row).

## Minimal HTML example

```html
<hb-messages-box
  messages="[]"
  authors='[{"id":"u1","name":"You","status":"online","me":true}]'
></hb-messages-box>
```

## Example with messages and send handler (vanilla JS)

```html
<hb-messages-box id="chat"></hb-messages-box>
<script>
  const el = document.getElementById("chat");
  const authors = JSON.stringify([
    { id: "u1", name: "You", status: "online", me: true },
    { id: "u2", name: "Bot", status: "offline" },
  ]);
  let messages = [];
  function render() {
    el.setAttribute("messages", JSON.stringify(messages));
    el.setAttribute("authors", authors);
  }
  el.addEventListener("sendMessage", (e) => {
    const { text, id, tags, files } = e.detail;
    // Send to your API, then append a row and re-render:
    messages = [
      ...messages,
      {
        id: id,
        text: text || "",
        timestamp: new Date().toISOString(),
        type: "text",
        status: "pending",
        authorId: "u1",
      },
    ];
    render();
  });
  render();
</script>
```

Adjust field names and `timestamp` format to whatever `hb-messages-list` expects for your integration.

---

<a id="wc-messages-list"></a>

# `hb-messages-list`

**Category:** messaging · **Tags:** messaging, chat · **Package:** `@htmlbricks/hb-messages-list`

## Description

`hb-messages-list` renders a scrollable transcript of chat messages. Each row joins a message to its author from the `authors` list, aligns the current user (`me: true`) to one side and everyone else to the other, and can show names, avatars, timestamps, and optional per-message action buttons. When `options.bubbles` is enabled, self vs peer bubbles use different surfaces and expose distinct CSS parts; when it is off, messages use a shared neutral bubble part.

Messages are **filtered**: only items whose `authorId` matches an author in `authors` are shown. Avatars collapse for **consecutive** messages from the same author if the gap between timestamps is under **five minutes** (the avatar column still reserves space when avatars are enabled).

The component loads **Bootstrap Icons** (via the shadow root stylesheet) for action buttons: each action’s `icon` value becomes the `bi-{icon}` class name.

## Message content and `type`

The `TMessage.type` field supports `"text" | "image" | "video" | "audio" | "file" | "code"`. In the current markup, only **`code`** is rendered differently (language tag when `language` is set, then a `<pre><code>` block). All other types, including `image`, `video`, `audio`, and `file`, are shown as **plain text** in the message body (the `text` field). Fields such as `uri`, `status`, `system`, `reply`, and `quotedMessageId` are part of the public shape for integrators but are **not** interpreted for extra UI in this component.

## Default options

If `options` is omitted or partially provided (after JSON merge), defaults are:

| Option           | Default | Effect |
|------------------|---------|--------|
| `showTimestamp`  | `true`  | Show time under or beside the message (grouped with the action row when `actions` is non-empty). |
| `showAvatar`     | `true`  | Avatar column and alignment spacers. |
| `showName`       | `true`  | Author row above the bubble; the current user is labeled **You**. |
| `bubbles`        | `false` | If `true`, self vs peer bubble parts and styling; if `false`, neutral `message` part. |

**Note:** Parsed `options` are merged with internal defaults. Property keys inside JSON attribute values follow the TypeScript / runtime shape (for example `authorId`, `showTimestamp`, `isAI`); booleans use JSON `true` / `false`.

## Actions and events

When `actions` is a non-empty array, each message row renders small outline buttons. Each action is `{ "icon": string, "name": string }` where `icon` is a Bootstrap Icons suffix (for example `"clipboard"` → `bi-clipboard`).

| Event    | `detail` |
|----------|----------|
| `action` | `{ messageId: string; action: string }` — `action` is the `name` from the clicked action. |

## Timestamps

Displayed times are derived with `Date` from `message.timestamp` (ISO strings from JSON are supported). Messages from **today** show time only; older messages show a locale-dependent date plus time.

## AI authors

If an author has `isAI: true`, the default avatar uses an AI-specific placeholder and a small **AI** badge appears when the avatar is visible.

## Custom element

`hb-messages-list`

## Attributes (snake_case; string values in HTML)

Complex props are **JSON strings** on attributes (the component parses them in effects). Arrays and objects must be valid JSON.

| Attribute   | Required | Description |
|-------------|----------|-------------|
| `id`        | No       | Optional element id. |
| `style`     | No       | Optional host inline style (typed on the component; use as for any HTMLElement). |
| `messages`  | Yes      | JSON array of `TMessage`. Each message should include `authorId` matching an author `id`. |
| `authors`   | Yes      | JSON array of `TAuthor`. |
| `options`   | No       | JSON object: `showTimestamp`, `showAvatar`, `showName`, `bubbles` (all optional booleans). |
| `actions`   | No       | JSON array of `{ "icon": string, "name": string }` for per-message buttons. |

## Styling

### Bulma theme variables (`:host`)

Documented in `extra/docs.ts` — override on the host with **`--bulma-*`** (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

| Variable | Purpose |
|----------|---------|
| `--bulma-primary` | Self bubble background and accents. |
| `--bulma-primary-invert` | Text on primary bubble background. |
| `--bulma-border` | Peer / neutral bubble borders. |
| `--bulma-text` | Default message and caption color inside bubbles. |
| `--bulma-text-weak` | Muted text (timestamps, You label). |
| `--bulma-scheme-main-bis` | Light bubble / neutral backgrounds. |
| `--bulma-radius-large` | Bubble corner radius. |
| `--bulma-radius-rounded` | Circular action buttons and avatars. |

### Component variables

| Variable | Default | Purpose |
|----------|---------|---------|
| `--hb-messages-list-max-height` | `600px` | Scrollable list max height. |
| `--hb-messages-list-padding` | `1rem` | Padding inside the host. |
| `--hb-messages-bubble-max-width-mobile` | `85%` | Max width of bubble content on small layouts. |

## CSS parts

Expose only on elements that are rendered for the current `options.bubbles` / layout branch.

| Part | When | Purpose |
|------|------|---------|
| `message-body` | Always (per message) | Inner content: text or code block. |
| `message-bubble-me` | `bubbles: true` | Bubble for the author with `me: true`. |
| `message-bubble-them` | `bubbles: true` | Bubble for other authors. |
| `message` | `bubbles: false` | Outer wrapper for the neutral bubble. |
| `message-timestamp` | When timestamps are shown | Timestamp label. |

## HTML slots

None.

## Types

```typescript
export type TMessage = {
  id: string;
  text: string;
  timestamp: Date;
  type: "text" | "image" | "video" | "audio" | "file" | "code";
  status?: "pending" | "sent" | "received" | "read";
  system?: boolean;
  reply?: boolean;
  quotedMessageId?: string;
  authorId?: string;
  uri?: string;
  language?: string; // for `type: "code"`
};

export type TAuthor = {
  id: string;
  name: string;
  avatar?: string;
  status?: "online" | "offline" | "away" | "busy";
  me?: boolean;
  isAI?: boolean;
};

export type Component = {
  id?: string;
  style?: string;
  messages: TMessage[];
  authors: TAuthor[];
  options?: {
    showTimestamp?: boolean;
    showAvatar?: boolean;
    showName?: boolean;
    bubbles?: boolean;
  };
  actions?: { icon: string; name: string }[];
};

export type Events = {
  action: { messageId: string; action: string };
};
```

## Examples

### Minimal markup

```html
<hb-messages-list
  id="thread"
  messages='[{"id":"m1","text":"Hello","timestamp":"2026-04-17T10:00:00.000Z","type":"text","authorId":"a1"}]'
  authors='[{"id":"a1","name":"You","me":true},{"id":"a2","name":"Support"}]'
></hb-messages-list>
```

### With bubbles, actions, and options

```html
<hb-messages-list
  id="messages-list-demo"
  messages='[{"id":"m1","text":"Hi","timestamp":"2026-04-17T10:00:00.000Z","type":"text","authorId":"u1"}]'
  authors='[{"id":"u1","name":"You","me":true}]'
  options='{"bubbles":true,"showTimestamp":true,"showAvatar":true,"showName":true}'
  actions='[{"icon":"clipboard","name":"copy"},{"icon":"trash","name":"delete"}]'
></hb-messages-list>
<script>
  document.getElementById("messages-list-demo").addEventListener("action", (e) => {
    console.log(e.detail.messageId, e.detail.action);
  });
</script>
```

Use valid JSON for every attribute value; escape quotes as needed for inline HTML.

## Package metadata

- **Custom element:** `hb-messages-list`
- **Package:** `@htmlbricks/hb-messages-list`

---

<a id="wc-messages-send"></a>

# `hb-messages-send`

**Category:** messaging · **Tags:** messaging, chat · **Package:** `@htmlbricks/hb-messages-send`

## Overview

`hb-messages-send` is a message composer for chat-style UIs. Users type in a textarea, optionally attach files, toggle tag chips, and send with the send control or **Enter** (**Shift+Enter** inserts a newline). When the message is sent, the element dispatches a **`sendMessage`** custom event whose `detail` carries the current text, your optional **`id`**, the selected tag ids, and the staged files.

The UI is built with **Bulma 1.x** inside the shadow root. **Bootstrap Icons** are loaded from jsDelivr in the component head for attach, send, tag, maximize, and file-type icons.

## Behavior

- **Text:** Bound to the composer. The send button stays disabled until there is non-empty text **or** at least one attached file.
- **Send:** Click the send button, or press **Enter** without **Shift**. An empty composer with no files logs a console warning and does not dispatch.
- **Expandable (`expandable`):** When enabled, the textarea grows with content up to an internal cap; when that cap is reached, a **maximize / minimize** control appears so users can expand the writing area (400px height while maximized).
- **Files (`files`):** If you pass a `files` configuration object, a paperclip control appears. **`mode: "single"`** keeps at most one file; **`mode: "multiple"`** allows many. Images get a thumbnail preview; other types show an icon by MIME type. Users can remove attachments before sending.
- **Tags (`tags`):** Optional chip buttons built from your definitions. Each tag must have an **`id`** (used in the event payload). **`icon`** is the Bootstrap Icons glyph name **without** the `bi-` prefix (the markup uses `bi bi-{icon}`). **`label`** is shown on the chip. **`color`** is optional; if omitted, the component picks a color for that chip.

## Styling (Bulma and CSS variables)

The component applies Bulma’s **light and dark** theme tokens on `:host` (including **`prefers-color-scheme: dark`** and **`data-theme` / `.theme-*`** on `html` or `body`, or `data-theme` on the element itself), so surfaces and borders track your app theme. Bulma also emits internal defaults as **`--bulma-hb-def-*`** on that same `:host`; the shadow styles resolve `var(--bulma-…, var(--bulma-hb-def-…))` so the composer never falls back to a hard-coded light surface when public `--bulma-*` are unset. Prefer **`--bulma-*`** (and the component **`--tag-color`**) from the host page when you need overrides. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| Variable | Role |
|----------|------|
| `--bulma-primary` | Send button fill (light theme) or outline color (dark theme: outlined send control). |
| `--bulma-primary-invert` | Icon color on the Send button in light theme (solid fill). |
| `--bulma-link` | Focus ring, file-attach accent, maximize control. |
| `--bulma-border` | Panel and file card borders. |
| `--bulma-text` | Default input and label color for readable text on the composer surface. |
| `--bulma-text-weak` | Muted text and disabled send state. |
| `--bulma-scheme-main` | Composer surface background. |
| `--bulma-scheme-main-bis` | File strip and toolbar background. |
| `--tag-color` | Accent for tag and file controls (per-tag overrides use inline `--tag-color` from each tag’s `color` or a fallback palette). |

### CSS parts

None.

### HTML slots

None.

## Custom element

`hb-messages-send`

## Attributes and properties (HTML consumers)

Web component **attributes** are strings. Use **`yes`** / **`no`** for booleans where noted (the implementation also treats the string **`true`** as enabled for **`expandable`**). Pass **objects and arrays as JSON strings** (e.g. `tags`, `files`).

| Name | Required | Description |
|------|----------|-------------|
| `id` | No | String echoed back on **`sendMessage`** so you can correlate the composer instance (e.g. thread or channel id). |
| `text` | No | Initial / current message text. |
| `expandable` | No | **`yes`** / **`no`** (or **`true`** / other) — taller minimum textarea, auto-grow, and maximize affordance when content hits the cap. |
| `tags` | No | JSON array of `{ id: string; label?: string; icon?: string; color?: string }`. **`id`** is required for selection; **`icon`** is a Bootstrap Icons name without the `bi-` prefix. |
| `files` | No | JSON object `{ "mode": "single" \| "multiple" }`. When set, the file attach control is shown and enforces single vs multi selection. |

The authoring **`Component`** type may list **`style`** for TypeScript wrappers; the inner UI is themed with **CSS variables** on `:host`, not that prop.

## Events

| Name | `detail` shape |
|------|----------------|
| `sendMessage` | **`text`:** `string` — composer text. **`id`:** `string` — value of the `id` prop. **`tags`:** `string[]` — ids of tags the user toggled on. **`files`:** array of staged items (`MessageSendFileItem`): **`id`**, **`name`**, **`mimetype`**, **`fileSize`**, **`content`** (`File`), optional **`objectUrl`**. Use **`detail.files.map((f) => f.content)`** when you need plain **`File`** instances. |

## TypeScript (authoring / wrappers)

```typescript
export type MessageSendFileItem = {
  id: string;
  name: string;
  mimetype: string;
  fileSize: number;
  content: File;
  objectUrl?: string;
};

export type Component = {
  id?: string;
  style?: string;
  text?: string;
  expandable?: boolean;
  tags?: {
    label?: string;
    icon?: string;
    id: string;
    color?: string;
  }[];
  files?: {
    mode: "single" | "multiple";
  };
};

export type Events = {
  sendMessage: {
    text?: string;
    id: string;
    tags: string[];
    files: MessageSendFileItem[];
  };
};
```

## Examples

### Minimal

```html
<hb-messages-send expandable="yes"></hb-messages-send>
```

### Tags, single file, and initial text

```html
<hb-messages-send
  id="thread-42"
  text="Reply here…"
  expandable="yes"
  tags='[{"id":"idea","label":"Idea","icon":"lightbulb"},{"id":"bug","label":"Bug","icon":"bug-fill"}]'
  files='{"mode":"single"}'
></hb-messages-send>
```

### Listen for sends (vanilla JS)

```javascript
const el = document.querySelector("hb-messages-send");
el.addEventListener("sendMessage", (e) => {
  const { text, id, tags, files } = e.detail;
  const blobs = files.map((f) => f.content);
  // Upload blobs, append to thread, clear composer via property if needed, etc.
});
```

---

<a id="wc-messages-topics-card"></a>

# `hb-messages-topics-card`

**Category:** messaging · **Tags:** messaging, chat · **Package:** `@htmlbricks/hb-messages-topics-card`

## Description

Renders a vertical list of chat or channel previews from a **`chats`** collection. Each row shows a rounded avatar, a title, a short message preview, a time label, and an unread counter rendered as a Bulma tag. Clicking a row updates internal selection state (`_selected`), highlights the row, and dispatches a **`select`** custom event with the clicked chat object so the host can open a thread or switch context.

## Styling (Bulma)

The component bundles **Bulma 1.x** inside the shadow root: **`layout/media`**, **`layout/level`**, **`elements/image`**, **`elements/tag`**, and typography / spacing helpers, with the light theme applied on `:host`. Public **`--bulma-*`** variables can be set on `body`, `:root`, or the host so they cascade consistently with the rest of the app — see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and the `styleSetup.vars` entries in `extra/docs.ts`.

| Variable | Purpose |
|----------|---------|
| `--bulma-text` | Title and preview snippet text. |
| `--bulma-text-strong` | Strong title weight color (falls back to `--bulma-text`). |
| `--bulma-text-weak` | Muted time label beside each preview. |
| `--bulma-border` | Divider between rows. |
| `--bulma-scheme-main-bis` | Hover background for rows that are not selected. |
| `--bulma-scheme-main-ter` | Selected row background. |
| `--bulma-radius-rounded` | Pill shape for the unread counter tag. |

**Shadow markup (simplified):**

- **`article.media.hb-topics-item`** — clickable row; **`hb-topics-item--selected`** when that chat is selected.
- **`figure.media-left`** + **`p.image.is-64x64`** + **`img.is-rounded`** — avatar from `img_uri`.
- **`div.media-content`** — Bulma **`level`** rows for title + time, then preview + counter tag.

## CSS parts

None.

## HTML slots

None.

## Custom element

`hb-messages-topics-card`

## Attributes (snake_case; string values in HTML)

Per the web component contract, attributes are strings. The primary payload is JSON on **`chats`**.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `id` | No | Optional host identifier. |
| `style` | No | Optional inline host styles (typed on the component surface). |
| `chats` | No* | JSON string: array of chat objects (`IChat[]`). Defaults to an empty list if omitted. |

\* In markup you may omit **`chats`** until you set the attribute; the component defaults to an empty list.

### `chats` JSON shape (`IChat`)

Fields used for layout and labels in the current implementation:

| Field | Role |
|-------|------|
| `chat_id` | Stable key for each row (also used in `{#each}` keyed updates). |
| `title` | Primary line (semibold). |
| `text` | Preview line under the title. |
| `img_uri` | Avatar URL (`img` `src`). |
| `time` | Source for the time label when `localeTimeString` is not set; use an **ISO 8601** string in JSON so it parses as a date. |
| `localeTimeString` | If set, shown as-is instead of deriving `HH:MM` from `time`. |
| `counter` | Unread count; rendered in the tag (missing or falsy shows **`0`**). |
| `_selected` | When `true`, that row starts selected and receives the selected background. |

Additional optional fields (`is_group`, `chat_name`, `chat_img`, `last_message_*`, and so on) are part of the **`IChat`** type so you can carry richer metadata; they are not read by the template today but are still present on objects emitted by **`select`**.

If the **`chats`** prop arrives as a string (for example from an HTML attribute), the component parses it with **`JSON.parse`**; invalid JSON is logged to the console and parsing is skipped.

## Events

| Event | `detail` |
|-------|----------|
| `select` | The clicked **`IChat`** object (same reference the list uses after selection is updated). |

Listen in plain JavaScript:

```javascript
const el = document.querySelector("hb-messages-topics-card");
el.addEventListener("select", (e) => {
  const chat = e.detail;
  console.log(chat.chat_id, chat.title);
});
```

## Usage notes

- **Initial selection:** set **`_selected`** on at most one item in the JSON if you want a default active row.
- **Time display:** without **`localeTimeString`**, the component fills it from **`time`** using the **local** hours and minutes (`HH:MM`). Provide **`localeTimeString`** yourself for relative times or locale-specific formatting.
- **Unread badge:** the tag always shows a value; **`0`** is shown when there is no positive counter.
- **Avatars:** the template sets **`alt=""`** on images; if you need accessible names, extend your integration (for example wrapping context or contributing upstream) because the component does not derive labels from `title` today.

## Types

```typescript
export type IChat = {
  time: Date;
  title: string;
  text: string;
  img_uri: string;
  is_group?: boolean;
  chat_name?: string;
  chat_img?: string;
  chat_id: string;
  last_message_author?: string;
  last_message_author_img?: string;
  last_message_time?: Date;
  last_message_text?: string;
  counter?: number;
  localeTimeString?: string;
  _selected?: boolean;
};

export type Component = {
  id?: string;
  style?: string;
  chats?: IChat[];
};

export type Events = {
  select: IChat;
};
```

## Minimal HTML example

```html
<hb-messages-topics-card
  chats='[{"chat_id":"1","title":"Team","text":"Hello","time":"2026-01-01T12:00:00.000Z","img_uri":"https://example.com/a.png","counter":2}]'
></hb-messages-topics-card>
```

## Storybook / metadata

Argument controls and additional named examples (selected row, unread badges, single chat) live in `extra/docs.ts`.

---

<a id="wc-modal-video"></a>

# `hb-modal-video`

**Category:** media · **Tags:** media, video, overlays · **Package:** `@htmlbricks/hb-modal-video`

## Overview

`hb-modal-video` is a video modal built on top of **`hb-dialog`**. When **`uri`** is non-empty, the nested dialog opens and shows either:

- an **HTML5 `<video>`** player (default), or  
- a **YouTube embed** when **`provider`** is set to `youtube`.

The host can supply a dialog heading via the **`title`** attribute and/or the **`title`** slot. Visibility is driven by **`uri`**: closing the dialog clears **`uri`**, **`title`**, and **`item`** on this component and emits **`videoModalEvent`** whenever the dialog reports a show/hide change.

This component declares a dependency on **`hb-dialog`** (register or load the dialog bundle as required by your integration).

## Custom element

| Tag |
|-----|
| `hb-modal-video` |

## Attributes

Web component attributes are **snake_case** and **string** values in HTML (booleans use `yes` / `no` where applicable in the stack; this component’s public surface is mostly URLs and labels).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `uri` | Yes (for TypeScript) | Media URL. Non-empty value opens the modal. Use an MP4 (or other browser-supported) URL for the default player, or a **YouTube embed** URL when `provider` is `youtube`. Cleared internally when the dialog closes. |
| `title` | No | Plain text used for the dialog title area. When set, the title row is rendered and the default slot content is `Video: {title}` unless you override the `title` slot. |
| `item` | No | Identifier passed through as the nested dialog’s `id` (opaque string for analytics, routing, or correlation). Defaults to `defaultItem` in markup when omitted. Reset to empty when the modal closes. |
| `provider` | No | Set to **`youtube`** to render an `<iframe>` instead of `<video>`. Any other value uses the HTML5 video path. |
| `id` | No | Optional host element id (typing only; use as in other `hb-*` components). |
| `style` | No | Optional inline style string on the host (present in typings for parity with other components). |

### YouTube vs HTML5

- **HTML5 (default):** `provider` empty or not `youtube`. The template uses `<video controls>` with a `<source src="{uri}" type="video/mp4" />` and a captions track placeholder. Ensure the URL and MIME type match what the browser can play.  
- **YouTube:** `provider="youtube"` and **`uri`** must be an **embed** URL (for example `https://www.youtube.com/embed/...`), not a watch page URL.

## Slots

| Slot | When it appears | Purpose |
|------|-----------------|----------|
| `title` | Only when the **`title`** attribute is set (non-empty). | Replaces the default `Video: {title}` heading in the dialog title area. |

Body content is not slotted: the player is always the dialog **body** slot content from this component.

## Events

| Event | `detail` | When |
|-------|----------|------|
| `videoModalEvent` | `{ id: string; show: boolean }` | After each `hb-dialog` **`modalShow`** notification. On open, `id` is updated from the dialog. On close, `id` is empty, `show` is `false`, and `uri` / `title` / `item` are cleared on `hb-modal-video`. |

Listen in HTML:

```html
<script>
  document.querySelector("hb-modal-video").addEventListener("videoModalEvent", (e) => {
    console.log(e.detail.id, e.detail.show);
  });
</script>
```

## Styling

Bulma is loaded in the shadow root; the video stage uses a 16:9 **letterboxed** area. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| CSS variable | Role |
|--------------|------|
| `--bulma-scheme-invert` | Background fill behind the iframe / `<video>` inside the embed container (falls back to `#000` in SCSS). |

Modal chrome (backdrop, dialog frame, buttons) comes from **`hb-dialog`**, not from this host’s `::part` list (`extra/docs.ts` defines no CSS parts on this package).

## TypeScript (authoring)

```typescript
export type Component = {
  id?: string;
  style?: string;
  item?: string;
  uri: string;
  title?: string;
  provider?: string;
};

export type Events = {
  videoModalEvent: { id: string; show: boolean };
};
```

## Examples

**HTML5 sample**

```html
<hb-modal-video
  uri="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
></hb-modal-video>
```

**With title**

```html
<hb-modal-video
  uri="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  title="Flower sample (MDN)"
></hb-modal-video>
```

**YouTube embed**

```html
<hb-modal-video
  uri="https://www.youtube.com/embed/tgbNymZ7vqY"
  provider="youtube"
  title="Sample YouTube clip"
></hb-modal-video>
```

**With opaque `item` id**

```html
<hb-modal-video
  uri="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  item="demo-flower-001"
></hb-modal-video>
```

## License

Apache-2.0 (see package `LICENSE.md` where applicable).

---

<a id="wc-navbar"></a>

# `hb-navbar`

**Category:** layout · **Tags:** layout, navigation · **Package:** `@htmlbricks/hb-navbar`

## Summary

`hb-navbar` is a top **navigation bar** built with Bulma layout (`level`, mobile helpers). It provides an optional **menu toggle** (hamburger) that flips an internal open flag and emits `navmenuswitch`, a **brand** area (default: logo + text, or fully replaced via slot), **three projection regions** (left, center, right slots), an optional **`nav-switcher`** slot to replace the default toggle label, and an optional **user avatar menu** backed by **`hb-dropdown-simple`** (declared dependency in `extra/docs.ts`).

## Behavior

- **Menu toggle:** A `<button>` is shown unless the `noburger` attribute is present with a **non-empty** string (use **`noburger="yes"`** to hide the control; avoid values like `"no"` if you want the button to remain visible, because any non-empty string hides it in the current `{#if !noburger}` logic). The button calls `switchMenu()` on click, toggling `isOpen` and dispatching **`navmenuswitch`** with `{ isOpen }`. Default content is the character `☰`; override with the **`nav-switcher`** slot.
- **`switchopen`:** String **`"yes"`** or **`"no"`**. On bind/update, the component syncs internal `isOpen` (`"yes"` → open). Omit or use **`"no"`** for a closed starting point (see implementation in `component.wc.svelte`).
- **Brand:** Default slot **`brand`** shows `companylogouri` as an `<img>` when non-empty, then `companybrandname`. Replace the whole block with **`slot="brand"`** content when you need custom markup.
- **User menu:** When `usermenu` is provided, `hb-dropdown-simple` is registered via `addComponent` and rendered with `list={JSON.stringify(usermenu.list)}` and the avatar from `imgUri`. Dropdown item selection is forwarded as **`navbarDropDownClick`** with `{ key }` (same shape as `hb-dropdown-simple` item keys).
- **`usermenu` parsing:** From HTML, pass **`usermenu` as a JSON string** (see Attributes). If the value is a string at runtime, the component attempts `JSON.parse`; parse errors are logged to the console.
- **`navbarSlotClick`:** Dispatched when the user **clicks** or presses **Enter** on the interactive wrappers around the **left** slot region and the **center / brand** region. The authoring types allow `side: "right"`; in the current markup, only **left** and **center** paths call `dispatch("navbarSlotClick", …)`.
- **Internationalization:** none (`extra/docs.ts` lists no `i18n` entries).

## Custom element tag

```html
<hb-navbar …></hb-navbar>
```

## Attributes (HTML / reflected props)

Attribute names are **snake_case**; values exposed from HTML are **strings** (booleans as **`yes`** / **`no`**, objects as **JSON**), per project conventions.

| Attribute | Required | Role |
|-----------|----------|------|
| `id` | No | Optional host id. |
| `style` | No | Optional inline style on the host (per typings). |
| `companybrandname` | Yes | Brand text shown in the default brand area. |
| `companylogouri` | Yes | Logo URL for the default brand `<img>`; use an empty string if you only want text or a custom `brand` slot. |
| `switchopen` | No | **`"yes"`** / **`"no"`** — aligns internal menu-open state with this value. |
| `usermenu` | No | JSON string: `{ "imgUri": string, "list"?: Array<{ "key": string, "label": string, "badge"?: number, "group"?: string }> }` for the avatar dropdown. |
| `noburger` | No | Set to **`yes`** to hide the hamburger / menu button entirely. |

## Events

Listen with `addEventListener` or framework bindings on the host element.

| Event | `detail` |
|-------|----------|
| `navmenuswitch` | `{ isOpen: boolean }` |
| `navbarDropDownClick` | `{ key: string }` — user menu item key from `hb-dropdown-simple`. |
| `navbarSlotClick` | `{ side: "left" \| "right" \| "center" }` — see Behavior for which regions emit this in the current implementation. |

## CSS custom properties (`::host`)

Documented in `extra/docs.ts` / `styleSetup` (Bulma tokens and one component override). Defaults below are the documented fallbacks; override from the light DOM on the host.

| Variable | Default (documented) | Notes |
|----------|----------------------|--------|
| `--hb-layout-navbar-background` | _(empty)_ | From **`hb-layout`** / page: wins over per-host and Bulma tokens. |
| `--hb-navbar-background-color` | _(empty)_ | Set on **`hb-navbar`** only: between layout override and Bulma. |
| `--bulma-navbar-background-color` | `#f5f5f5` | Bulma navbar strip token ([docs](https://bulma.io/documentation/features/css-variables/)). |
| `--bulma-navbar-item-color` | `#363636` | Items and icon color on the bar. |
| `--bulma-navbar-height` | `3.25rem` | Minimum navbar block height. |
| `--bulma-navbar-item-img-max-height` | `1.75rem` | Max height for the brand logo image. |
| `--bulma-text` | `#363636` | Text fallback. |
| `--bulma-scheme-main-bis` | `#f5f5f5` | Background fallback after Bulma navbar token. |
| `--bulma-radius` | `0.375rem` | Menu toggle and control corner radius. |
| `--hb-navbar-menu-btn-radius` | _(empty)_ | Optional override for the hamburger / menu button radius. |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for the wider `--bulma-*` system.

## `::part` and slots

**Parts** (for `::part(...)` from outside the shadow root):

| Part | Purpose |
|------|---------|
| `left-slot` | Left cluster: toggle + left slot (see `extra/docs.ts`). |
| `center-slot` | Center column: brand + `center-slot` projection. |
| `right-slot` | Right cluster: `right-slot` + optional user dropdown. |

**Slots:**

| Slot | Purpose |
|------|---------|
| `left-slot` | Custom content to the left of the brand (inside the left region). |
| `center-slot` | Extra content centered on the bar (sibling to the brand control in the center column). |
| `right-slot` | Actions or widgets to the right of the brand (before the user menu, if any). |
| `nav-switcher` | Replaces the default `☰` label inside the menu button. |
| `brand` | Replaces the default logo + `companybrandname` block. |

## TypeScript typings (authoring)

From `types/webcomponent.type.d.ts`:

```ts
interface IUserMenuListItem {
  key: string;
  label: string;
  badge?: number;
  group?: string;
}
export interface IUserMenu {
  imgUri: string;
  list?: IUserMenuListItem[];
}
export type Component = {
  id?: string;
  style?: string;
  companybrandname: string;
  companylogouri: string;
  switchopen?: "yes" | "no";
  usermenu?: IUserMenu;
  noburger?: string;
};

export type Events = {
  navbarDropDownClick: { key: string };
  navmenuswitch: { isOpen: boolean };
  navbarSlotClick: { side: "left" | "right" | "center" };
};
```

## HTML examples

**Minimal bar (text-only brand):**

```html
<hb-navbar companybrandname="Acme" companylogouri=""></hb-navbar>
```

**Brand with logo and menu closed explicitly:**

```html
<hb-navbar
  companybrandname="Acme"
  companylogouri="https://example.com/logo.svg"
  switchopen="no"
></hb-navbar>
```

**No hamburger (e.g. desktop shell with another way to open the drawer):**

```html
<hb-navbar
  companybrandname="Studio"
  companylogouri=""
  noburger="yes"
></hb-navbar>
```

**User avatar dropdown** (`usermenu` must be a single JSON string in HTML):

```html
<hb-navbar
  companybrandname="Acme"
  companylogouri=""
  usermenu='{"imgUri":"https://example.com/avatar.png","list":[{"key":"profile","label":"Profile"},{"key":"signout","label":"Sign out"}]}'
></hb-navbar>
```

**Custom toggle glyph and right-slot actions:**

```html
<hb-navbar companybrandname="App" companylogouri="">
  <span slot="nav-switcher">Menu</span>
  <span slot="right-slot"><!-- host app controls --></span>
</hb-navbar>
```

```js
const el = document.querySelector("hb-navbar");
el.addEventListener("navmenuswitch", (e) => console.log("open:", e.detail.isOpen));
el.addEventListener("navbarDropDownClick", (e) => console.log("menu key:", e.detail.key));
el.addEventListener("navbarSlotClick", (e) => console.log("slot side:", e.detail.side));
```

---

<a id="wc-offcanvas"></a>

# `hb-offcanvas` — integrator guide

**Category:** layout · **Tags:** layout, navigation · **Package:** `@htmlbricks/hb-offcanvas`

## Summary

`hb-offcanvas` is a fixed, slide-in drawer on the start edge of the viewport that embeds **`hb-sidebar-desktop`** for branded navigation, page selection, optional theme switching, and optional language switching. When **`type`** is **`autohide`**, a full-viewport Bulma **`modal-background`** layer sits under the panel; clicking the dimmed area closes the drawer. The host raises custom events when the drawer opens or closes, when the active page changes, and when the nested sidebar changes language or theme.

The package depends on **`hb-sidebar-desktop`** (which in turn uses **`hb-sidenav-link`**). Navigation items use the same JSON shape as the sidebar, including optional Bootstrap Icons glyph names on each link.

## Behavior

- **`type`**
  - **`autohide`** (default): Renders the backdrop while the drawer is open. Clicking the backdrop calls the internal close path and dispatches **`offcanvasswitch`** with **`isOpen: false`**.
  - **`open`**: After the host’s reactive setup runs, the drawer is treated as **always open** (the **`opened`** attribute is forced to an open state for display).
  - **`small`**: No backdrop; open state follows **`opened`** like a non-`open` mode. Use when you want a sliding panel without modal dismissal (same stacking and panel animation as other modes, without the overlay).

- **`opened`**: Initial and runtime open state for modes that are not **`type="open"`**. In HTML, use string booleans **`yes`** / **`no`** as for other web components in this library. The implementation treats a truthy **`opened`** that is not the string **`no`** as open.

- **Page changes**: Choosing a nav entry inside **`hb-sidebar-desktop`** runs the sidebar’s page handler; the host re-dispatches **`pageChange`** with **`{ page: string }`** and closes the drawer via **`OpenSwitch(false)`** (so the drawer does not stay open after navigation in the default wiring).

- **`groups`**: May be a parsed JSON string in the effect; invalid JSON is ignored and leaves the previous value.

## Styling (Bulma and host tokens)

The shadow tree uses Bulma-derived tokens for the panel fill, shadow, and (when **`autohide`**) backdrop color. Stacking and drawer width are controlled on **`:host`** so the drawer can sit above siblings such as a mobile layout shell. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and `extra/docs.ts` for metadata.

| Variable | Purpose |
|----------|---------|
| `--hb-offcanvas-host-z` | Host stacking context so fixed panel and backdrop paint above following light-DOM siblings (default `1038`). |
| `--hb-offcanvas-panel-z` | Drawer layer above the backdrop (default `1045`). |
| `--hb-offcanvas-backdrop-z` | Backdrop layer (default `1040`). |
| `--hb-offcanvas-panel-width` | Drawer width, capped by the viewport (default `240px`). |
| `--bulma-scheme-main` | Panel background. |
| `--bulma-shadow` | Elevation shadow on the open drawer. |
| `--bulma-modal-background-background-color` | Backdrop tint when **`type`** is **`autohide`**. |

The component also loads **Bootstrap Icons** from jsDelivr for icon font classes used by nested navigation.

## CSS parts

None on **`hb-offcanvas`**. **`hb-sidebar-desktop`** defines its own parts (for example the header row); see the sidebar README for **`::part`** names.

## HTML slots

| Slot | Purpose |
|------|---------|
| `test` | Optional content at the host root inside **`#webcomponent`**, before the panel (for advanced integrations). Main navigation UI lives inside the nested **`hb-sidebar-desktop`**, not in this slot. |

## Custom element tag

```html
<hb-offcanvas …></hb-offcanvas>
```

## Attributes (snake_case; string values in HTML)

Reflectable props follow the usual conventions: booleans as **`yes`** / **`no`**, objects and arrays as JSON strings, numbers as decimal strings where applicable.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `id` | No | Optional element id. |
| `style` | No | Optional inline host styles. |
| `type` | No | **`autohide`** \| **`open`** \| **`small`** — backdrop and forced-open behavior (see above). Default in implementation: **`autohide`**. |
| `opened` | No | **`yes`** / **`no`** — whether the drawer is open (ignored for forced-open when **`type`** is **`open`**). |
| `navlinks` | No | JSON string — **`INavLink[]`**: **`key`**, **`label`**, optional **`icon`** (Bootstrap Icons name), **`group`**, **`badge`**, **`subLinks`**, etc. Same contract as **`hb-sidebar-desktop`**. |
| `navpage` | No | Active page **`key`** string. |
| `groups` | No | JSON string — **`{ key: string; label: string }[]`** for section labels in the sidebar. |
| `companylogouri` | No | Logo image URI for the sidebar header. |
| `companytitle` | No | Company or product title next to the logo. |
| `enablefooter` | No | **`yes`** / **`no`** / **`false`** (same contract as **`hb-sidebar-desktop`**). Forwarded to the nested sidebar; omitted or truthy values keep the sidebar footer enabled (**`yes`**). |
| `enablethemeswitch` | No | **`yes`** / **`no`** — passed through to **`hb-sidebar-desktop`** (defaults to **`yes`** when unset). |
| `themepreference` | No | **`light`** \| **`dark`** \| **`auto`** — optional controlled theme for the nested sidebar. |
| `i18nlang` | No | Language code for the nested sidebar. |
| `i18nlanguages` | No | JSON string or structured options — **`{ code: string; label: string }[]`** for the language picker. |

## Events

| Event | Detail |
|-------|--------|
| `offcanvasswitch` | `{ isOpen: boolean }` — emitted when the drawer opens or closes (including initial open when **`type`** is **`open`** or **`opened`** is truthy, and when the backdrop closes the drawer). |
| `pageChange` | `{ page: string }` — forwarded from **`hb-sidebar-desktop`** when the user selects a page; the offcanvas also closes after dispatching. |
| `languageChange` | `{ code: string }` — bubbled from the nested sidebar’s language control. |
| `themeChange` | `{ mode: ThemePreference }` where **`ThemePreference`** is **`"light"` \| `"dark"` \| `"auto"`** — bubbled from the nested sidebar’s theme control. |

## Usage notes

- **Icons:** Use Bootstrap Icons **names** only (for example **`house-door`**), not class strings — same rule as **`hb-sidebar-desktop`**.
- **Layering:** If the drawer or backdrop appears under a sibling layout, raise **`--hb-offcanvas-host-z`** (and panel/backdrop z-tokens if needed) on the host.
- **Examples:** Storybook-style **`examples`** in `extra/docs.ts` cover default, closed, autohide, and grouped navigation with footer metadata on the data model.

## TypeScript typings (authoring)

From `types/webcomponent.type.d.ts`:

```typescript
import type { INavLink } from "../../sidenav-link/types/webcomponent.type";
import type {
  I18nLanguageOption,
  ThemePreference,
} from "../../sidebar-desktop/types/webcomponent.type";

export type Component = {
  id?: string;
  style?: string;
  opened?: boolean;
  navlinks?: INavLink[];
  navpage?: string;
  groups?: { key: string; label: string }[];
  companylogouri?: string;
  companytitle?: string;
  enablefooter?: "yes" | "no" | "false" | null | "" | undefined;
  enablethemeswitch?: "yes" | "no" | null | "" | undefined;
  themepreference?: ThemePreference | null | "" | undefined;
  i18nlang?: string | null | "" | undefined;
  i18nlanguages?: I18nLanguageOption[] | string | null | "" | undefined;
  type?: "open" | "autohide" | "small";
};

export type Events = {
  offcanvasswitch: { isOpen: boolean };
  pageChange: { page: string };
  languageChange: { code: string };
  themeChange: { mode: ThemePreference };
};
```

## Minimal HTML

```html
<hb-offcanvas
  opened="yes"
  type="autohide"
  companytitle="Acme"
  navpage="home"
  navlinks='[{"label":"Home","key":"home","icon":"house-door"}]'
></hb-offcanvas>
```

Listen for open state and navigation:

```html
<script>
  const el = document.querySelector("hb-offcanvas");
  el.addEventListener("offcanvasswitch", (e) => {
    console.log("open:", e.detail.isOpen);
  });
  el.addEventListener("pageChange", (e) => {
    console.log("page:", e.detail.page);
  });
</script>
```

---

<a id="wc-order-list"></a>

# `hb-order-list` — integrator guide

**Category:** commerce · **Tags:** commerce, order · **Package:** `@htmlbricks/hb-order-list`

## Summary

`hb-order-list` is a read-only order summary for commerce flows. It renders a header with the order number, a list of line items (thumbnail and name), and a footer with the computed grand total. The view is driven by a **`payment`** payload (JSON string from HTML).

Layout uses Bulma **`container`** and **`columns`**. Theme via **`--bulma-*`** on `:host`; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and `extra/docs.ts`.

## Behavior

1. **Header** — “Order N.” plus `payment.orderNumber`, and a static “tracking” label (placeholder in the current markup).
2. **Line items** — For each `payment.items` row: product **`image`** (CSS part `item_image`), **`name`**, and a static “quantity” label (numeric `quantity` affects totals only).
3. **Footer** — Static “cancel” and placeholder middle text (`bbb` in the template), plus **total** as `{total}{currencySymbol}`.

**Totals:** Subtotal = sum of `unitaryPrice * (quantity ?? 1)`; tax = sum of line VAT rounded at the sum level; total = subtotal + tax + `(shipmentFee ?? 0)`.

If **`payment`** is a string, it is JSON-parsed. Missing **`countryCode`** defaults to **`"IT"`**. Missing **`currencySymbol`** is inferred from **`countryCode`** (`IT` / `EU` → `€`, `US` → `$`).

## Custom element tag

```html
<hb-order-list …></hb-order-list>
```

## Attributes (HTML / reflected props)

Web component attributes are **strings** (snake_case). Structured data is a **JSON** string on **`payment`**.

| Attribute | Role |
|-----------|------|
| `id` | Optional host id. |
| `style` | Optional inline style on the host. |
| `payment` | JSON string (or object when used from JS) describing **`OrderPayment`** (see `types/webcomponent.type.d.ts`). The implementation falls back to an empty in-memory default when omitted. |

## Events

No custom events (`Events` is `{}` in `types/webcomponent.type.d.ts`).

## CSS custom properties, parts, and slots

**Documented variables** (`extra/docs.ts` / `styleSetup`):

| Variable | Default | Notes |
|----------|---------|--------|
| `--bulma-text` | `#363636` | Foreground for copy and totals. |
| `--bulma-scheme-main` | `#ffffff` | Surface background. |
| `--bulma-column-gap` | `0.75rem` | Column gutter. |

| `::part` | Role |
|---------|------|
| `item_image` | Line item `<img>` thumbnail. |

**Slots:** none.

## TypeScript typings (authoring)

From `types/webcomponent.type.d.ts` (see that file for **`OrderPayment`** and line item shapes):

```ts
export type Component = {
  id?: string;
  style?: string;
  payment?: OrderPayment | string;
};
export type Events = {};
```

## Minimal HTML

```html
<hb-order-list
  payment='{"orderNumber":"1001","countryCode":"EU","items":[{"id":"1","name":"Sample item","unitaryPrice":10,"taxPercentage":22,"image":"https://example.com/product.jpg"}]}'
></hb-order-list>
```

With shipment fee:

```html
<hb-order-list
  payment='{"orderNumber":"ORD-1002","countryCode":"EU","currencySymbol":"€","shipmentFee":4.99,"items":[{"id":"a","name":"Item A","unitaryPrice":5,"taxPercentage":7,"quantity":2,"image":"https://example.com/a.jpg"}]}'
></hb-order-list>
```

---

<a id="wc-pad-joystick"></a>

# `hb-pad-joystick` — integrator guide

**Category:** utilities · **Tags:** utilities, controls · **Package:** `@htmlbricks/hb-pad-joystick`

## Summary

`hb-pad-joystick` is a touch-friendly directional control for games, kiosks, or remote UIs. It renders either a **four-way D-pad** (discrete presses) or an **analog joystick** (`html5-joystick-new`) that streams stick position and a coarse cardinal direction.

## Behavior

| Mode | `pad_or_joystick` | Interaction | Events |
|------|-------------------|---------------|--------|
| D-pad (default) | omitted or **`dpad`** | Four `<button>` tiles | **`sendDirection`** |
| Joystick | **`joystick`** | Drag in the stick zone | **`sendJoystickPosition`** |

Other **`pad_or_joystick`** values show a small “Unknown type” placeholder.

## Custom element tag

```html
<hb-pad-joystick …></hb-pad-joystick>
```

## Attributes (HTML / reflected props)

Strings only from HTML; pass **`joystick_parameters`** as a **JSON** string.

| Attribute | Role |
|-----------|------|
| `id` | Echoed on every event **`detail.id`**. |
| `style` | Optional inline host style (typed on **`Component`**). |
| `pad_or_joystick` | **`dpad`** \| **`joystick`** (default **`dpad`**). |
| `joystick_parameters` | JSON object: `internalFillColor`, `internalStrokeColor`, `externalStrokeColor`, `internalLineWidth`, `externalLineWidth` (joystick mode only). |

## Events

| Event | `detail` |
|-------|----------|
| `sendDirection` | `{ direction: "up" \| "right" \| "down" \| "left"; id: string }` |
| `sendJoystickPosition` | `{ x: number; y: number; cardinalDirection: CardinalDirection; id: string }` |

## CSS custom properties, parts, and slots

D-pad skin uses **`--hb-pad-joystick-size`**, **`--dpad-*`**, **`--arrowcolor`**, **`--tri-*`** tokens documented in `extra/docs.ts`. Defaults align with Bulma scheme variables; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

**`::part`:** none on this host. **Slots:** none.

## TypeScript typings (authoring)

See `types/webcomponent.type.d.ts` for **`Component`**, **`CardinalDirection`**, and **`Events`**.

## Minimal HTML

```html
<hb-pad-joystick id="player-1"></hb-pad-joystick>
```

Joystick with custom colors:

```html
<hb-pad-joystick
  id="cam-ptz"
  pad_or_joystick="joystick"
  joystick_parameters='{"internalFillColor":"#485fc7","internalStrokeColor":"#363636","externalStrokeColor":"#b5b5b5"}'
></hb-pad-joystick>
```

---

<a id="wc-page-checkout"></a>

# `hb-page-checkout` — integrator guide

**Category:** commerce · **Tags:** commerce, checkout, page · **Package:** `@htmlbricks/hb-page-checkout`

## Summary

`hb-page-checkout` is a full-width checkout **page shell** that places the interactive checkout flow beside the order summary. The main column renders **`hb-checkout`** (billing or shipping identity, shipment choice, gateways, and payment). The side column renders **`hb-checkout-shopping-cart`** with the same payment payload so totals stay aligned.

The host supplies structured data as **JSON strings** (or objects when using the element from JavaScript) for shipments, gateways, and payment. The component **normalizes totals** on each reactive update: it derives **`payment.shipmentFee`** from the **selected** or **standard** shipment when applicable, sums each line as `unitaryPrice × (quantity ?? 1)` plus tax, adds the shipment fee, rounds the total to two decimals, and assigns **`payment.currencyCode`** from an internal mapping (the current implementation resolves every `countryCode` branch to **`EUR`**). Nested children receive stringified `shipments`, `gateways`, and `payment` where their APIs expect serialized props.

Shipment line selection triggered by **`hb-checkout`** is handled **inside** this page (`saveShipment`): it updates the local `shipments` array, clears other selections, sets **`payment.shipmentFee`**, and does **not** emit a dedicated `saveShipment` event to the host. **`saveUser`** and **`paymentCompleted`** are forwarded to the host unchanged when the child checkout dispatches them. After a successful payment completion detail, the page sets its own **`completed`** state to **`yes`** and reuses that for both columns.

## Custom element tag

```html
<hb-page-checkout …></hb-page-checkout>
```

## Layout and markup

- Bulma **`container is-fluid`** with **`columns`**; checkout uses **`column is-7`**, cart **`column is-5`** (see `styles/webcomponent.scss` for responsive tweaks).
- No **HTML slots** and no **`::part`** hooks (see `extra/docs.ts`).

## Attributes (snake_case; HTML values are strings)

Web components reflect **string** attributes. Complex fields must be **JSON**. Booleans use **`yes`** / **`no`** where noted.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `id` | No | Optional element id. |
| `style` | No | Present on the public `Component` type for parity with other packages; the current implementation does not apply a dedicated `style` prop to the layout (theme via CSS variables instead). |
| `shipments` | No | JSON array of **`IShipment`** objects (defaults to `[]`). Parsed when provided as a string. See `builder/src/wc/checkout/types/webcomponent.type.d.ts`. |
| `user` | Yes | **`IUser`** object or JSON string. Forwarded to **`hb-checkout`**. |
| `payment` | No | JSON object combining **`IShoppingPayment`** and **`IPayment`**. Defaults internally to an empty cart with `EUR` / `IT` if omitted. See `builder/src/wc/checkout-shopping-cart/types/webcomponent.type.d.ts` and `builder/src/wc/checkout/types/webcomponent.type.d.ts`. |
| `gateways` | No | JSON array of **`IGateway`** entries (`id`: `paypal` \| `google`, labels, optional PayPal / Google Pay fields). Defaults to `[]`. |
| `completed` | No | **`yes`** or **`no`**. Drives completion UI for both embedded components; set to **`yes`** locally after a successful **`paymentCompleted`** from checkout. |

### Shipment and totals

- If any shipment has **`selected`** or **`standard`**, **`payment.shipmentFee`** is overwritten in an effect with the price of the selected row, or otherwise the standard row.
- **`payment.total`** is recalculated as the sum of line totals (including tax) plus **`payment.shipmentFee`** (or `0`).

### Gateways

`IGateway` supports PayPal (`paypalid`, …) and Google Pay (`gatewayId`, `gatewayMerchantId`, …). Match the shapes expected by **`hb-checkout`** / **`hb-payment-paypal`**.

## Events

| Event | `detail` |
|-------|----------|
| `saveUser` | **`IUser`** — updated customer payload from **`hb-checkout`**. |
| `paymentCompleted` | **`{ total: number; method: string; completed: true }`** — emitted when checkout reports a completed payment; the page also sets **`completed`** to **`yes`**. |

There is **no** `saveShipment` event at the page level; selection is kept in the host-provided **`shipments`** prop state inside the component.

## Styling

The host uses **Bulma 1.x** CSS variables on `:host`; nested **`hb-*`** widgets inherit compatible **`--bulma-*`** tokens. Public variables documented in `extra/docs.ts`:

| Variable | Role |
|----------|------|
| `--bulma-column-gap` | Horizontal gap between checkout and cart columns (default `0.75rem`). |
| `--bulma-text` | Base text color inherited by nested checkout UI. |
| `--bulma-scheme-main` | Background inside the page shell. |
| `--hb-checkout-border` | Border around the cart column (also forwarded for related inner nodes). |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for the full token set.

## Bundle dependencies

Publishing metadata lists **`hb-checkout`** and **`hb-checkout-shopping-cart`** (and their transitive tree: forms, inputs, table, PayPal, dialogs, etc.). Load compatible versions of those packages alongside this element.

## Minimal HTML example

```html
<hb-page-checkout
  shipments="[]"
  user='{"fullName":"Jane Doe","addressWithNumber":"Via Roma 1","city":"Rome","nationality":"IT","zip":"00100","fixed":true}'
  payment='{"countryCode":"IT","merchantName":"Demo Shop","currencyCode":"EUR","total":12.2,"items":[{"id":"a","name":"Item A","unitaryPrice":10,"taxPercentage":22,"quantity":1}]}'
  gateways='[{"id":"paypal","label":"PayPal","paypalid":"YOUR_CLIENT_ID"}]'
  completed="no"
></hb-page-checkout>
```

## TypeScript typings (authoring)

See `types/webcomponent.type.d.ts`: **`Component`** (optional **`shipments`**, **`payment`**, **`gateways`** as objects or JSON strings; **`user`** as **`IUser`** or string; **`completed`**) and **`Events`** (**`saveUser`**, **`paymentCompleted`**).

---

<a id="wc-page-invoice"></a>

# `hb-page-invoice` — integrator guide

**Category:** commerce · **Tags:** commerce, invoice, page · **Package:** `@htmlbricks/hb-page-invoice`

## Summary

Printable invoice layout: seller and buyer blocks from **`headers`**, invoice date and serial, line items rendered inside **`hb-table`** (price per unit with currency symbol, quantity, line subtotal, VAT amount with rate, line total), and a footer with subtotal, total tax, and grand total. Currency symbol follows **`headers.country`** after **`headers`** is deserialized (Italy and EU use €, US uses `$`).

When **`printer`** is not **`yes`**, the host shows two actions (debounced): print (opens a helper window, injects another `hb-page-invoice` with **`printer="yes"`**, loads the bundle, then triggers the browser print dialog) and open in window (fullscreen-style preview in a new window with the same bundle). When **`printer`** is **`yes`**, those toolbar buttons are hidden—suitable for print-only or embedded previews.

The component registers **`hb-table`** at runtime (`addComponent` for `@htmlbricks/hb-table`). Nested bundle dependencies include **`hb-dialog`**, **`hb-dialogform`**, **`hb-form`**, many **`hb-input-*`** components, **`hb-paginate`**, **`hb-tooltip`**, and others as listed in `extra/docs.ts`.

If **`headers.from`** or **`headers.to`** is missing, the component does not render the invoice body (it shows a minimal error state).

## Styling (Bulma)

Markup uses Bulma **`container`**, **`columns`** / **`column`**, and **`button is-light`** for the toolbar. **Bootstrap Icons** are loaded from the CDN via `<svelte:head>` for the print and fullscreen icons.

Theme via **`--bulma-*`** on the host or ancestors; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and `extra/docs.ts` for defaults.

| Variable | Purpose |
|----------|---------|
| `--bulma-text` | Default text for headings, addresses, descriptions, and totals. |
| `--bulma-border` | Optional rules if you extend layout with custom CSS. |
| `--bulma-scheme-main` | Page background inside the host. |
| `--bulma-block-spacing` | Vertical block spacing for the invoice section. |

## CSS parts

None.

## HTML slots

None.

## Attributes (snake_case; string values in HTML)

Web component attributes are strings. Pass structured data as **JSON strings** for **`headers`** and **`items`**.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `id` | No | Optional host id (typed on `Component`; use as needed for DOM or tests). |
| `style` | No | Optional inline host style string (typed on `Component`). |
| `printer` | No | `"yes"` or `"no"` (default **`no`**). `"yes"` hides the print / open-in-window toolbar. |
| `headers` | No | JSON string — see **Headers object** below. Without valid **`from`** / **`to`**, the template shows the error branch. |
| `items` | No | JSON string — array of **Item objects** below (defaults to **`[]`** in the implementation). |

### Deserialization and defaults

When **`headers`** arrives as a string, it is **`JSON.parse`**’d. If **`date`** is absent after parse, it is set to the current date. If **`category`** is absent, it defaults to **`"items"`**. If **`country`** is absent, it defaults to **`"it"`**, which also drives the default currency symbol (€ for **`it`** / **`eu`**, `$` for **`us`**).

When **`items`** is a string, it is parsed to an array.

Boolean-like props follow project conventions: use **`yes`** / **`no`** for **`printer`** in HTML.

## Events

None (`Events` is an empty object in `types/webcomponent.type.d.ts`).

## Data model (TypeScript)

Authoring types live in `types/webcomponent.type.d.ts`.

### Item (`IItem`)

| Field | Type | Notes |
|-------|------|--------|
| `desc` | `string` | Line description (required). |
| `unitaryPrice` | `number` | Unit price before tax (required). |
| `taxPercentage` | `number` | VAT rate as a percentage (e.g. `22` for 22%) (required). |
| `quantity` | `number` | Optional; defaults to **1** in totals and table if omitted. |
| `unit` | `string` | Optional unit label; otherwise the price column shows a generic “unità” segment. |
| `discount` | `object` | Optional. Shape: `type: "gross" \| "item"`, optional `includeVat?: boolean`. Present in types and examples; **line and footer totals in the current markup sum `unitaryPrice * quantity` and tax on that amount**—they do not subtract this discount object yet. |

### Company (`ICompany`)

Used for **`from`** (with extra fields) and **`to`**.

| Field | Type | Notes |
|-------|------|--------|
| `piva` | `string` | Tax / company id (labelled as VAT in the Italian UI). |
| `name` | `string` | Legal or trading name. |
| `address` | `string` | Address block. |
| `email` | `string` | Email. |
| `phone` | `string` | Phone. |
| `iban` | `string` | Optional (not shown in the default template). |

### Seller (`headers.from`)

Extends **`ICompany`** with:

| Field | Type | Notes |
|-------|------|--------|
| `logo` | `string` | Image URL for the header logo. |
| `shortName` | `string` | Short name beside the logo. |

### Headers (`IHeaders`)

| Field | Type | Notes |
|-------|------|--------|
| `serial` | `string` | Invoice number (required). |
| `from` | Seller | Required. |
| `to` | `ICompany` | Required. |
| `country` | `"it"` \| `"eu"` \| `"us"` | Optional; affects default currency when parsed from JSON. |
| `date` | `Date` | Optional in object form; set on parse if missing. Displayed as **`DD/MM/YY`** via Day.js. |
| `expirationDate` | `Date` | Optional in types; **not rendered** in the current template. |
| `category` | `"items"` \| `"services"` | Optional; defaults to **`items`** when parsed from JSON; **not shown** in the current template. |

### Root component props (`Component`)

```typescript
export type Component = {
  id?: string;
  style?: string;
  printer?: "yes" | "no";
  items?: IItem[] | string;
  headers?: IHeaders | string;
};
```

## Behavior notes

- **Table:** Child **`hb-table`** is used with **`disablepagination="yes"`** and **`rows`** / **`headers`** passed as JSON strings built from the computed row model (Italian column labels in the built-in headers: e.g. “descrizione”, “prezzo”, “quantità”, “iva”, “totale”).
- **Totals:** Subtotal is the sum of `unitaryPrice * (quantity ?? 1)` over **`items`**. Tax total is the sum of VAT on each line (rate × line base), rounded to two decimals at the sum level. Grand total is subtotal + tax total.
- **Print helpers:** Consumer apps can reuse `printInvoice` / `OpenInvoiceWindow` from `libs/utils.ts`; they open a window, write minimal HTML with an `hb-page-invoice` instance, and load **`@htmlbricks/hb-page-invoice@<version>/main.iife.js`** from jsDelivr (or a localhost Storybook path when the page URL contains `localhost`).

## Minimal HTML example

```html
<hb-page-invoice
  printer="no"
  headers='{"serial":"INV-1","date":"2026-04-17T00:00:00.000Z","from":{"piva":"IT123","name":"Seller Srl","address":"Via Roma 1","email":"billing@seller.example","phone":"+39 02 0000000","logo":"https://example.com/logo.svg","shortName":"Seller"},"to":{"piva":"IT456","name":"Buyer SpA","address":"Corso Italia 2","email":"ap@buyer.example","phone":"+39 011 0000000"}}'
  items='[{"desc":"Consulting","unitaryPrice":100,"taxPercentage":22,"quantity":1}]'
></hb-page-invoice>
```

Escape quotes inside JSON attributes as required by your templating environment.

## Related files

- `component.wc.svelte` — layout, totals, **`hb-table`** integration, toolbar.
- `types/webcomponent.type.d.ts` — public **`Component`** / **`Events`** and invoice shapes.
- `extra/docs.ts` — Storybook args, **`styleSetup`**, examples, dependency tree.

---

<a id="wc-paginate"></a>

# `hb-paginate` — integrator guide

**Category:** utilities · **Tags:** utilities, navigation · **Package:** `@htmlbricks/hb-paginate`

## Summary

Pagination bar with first, previous, next, and last controls plus nearby page number buttons. Optionally shows a “from–to of total” range line, a configurable page size control (numeric input or dropdown backed by nested web components), and optional sort field and direction controls with localized labels.

**Nested components:** this element registers **`hb-input-number`** and **`hb-input-select`** (same bundle version) for page size editing when `info.page_size_type` is set.

## Internationalization

Supported languages in metadata: **English** (`en`) and **Italian** (`it`). Set the optional **`i18nlang`** attribute to choose dictionary strings (for example labels such as “of”, “size”, “sort”, “default”, “asc”, “desc”).

## Attributes (HTML / reflected props)

Web component attributes use **snake_case**. Values from HTML are strings; numeric props accept string forms that parse to integers.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `page` | No | Current **zero-based** page index (coerced to a non‑negative integer; invalid or empty values fall back to **`0`**). |
| `pages` | No | Total number of pages (coerced to a non‑negative integer; implementation default **`1`**). When **`0`**, numbered navigation is not shown. |
| `id` | No | Optional element id. |
| `style` | No | Optional inline host style (typed on the component interface). |
| `info` | No | Optional toolbar configuration: either an object (e.g. Svelte/JS) or a **JSON string** (typical in HTML). Parsed once; invalid JSON is ignored. See [Info object](#info-object). |
| `i18nlang` | No | Language code, e.g. `en` or `it`. |

### Info object

When present, `info` drives the optional top row (range text and/or page size) and sort UI. All fields are optional unless noted.

| Field | Type (logical) | Description |
|-------|----------------|-------------|
| `total` | number | Total item count. Together with `size`, enables the range label **“start–end of total”**. `total` may be `0` and still show the line if `size` is set. |
| `size` | number | Page size used for the range calculation and as the current value for page size controls. |
| `page_size_type` | `"number"` \| `"select"` | If set, shows a page size block: **`number`** uses `hb-input-number` (min 1); **`select`** uses `hb-input-select` when options resolve to a non‑empty list. |
| `page_size_options` | string, string[], or number[] | For **`select`**: comma‑separated list (e.g. `"10,25,50,100"`), a JSON array string, or an array after `info` is parsed. Values become option value and label. |
| `sort_fields` | `{ value: string; label?: string }[]` | If the array has at least one entry, the sort field dropdown appears. Option values are `field.value`; labels default to the value when `label` is omitted. |
| `sort_by` | string | Currently selected sort field value. Empty string selects the synthetic **Default** row when applicable. |
| `sort_direction` | `"asc"` \| `"desc"` \| `"default"` | Current sort direction; drives the direction toggle icon and labels. |
| `sort_disabled` | boolean | Disables the sort `<select>` and the direction button. |
| `sort_direction_disabled` | boolean | Hides the direction toggle entirely (sort field may still show). |
| `sort_strict_direction` | boolean | When `true`, direction cycles **asc ↔ desc** only (no **default** state). Choosing “Default” in the field list sets direction to **asc** instead of **default**. |
| `sort_default_value` | string | Value for the **Default** sort option (per type definition: emitted as **`sort_by`** when default is selected). Example: **`withSortDefaultMatchingField`** in `extra/docs.ts`. |
| `sort_default_label` | string | Overrides the translated **Default** label for the empty-value option (e.g. `"Relevance"`). Also affects whether an extra default `<option>` is rendered when it would duplicate a field label. |

**Top row visibility:** A compact **paginate bar** (range and/or page size) is rendered when `total` is defined (including `0`) together with `size`, and/or when `page_size_type` is set. If neither applies, only the pagination `<nav>` is shown so layout stays tight.

**Range text:** Displays indices **`size * page + 1`** through **`min(size * (page + 1), total)`**, then the localized **“of”** and **`total`**.

**Page size change:** `changePageSize` fires only for a finite integer **> 0** that differs from the current `info.size` (after numeric coercion).

**Sort change:** `changeSort` fires when the user changes the sort field or toggles direction. The component syncs internal state from `info.sort_by` / `info.sort_direction` when those props update. Changing the field to empty sets direction to **default** unless `sort_strict_direction` is set (then **asc**). Selecting a non‑empty field while direction was **default** moves direction to **asc**.

## Events

Listen with `addEventListener` or framework equivalents. **`detail`** shapes:

| Event | `detail` |
|-------|----------|
| `pageChange` | `{ page: number; pages: number }` — `page` is the new zero-based index; `pages` is the total page count the component used. |
| `changePageSize` | `{ page_size: number }` |
| `changeSort` | `{ sort_by: string; sort_direction: "asc" \| "desc" \| "default" }` |

## Styling (Bulma CSS variables)

The component uses **Bulma 1.x** inside the shadow root. Theme it by setting these **`--bulma-*`** variables on a host ancestor (for example `:root` or `body`) so they inherit onto the custom element. Defaults below match `extra/docs.ts`.

| Variable | Type | Default | Role |
|----------|------|---------|------|
| `--bulma-primary` | color | `#00d1b2` | Primary brand color (current page and accents). |
| `--bulma-link` | color | `#485fc7` | Link and interactive accent. |
| `--bulma-border` | color | `#dbdbdb` | Borders for controls and pagination. |
| `--bulma-radius` | length | `0.375rem` | Base border radius for controls. |
| `--bulma-text` | color | `#363636` | Primary copy (labels, page numbers, toolbar). |
| `--bulma-text-weak` | color | `#7a7a7a` | Muted text (e.g. range line). |

## CSS `::part`

| Part | Description |
|------|-------------|
| `page-size-input` | Exposed on the nested **`hb-input-number`** used for **`page_size_type: "number"`** (page size width and styling). |

## Slots

None (`htmlSlots` is empty in `extra/docs.ts`).

## TypeScript typings (authoring)

From `types/webcomponent.type.d.ts`:

```typescript
export type Component = {
  id?: string;
  style?: string;
  pages?: number;
  page?: number;
  info?: {
    total?: number;
    size?: number;
    /** Type of page size selector: "number" for free input, "select" for dropdown */
    page_size_type?: "number" | "select";
    /** Page size options for select mode: comma-separated string, JSON array string, or array after `info` is parsed */
    page_size_options?: string | string[] | number[];
    /** Available sort fields. At least one must be present to show the sort indicator */
    sort_fields?: { value: string; label?: string }[];
    /** Currently selected sort field */
    sort_by?: string;
    /** Current sort direction */
    sort_direction?: "asc" | "desc" | "default";
    /** Whether the sort controls are disabled */
    sort_disabled?: boolean;
    /** Whether the sort direction is disabled */
    sort_direction_disabled?: boolean;
    /** When true, sort_direction can only be "asc" or "desc" (no "default" state) */
    sort_strict_direction?: boolean;
    /** Value for the "Default" sort option (emitted as sort_by when default is selected) */
    sort_default_value?: string;
    /** Custom label for the "Default" sort option (e.g. "Relevance") */
    sort_default_label?: string;
  };
  i18nlang?: string;
};

export type Events = {
  pageChange: { page: number; pages: number };
  changePageSize: { page_size: number };
  changeSort: { sort_by: string; sort_direction: "asc" | "desc" | "default" };
};
```

## HTML examples

Minimal pagination (five pages, start on first page):

```html
<hb-paginate page="0" pages="5" i18nlang="en"></hb-paginate>
```

Range line and disabled sort direction control:

```html
<hb-paginate
  page="1"
  pages="4"
  i18nlang="en"
  info='{"total":47,"size":10,"sort_direction_disabled":true}'
></hb-paginate>
```

Page size as a number input:

```html
<hb-paginate
  page="0"
  pages="10"
  i18nlang="en"
  info='{"total":100,"size":10,"page_size_type":"number"}'
></hb-paginate>
```

Page size as a select (comma-separated options):

```html
<hb-paginate
  page="0"
  pages="4"
  i18nlang="en"
  info='{"total":100,"size":25,"page_size_type":"select","page_size_options":"10,25,50,100"}'
></hb-paginate>
```

Sort field and direction (JSON array for `sort_fields` is also supported when encoded as a string attribute):

```html
<hb-paginate
  page="0"
  pages="4"
  i18nlang="en"
  info='{"total":100,"size":25,"sort_fields":[{"value":"title","label":"Title"},{"value":"date","label":"Date"}],"sort_by":"title","sort_direction":"asc"}'
></hb-paginate>
```

```javascript
const el = document.querySelector("hb-paginate");
el.addEventListener("pageChange", (e) => {
  console.log(e.detail.page, e.detail.pages);
});
el.addEventListener("changePageSize", (e) => {
  console.log(e.detail.page_size);
});
el.addEventListener("changeSort", (e) => {
  console.log(e.detail.sort_by, e.detail.sort_direction);
});
```

Additional named scenarios (strict direction, custom default label, combined sort and page size) are listed under **`examples`** in `extra/docs.ts`.

---

<a id="wc-paragraps-around-image"></a>

# `hb-paragraps-around-image` — integrator guide

**Category:** content · **Tags:** content · **Package:** `@htmlbricks/hb-paragraps-around-image`

**Dependency:** registers **`hb-paragraps-around-image-cell`** at runtime (`addComponent`, same bundle version).

## Summary

`hb-paragraps-around-image` is an editorial layout: a **center column image** flanked by **up to six** rich text blocks rendered as `hb-paragraps-around-image-cell` instances. Paragraph data is supplied as a **JSON string**; each cell is capped at **eight lines** of body text (`max_lines="8"` in the implementation).

The host does not render until **both** a non-empty `paragraphs` array (after parsing) and a non-empty `img` URL are present.

## Layout and paragraph order

On viewports **900px and wider**, the outer container is a horizontal flex row with three columns: **left**, **center** (image), **right**. Below that breakpoint the block still uses the same DOM order; spacing and flex behavior follow `styles/webcomponent.scss`.

Paragraphs map to columns by **array index** (0-based):

| Index | Column |
|-------|--------|
| 0, 2, 4 | Left |
| 1, 3, 5 | Right |

Omitted indices simply leave a gap in that column (for example, a single object only fills the top-left cell). You can use up to **six** entries for a full two-column stack.

## Paragraph object shape

Each item in the `paragraphs` array matches the `Paragraphs` type:

| Field | Required | Description |
|-------|----------|-------------|
| `text` | Yes | Body copy (line-clamped per cell). |
| `title` | No | Heading shown by the cell. |
| `icon` | No | Bootstrap Icons **icon name** (e.g. `globe`, `journal-text`) used inside the cell. |
| `link` | No | If set, the title behaves as a link to this URL. |
| `key` | No | Identifier forwarded on `paragraphPressed` when the cell reports a press (see Events). |

Icons rely on **Bootstrap Icons**; the component loads the icon font from a CDN (`<svelte:head>` in the implementation, plus a font import in `styles/webcomponent.scss`).

## Custom element tag

```html
<hb-paragraps-around-image …></hb-paragraps-around-image>
```

## Attributes (HTML / reflected props)

Web component attributes are **strings** (**snake_case**).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `img` | No | Center column image URL (non-empty **`img`** plus parsed **`paragraphs`** required for the layout to render). |
| `paragraphs` | No | JSON string or array: paragraph objects (see above). Parsed in an **`$effect`**; invalid JSON is ignored. |
| `id` | No | Optional host id. |
| `style` | No | Optional on **`Component`**; host **`style`** still applies in the light DOM. |

## Events

| Event | `detail` | When |
|-------|----------|------|
| `paragraphPressed` | `{ key: string }` | Bubbled from a child `hb-paragraps-around-image-cell` when it emits `paragraphPressed`. Consumers should set a stable `key` on each paragraph object if they need to identify which block was activated. |

Listen with `addEventListener("paragraphPressed", ...)` or your framework’s equivalent.

## Styling (Bulma theme variables)

The component forwards shared Bulma setup (`styles/bulma.scss`) and local layout (`styles/webcomponent.scss`). Set public **`--bulma-*`** variables on `body`, `:root`, or an ancestor so they cascade into `:host` and nested cells. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| Variable | Role |
|----------|------|
| `--bulma-block-spacing` | Horizontal padding on the left and right columns (`.col`). Default in metadata: `1.5rem`. |
| `--bulma-text` | Default body text color (inherited by nested `hb-paragraps-around-image-cell`). |
| `--bulma-link` | Title links and interactive affordances in child cells. |
| `--bulma-text-strong` | Strong / title-weight copy where the theme distinguishes it. |

**Slots:** none — layout is entirely data-driven.  
**CSS parts:** none on this host.

## Minimal example

```html
<hb-paragraps-around-image
  img="https://placehold.co/300x200"
  paragraphs='[{"text":"Body copy here.","title":"Title","icon":"globe","link":"https://example.com","key":"intro"}]'
></hb-paragraps-around-image>
```

## Multi-block example (two columns)

```html
<hb-paragraps-around-image
  img="https://placehold.co/300x200"
  paragraphs='[
    {"text":"Left column first block.","title":"Left 1","icon":"journal-text","link":"https://example.com","key":"L1"},
    {"text":"Right column first block.","title":"Right 1","icon":"journal-text","link":"https://example.org","key":"R1"},
    {"text":"Left column second block.","title":"Left 2","icon":"journal-text","key":"L2"},
    {"text":"Right column second block.","title":"Right 2","icon":"journal-text","key":"R2"}
  ]'
></hb-paragraps-around-image>
```

## Script listener example

```html
<hb-paragraps-around-image
  id="editorial"
  img="https://placehold.co/300x200"
  paragraphs='[{"text":"Click the title if it is a link; keys identify blocks.","title":"Press me","key":"block-a"}]'
></hb-paragraps-around-image>

<script>
  document.getElementById("editorial")?.addEventListener("paragraphPressed", (e) => {
    console.log("key:", e.detail.key);
  });
</script>
```

## Implementation notes for integrators

- **Empty state:** If `paragraphs` is missing, empty, not parseable as JSON, or `img` is missing, nothing inside `#container` is shown.
- **Child package:** Ensure `hb-paragraps-around-image-cell` is available (same bundle or registered before use) so nested custom elements upgrade correctly.
- **Accessibility:** Central image uses `alt=""`; prefer meaningful `img` URLs or extend the cell package if you need richer semantics.

---

<a id="wc-paragraps-around-image-cell"></a>

# `hb-paragraps-around-image-cell` — integrator guide

**Category:** content · **Tags:** content · **Package:** `@htmlbricks/hb-paragraps-around-image-cell`

## Summary

`hb-paragraps-around-image-cell` renders a single content tile: a Bootstrap icon, an optional title row (plain text, external link, or interactive control), and a body paragraph whose height is limited by a line clamp so tiles stay visually consistent in multi-column layouts. It is designed to sit inside `hb-paragraps-around-image` (the parent “paragraphs around image” layout) but can be used on its own wherever you need a compact icon + text block.

## Custom element

```html
<hb-paragraps-around-image-cell></hb-paragraps-around-image-cell>
```

## Attributes (snake_case, string values from HTML)

Web component attributes are always strings. Pass structured data as JSON on `paragraph`, and numeric limits as their string form (for example `"8"`).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `paragraph` | No | JSON string (or object from JS) describing the tile. Parsed in the component; missing or invalid values may not render. |
| `max_lines` | No | Maximum number of lines for the body text clamp. Coerced to a number internally; default **8** if omitted. |
| `id` | No | Optional host `id` (standard HTML). |

The authoring TypeScript `Component` type also lists an optional `style` field; the current implementation does not wire a `style` prop into the markup, so prefer styling the host from the light DOM (selectors on the custom element) or Bulma variables below.

### `paragraph` object shape

The `paragraph` value matches the shared `Paragraphs` type (same family as the parent `hb-paragraps-around-image` items):

| Field | Type | Description |
|-------|------|-------------|
| `text` | string | Body copy (required). |
| `title` | string (optional) | Heading row above the body. If omitted, only the icon and body are shown. |
| `icon` | string (optional) | Bootstrap Icons **name without** the `bi-` prefix (the template uses `class="bi bi-{icon}"`). |
| `link` | string (optional) | If set together with `title`, the title is rendered as an external link (`target="_blank"`) using link styling. |
| `key` | string (optional) | If `title` is set and `link` is **not**, and `key` **is** set, the title row becomes an underlined, clickable control. A click dispatches `paragraphPressed` with this `key` in the event detail. |

**Title row rules (in order):**

1. `link` present → title is a normal anchor to `link`.
2. Else `key` present → title is clickable and emits `paragraphPressed`.
3. Else → title is plain bold text.

If `paragraph` is missing or not an object after parsing, the component logs a warning and renders nothing.

## Events

Listen for the following custom event on the element (or on a parent in the bubbling phase):

| Event | `detail` | When |
|-------|-----------|------|
| `paragraphPressed` | `{ key: string }` | User activates the title control when `paragraph.key` is set (and `paragraph.link` is not). |

## Styling

### Bulma CSS variables

The host uses the shared Bulma web-component theme. From the light DOM you can override Bulma variables on `:host` or ancestors; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| Variable | Role |
|----------|------|
| `--bulma-text` | Body and non-link titles. |
| `--bulma-link` | Linked title (`paragraph.link`). |
| `--bulma-text-strong` | Bold title row. |
| `--bulma-block-spacing` | Vertical rhythm when nested in parent column layouts. |

### Icons

Bootstrap Icons are loaded for this component (font CSS). Ensure `paragraph.icon` matches an icon name from the Bootstrap Icons set.

### Layout notes

The inner layout uses fixed inline flex/spacing for the icon column and text column. `:host` is `display: block`. There are **no** exposed `::part()` hooks and **no** slots.

## Examples

### Clickable title (dispatches `paragraphPressed`)

```html
<hb-paragraps-around-image-cell
  paragraph='{"text":"Supporting copy for this section.","title":"Open details","icon":"globe","key":"section-1"}'
  max_lines="6"
></hb-paragraps-around-image-cell>
```

### Title as external link

```html
<hb-paragraps-around-image-cell
  paragraph='{"text":"Short intro text.","title":"Read documentation","icon":"box-arrow-up-right","link":"https://bulma.io/documentation/"}'
  max_lines="4"
></hb-paragraps-around-image-cell>
```

### Body only (no title)

```html
<hb-paragraps-around-image-cell
  paragraph='{"text":"Sidebar note or caption without a heading row.","icon":"info-circle"}'
></hb-paragraps-around-image-cell>
```

### Default clamp (omit `max_lines` → eight lines)

```html
<hb-paragraps-around-image-cell
  paragraph='{"text":"Long body …","title":"Title","icon":"text-paragraph","key":"block-a"}'
></hb-paragraps-around-image-cell>
```

## Related

- Parent layout: `hb-paragraps-around-image` (grid of cells + central image).
- Package name in metadata: `@htmlbricks/hb-paragraps-around-image-cell`.

---

<a id="wc-payment-paypal"></a>

# `hb-payment-paypal` — integrator guide

**Category:** commerce · **Tags:** commerce, payment · **Package:** `@htmlbricks/hb-payment-paypal`

## Summary

`hb-payment-paypal` loads the official [PayPal JS SDK](https://developer.paypal.com/docs/checkout/) using your **client id** and **currency**, renders **PayPal Buttons** for a **single fixed order total**, and on buyer approval runs **order capture** (`intent: "CAPTURE"`). When capture succeeds, the element dispatches a **`paymentCompleted`** custom event so your host page can continue checkout, clear a cart, or show a receipt.

The visible PayPal button chrome is drawn by PayPal inside the mounted region; this web component owns wiring, totals, and the completion event.

## Custom element tag

```html
<hb-payment-paypal …></hb-payment-paypal>
```

## Attributes (HTML / reflected props)

All names are **snake_case**. Values from HTML are **strings**; **`total`** is coerced with **`Number(...)`** inside the component.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `paypalid` | Yes | PayPal **client id** (passed to the SDK as **`client-id`**). |
| `currency` | No | **`EUR`** or **`USD`** (uppercased when set; defaults to **`EUR`**). |
| `total` | No | Order amount (string or number after coercion; default **`0`** in the implementation). |
| `id` | No | Optional host id. |
| `style` | No | Optional on **`Component`**; normal host **`style`** still applies in the light DOM. |

## Behavior

1. **Script load:** On mount and when `paypalid` / `currency` / `total` change, the component calls `loadScript` from `@paypal/paypal-js` with `client-id` and `currency`.
2. **Buttons:** Renders PayPal Buttons with `layout: "horizontal"`, `tagline: false`, `height: 40`.
3. **Order creation:** `createOrder` builds one purchase unit with `intent: "CAPTURE"` and `amount` `{ currency_code, value: String(total) }`.
4. **Approval:** `onApprove` calls `actions.order.capture()`. On success it dispatches **`paymentCompleted`** with `{ method: "paypal", total }` where `total` is the **numeric** prop value used for the order (not necessarily re-read from PayPal’s response).
5. **Teardown:** On component destroy, it attempts `paypal.Buttons?.().close?.()` to release the button instance.
6. **Errors:** SDK load or render failures are logged with `console.error`; they do not emit a dedicated custom event in the current implementation.

Changing **`paypalid`**, **`currency`**, or **`total`** after load can trigger a remount path (existing buttons are closed before a new load). Ensure your host state stays in sync with the amount shown to the buyer.

## Events

Listen with `addEventListener` or framework bindings on the custom element.

| Event | `detail` |
|-------|----------|
| `paymentCompleted` | `{ method: "paypal"; total: number }` |

Use this event as the signal that **capture completed on the client**; still verify the transaction on your **server** (webhooks or REST) before fulfilling goods or services.

## Styling

The shadow tree forwards Bulma theme setup on `:host`. PayPal’s iframe still controls the inner button pixels.

**CSS custom properties** (documented in `extra/docs.ts`):

| Variable | Role |
|----------|------|
| `--bulma-background` | Surface behind the PayPal mount region. |
| `--bulma-text` | Inline copy or labels beside the SDK (if any). |
| `--bulma-border` | Dividers next to sibling checkout panels. |
| `--bulma-radius` | Corner radius for Bulma-framed wrappers. |
| `--hb-checkout-border` | Legacy divider token for composite checkout layouts (defaults to `--bulma-border`). |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for how to set `--bulma-*` on ancestors.

**CSS part**

| Part | Target |
|------|--------|
| `btn` | Host wrapper around the PayPal Buttons mount (`#paypalbtn`). Use `::part(btn)` for **layout and spacing** around the SDK; do not expect to theme PayPal’s internal button art. |

## TypeScript typings (authoring)

From `types/webcomponent.type.d.ts`: **`Component`** (`paypalid`, optional **`currency`**, **`total`**, **`id`**, **`style`**) and **`Events`** (`paymentCompleted`).

## Integration notes

- **Content Security Policy:** Allow PayPal’s script and frame origins required by the JS SDK and Smart Payment Buttons, or the load/render step will fail.
- **Sandbox vs live:** Use a **sandbox client id** during development and your **live client id** in production; totals and currency must match what you persist for the order.
- **Currencies:** Only **EUR** and **USD** are handled in the type layer; other codes are not part of the declared `Component` contract.
- **Card UI:** Older commented markup in `component.wc.svelte` referenced an `hb-form` card flow; that path is **not** active in the shipped template. This component is **PayPal-button only** today.

## Examples

### Minimal markup

```html
<hb-payment-paypal
  paypalid="YOUR_CLIENT_ID"
  currency="EUR"
  total="40"
></hb-payment-paypal>
```

### With completion handler

```html
<hb-payment-paypal
  id="checkout-paypal"
  paypalid="YOUR_CLIENT_ID"
  currency="USD"
  total="12.50"
></hb-payment-paypal>

<script>
  const el = document.getElementById("checkout-paypal");
  el.addEventListener("paymentCompleted", (e) => {
    const { method, total } = e.detail;
    console.log("Captured", method, total);
  });
</script>
```

### Fractional totals

```html
<hb-payment-paypal
  paypalid="YOUR_CLIENT_ID"
  currency="EUR"
  total="1499.99"
></hb-payment-paypal>
```

## Related files

| File | Purpose |
|------|---------|
| `component.wc.svelte` | PayPal SDK load, buttons, capture, event dispatch. |
| `types/webcomponent.type.d.ts` | `Component` and `Events` typings for authors and generated declarations. |
| `extra/docs.ts` | Catalog metadata, `styleSetup` (vars / parts), Storybook args, examples. |

---

<a id="wc-player-input-streaming"></a>

# `hb-player-input-streaming` — integrator guide

**Category:** media · **Tags:** media, video, camera · **Package:** `@htmlbricks/hb-player-input-streaming`

## Summary

This web component captures the user’s **camera and microphone** using the browser [`MediaDevices.getUserMedia()`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) API, assigns the resulting [`MediaStream`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream) to an in-shadow `<video>` element, and renders a small **toolbar** for playback, fullscreen, and toggling video/audio tracks.

Access is requested **automatically when the component mounts** (see implementation in `component.wc.svelte`). If permission is denied, the component dispatches an event with the failure outcome and throws internally after logging; there is no separate “request” button in the default UI.

## Custom element tag

```html
<hb-player-input-streaming …></hb-player-input-streaming>
```

## Attributes (HTML / reflected props)

| Attribute | Role |
|-----------|------|
| `id` | Included in event **`detail`** (default empty string). |
| `style` | Optional on **`Component`**; host **`style`** still affects layout in the light DOM. |

## User interface

Inside the shadow root, the layout is roughly:

- A **`<video>`** element (with a captions `<track>` placeholder) showing the live preview once a stream is attached.
- A **control row** showing the current playback time (numeric) and buttons:
  - **Play / pause** — toggles `HTMLMediaElement.play()` / `pause()` on the preview video.
  - **FH** — calls `requestFullscreen()` on the video element.
  - **remove / add video** — enables or disables **video** [`MediaStreamTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) instances on the bound stream.
  - **remove / add audio** — same for **audio** tracks.

Labels use plain English strings (`play`, `pause`, `FH`, etc.) as in the source.

## Events

| Event | `detail` |
|-------|----------|
| `AudioVideoAccess` | `{ granted: boolean; id: string }` — after **`getUserMedia`** succeeds or fails. |
| `VideoInitialized` | `{ videoElement: HTMLVideoElement; id: string }` — after the stream is bound and **`play()`** is invoked on success. |

## TypeScript typings (authoring)

From `types/webcomponent.type.d.ts`:

```ts
export type Component = { id?: string; style?: string };
export type Events = {
  AudioVideoAccess: { granted: boolean; id: string };
  VideoInitialized: { videoElement: HTMLVideoElement; id: string };
};
```

## Requirements and limitations

- **Secure context:** `getUserMedia` is restricted to [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS or `localhost`).
- **Permissions:** The browser shows its own prompt for camera and microphone; the user can deny either or both.
- **Privacy:** Embedding this component implies you will request sensitive device access as soon as it loads; consider UX and legal copy (privacy policy, consent) on the host page.

## Styling (Bulma)

The component forwards the shared Bulma web-component theme on `:host`. Consumer pages can override Bulma **CSS variables** from the light DOM. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and `extra/docs.ts` for the curated list used in metadata:

| Variable | Role |
|----------|------|
| `--bulma-text` | Toolbar / text beside the preview. |
| `--bulma-link` | Accent for interactive controls when Bulma tokens apply. |
| `--bulma-background` | Surface behind the video area. |
| `--bulma-border` | Optional separators if you extend layout around the shell. |

Host baseline (`styles/webcomponent.scss`) sets `:host { display: block; }` and a relative container for the player shell.

There are no documented **`::part`** names or **slots** for this package (`extra/docs.ts`).

## Minimal HTML

Minimal host with a stable id (useful for tests or logging):

```html
<hb-player-input-streaming id="cam1"></hb-player-input-streaming>
```

Sized host (layout via host `style`):

```html
<hb-player-input-streaming
  id="player-input-demo"
  style="display:block;width:100%;max-width:640px;"
></hb-player-input-streaming>
```

Listening for access and initialization in vanilla JS:

```html
<hb-player-input-streaming id="preview"></hb-player-input-streaming>
<script>
  const el = document.querySelector("hb-player-input-streaming");
  el.addEventListener("AudioVideoAccess", (e) => {
    console.log("granted:", e.detail.granted, "id:", e.detail.id);
  });
  el.addEventListener("VideoInitialized", (e) => {
    console.log("video element:", e.detail.videoElement);
  });
</script>
```

License: **Apache-2.0** per `extra/docs.ts` / published `LICENSE.md`.

---

<a id="wc-player-live"></a>

# `hb-player-live` — integrator guide

**Category:** media · **Tags:** media, video, streaming · **Package:** `@htmlbricks/hb-player-live`

## Summary

Live streaming player built on a native `<video>` element. It supports **HLS** (via [hls.js](https://github.com/video-dev/hls.js/) when Media Source Extensions are available, or the browser’s built-in HLS where supported), **WebRTC over WebSocket** ([`simple-webrtc-element`](https://www.npmjs.com/package/simple-webrtc-element)), and **WHEP** ([`MediaMTXWebRTCReader`](https://www.npmjs.com/package/simple-webrtc-element-whep) for MediaMTX-style endpoints).

For **HLS**, the component periodically **fetches the manifest URL** to decide whether the source is reachable (“live” for overlay purposes). For **WebRTC** and **WHEP**, **online/offline** is driven by the underlying player callbacks, which also update `liveStatus`.

When the URI is missing, the stream is considered offline, or you force a cover state, the component can show a **full-area overlay** (default copy, `replacewithtext` JSON, and optional slots).

## Custom element tag

```html
<hb-player-live …></hb-player-live>
```

## Attributes (snake_case; string values in HTML)

Web component attributes are always strings. Use **`yes`** / **`no`** for boolean-like flags where noted. Pass **JSON as a single string** for object props (for example `replacewithtext='{"title":"…"}'`).

| Attribute | Required | Description |
| --- | --- | --- |
| `mediauri` | Yes | Stream endpoint: HLS playlist (`.m3u8`), WebSocket signaling URL for `webrtc`, or WHEP URL for `whep`. Use an empty string if you intentionally have no URI (placeholder state). |
| `media_type` | No | Playback mode: `hls`, `webrtc`, `whep`, or `auto` (see [Media type](#media-type)). Default in the implementation is `auto`. The `<video>` node is only rendered when both `mediauri` and `media_type` are non-empty. |
| `forcecover` | No | Any non-empty value (commonly `yes`) forces the structured overlay when the conditions in the template are met (together with `replacewithtext` shape). |
| `replacewithtext` | No | JSON string: `{ "title": string, "subtitle"?: string, "text"?: string }`. Used for default overlay copy; the component also parses a string value into an object when possible. |
| `no_controls` | No | Hide native `<video>` controls when set to any truthy value except the strings `no` or `false` (for example `yes`). |
| `id` | No | Passed through on custom events and when exposing the video element. |

The authoring `Component` type also allows `style`; styling is normally done via CSS variables, `::part`, and host layout.

---

## Media type

Set `media_type` explicitly for working playback:

- **`hls`** — Loads `mediauri` with hls.js (or assigns `src` on Safari / browsers that report native HLS support). Autoplay is attempted with **muted** video.
- **`webrtc`** — Uses `simple-webrtc-element` with `wsUri: mediauri` on the bound `<video>`.
- **`whep`** — Uses `MediaMTXWebRTCReader` with `url: mediauri`; the first stream track is assigned to `video.srcObject`.

The literal value **`auto`** appears in the public type union and is the Svelte default, but the internal `setVideo` path only wires **`hls`**, **`webrtc`**, and **`whep`**. Treat **`auto`** as reserved / not implemented for playback until a future version adds detection.

---

## Live status and polling

- **`hls`**: `loadLive()` performs a `fetch(mediauri)` on the manifest. A response in the **1xx–299** range sets internal “live” to true and dispatches `liveStatus` with `live: true`. On failure, it dispatches `live: false` and **retries after 5 seconds** (also when `mediauri` is empty).
- **`webrtc` / `whep`**: `liveStatus` is dispatched from **online/offline** handlers in the respective integrations (`live: true` / `live: false`).

---

## Overlay and fallback UI

The dark **16:9** surface uses Bulma variables `--bulma-dark` and `--bulma-dark-invert` (see [Styling](#styling)).

Rough behavior (see `component.wc.svelte` for exact conditions):

1. **`forcecover`** is set, or **`mediauri` is set**, **`isLive` is false**, and **`replacewithtext`** includes at least one of `title`, `subtitle`, or `text` — shows the **`replacewithtext`** part with the **default slot layout** (one, two, or three lines depending on which JSON fields are present), unless you override the `replacewithtext` slot entirely.
2. Otherwise, if there is a **`mediauri`** but the stream is **not live** and there is **no** `replacewithtext` copy — shows the literal **`offline`** label in the overlay.
3. If there is **no `mediauri`** — shows **`nouri`** in the overlay.

If you supply the **`replacewithtext`** slot, you replace the entire default overlay markup for that branch; the inner slots (`replacetitle`, `replacesubtitle`, `replacetext`) only apply inside the **default** structure.

---

## Events

Listen with `addEventListener` on the host element (names are camelCase in the type definitions).

| Event | `detail` (runtime) |
| --- | --- |
| `liveStatus` | `{ live: boolean; id: string }` |
| `htmlVideoInit` | `{ htmlVideoElement: HTMLVideoElement; id: string }` — fired when the `<video>` is bound. |

## TypeScript typings (authoring)

`types/webcomponent.type.d.ts` defines **`Component`**, **`Events`** (`liveStatus`, `htmlVideoInit` with **`htmlVideoElement`**), and nested shapes for **`replacewithtext`**.

## Host API

After upgrade, the custom element exposes:

- **`getVideoElement()`** — Returns the internal `HTMLVideoElement` when it exists, otherwise `undefined`.

---

## Styling (CSS custom properties)

Set on the host or an ancestor. The video frame and overlay use Bulma dark tokens.

| Variable | Role |
| --- | --- |
| `--bulma-dark` | Background of the 16:9 video area and offline/cover overlay. |
| `--bulma-dark-invert` | Foreground (text) on that surface. |

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for theme-wide tuning.

---

## CSS parts (`::part(...)`)

| Part | Purpose |
| --- | --- |
| `container` | Root wrapper around the player and overlay; sizing and positioning of the whole component. |
| `replacewithtext` | Full-bleed overlay when the stream is missing, not live, `forcecover` applies, or placeholder copy is shown. |
| `video` | The native `<video>` element (streaming surface, native controls when enabled). |

---

## HTML slots

| Slot | When it matters | Purpose |
| --- | --- | --- |
| `replacewithtext` | Overlay branch with default structure | Replace the entire offline/cover layout. If you fill this slot, the default grid and nested slots are not used unless you reintroduce them in your markup. |
| `replacetitle` | Default overlay only | Title line; falls back to `replacewithtext.title`. |
| `replacesubtitle` | Default overlay (two- or three-line layout) | Subtitle line; falls back to `replacewithtext.subtitle` when the prop defines both title and subtitle. |
| `replacetext` | Default three-line layout | Body copy; falls back to `replacewithtext.text`. |

---

## Usage notes

- **Autoplay**: The implementation sets **`muted`** and calls **`play()`** where possible; browser policies may still block autoplay with sound or without user gesture.
- **Changing `mediauri`**: When the URI changes, WHEP readers are closed, live state is reset, and the video is reinitialized.
- **Accessibility**: The markup includes a captions `<track>` placeholder; provide real captions/tracks in your integration if you need accessibility compliance.

---

## Examples

**HLS (public test stream)**

```html
<hb-player-live
  mediauri="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
  media_type="hls"
></hb-player-live>
```

**WHEP endpoint** (replace with your server URL)

```html
<hb-player-live
  mediauri="https://example.com/path/to/whep"
  media_type="whep"
></hb-player-live>
```

**Forced cover with JSON copy** (`replacewithtext` must be a valid JSON string attribute)

```html
<hb-player-live
  mediauri="https://example.com/stream/whep"
  media_type="whep"
  forcecover="yes"
  replacewithtext='{"title":"Stream paused","subtitle":"We will be back soon","text":"Thank you for waiting."}'
></hb-player-live>
```

**Hide native controls**

```html
<hb-player-live
  mediauri="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
  media_type="hls"
  no_controls="yes"
></hb-player-live>
```

**Listen for live state and grab the video element**

```html
<hb-player-live
  id="cam1"
  mediauri="https://example.com/live.m3u8"
  media_type="hls"
></hb-player-live>
<script>
  const el = document.querySelector("#cam1");
  el.addEventListener("liveStatus", (e) => {
    console.log(e.detail.live, e.detail.id);
  });
  el.addEventListener("htmlVideoInit", (e) => {
    console.log(e.detail.htmlVideoElement, e.detail.id);
  });
  customElements.whenDefined("hb-player-live").then(() => {
    const v = el.getVideoElement?.();
    if (v) console.log("video", v);
  });
</script>
```

---

<a id="wc-player-live-camera-ptz"></a>

# `hb-player-live-camera-ptz` — integrator guide

**Category:** media · **Tags:** media, video, streaming, camera · **Package:** `@htmlbricks/hb-player-live-camera-ptz`

## Summary

`hb-player-live-camera-ptz` embeds a live stream using `hb-player-live` and layers a **PTZ (pan–tilt–zoom) control panel** on top of the video. The component handles the UI only: D-pad or analog joystick (via `hb-pad-joystick`), zoom buttons, go-home confirmation, **click-to-center** (draw a rectangle on the video), optional **grid overlay**, fullscreen, mute, and play/pause. Presets are listed and managed through `hb-dialog` and `hb-table` (add scene, go to preset, delete preset with confirmations).

Your application listens for **custom events** and calls your camera or robotics backend. Movement **speed** and **precision** are edited in the panel (sliders) and included on each movement-related event as `movementSettings`.

**Nested dependencies** (for bundling or CDN ordering) include `hb-player-live`, `hb-pad-joystick`, `hb-table`, and `hb-dialog` (see `extra/docs.ts` for the full tree).

## Custom element

```html
<hb-player-live-camera-ptz …></hb-player-live-camera-ptz>
```

Bootstrap Icons are loaded for toolbar glyphs (see `component.wc.svelte` / `styles/webcomponent.scss`).

## Attributes (snake_case; string values from HTML)

From HTML or `setAttribute`, values are **strings**. Objects and arrays must be **JSON strings**. Booleans should use **`yes`** / **`no`** where possible; the implementation also treats the strings `true` / `false` for some flags when coerced from attributes.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `live_uri` | Yes | Stream URI passed to `hb-player-live` as `mediauri`. |
| `id` | No | Identifier echoed on dispatched events. |
| `style` | No | Host-level inline style (typed on `Component`; forwarding to inner players may vary). |
| `media_type` | No | `"hls"`, `"webrtc"`, `"auto"`, `"whep"`, or `""`. Default in code: `"auto"`. |
| `is_ptz_connected` | No | When falsy after coercion, the PTZ strip and opener are hidden. Use `yes` / `no`. |
| `is_ptz_panel_opened` | No | When falsy, the control panel is collapsed; the opener tab remains if connected. Use `yes` / `no`. |
| `is_home` | No | Drives the home button highlight (`yes` / `no`). |
| `presets` | No | JSON array of presets: `{ token, name, x, y, z }` (see **Preset shape** below). |
| `current_preset` | No | Token of the preset shown as active on the presets button. |
| `position` | No | Typed layout hint (`top`, `right-bottom`, `left-bottom`, `bottom`, `right-top`, `left-top`). **Not wired** in the current stylesheet; reserved for typing / future layout. |
| `configuration` | No | JSON object of feature flags (see **Configuration**). Defaults enable all documented features. |

### Preset shape (`presets` JSON)

Each preset is an object:

- `token` (string): stable id used in UI and events.
- `name` (string): label in the table and button.
- `x`, `y`, `z` (numbers): logical camera pose; the component does not interpret them beyond displaying names/tokens—it forwards them on preset events for your backend.

### Configuration (`configuration` JSON)

All keys are optional; defaults match the in-component `defaultConfiguration`.

| Key | Type | Role |
|-----|------|------|
| `joystick` | boolean | Allow switching between D-pad and analog joystick. |
| `presets` | boolean | Open the presets table dialog. |
| `addPreset` | boolean | “Add scene to preset” flow. |
| `switchPreset` | boolean | Confirm and dispatch go-to preset. |
| `deletePreset` | boolean | Delete preset from table flow. |
| `home` | boolean | Go-home button and confirmation. |
| `zoom.in`, `zoom.out` | boolean | Zoom buttons. |
| `pan`, `tilt` | boolean | Enable D-pad direction events; analog joystick path also checks these flags in code. |
| `clickToCenter` | boolean | Rectangle selection on the video and `goToSelectedArea`. |
| `settings` | boolean | Enables the settings button in the toolbar (handler is currently a **no-op**). |

## Styling (Bulma and CSS variables)

Bulma-oriented variables are documented in `extra/docs.ts` (`styleSetup.vars`). Set public **`--bulma-*`** (and the joystick variable) on the host or `:root` as needed; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| Variable | Role |
|----------|------|
| `--bulma-danger` | Click-to-center selection rectangle (`#area_selector`). |
| `--bulma-border` | Grid overlay lines on the video. |
| `--bulma-link` | Toolbar buttons, PTZ accents, active states. |
| `--bulma-text` | Labels in dialogs and toolbars. |
| `--bulma-scheme-main` | Panel / dialog surfaces (with transparency in the panel). |
| `--hb-pad-joystick-size` | Joystick size forwarded to nested `hb-pad-joystick` (see component SCSS). |

**CSS parts:** none on this element (`extra/docs.ts`). Child components may expose their own parts in their docs.

**Slots:** none.

## User-facing behavior

- **Stream:** `hb-player-live` with `no_controls="yes"`; outer toolbar handles mute and play/pause on the underlying `HTMLVideoElement`.
- **Panel visibility:** If `is_ptz_connected` is off, no PTZ UI. If connected but `is_ptz_panel_opened` is off, only the side **opener** control is shown; clicking it toggles the panel and dispatches `panelMove`.
- **Movement UI:** Default mode is **D-pad**; users can switch to **joystick** when `configuration.joystick` is enabled. **Speed** and **precision** sliders update `movementSettings` sent with events.
- **Click to center:** Toggle the square tool, then **two clicks** on the video define a rectangle; on completion the component dispatches `goToSelectedArea` with pixel geometry relative to the video box and the video’s width/height.
- **Presets:** “+” opens add confirmation → `setPreset`. Presets button opens `hb-table`; row actions trigger go/delete confirmations → `goToPreset` / `deletePreset`.
- **Fullscreen:** Requests fullscreen on the video element (browser UX).

## Events

Listen with `addEventListener` or framework equivalents. Payloads match `types/webcomponent.type.d.ts`.

| Event | `detail` summary |
|-------|------------------|
| `initVideo` | `{ id, time, htmlVideoElement }` — fired after the inner player exposes the video node. |
| `panelMove` | `{ id, opened }` — panel opened or closed via the opener control. |
| `goToHome` | `{ id, movementSettings, time }` — user confirmed home (types); wire to your PTZ home command. |
| `goToSelectedArea` | `{ id, movementSettings, time, top, left, width, height, htmlVideoElementWidth, htmlVideoElementHeight }` — rectangle in video coordinates plus intrinsic video layout size. |
| `setPreset` | `{ id, time }` — user confirmed adding the current scene as a preset; your backend should persist and then update `presets` / `current_preset` via attributes or properties. |
| `goToPreset` | Preset fields (`token`, `name`, `x`, `y`, `z`) plus `{ playerId, movementSettings, time }`. |
| `deletePreset` | Preset fields plus `{ playerId, time }`. |
| `zoomAction` | `{ id, movementSettings, direction: "in" \| "out", time }`. |
| `sendJoystickPosition` | `{ id, joyId, movementSettings, x, y, cardinalDirection, time }` — analog joystick samples (`CardinalDirection` in types). |
| `sendDirection` | `{ id, joyId, movementSettings, direction: "up" \| "right" \| "down" \| "left" }`. |

`movementSettings` is `{ speed: number; precision: number }` (defaults `50` / `50` in the component, user-adjustable in the panel).

## Integration checklist

1. Set `live_uri` and `media_type` appropriately for your stack (HLS, WebRTC, WHEP, etc.).
2. Set `is_ptz_connected="yes"` when your session can accept PTZ commands.
3. Subscribe to **movement and preset** events and translate them to your API.
4. Push updated **`presets`** (JSON string) and **`current_preset`** from your server or client state when presets change.
5. Optionally toggle **`configuration`** JSON to disable controls your backend does not support.

## Minimal HTML example

```html
<hb-player-live-camera-ptz
  id="cam-1"
  live_uri="https://example.com/live/stream.m3u8"
  media_type="auto"
  is_ptz_connected="yes"
  is_ptz_panel_opened="yes"
></hb-player-live-camera-ptz>

<script>
  const el = document.querySelector("hb-player-live-camera-ptz");
  el.addEventListener("sendDirection", (e) => {
    console.log(e.detail);
  });
</script>
```

### Example with presets (JSON attributes)

```html
<hb-player-live-camera-ptz
  live_uri="https://example.com/live/stream.m3u8"
  media_type="auto"
  is_ptz_connected="yes"
  current_preset="preset1"
  presets='[{"token":"preset1","name":"Door","x":0,"y":0,"z":0},{"token":"preset2","name":"Yard","x":0,"y":0,"z":0}]'
></hb-player-live-camera-ptz>
```

## TypeScript typings (authoring)

`types/webcomponent.type.d.ts` — **`Component`**, **`Events`**, **`TPreset`**, **`TMovementSettings`**. Metadata: `extra/docs.ts`. Implementation: `component.wc.svelte`. Generated consumer typings: package `types/html-elements.d.ts` and `types/svelte-elements.d.ts` after `npm run build:wc`.

---

<a id="wc-product-comparison"></a>

# `hb-product-comparison` (product-comparison)

**Category:** commerce  
**Tags:** commerce, product  
**Package:** `@htmlbricks/hb-product-comparison`

## Overview

Product comparison matrix: one column per product (model, description, optional “preferred choice” / “on sale” callout, currency + price, purchase button) and one row per feature header. Each cell shows a Bootstrap Icon or plain text based on that product’s `characteristics` entry for the header’s `id`. Supports highlighting a preferred plan (`preferredProductId`) and sale pricing (`promotion.originalPrice` with strikethrough on the original amount). Dispatches **`purchaseClick`** with the chosen product’s `id`. **Desktop:** wide table-style grid with a corner slot. **Mobile:** one stacked card per product with the same headers rendered inline.

## Custom element

```html
<hb-product-comparison …></hb-product-comparison>
```

## Attributes

Attributes use **snake_case**. In plain HTML, **`headers`**, **`products`**, and **`options`** are passed as **JSON strings** (the component parses them at runtime). Invalid JSON for `headers` or `products` yields empty arrays; invalid `options` JSON falls back to `{}`. When `options` is parsed and **`currency`** is missing or empty, it defaults to **`€`**.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `headers` | Yes | JSON array of **`Header`** objects: `{ id: string; label: string }[]`. Each `id` is a row key; `label` is shown in the first column (desktop) or beside each value (mobile). |
| `products` | Yes | JSON array of **`Product`** objects. See [Product](#product). |
| `options` | Yes | JSON object **`Options`**: optional `currency` (string), optional `preferredProductId` (string). |
| `id` | No | Optional host id (typed on the component interface). |
| `style` | No | Optional inline host style (typed on the component interface). |

### `Header`

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Key into each product’s `characteristics` for that comparison row. |
| `label` | string | Human-readable row title. |

### `Product`

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Stable product identifier; emitted on **`purchaseClick`**. |
| `model` | string | Shown as the column/card title (uppercased styling in the template). |
| `price` | number | Current price shown next to `options.currency`. |
| `characteristics` | `Record<string, string>` | Map from header `id` to cell value; see [Characteristic values](#characteristic-values). |
| `description` | string (optional) | Subtitle under the model (muted). |
| `note` | string (optional) | Present in typings; not read by the current template. |
| `promotion` | object (optional) | Sale block: `originalPrice` (number, required for strikethrough), optional `type`, optional `note`. Only **`originalPrice`** drives the “on sale” label and struck-through price in the UI. |
| `columnColor` | object (optional) | Optional per-column colors in typings (`headerBackground`, `columnBackground`, `purchaseButton`); not applied by the current markup. |

### `Options`

| Field | Type | Description |
|-------|------|-------------|
| `currency` | string (optional) | Prefix/symbol before the price; defaults to **`€`** when omitted after parse. |
| `preferredProductId` | string (optional) | When it matches a product’s `id`, that column/card shows **“preferred choice”** in the badge area (desktop layout reserves badge height when any product has a promotion or a preferred id is set). |

### Characteristic values

For each header `id`, the cell value `product.characteristics[header.id]` is interpreted as follows (string match):

| Value | UI |
|-------|-----|
| `"valid"` | Check icon (`bi-check`). |
| missing or `"disabled"` | Dash icon (`bi-dash`). |
| `"blocked"` | X icon (`bi-x`). |
| `"star"` | Filled star (`bi-star-fill`). |
| `"plus"` | Plus icon (`bi-plus`). |
| any other string | Raw text in the cell. |

## Events

| Event | `detail` |
|-------|----------|
| `purchaseClick` | `{ id: string }` — the `id` of the product whose **Acquista** button was clicked. |

Listen with `addEventListener("purchaseClick", …)` or your framework’s DOM event binding.

## Slots

| Slot | Description |
|------|-------------|
| `corner` | Top-left cell of the desktop grid (first column of the header row). Empty by default; use for a label such as “Features” or branding. |

## Styling (Bulma CSS variables)

The component uses **Bulma** inside the shadow root. Theme it with public **`--bulma-*`** variables (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)). Defaults below match `extra/docs.ts`.

| Variable | Type | Default | Role |
|----------|------|---------|------|
| `--bulma-primary` | color | `#00d1b2` | Purchase button background (`button is-primary`). |
| `--bulma-text` | color | `#363636` | Body text (models, prices, labels, non-icon cell text). |
| `--bulma-column-gap` | length | `0.75rem` | Horizontal gap between comparison columns. |
| `--bulma-background` | color | `#ffffff` | Card / column surfaces. |
| `--bulma-radius` | length | `0.375rem` | Radius for buttons and shells. |
| `--bulma-border` | color | `#dbdbdb` | Grid borders and dividers (desktop table and mobile cards). |

**Icons:** Bootstrap Icons are loaded for the feature cells (and the desktop layout also links the icon font in the component head). Ensure network access to the CDN or mirror the font if you bundle offline.

## CSS `::part`

| Part | Description |
|------|-------------|
| `container` | Root comparison wrapper (desktop columns layout or mobile stacked cards). |
| `col` | A single column cell (product header stack or a feature cell). |
| `row` | A horizontal strip of columns (desktop header row or one feature row). |
| `mobile-card` | One stacked mobile card per product (image header block plus feature rows). |

## Layout and copy

- **Desktop:** `container` / `columns` / `column` with **`is-hidden-touch`**; **mobile:** separate block with **`is-hidden-desktop`**, one card per product.
- Purchase control label in the template is **Acquista** (Italian). Override visually via Bulma/CSS as needed; there is no i18n dictionary on this component in metadata.

## Minimal HTML example

```html
<hb-product-comparison
  headers='[{"id":"f1","label":"Feature 1"}]'
  products='[{"id":"p1","model":"Basic","price":10,"characteristics":{"f1":"valid"}}]'
  options='{"currency":"€"}'
></hb-product-comparison>
```

### Example with preferred plan and promotion-shaped data

```html
<hb-product-comparison
  headers='[{"id":"cpu","label":"CPU"},{"id":"ram","label":"RAM"}]'
  products='[
    {"id":"s","model":"S","price":399,"characteristics":{"cpu":"4 cores","ram":"8 GB"}},
    {"id":"l","model":"L","price":899,"characteristics":{"cpu":"8 cores","ram":"32 GB"}}
  ]'
  options='{"currency":"€","preferredProductId":"l"}'
></hb-product-comparison>
```

```html
<hb-product-comparison
  headers='[{"id":"f1","label":"Included"}]'
  products='[
    {"id":"sale","model":"Promo","price":59,"description":"Limited","promotion":{"originalPrice":99,"type":"percent","note":"Spring sale"},"characteristics":{"f1":"valid"}},
    {"id":"std","model":"Standard","price":99,"characteristics":{"f1":"valid"}}
  ]'
  options='{"currency":"€"}'
></hb-product-comparison>
```

## TypeScript authoring types

Authoring interfaces for this element live in `types/webcomponent.type.d.ts` (`Component`, `Header`, `Product`, `Options`, `Events`). Keep JSON payloads in HTML aligned with those shapes.

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-range-slider"></a>

# `hb-range-slider` (range-slider)

**Category:** inputs  
**Tags:** inputs, slider  
**Package:** `@htmlbricks/hb-range-slider`

## Overview

`hb-range-slider` is a shadow-DOM web component that renders a **dual-handle range slider**. Two thumbs select a sub-range on a linear domain defined by **`min`** and **`max`**; the selected interval is mapped to **real values** and **percentages** on the track. An optional **third handle** acts as a **single position marker** along the same domain; its value appears in the emitted payload as **`positionValReal`** and **`positionPercent`**.

Optional **value bubbles** (`withbubbles`) show rounded min/max values above the outer thumbs. The component uses native `<input type="range">` controls (visually hidden) for pointer and keyboard interaction; the visible track, thumbs, and bubbles are styled with Bulma-driven CSS variables.

When the user **commits** a change (native `change` on the range inputs), the component dispatches **`changeRangeValues`** with the current min, max, and marker positions in both **domain units** and **0–100 track percent**.

## Custom element

```html
<hb-range-slider …></hb-range-slider>
```

## Attributes (host)

Web component attributes use **snake_case**. Values from HTML are **strings**. Use **numeric strings** for numbers (for example `"0"`, `"42.5"`). For booleans where noted, use **`yes`** or **`no`** (project convention for host markup).

| Attribute | Required | Description |
|-----------|----------|-------------|
| **`min`** | No | Lower bound of the value domain. Default **`0`** (internal default when missing or invalid). Coerced with `Number()`. |
| **`max`** | No | Upper bound of the value domain. Default **`100`**. Must span a range with **`min`** for meaningful mapping. Coerced with `Number()`. |
| **`minval`** | No | Initial **left** thumb value in domain units. Clamped to **`min`**..**`max`**, then converted to an internal left percentage. Consumed once when applied (not kept as a live two-way binding from attributes alone). |
| **`maxval`** | No | Initial **right** thumb value in domain units. Clamped to **`min`**..**`max`**, then converted to an internal right percentage. Consumed once when applied. |
| **`position_value`** | No | Initial **marker** thumb value in domain units. Clamped to **`min`**..**`max`**, then converted to internal marker percentage. Consumed once when applied. **`0`** is a valid value. |
| **`withbubbles`** | No | Set to **`yes`** to show value bubbles on the min and max thumbs. Omit the attribute for the default (bubbles off). Prefer **`yes`** / **`no`** for boolean host strings in line with other `hb-*` inputs. |
| **`id`** | No | Optional host element id. |
| **`style`** | No | Optional inline styles on the host element. |

**Domain mapping:** Internal thumbs move on an abstract **0–100** track. A thumb at percent `p` maps to real value `min + ((max - min) / 100) * p`. The marker thumb is kept **between** the left and right thumbs when percentages are adjusted.

## Events

### `changeRangeValues`

Fired on **change** (after the user finishes adjusting a handle), not on every intermediate `input` tick.

**`event.detail`** (all numbers):

| Property | Description |
|----------|-------------|
| **`minValue`** | Left thumb value in domain units (derived from **`min`**, **`max`**, and left percent). |
| **`maxValue`** | Right thumb value in domain units. |
| **`minPercent`** | Left thumb position on the 0–100 internal track. |
| **`maxPercent`** | Right thumb position on the 0–100 internal track. |
| **`positionPercent`** | Marker thumb position on the 0–100 internal track. |
| **`positionValReal`** | Marker value in domain units. |

TypeScript shapes for **`Component`** and **`Events`** live in **`types/webcomponent.type.d.ts`**.

**Example listener:**

```html
<hb-range-slider id="range" min="10" max="25" minval="13" maxval="20" withbubbles="yes"></hb-range-slider>
<script>
  document.getElementById("range").addEventListener("changeRangeValues", (e) => {
    console.log(e.detail.minValue, e.detail.maxValue, e.detail.positionValReal);
  });
</script>
```

## Styling

The host forwards **Bulma 1.x** CSS variables into internal tokens used by the track, thumbs, and bubbles. Prefer **`--bulma-*`** on `:host` or ancestors; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

| Variable | Role |
|----------|------|
| **`--bulma-primary`** | Selected range fill and bubble background (via `--hb-slider-fill` / `--hb-slider-bubble`). |
| **`--bulma-primary-invert`** | Bubble label text color. |
| **`--bulma-border`** | Unselected track segments (`--hb-slider-track`). |
| **`--bulma-scheme-main`** | Outer thumb face (`--hb-slider-thumb-bg`). |
| **`--bulma-text`** | Inner thumb contrast / marker thumb tone (`--hb-slider-thumb-inner`). |
| **`--hb-slider-background-color`** | Optional override for fill and bubble tint (defaults through **`--bulma-primary`**). |

Internal wiring (from **`styles/webcomponent.scss`**): **`--hb-slider-fill`**, **`--hb-slider-track`**, **`--hb-slider-thumb-bg`**, **`--hb-slider-thumb-inner`**, **`--hb-slider-bubble`**, **`--hb-slider-bubble-text`**.

**`::part` names** (from **`extra/docs.ts`** / `styleSetup`):

| Part | Description |
|------|-------------|
| **`inverse`** | Host-side wrappers that paint the unfilled track beside the selected range. |
| **`the-range`** | Filled segment between the two outer thumbs. |
| **`the-thumb`** | Draggable handles for the range endpoints (and the marker’s outer thumb styling). |
| **`the-thumb-internal`** | Smaller center handle for the optional marker thumb between the endpoints. |
| **`sign`** | Value bubble / label attached to a thumb when **`withbubbles`** is enabled. |

When bubbles are enabled, the host adds the class **`hb-range-slider--bubbles`** on the root container for extra vertical spacing.

## Slots

None (`htmlSlots` is empty in **`extra/docs.ts`**).

## Examples

**Basic range with bubbles:**

```html
<hb-range-slider min="0" max="100" minval="20" maxval="80" withbubbles="yes"></hb-range-slider>
```

**Narrow domain with initial marker** (matches the `withPositionValue` example in **`extra/docs.ts`**):

```html
<hb-range-slider
  min="10"
  max="25"
  minval="13"
  maxval="20"
  position_value="17"
  withbubbles="yes"
></hb-range-slider>
```

## License

Component metadata in **`extra/docs.ts`** lists **Apache-2.0** for package licensing information; see the referenced **`LICENSE.md`** in that context when distributing the package.

---

<a id="wc-searchbar"></a>

# `hb-searchbar` (searchbar)

**Category:** utilities  
**Tags:** utilities, search  
**Package:** `@htmlbricks/hb-searchbar`

## Overview

`hb-searchbar` is a Bulma-styled search control that lives in a shadow root. It renders either a single-line `<input type="search">` or a multiline `<textarea>`, an in-field submit (magnifier) button, and an optional suggestion dropdown driven by a `searchlist` you pass in.

Use it when you want a self-contained query field with debounced “search while typing”, optional fixed or fuzzy-matched suggestions, rich rows (icons, badges, tags, links), and explicit `search` / `clear` custom events for your host application.

## Custom element

```html
<hb-searchbar …></hb-searchbar>
```

## Attributes and properties

Web component attributes are **strings**. For structured data (`searchlist`, `input_info`), pass **JSON** as a string attribute (or set the corresponding property with a parsed object in JavaScript). Boolean-like flags use **`yes`** / **`no`** where noted.

| Name | Required | Description |
|------|----------|-------------|
| `value` | Yes | Current query text. |
| `id` | No | Host id string; included on `clear` event detail. |
| `style` | No | Inline style string for the host (optional). |
| `searchlabel` | No | Placeholder text. Default: `"Search"`. |
| `minlength` | No | Minimum query length (number) before input-driven search runs and before the preview can open; default `0`. Compares `value.length` to this number. |
| `searchlist` | No | Array of suggestions (`TSearchListItem[]`). From HTML, use a JSON string. Parsed at runtime if a string is received. |
| `input_info` | No | Single `TSearchListItem` shown when its `text` equals the current `value` (JSON string from HTML). |
| `textarea` | No | Multiline mode: `"yes"`, `"true"`, or `""` (empty string) enables `<textarea>`; otherwise a search `<input>` is used. |
| `disable_preview` | No | When `"yes"`, `"true"`, or `"on"`, the suggestion dropdown does not open. Any other value (including `"no"`, `"false"`, `"off"`) allows preview when other conditions are met. |
| `disabled` | No | `"yes"` disables the field and action buttons; `"no"` or omitted leaves them enabled. |

### `TSearchListItem` shape

Each suggestion (and `input_info`) uses:

| Field | Type | Notes |
|-------|------|--------|
| `id` | string | Required stable id for the row. |
| `text` | string | Primary label. |
| `url` | string | Optional link target for the row. |
| `icon` | string | Optional Bootstrap Icons icon name (without `bi-` prefix in markup; the component uses `bi bi-{icon}` classes). |
| `number_of_results` | number | Optional count display. |
| `fixed` | boolean | When `true`, the row stays visible regardless of the current filter. |
| `tags` | `TSearchListItemTag[]` | Optional chips: `text`, `color`, `icon`. |
| `badge` | `TSearchListItemBadge` | Optional badge: `text`, `color`, `icon`. |

Tags and badges use Bulma-style color names (for example `primary`, `success`, `danger`) as consumed by the row renderer.

### How the suggestion list behaves

- **Filtering:** Non-fixed items are shown when their `text` contains the current `value` (case-insensitive substring match). Items with `fixed: true` are always included.
- **Opening:** The dropdown is shown when preview is not disabled, the query length is **greater than** `minlength`, and the field is considered “open” after interaction (typing opens it; choosing an action may close it depending on the trigger).
- **Debounced input search:** After input changes, a **500 ms** debounce runs; if the value is non-empty and satisfies `minlength`, a `search` event fires with `by: "input"`. Enter in the field triggers submit-style search (`by: "button"`) without waiting for the timer.
- **Submit button:** Clicks dispatch `search` with `by: "button"`.
- **Picking a suggestion:** Dispatches `search` with `by: "searchlist"` and updates the field text to the chosen item’s `text`.
- **Clearing:** For the native search input, clearing via the browser’s search clear control dispatches `clear` with `{ id }`. In textarea mode, an explicit clear button empties the value, dispatches `clear`, and closes the dropdown. Clicks outside the host close the dropdown without clearing the value.

## Events

Listen with `addEventListener` on the host element.

| Event | Detail (TypeScript `Events`) |
|-------|-------------------------------|
| `search` | `{ input: string; by: "button" \| "input" \| "searchlist"; item?: TSearchListItem }` — when the user picks a suggestion, **`item`** is the matched list entry; for **`by: "input"`** or **`"button"`**, **`item`** is omitted. |
| `clear` | `{ id: string }` — the host **`id`** attribute (empty string if unset). |

## Styling

### Bulma CSS variables

The component uses Bulma layout and form patterns inside the shadow tree. Theme it with **`--bulma-*`** variables on a wrapping scope or `:root` / `body` so they apply to `:host`. Relevant groups include:

- **Accent and focus:** `--bulma-primary`, `--bulma-danger`
- **Surfaces and chrome:** `--bulma-background`, `--bulma-scheme-main`, `--bulma-border`, `--bulma-border-weak`
- **Typography:** `--bulma-text`, `--bulma-text-strong`, `--bulma-input-placeholder-color`
- **Shape and stacking:** `--bulma-radius`, `--bulma-radius-small`, `--bulma-dropdown-content-z`

See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) for the full variable system.

### Component-specific variables (`--hb-searchbar-*`)

These override frosted layers and panel backgrounds (defaults use `color-mix()` with Bulma tokens):

- `--hb-searchbar-host-background` — host background at rest  
- `--hb-searchbar-host-background-focus` — host background while focused  
- `--hb-searchbar-input-strip-background` — input row strip  
- `--hb-searchbar-dropdown-panel-background` — open suggestion panel  
- `--hb-searchbar-input-glass-blur` — backdrop blur behind the focused field  
- `--hb-searchbar-input-glass-saturate` — backdrop saturation factor  

Authoritative descriptions and defaults are listed in `extra/docs.ts` (`styleSetup.vars`).

### CSS shadow parts

| Part | Targets |
|------|---------|
| `dropdown-menu` | Open suggestion list panel |
| `search-input` | Native `<input>` or `<textarea>` |
| `search-input-glass` | Frosted layer behind the field while focused |
| `search-submit` | In-field magnifier / submit control |

Example:

```css
hb-searchbar::part(search-submit) {
  color: var(--bulma-primary);
}
```

## Icons and assets

The component loads **Bootstrap Icons** from jsDelivr in the shadow document head (`bootstrap-icons.css`). Icon names in `searchlist` / `input_info` refer to that font set.

## Slots

None (`htmlSlots` is empty).

## Usage notes

- **Autocomplete:** The control sets `autocomplete="off"`. Browsers may still offer their own UI; behavior varies.
- **Storybook:** Controls may expose names like `initial_value` for demos; the runtime contract for authors is `types/webcomponent.type.d.ts` (`value` is the live bound field).
- **Structured props from HTML:** Always serialize `searchlist` and `input_info` as JSON strings on attributes; never rely on passing raw objects through HTML attributes.

## Examples

### Minimal search field

```html
<hb-searchbar
  value=""
  searchlabel="Search"
></hb-searchbar>
```

### Suggestions with JSON `searchlist`

```html
<hb-searchbar
  value=""
  searchlabel="Try a suggestion"
  searchlist='[{"id":"1","text":"Documentation"},{"id":"2","text":"API reference"}]'
></hb-searchbar>
```

### Multiline textarea mode

```html
<hb-searchbar
  value=""
  searchlabel="Describe your query"
  textarea="yes"
  searchlist='[{"id":"1","text":"Example prompt"}]'
></hb-searchbar>
```

### Disable preview, require two characters before input search

```html
<hb-searchbar
  value=""
  disable_preview="yes"
  minlength="2"
></hb-searchbar>
```

### JavaScript: listen for `search` and `clear`

```javascript
const el = document.querySelector("hb-searchbar");

el.addEventListener("search", (e) => {
  const { input, by } = e.detail;
  console.log("search", { input, by });
});

el.addEventListener("clear", (e) => {
  console.log("cleared", e.detail.id);
});
```

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-shop-item-cell"></a>

# `hb-shop-item-cell` (shop-item-cell)

**Category:** commerce  
**Tags:** commerce, product  
**Package:** `@htmlbricks/hb-shop-item-cell`

## Overview

`hb-shop-item-cell` is a vertical **shop product card** web component: a top **image** area with an optional **badge**, then a **Bulma**-styled `card` body (title, subtitle, description, star rating, reviews link, prices) and an optional **footer** row. The content model mirrors **`hb-shop-item-row`** so you can reuse the same attributes and slot names across grid and list layouts.

Star icons use **Bootstrap Icons** SVGs embedded in the shadow tree; layout and typography follow **Bulma 1.x** (`card`, `image`, `content`, `title`, `subtitle`, `tag`, flex helpers).

## Custom element

```html
<hb-shop-item-cell …></hb-shop-item-cell>
```

## Layout behavior

- **Image** — The image block (linked figure + optional badge) renders only when the `img` attribute is non-empty. The image link uses `url` when set; otherwise the anchor has no effective destination (treat empty `url` as “image only”).
- **Title** — The main title is a link when `url` is provided; same `url` is used for the image link.
- **Rating row** — The row appears when there is a non-zero **`rating`**, or when **`reviews`** / the **`reviews`** slot supplies content. Star icons render only when **`rating`** is truthy (non-zero in practice). After the stars, the numeric score uses the **`rating`** slot or the `rating` value as text.
- **Stars** — The component renders **`ratingscale`** star positions (default in examples is `5`; you can use e.g. `10` for a 10-point scale). Values support **fractional** ratings for half-star display.
- **Reviews** — The text in parentheses is linked with **`reviewsurl`** when that attribute is set.
- **Prices** — Current **`price`** (bold) and optional struck-through **`regularprice`**; either can be driven by attributes or slots.
- **Footer** — Optional **`footer`** row at the bottom of the card.

## Attributes

Web component **attribute names are `snake_case`**. In HTML, pass **strings** only: use `""` for empty text fields, and encode **numbers** as their string form (e.g. `"4"`, `"5"`).

| Attribute | Required | Type / encoding | Description |
|-----------|----------|-----------------|-------------|
| `id` | No | string | Optional host id on the root element. |
| `style` | No | string | Optional inline style string (see component typings). |
| `img` | Yes* | string | Product image URL. If empty, the image (and image badge) section is omitted. |
| `url` | Yes* | string | Destination for the image and title links; empty disables a useful `href`. |
| `badge` | Yes* | string | Short label on the image (rounded tag). Hidden when empty and the `badge` slot is unused. |
| `title` | Yes* | string | Primary product name (linked with `url`). |
| `subtitle` | Yes* | string | Muted secondary line under the title. |
| `text` | Yes* | string | Body / marketing copy in the card content. |
| `rating` | Yes* | number as string | Score used for stars and numeric display; `0` or empty suppresses the star strip (reviews-only row can still show). |
| `ratingscale` | Yes* | number as string | Number of star icons (scale length), e.g. `"5"` or `"10"`. |
| `reviews` | Yes* | string | Text inside the linked parentheses next to the rating row. |
| `reviewsurl` | Yes* | string | URL for the reviews link. |
| `price` | Yes* | string | Current or sale price (bold). |
| `regularprice` | Yes* | string | Compare-at / list price (struck through beside `price`). |
| `footer` | Yes* | string | Footer strip content below the main card body. |

\*The TypeScript `Component` type lists these fields for authoring; in HTML you still pass **strings** (including empty `""` where something is unused).

## Slots

Each named slot falls back to the **same-named attribute** when the slot has no content (see `extra/docs.ts`).

| Slot | Role |
|------|------|
| `badge` | Label over the product image; default: `badge`. |
| `title` | Main product name in the title link; default: `title`. |
| `subtitle` | Muted line under the title; default: `subtitle`. |
| `text` | Description in the card body; default: `text`. |
| `rating` | Text shown after the stars; default: string form of `rating`. |
| `reviews` | Text inside the parentheses; default: `reviews` (link uses `reviewsurl`). |
| `price` | Bold current price; default: `price`. |
| `regularprice` | Struck-through compare price; default: `regularprice`. |
| `footer` | Full-width footer row; default: `footer`. |

There are no documented **`::part`** hooks for this package (`styleSetup.parts` is empty).

## Events

No custom events are declared in `types/webcomponent.type.d.ts`.

## Theming (CSS custom properties)

The card respects **Bulma CSS variables** on the host page (for example on `body` or `:root`). Typical variables used by this layout are listed in `extra/docs.ts` / `styleSetup`:

| Variable | Role |
|----------|------|
| `--bulma-primary` | Accent / interactive emphasis. |
| `--bulma-card-background-color` | Card shell background (image area, content, footer). |
| `--bulma-text` | Primary text (titles, body, numeric rating, bold price). |
| `--bulma-text-weak` | Muted text (subtitle, reviews line, struck price). |
| `--bulma-border` | Card frame and separators. |
| `--bulma-radius` | Corner rounding for card and media. |
| `--bulma-link` | Linked title and reviews link color. |

See [Bulma: CSS variables](https://bulma.io/documentation/features/css-variables/) for the full variable system.

## Examples

### Full card (image, badge, rating, sale price, footer)

```html
<hb-shop-item-cell
  img="https://placehold.co/320x240/363636/ffffff?text=Product"
  url="https://example.com/p/1"
  badge="New"
  title="Wireless earbuds"
  subtitle="ANC · 32h battery"
  text="Compact charging case and IPX4 splash resistance."
  rating="4"
  ratingscale="5"
  reviews="128 reviews"
  reviewsurl="https://example.com/p/1#reviews"
  price="€79.00"
  regularprice="€99.00"
  footer="Free returns within 30 days"
></hb-shop-item-cell>
```

### Minimal listing (image, title, price)

```html
<hb-shop-item-cell
  img="https://placehold.co/320x240/363636/ffffff?text=Product"
  url=""
  badge=""
  title="Mug — ceramic"
  subtitle=""
  text=""
  rating="0"
  ratingscale="5"
  reviews=""
  reviewsurl=""
  price="€14.00"
  regularprice=""
  footer=""
></hb-shop-item-cell>
```

### Ten-point rating scale

```html
<hb-shop-item-cell
  img="https://placehold.co/320x240/363636/ffffff?text=Product"
  url="https://example.com/p/2"
  badge=""
  title="Desk lamp"
  subtitle="LED dimmable"
  text=""
  rating="9"
  ratingscale="10"
  reviews="See all"
  reviewsurl="https://example.com/p/2#reviews"
  price="€45.00"
  regularprice=""
  footer=""
></hb-shop-item-cell>
```

## Related components

Use **`hb-shop-item-row`** when you need the same product fields in a horizontal row layout; attributes and slots are aligned between the two.

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-shop-item-row"></a>

# `hb-shop-item-row` (shop-item-row)

**Category:** commerce  
**Tags:** commerce, product  
**Package:** `@htmlbricks/hb-shop-item-row`

## Overview

Horizontal shop product row built as a Bulma **card** with a two-column layout: a narrow **thumbnail** column (optional image, optional badge tag) and a **main** column (linked title, muted subtitle, body text, star rating with numeric score, linked reviews line, current and compare-at prices, optional **card footer**). Content can come from **attributes** or from **named slots**; when a slot has no light DOM children, the component falls back to the matching string attribute.

Use this element in dense product lists, search results, or comparison tables where a wide, single-row preview reads better than a stacked card. For a vertical card with the same content model, see **`hb-shop-item-cell`**.

### Layout and behavior

- **Thumbnail:** The image and badge render only when `img` is non-empty. The image (and badge overlay) are wrapped in a link that uses `url` when it is set; otherwise the link has no `href`.
- **Title:** When `title` is set or the `title` slot has content, the title appears as a level-5 heading with a link that uses the same `url` rules as the image.
- **Rating row:** A rating block appears when `rating` is set, or when the `reviews` slot or `reviews` attribute has content. **Star icons** (Bootstrap Icons–style SVGs) render only when `rating` is truthy (for example, omit or use `"0"` to hide stars). The number of icons equals **`ratingscale`**. Stars support **full** and **half** fills from the numeric `rating` value. The numeric label after the stars uses the **`rating`** slot or attribute. The **reviews** text is shown in parentheses and wrapped in a link that uses `reviewsurl` when set.
- **Prices:** The current price is bold; **`regularprice`** is shown struck-through beside it when either the attribute or the `regularprice` slot provides content.
- **Footer:** An optional Bulma **card footer** spans the text column when `footer` or the `footer` slot has content.

## Custom element

```html
<hb-shop-item-row …></hb-shop-item-row>
```

## Attributes

Web component **attribute names use snake_case**. In plain HTML, **all attribute values are strings**. Use empty `""` for unused text fields where you still need the attribute present for tooling or consistency.

| Attribute | Required | Description |
| --- | --- | --- |
| `id` | No | Optional host id (forwarded to the inner card wrapper). |
| `style` | No | Optional; present on the `Component` typing for host styling. |
| `img` | Yes* | Image URL. If empty, the thumbnail column does not show an image or badge wrapper. |
| `url` | Yes* | Product URL for the image and title links. |
| `badge` | Yes* | Short label (for example sale text); shown in a **danger** rounded tag over the image when combined with `img`. |
| `title` | Yes* | Primary product name. |
| `subtitle` | Yes* | Muted secondary line under the title. |
| `text` | Yes* | Main descriptive copy. |
| `rating` | Yes* | Numeric score for stars and the numeric label; pass as a string (for example `"4.5"`). Falsy values hide the star row but reviews-only content can still show. |
| `ratingscale` | Yes* | Number of star icons; pass as a string (for example `"5"`). |
| `reviews` | Yes* | Text inside the linked parentheses (for example review count). |
| `reviewsurl` | Yes* | URL for the reviews parentheses link. |
| `price` | Yes* | Current or promotional price string. |
| `regularprice` | Yes* | Compare-at price string (struck through when set). |
| `footer` | Yes* | Optional footer line. |

\*Marked “Yes” because the published **`Component`** interface lists them as required; in markup, supply **`""`** for fields you do not use.

## Events

No custom events are declared in `types/webcomponent.type.d.ts` (`Events` is `{}`).

## Slots

Each named slot falls back to the **same-named attribute** when the slot has no content (see `extra/docs.ts`).

| Slot | Purpose |
| --- | --- |
| `badge` | Optional label over the row thumbnail inside a rounded tag; defaults to the `badge` attribute when the slot is empty. |
| `title` | Main product name in the title link beside the image; defaults to the `title` attribute when the slot is empty. |
| `subtitle` | Secondary line under the title (muted); defaults to the `subtitle` attribute when the slot is empty. |
| `text` | Product description or pitch in the main column; defaults to the `text` attribute when the slot is empty. |
| `rating` | Numeric score shown after the star icons when `rating` is set; defaults to the `rating` attribute as plain text. |
| `reviews` | Review count or label inside the linked parentheses next to the stars; defaults to the `reviews` attribute (the link uses `reviewsurl`). |
| `price` | Current or sale price (bold, baseline row); defaults to the `price` attribute when the slot is empty. |
| `footer` | Optional footer strip under the text column (still inside the card); defaults to the `footer` attribute when the slot is empty. |
| `regularprice` | List or compare-at price shown struck-through beside `price`; defaults to the `regularprice` attribute when the slot is empty. |

There are no documented **`::part`** hooks for this package (`styleSetup.parts` is empty).

## Styling (Bulma)

Cherry-picked **Bulma 1.x** Sass (`card`, `columns`, `image`, `content`, `title`, `tag`, helpers) is scoped to **`:host`** via `styles/bulma.scss`. Theme tokens are the public **`--bulma-*`** variables; set them on `body`, `:root`, or any ancestor so they inherit into the shadow tree. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/) and `extra/docs.ts` for the full `styleSetup` list.

| Variable | Purpose |
| --- | --- |
| `--bulma-primary` | Accent and interactive highlights (theme setup). |
| `--bulma-text` | Body and title text. |
| `--bulma-card-background-color` | Background for the horizontal card shell (thumbnail column, text column, footer). |
| `--bulma-border` | Row frame border (component SCSS supplies a fallback if unset). |
| `--bulma-shadow` | Row frame shadow (fallback in SCSS if unset). |
| `--bulma-text-weak` | Subtitle, strikethrough context, and secondary lines. |
| `--bulma-link` | Linked title and reviews URL color. |
| `--bulma-radius` | Card corners for the horizontal row shell. |

The host also uses **`--bulma-block-spacing`** (with a built-in fallback) for default vertical **margin** between rows when the element is stacked in a list.

## Usage notes

- **Slots and attributes** share the same names: each slot’s default content is the corresponding attribute when the slot is empty.
- **Storybook** exposes an `action` control for demos; it is **not** part of the `Component` props type.
- **Internationalization:** `extra/docs.ts` does not register `i18n` entries for this package; translate strings in the host app before passing them in.

## Examples

### Full row

```html
<hb-shop-item-row
  img="https://placehold.co/200x150"
  url="/products/running-shoes"
  badge="-15%"
  title="Running shoes"
  subtitle="Unisex · sizes 36–48"
  text="Lightweight mesh upper and cushioned sole for daily miles."
  rating="5"
  ratingscale="5"
  reviews="2.4k reviews"
  reviewsurl="/products/running-shoes#reviews"
  price="€102.00"
  regularprice="€120.00"
  footer="Ships in 24h"
></hb-shop-item-row>
```

### Title and price only

```html
<hb-shop-item-row
  img="https://placehold.co/120x90"
  url=""
  badge=""
  title="Gift card €25"
  subtitle=""
  text=""
  rating=""
  ratingscale="5"
  reviews=""
  reviewsurl=""
  price="€25.00"
  regularprice=""
  footer=""
></hb-shop-item-row>
```

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-sidebar-cards-navigator"></a>

# `hb-sidebar-cards-navigator` (sidebar-cards-navigator)

**Category:** layout  
**Tags:** layout, navigation  
**Package:** `@htmlbricks/hb-sidebar-cards-navigator`

## Overview

`hb-sidebar-cards-navigator` is a stacked sidebar layout that drives navigation from a JSON `panels` tree. It resolves one **active panel**, renders a compact **`hb-navbar`** header for that panel, then lists each card’s rows as **`hb-sidenav-button`** entries. Row clicks emit a single **`itemClick`** custom event with full context (panel, card, row). Rows may optionally change the active panel via **`switchToPanelId`**.

Nested **`hb-navbar`** and **`hb-sidenav-button`** are registered at runtime (same package version as this component). Bootstrap Icons are loaded from jsDelivr in the component head for row icons.

## Active panel resolution

The visible panel is chosen in this order:

1. If an internal **`switchToPanelId`** state is set (after a row with `switchToPanelId` was clicked), the panel whose **`id`** matches that value is shown.
2. Otherwise the first panel with **`main: true`** is used.
3. If none is marked `main`, the first panel **without** **`parentPanelId`** is used.

If no panel matches, nothing is rendered inside the host.

## UI behavior

- **`hb-navbar`**: **`companybrandname`** is bound to the active panel’s **`title`**. The **`nav-switcher`** slot shows a **list** icon for root-level panels and a **back** arrow when **`parentPanelId`** is set. **`navmenuswitch`** switches the active panel to **`parentPanelId`** when a parent exists (back navigation).
- **Cards**: For each card on the active panel, if **`rows`** is non-empty, each row becomes one **`hb-sidenav-button`** with **`navlink`** serialized from the row (`text`, `key`, optional `bootstrapIcon`, optional `badge`).
- **Empty cards**: Cards with no rows or an empty **`rows`** array are skipped (no placeholder block).

## Custom element

```html
<hb-sidebar-cards-navigator …></hb-sidebar-cards-navigator>
```

## Attributes (snake_case; string values from HTML)

Web component attributes are strings. Pass structured data as a **JSON string** on **`panels`**.

| Attribute | Required | Description |
|-----------|----------|-------------|
| **`id`** | No | Host identifier; echoed on emitted **`itemClick`** detail when set. |
| **`style`** | No | Standard inline host styles (string). |
| **`panels`** | No | JSON string representing **`Panel[]`** (see data model below). If the runtime value is a string, the component parses it with **`JSON.parse`**; invalid JSON is logged and parsing is skipped. |

### Boolean and numbers in JSON

Inside the **`panels`** JSON, use normal JSON booleans and numbers (e.g. **`"main": true`**). Those rules apply to the JSON payload, not to HTML boolean attributes on this tag.

## Data model (`panels` JSON)

Types live in **`types/webcomponent.type.d.ts`**. Shapes below match authoring / TypeScript names; serialize the whole array as one string for the **`panels`** attribute.

### `Panel`

| Field | Type | Notes |
|-------|------|--------|
| **`id`** | string | Panel identifier; used with **`switchToPanelId`** and hierarchy. |
| **`title`** | string (optional) | Shown in **`hb-navbar`**. |
| **`icon`** | string (optional) | Available on the type; not read directly by this host’s markup. |
| **`sort`** | number (optional) | Available on nested card types; not used for panel ordering in the current template. |
| **`parentPanelId`** | string (optional) | If set, navbar shows back and **`navmenuswitch`** returns to this parent panel id. |
| **`main`** | boolean (optional) | When **`true`**, this panel is preferred when no **`switchToPanelId`** override is active. |
| **`cards`** | array | List of **`CardNavigator`** objects. |

### `CardNavigator` (card under a panel)

| Field | Type | Notes |
|-------|------|--------|
| **`id`** | string | Card id; used when resolving clicks. |
| **`title`** | string (optional) | Card-level metadata (not rendered as a separate heading in the current template). |
| **`icon`** | string (optional) | Metadata. |
| **`sort`** | number (optional) | Metadata. |
| **`rows`** | **`CardRow[]`** | Rows rendered as **`hb-sidenav-button`** instances. |

### `CardRow`

| Field | Type | Notes |
|-------|------|--------|
| **`key`** | string | Stable row id; matched on click. |
| **`text`** | string | Primary label (passed to **`hb-sidenav-button`** **`navlink`**). |
| **`bootstrapIcon`** | string (optional) | Bootstrap Icons name without the **`bi-`** prefix (e.g. **`house-door`**). |
| **`badge`** | object (optional) | **`text`**, optional **`class`**, **`classcolor`** — forwarded to **`navlink`**. |
| **`subtext`** | string (optional) | On the type for richer payloads; **not** forwarded to **`navlink`** in the current implementation. |
| **`value`** | string, number, or boolean (optional) | Available in **`itemClick`** detail’s **`row`**; not sent to **`navlink`**. |
| **`selected`** | boolean (optional) | Available on **`row`** in **`itemClick`**; not sent to **`navlink`**. |
| **`type`** | **`"switch"` \| `"range"` \| `"radio"` \| `"checkbox"` \| `"button"`** (optional) | Available on **`row`** in **`itemClick`**; not sent to **`navlink`**. |
| **`switchToPanelId`** | string (optional) | After **`itemClick`**, the navigator switches the active panel to this **`id`**. |

## Events

### `itemClick`

Fired when the user activates a row (via **`hb-sidenav-button`** **`pageChange`**). **`event.detail`** matches **`CardRowSelected`**:

| Field | Meaning |
|-------|---------|
| **`id`** | Host **`id`** prop value (may be empty). |
| **`panel`** | Active panel fields **without** the **`cards`** array (plain object clone). |
| **`card`** | Selected card fields **without** the **`rows`** array. |
| **`row`** | The full **`CardRow`** that was clicked. |

If **`row.switchToPanelId`** is set, the internal active panel updates after the event is dispatched.

## Styling (Bulma)

The component uses Bulma layout utilities and theme variables. Prefer **`--bulma-*`** on **`body`**, **`:root`**, or a parent that pierces into shadow DOM only if your bundler setup allows; otherwise set variables on elements that apply to this tree per your integration.

Documented variables (see **`extra/docs.ts`** / **`styleSetup`**):

| Variable | Role |
|----------|------|
| **`--bulma-block-spacing`** | Vertical gap between stacked groups of rows. |
| **`--bulma-text`** | Default text (rows, navbar). |
| **`--bulma-text-strong`** | Strong headings (titles). |
| **`--bulma-link`** | Interactive accents from nested navigation components. |
| **`--bulma-scheme-main`** | Surfaces behind stacks. |

Reference: [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

## Slots and CSS parts

- **Slots:** none on this host.
- **`::part`:** none declared for this host; nested **`hb-*`** children may expose their own documented parts.

## Dependencies

Declared in **`extra/docs.ts`**:

- **`hb-navbar`** (which depends on **`hb-dropdown-simple`**)
- **`hb-sidenav-button`**

Ensure these custom elements are defined (for example via **`@htmlbricks/hb-bundle`** or the matching standalone packages) before using this navigator.

## Examples

### Minimal single panel

```html
<hb-sidebar-cards-navigator
  panels='[{"id":"home","title":"Home","cards":[{"id":"menu","rows":[{"key":"dash","text":"Dashboard","bootstrapIcon":"speedometer2"}]}]}]'
></hb-sidebar-cards-navigator>
```

### Main panel, child panel with back, and panel switch from a row

```html
<hb-sidebar-cards-navigator
  id="app-nav"
  panels='[
    {
      "id": "root",
      "main": true,
      "title": "Overview",
      "cards": [
        {
          "id": "account",
          "title": "Account",
          "rows": [
            { "key": "profile", "text": "Profile", "bootstrapIcon": "person" },
            {
              "key": "advanced",
              "text": "Advanced",
              "bootstrapIcon": "gear",
              "switchToPanelId": "advanced-panel"
            }
          ]
        }
      ]
    },
    {
      "id": "advanced-panel",
      "parentPanelId": "root",
      "title": "Advanced",
      "cards": [
        {
          "id": "opts",
          "rows": [
            { "key": "security", "text": "Security", "bootstrapIcon": "shield-lock" }
          ]
        }
      ]
    }
  ]'
></hb-sidebar-cards-navigator>
```

### Listening from JavaScript

```js
const el = document.querySelector("hb-sidebar-cards-navigator");
el.addEventListener("itemClick", (e) => {
  const { panel, card, row, id } = e.detail;
  console.log(panel.id, card.id, row.key, id);
});
```

## TypeScript

Authoring types for this package: **`types/webcomponent.type.d.ts`** — **`Component`**, **`Panel`**, **`CardNavigator`**, **`CardRow`**, **`CardRowSelected`**, and **`Events`** (including **`itemClick`** detail typing for Svelte / wrappers).

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-sidebar-desktop"></a>

# `hb-sidebar-desktop` (sidebar-desktop)

**Category:** layout  
**Tags:** layout, navigation  
**Package:** `@htmlbricks/hb-sidebar-desktop`

## Overview

`hb-sidebar-desktop` is a fixed-width desktop sidebar shell: optional company branding (logo URI and title), a scrollable navigation list rendered with nested **`hb-sidenav-link`** elements, optional section **groups**, an optional **language** dropdown, and a **Bulma-aligned theme** control (light, dark, or system). It is styled with Bulma tokens and Bootstrap Icons (loaded for menu icons).

The host drives the active route with **`navpage`** (page key) and supplies **`navlinks`** (and optionally **`groups`**). When the user selects a page, a child link, or a theme/language option, the component dispatches bubbling, composed custom events so the host can update routing, persist theme, and sync language.

## Dependency

This package expects **`hb-sidenav-link`** to be available (same version family as your bundle). Each row is a `hb-sidenav-link` with `navlink` passed as a JSON string and `navpage` mirrored from the sidebar.

## Slots

| Slot | Description |
|------|-------------|
| **`test`** | Optional light-DOM content at the very top of the sidebar shell, before the padded inner column (outside brand, nav, and controls). |
| **`header`** | Optional content inside the brand title row, before the logo and company title (same row as the `header` CSS part). |
| **`footer`** | Optional footer region below the nav list and the theme/language toolbar. Rendered only when **`enablefooter`** is **`yes`** (default slot content in the implementation is placeholder text until you override it). |

## CSS parts

| Part | Description |
|------|-------------|
| **`header`** | Top chrome in the brand title row (the surface that wraps the `header` slot and brand). |

## Styling (Bulma CSS variables)

Public theming uses **`--bulma-*`** on the host document or on ancestors; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/). Documented variables match `extra/docs.ts` / `styleSetup`:

| Variable | Role |
|----------|------|
| `--hb-layout-sidebar-background` | From **`hb-layout`** / page: sidebar shell only (highest priority). |
| `--hb-sidebar-background-color` | Set on **`hb-sidebar-desktop`**: between layout override and Bulma scheme surfaces. |
| `--bulma-scheme-main-bis` | Default sidebar host background (Bulma secondary scheme). |
| `--bulma-scheme-main-ter` | Tertiary fallback when **`bis`** is unset. |
| `--bulma-text` | Default labels and menu copy. |
| `--bulma-link` | Active row accent and theme/language controls. |
| `--bulma-block-spacing` | Brand block spacing and vertical rhythm inside the shell. |
| `--bulma-border` | Section separators / hairlines. |

The **`hb-navbar`** strip uses **`--hb-layout-navbar-background`**, **`--hb-navbar-background-color`**, and **`--bulma-navbar-background-color`** instead; the sidebar host does **not** read the navbar background token by default so the two surfaces can diverge.

**Icons:** pass Bootstrap Icons **suffix only** on each `INavLink` (for example `house-door`, not `bi` or `bi-`). The component loads the Bootstrap Icons font stylesheet in the shadow tree.

## Navigation model

### `navlinks`

`navlinks` is an array of **`INavLink`** objects (or a **JSON string** that parses to that array). Each item includes:

- **`key`** — stable page identifier (emitted on `pageChange` and matched against **`navpage`** for selection).
- **`label`** — visible text.
- **`icon`** — optional Bootstrap Icons suffix.
- **`group`** — optional string key; used to place the item under a grouped section (see below).
- **`badge`** — optional `{ text, class?, classcolor? }`.
- **`subLinks`** — optional nested links (handled by `hb-sidenav-link`).

### `groups`

Optional **`groups`** is `{ key: string; label: string }[]` (or a JSON string). When **`groups`** is non-empty, the sidebar renders each group’s **`label`** as a Bulma **`menu-label`**, then every **`navlinks`** item whose **`group`** equals that **`key`**.

If some **`navlinks`** entries have a **`group`** that is **not** listed in **`groups`**, the component **auto-generates** sections: it derives distinct `group` values from those links and uses the raw **`group`** string as the section heading label.

Links **without** a **`group`** are listed first, above any grouped sections.

## Theme behavior

- **`enablethemeswitch`**: when **`yes`**, three buttons appear: light, dark, and auto (system).
- Choosing a mode updates internal state, applies **Bulma document hooks** on **`document.documentElement`** and **`document.body`** (`data-theme`, `theme-light` / `theme-dark` classes, cleared for auto), and dispatches **`themeChange`** with `{ mode: "light" | "dark" | "auto" }`.
- **`themepreference`**: when set to **`light`**, **`dark`**, or **`auto`**, the sidebar treats it as a **controlled** value and syncs hooks to match. If it is unset or invalid on mount, the component reads the current preference from the document (`data-theme` / theme classes).
- The host may ignore **`themeChange`** and only drive **`themepreference`**, or combine both (for example persist `themeChange` then set **`themepreference`**).

## Language behavior

- **`i18nlanguages`**: array of `{ code, label }` or a **JSON string** (array of objects or of string codes; string codes use the code as the label).
- **`i18nlang`**: active BCP-47 / app language code; should match a **`code`** in **`i18nlanguages`** when you rely on the “active” state in the dropdown. When the user picks a language, the sidebar updates its own UI immediately and sets **`document.documentElement.lang`**. If **`i18nlang`** was empty (uncontrolled usage), it also sets the **`i18nlang`** attribute on **`hb-sidebar-desktop`** so plain HTML can read the current code.
- The language dropdown appears when there is at least one parsed option. Picking an option dispatches **`languageChange`** with `{ code: string }`. On **mount**, the component emits the same event once with the **effective** language (after the same rules). The host should still update **`i18nlang`** on **`hb-layout`** (or equivalent) when it needs shell-wide copy; the sidebar applies local + document language as above even before the host reacts.

The bottom toolbar (language + theme) is shown when **`enablethemeswitch`** is **`yes`** **or** there is at least one language option.

### Toolbar density (theme + language)

Bulma’s **small** icon size uses **root `rem`**, so if your page sets a large **`html { font-size }`**, the sun/moon/auto icons can look much bigger than the button text. The sidebar scopes the toolbar with a slightly smaller **`em`** context and overrides icon/body sizes. You can tune density with **`--hb-sidebar-toolbar-button-font-size`**, **`--hb-sidebar-toolbar-button-min-height`**, **`--hb-sidebar-toolbar-icon-size`**, and **`--hb-sidebar-toolbar-lang-min-inline`** on **`hb-sidebar-desktop`** (see **`extra/docs.ts`** / catalog **`styleSetup.vars`**).

## Custom element

```html
<hb-sidebar-desktop …></hb-sidebar-desktop>
```

## Attributes and properties (snake_case)

Web component **attributes** are strings. Booleans use **`yes`** / **`no`** where noted. Objects and arrays are typically passed as **JSON strings** from HTML, or as JS properties.

| Name | Description |
|------|-------------|
| **`id`** | Optional element id. |
| **`companylogouri`** | Optional image URL for the brand logo (shown when **`companytitle`** is set and a URI is provided). |
| **`companytitle`** | Optional company or product title next to the logo. |
| **`navpage`** | Active page **`key`**; forwarded to each `hb-sidenav-link`. |
| **`navlinks`** | JSON string or array: list of **`INavLink`** entries. |
| **`groups`** | Optional JSON string or array: `{ key, label }[]` for explicit section headings and membership. |
| **`enablefooter`** | **`yes`** (default) shows the footer chrome and the **`footer`** slot; **`no`** or **`false`** hides it. |
| **`enablethemeswitch`** | **`yes`** (default) shows the theme control; **`no`** hides it (language-only toolbar still possible if languages are set). |
| **`themepreference`** | Optional controlled theme: **`light`**, **`dark`**, or **`auto`**. |
| **`i18nlang`** | Optional current language code for the selector. |
| **`i18nlanguages`** | Optional JSON string or array of `{ code, label }` (or string codes). |

The authoring **`Component`** type also includes **`style`**, **`cookielawallowdecline`**, **`cookielawlanguage`**, **`cookielawuri4more`**, and **`single_screen`** for catalog alignment. They are **not** read by the current `component.wc.svelte` implementation; treat them as reserved unless a future version wires them in.

## Events

All events **`bubble`** and are **`composed`** (usable from the light DOM).

| Event | `detail` |
|-------|----------|
| **`pageChange`** | `{ page: string }` — selected nav **`key`**. |
| **`themeChange`** | `{ mode: "light" \| "dark" \| "auto" }` — user-chosen theme; host may persist and/or set **`themepreference`**. |
| **`languageChange`** | `{ code: string }` — effective language on **mount** and when the user picks; host may set **`i18nlang`** on the shell. The sidebar also updates **`document.documentElement.lang`** and its own selector; with an empty initial **`i18nlang`**, it sets the **`i18nlang`** attribute on the host element. |

## Examples

### Minimal HTML

```html
<hb-sidebar-desktop
  companytitle="Acme"
  navpage="home"
  navlinks='[{"label":"Home","key":"home","icon":"house-door"}]'
></hb-sidebar-desktop>
```

### Grouped navigation (explicit `groups`)

```html
<hb-sidebar-desktop
  navpage="settings"
  groups='[{"key":"admin","label":"Administration"},{"key":"stats","label":"Statistics"}]'
  navlinks='[
    {"label":"Home","key":"home","icon":"house-door"},
    {"label":"Settings","key":"settings","icon":"gear","group":"admin"},
    {"label":"Users","key":"users","icon":"people-fill","group":"admin"},
    {"label":"Stats","key":"stats","icon":"graph-up","group":"stats"}
  ]'
></hb-sidebar-desktop>
```

### Theme + language

```html
<hb-sidebar-desktop
  navpage="home"
  enablethemeswitch="yes"
  themepreference="auto"
  i18nlang="en"
  i18nlanguages='[
    {"code":"en","label":"English"},
    {"code":"it","label":"Italiano"}
  ]'
  navlinks='[{"label":"Home","key":"home","icon":"house-door"}]'
></hb-sidebar-desktop>
```

### JavaScript property API

```js
const el = document.querySelector("hb-sidebar-desktop");
el.navlinks = [
  { label: "Home", key: "home", icon: "house-door" },
  { label: "Settings", key: "settings", icon: "gear", group: "admin" },
];
el.navpage = "home";
el.addEventListener("pageChange", (e) => {
  console.log(e.detail.page);
});
```

## Related components

Navigation shape and icon rules align with other layout shells that reuse the same **`navlinks`** / **`hb-sidenav-link`** pattern (for example mobile off-canvas navigation in **`hb-offcanvas`**).

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-sidenav-button"></a>

# `hb-sidenav-button` (sidenav-button)

**Category:** layout  
**Tags:** layout, navigation  
**Package:** `@htmlbricks/hb-sidenav-button`

## Overview

`hb-sidenav-button` is a single **sidebar navigation row**: a full-width ghost Bulma button inside a list item (`<li>`). It is meant to be used in stacked menus (for example together with `hb-sidebar-cards-navigator`). Clicking the row dispatches a **`pageChange`** custom event whose detail is the link’s **`key`**, so the host can switch views or routes.

The row can show:

- a **Bootstrap Icons** glyph (when `bootstrapIcon` is set),
- a **Bulma tag** badge (when `badge` is set),
- or **control-style visuals** for `type` **`checkbox`**, **`radio`**, or **`switch`** (when there is no `badge`, using icon state from `value`).

Optional **`selected`** sets `aria-current="page"` and Bulma “selected” styling for the active row.

## Custom element

```html
<hb-sidenav-button …></hb-sidenav-button>
```

## Attributes

Web component **attributes are strings**. Pass complex data as a **JSON string** on `navlink`.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `navlink` | yes | JSON string describing one navigation item (see **Navlink object** below). If the runtime binding delivers a string, the component attempts `JSON.parse` on it. |

The TypeScript **`Component`** type in `types/webcomponent.type.d.ts` also lists optional **`id`** and **`style`**. Rendering is driven by **`navlink`**; you can still set a normal HTML **`id`** on `<hb-sidenav-button>` in the document when useful.

### Navlink object (`INavLink`)

All consumer-facing keys use the shape below (logical names; in JSON use these exact property names).

| Property | Type | Description |
|----------|------|-------------|
| `key` | string | Identifier for this row; sent as `pageChange` detail `page`. |
| `text` | string | Primary label (main text in the row). |
| `bootstrapIcon` | string (optional) | Bootstrap Icons name **without** the `bi-` prefix (for example `"house-door"` → `bi-house-door`). Shown when the row is not using the checkbox/radio-only layout, or as the leading icon for a generic row. |
| `subtext` | string (optional) | Present in typings for shared nav models; **this component does not render `subtext`**. |
| `badge` | object (optional) | `{ "text": string, "class"?: string, "classcolor"?: string }`. Renders a Bulma `tag` on the right. Bulma modifier classes can be passed via `class` (default-like behavior in markup uses `is-rounded`) and `classcolor` (defaults toward `is-light`). When `badge` is set, **`type`-based switch UI is not shown** (badge takes precedence for the trailing area). |
| `value` | string, number, or boolean (optional) | Drives **checkbox** / **radio** / **switch** visuals (`true` / filled vs off). |
| `selected` | boolean (optional) | When true, the row is styled as current and exposes `aria-current="page"`. |
| `type` | string (optional) | One of `"switch"`, `"range"`, `"radio"`, `"checkbox"`, `"button"`. **Rendered behavior:** `checkbox` and `radio` show check/circle icons when there is **no** `badge`; `switch` shows a toggle icon when there is **no** `badge`; `"button"` and `"range"` do not add extra control icons beyond `bootstrapIcon` / text. |

## Events

| Event | Detail | When |
|-------|--------|------|
| `pageChange` | `{ page: string }` | Every click on the row button. `page` is the navlink **`key`**. |

Listen in the DOM:

```js
el.addEventListener("pageChange", (e) => {
  console.log(e.detail.page);
});
```

## Styling

Styles use **Bulma** inside the shadow root. Theme the host page with public **`--bulma-*`** variables (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)). Variables referenced by this component are documented in `extra/docs.ts`; the main ones are:

| Variable | Role |
|----------|------|
| `--bulma-link` | Left accent stripe and mixes for the selected (`aria-current`) row. |
| `--bulma-link-text` | Text color on the selected row background mix. |
| `--bulma-scheme-main-bis` | Hover wash for rows. |
| `--bulma-radius` | Corner rounding for each row. |
| `--bulma-text-weak` | Muted tone where weak contrast is used (for example badge-related styling). |

**CSS part:** `li` — the outer list item wrapper (`::part(li)` from the light DOM).

**Icons:** rows use **Bootstrap Icons** (`bi-*`). The component loads the icon font (see `component.wc.svelte` / styles). For predictable versions in production pages, prefer pinning a known **Bootstrap Icons** URL instead of relying only on `@latest` where you control the document.

## Slots

None (`htmlSlots` is empty in `extra/docs.ts`).

## Integration notes

- In Storybook, **`nav_type`** is a control helper for picking a `type` value; at runtime the field lives on **`navlink.type`** inside the JSON `navlink` string.
- Wrap multiple `hb-sidenav-button` elements in a `<ul class="...">` in the light DOM if you need a semantic list; each instance renders one `<li part="li">`.

## HTML examples

**Plain icon + label**

```html
<hb-sidenav-button
  navlink='{"text":"Home","key":"home","bootstrapIcon":"house-door"}'
></hb-sidenav-button>
```

**Selected row**

```html
<hb-sidenav-button
  navlink='{"text":"Home","key":"home","bootstrapIcon":"house-door","selected":true}'
></hb-sidenav-button>
```

**Badge**

```html
<hb-sidenav-button
  navlink='{"text":"Messages","key":"messages","bootstrapIcon":"envelope","badge":{"text":"3","classcolor":"is-danger"}}'
></hb-sidenav-button>
```

**Switch (no badge)**

```html
<hb-sidenav-button
  navlink='{"text":"Notifications","key":"notifications","bootstrapIcon":"bell","type":"switch","value":true}'
></hb-sidenav-button>
```

**Checkbox / radio (no badge)**

```html
<hb-sidenav-button
  navlink='{"text":"Option A","key":"a","type":"checkbox","value":true}'
></hb-sidenav-button>
```

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-sidenav-link"></a>

# `hb-sidenav-link` (sidenav-link)

**Category:** layout  
**Tags:** layout, navigation  
**Package:** `@htmlbricks/hb-sidenav-link`

## Overview

`hb-sidenav-link` is a single sidebar navigation row for hierarchical menus. It renders a Bootstrap Icons label row, an optional Bulma-style tag badge (outlined pill: transparent fill, border and text from the tag color), and either:

- a **leaf** link that dispatches `pageChange` when the user activates it, or  
- a **parent** row with `subLinks` that expands to show nested links.

Active styling applies when the current page key (`navpage`) matches a link key, or when the `selected` flag is enabled. The component is intended for use inside `hb-sidebar-desktop` lists (and shares the `INavLink` shape with related layout components).

Bootstrap Icons are loaded from the component (`bootstrap-icons` CSS on jsDelivr). Icon names are the `bi-*` suffix only (for example `house-door` → `bi-house-door`).

## Custom element

```html
<hb-sidenav-link …></hb-sidenav-link>
```

## Props (TypeScript `Component`)

| Property    | Type        | Required | Notes |
|------------|-------------|----------|--------|
| `navlink`  | `INavLink`  | yes      | From HTML, pass a **JSON string** (see below). |
| `navpage`  | `string`    | no       | Current page key; drives active row and expanded groups. |
| `selected` | `boolean`   | no       | When true, forces the “current page” appearance. From HTML use string encodings (see [Attributes](#attributes-snake_case-string-values-in-html)). |
| `id`       | `string`    | no       | Optional identifier (typed on the component API). |
| `style`    | `string`    | no       | Optional inline style (typed on the component API). |

## `INavLink` shape

Used for the root `navlink` and, recursively, for each entry in `subLinks`.

| Field       | Type           | Required | Description |
|------------|----------------|----------|-------------|
| `key`      | `string`       | yes      | Stable id for the link; sent as `page` in `pageChange` for leaf clicks. |
| `label`    | `string`       | yes      | Visible label. |
| `icon`     | `string`       | no       | Bootstrap Icons name without the `bi-` prefix. |
| `group`    | `string`       | no       | Present in the shared nav typings for interoperability. |
| `badge`    | object         | no       | `text` (string), optional Bulma modifier classes `class` and `classcolor` (for example `is-rounded`, `is-light`). |
| `subLinks` | `INavLink[]`   | no       | If non-empty, the row becomes an expandable group instead of a single leaf. |
| `active`   | `boolean`      | no       | Present on the shared type; not read by this component’s markup. |
| `open`     | `boolean`      | no       | When `navlink` is provided as a JSON string, `open: true` initializes the group as expanded. |

### Badges

`badge` supports:

- `text` — badge label.  
- `class` — optional Bulma tag class (defaults to `is-rounded` in the template).  
- `classcolor` — optional color modifier (defaults to `is-light`).

## Attributes (snake_case; string values in HTML)

Reflects the web component contract: attributes are strings.

| Attribute   | Required | Description |
|------------|----------|-------------|
| `navlink`  | yes      | JSON string representing `INavLink`. |
| `navpage`  | no       | Current page key string. |
| `selected` | no       | Boolean-as-string: `yes` / `no` per project conventions. The implementation also treats the literal strings `true` / `yes` as true, **and treats an empty string as true**; any other value becomes false unless already boolean true inside the app. Prefer explicit `yes` or `no` from HTML. |
| `id`       | no       | String. |
| `style`    | no       | String. |

If `navlink` arrives as a string, the component parses it as JSON and applies `open` from the parsed object when `open === true`.

## Events

| Event        | `detail`              | When it fires |
|-------------|------------------------|----------------|
| `pageChange` | `{ page: string }`    | User activates a **non-active** leaf: either the root row (no `subLinks`) or a sub-link whose `key` does not match `navpage` and is not forced selected. **Active** rows (matching `navpage` or `selected`) are static for accessibility (`aria-current="page"`) and do **not** dispatch `pageChange` on click. |

Listen in plain DOM:

```js
document.querySelector('hb-sidenav-link').addEventListener('pageChange', (e) => {
  console.log(e.detail.page);
});
```

## Behavior

### Leaf row (no `subLinks`)

- If `navlink.key === navpage` **or** `selected` is true: one full-width “current” button (link styling), no click navigation event.  
- Otherwise: ghost button; click dispatches `pageChange` with `detail.page === navlink.key`.

### Parent row (non-empty `subLinks`)

- The parent row is a control that toggles expand/collapse and shows **▼** when open or when any child `key` equals `navpage`, otherwise **►**.  
- The nested list is shown when the group is open **or** when some child has `key === navpage`.  
- Each child follows the same active vs ghost rules as a leaf; inactive children dispatch `pageChange` with `detail.page ===` that child’s `key`.

### Layout

Labels use a flexible middle column; a fixed-width trail column (`--hb-sidenav-trail-width`) keeps titles aligned when some rows omit a badge or use the expand chevron. Long badge text is truncated with an ellipsis in the component styles.

## Styling (Bulma and component variables)

Buttons and tags live in the **shadow root**. Theme with public **`--bulma-*`** variables on `body`, `:root`, or an ancestor; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/). Additional tokens are listed in `extra/docs.ts`.

| CSS custom property | Default (from docs) | Purpose |
|--------------------|---------------------|---------|
| `--bulma-link` | `#485fc7` | Active row stripe, chevrons, and interactive accents. |
| `--bulma-link-text` | `#ffffff` | Text color on the active row background mix. |
| `--bulma-text` | `#363636` | Default label color for inactive rows. |
| `--bulma-scheme-main` | `#ffffff` | Row surface behind ghost buttons. |
| `--bulma-radius` | `0.375rem` | Row rounding for list items. |
| `--hb-sidenav-trail-width` | `3.5rem` | Width of the right column (badges / expand chevron); keeps labels aligned when some rows omit a badge. |

### CSS parts

| Part | Description |
|------|-------------|
| `li` | Bulma menu list row (`li`) wrapping the ghost or link button, label column, badge or chevron trail, and nested sub-links. |

**Slots:** none (`htmlSlots` is empty).

## Examples

### Simple leaf

```html
<hb-sidenav-link
  navpage="home"
  navlink='{"label":"Home","key":"home","icon":"house-door"}'
></hb-sidenav-link>
```

### Leaf with badge

```html
<hb-sidenav-link
  navlink='{"label":"Home","key":"home","icon":"house-door","badge":{"text":"3","classcolor":"is-danger"}}'
></hb-sidenav-link>
```

### Expandable group with sub-links

```html
<hb-sidenav-link
  navpage="dash"
  navlink='{"label":"Workspace","key":"workspace","icon":"folder2-open","subLinks":[{"label":"Dashboard","key":"dash","icon":"speedometer2"},{"label":"Projects","key":"projects","icon":"kanban"}]}'
></hb-sidenav-link>
```

### Force selected appearance

```html
<hb-sidenav-link
  selected="yes"
  navlink='{"label":"Home","key":"home","icon":"house-door"}'
></hb-sidenav-link>
```

## Related types

`INavLink` matches the structure used by **`hb-offcanvas`** and **`hb-sidebar-desktop`** for `navlinks` arrays, so the same serialized objects can be reused across layout components.

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-site-contacts-row"></a>

# `hb-site-contacts-row` (site-contacts-row)

**Category:** content  
**Tags:** content, contact  
**Package:** `@htmlbricks/hb-site-contacts-row`

## Overview

`hb-site-contacts-row` is a contact strip that groups **addresses**, **phones**, **emails**, **websites**, and **availability** (opening hours plus an optional appointment note) into a responsive Bulma layout. Each group can have its own **title**, **Bootstrap Icons** glyph, and list content.

The component registers the custom element **`hb-site-contacts-row`**. It runs inside a **shadow root** with Bulma layout and typography, and loads the **Bootstrap Icons** font from jsDelivr in `<svelte:head>`.

## Custom element

```html
<hb-site-contacts-row …></hb-site-contacts-row>
```

## Layout: `model`

- **`small`** — Compact blocks: addresses use an inline “icon + content” row; phones, emails, websites, and availability use column halves on tablet and full width on mobile.
- **`big`** — Larger “card-style” columns (`col_big`) with a prominent icon row, title, and list.

**Default:** If you omit `model`, the implementation defaults to **`small`**.

**Invalid values:** Any `model` other than `small` or `big` renders the fallback text `wrong model provided` plus the value you passed (still inside the shadow tree).

## Attributes (HTML)

Use **snake_case** attribute names. From plain HTML, pass **objects as JSON strings** (single-quoted in HTML, escaped quotes inside JSON).

| Attribute | Required | Description |
| --- | --- | --- |
| `id` | No | Optional host element id (forwarded as a normal attribute / prop). |
| `style` | No | Present in the TypeScript `Component` shape; **not** wired into the template in the current implementation. |
| `model` | No | `"small"` \| `"big"`. Defaults to `"small"`. |
| `addresses` | No | JSON: `{ "icon"?: string, "title"?: string, "addresses": Address[] }`. |
| `phones` | No | JSON: `{ "icon"?: string, "title"?: string, "phones": Phone[] }`. |
| `emails` | No | JSON: `{ "icon"?: string, "title"?: string, "emails": Email[] }`. |
| `websites` | No | JSON: `{ "icon"?: string, "title"?: string, "websites": Website[] }`. |
| `availability` | No | JSON: `{ "icon"?: string, "title"?: string, "times": string[], "appointment"?: boolean }`. |

### JSON shapes (TypeScript reference)

```ts
type Address = {
  address: string;
  latLng?: number[];  // [lat, lng] → Google Maps link when no `link`
  link?: string;      // external URL; takes precedence over `latLng`
  name?: string;      // shown as a prefix in `big` mode only
};

type Phone = {
  number: string;
  name?: string;      // prefix in `big` mode only
};

type Email = {
  address: string;
  label?: string;     // link text in `small`; in `big`, link text vs address
  name?: string;      // prefix in `big` mode only
};

type Website = {
  url: string;
  label?: string;
  name?: string;      // prefix in `big` mode only
};
```

**Parsing:** When any of `addresses`, `phones`, `emails`, `websites`, or `availability` arrives as a **string**, the component attempts `JSON.parse` inside an effect. Malformed JSON is logged to the console and leaves the previous value behavior up to the runtime.

### Icons

Each block accepts an optional `icon` string: the **Bootstrap Icons** short name **without** the `bi-` prefix (the markup adds `class="bi bi-{icon}"`). Defaults if omitted:

| Block | Default icon |
| --- | --- |
| addresses | `geo-alt` |
| phones | `telephone` |
| emails | `envelope` |
| websites | `globe` |
| availability | `clock` |

### Addresses: links

For each address with non-empty `address`:

1. If `link` is set → `<a href="{link}" target="_blank">`.
2. Else if `latLng` has length → Google Maps URL `https://www.google.com/maps/@{lat},{lng},15z` in a new tab.
3. Else → plain text.

### Emails and websites

- **Emails:** `mailto:` links. In **`small`** mode, the list shows `label` as the link text when set, otherwise the address. In **`big`** mode, optional `name:` prefix, then a single mailto link using `label` or the address as visible text.
- **Websites:** External `<a href="{url}">` with `label` or URL as text, with the same `small` / `big` differences as emails for prefixes.

### Availability

- Renders when `availability.times` is a non-empty array; each string is one list item.
- When `availability.appointment` is truthy, an extra line is shown **in Italian** (implementation detail): `*riceve per appuntamento` in `small` mode and `riceve per appuntamento` in `big` mode.

## Events

`types/webcomponent.type.d.ts` declares **`Events`** as `{}` (no custom events). The current `component.wc.svelte` does not dispatch custom events.

## Styling

The component forwards **`styles/bulma.scss`** (Bulma 1.x CSS variables on `:host`) and **`styles/webcomponent.scss`**.

### CSS custom properties (`:host`)

| Variable | Role |
| --- | --- |
| `--bulma-text` | Body copy in lists and labels. |
| `--bulma-link` | Linked addresses, phones, emails, and websites. |
| `--bulma-block-spacing` | Vertical rhythm between stacked blocks and icon sizing. |
| `--bulma-column-gap` | Inline gaps in rows and list spacing. |
| `--bulma-size-small` | Appointment line and small meta text. |
| `--bulma-weight-bold` | Section titles and appointment emphasis. |

See Bulma’s [CSS variables](https://bulma.io/documentation/features/css-variables/) documentation for value types and inheritance.

### CSS parts

| Part | Description |
| --- | --- |
| `container` | Root `columns is-multiline` wrapper (`part="container"`). Use for layout, max-width, or background overrides. |

### Slots

None. All content is supplied via JSON attributes.

## Minimal example

```html
<hb-site-contacts-row
  model="small"
  emails='{"emails":[{"label":"Info","address":"info@example.com","name":"main"}]}'
></hb-site-contacts-row>
```

### Example with multiple groups

```html
<hb-site-contacts-row
  model="big"
  addresses='{"title":"Locations","addresses":[{"address":"1 Example Rd","latLng":[51.5,-0.12],"name":"HQ"}]}'
  phones='{"phones":[{"name":"Desk","number":"+1 555 0100"}]}'
  availability='{"times":["Mon–Fri 9–18"],"appointment":true}'
></hb-site-contacts-row>
```

## Authoring types

For Svelte or TypeScript consumers, see `types/webcomponent.type.d.ts` for the full `Component`, `Address`, `Phone`, `Email`, `Website`, and `Events` definitions.

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-site-paragraph-with-image"></a>

# `hb-site-paragraph-with-image` (site-paragraph-with-image)

**Category:** content  
**Tags:** content  
**Package:** `@htmlbricks/hb-site-paragraph-with-image`

## Overview

A marketing-style content block that pairs a hero **image** with **title**, **body**, and an optional **call-to-action** button. Layout switches between a stacked **mobile** presentation and a side-by-side **wide** row based on the host viewport width. Styling is **Bulma 1.x** inside the shadow root (section spacing, typography, and a primary `button` for the CTA).

## Layout and breakpoints

- **Narrow layout** (when the window inner width is **under 800px**): copy appears first (when `text.title` or `text.body` is present), then the image. Each region exposes dedicated `::part` names prefixed with `mobile_`.
- **Wide layout** (**800px and wider**): a horizontal flex row places the **image** and **text** columns according to **`text_side`**:
  - **`text_side="right"`** (default): image column first, text column second.
  - **`text_side="left"`**: text column first, image column second.
- Any value other than **`left`** is treated as **`right`** (including empty or unknown strings after normalization).
- **`half_space`**: on wide layouts, the **image** column uses **`flex-grow: 5`** when `half_space` is enabled, and **`flex-grow: 2`** when it is disabled, so you can bias how much horizontal space the image takes relative to the text column.

If neither `text.title` nor `text.body` is set, the component shows a plain **“no title or body”** placeholder in the text area. If `img.src` is missing, it shows **“no img”** in the image area.

## Custom element

```html
<hb-site-paragraph-with-image …></hb-site-paragraph-with-image>
```

## Attributes

Attributes use **snake_case**. From HTML, pass **strings**; nested structures use **JSON strings** (see project conventions: booleans as **`yes`** / **`no`** where applicable).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `img` | Yes | JSON object: **`{ "src": string, "alt"?: string }`**. `src` is the image URL. `alt` is passed to the `<img>` when present. |
| `text` | Yes | JSON object: optional **`title`**, **`body`**, and optional **`link`** (see [Link object](#link-object)). At least one of `title` or `body` should be set for meaningful copy. |
| `text_side` | No | **`"left"`** or **`"right"`**. Controls column order on wide layouts. Defaults to **`right`**. |
| `half_space` | No | Wider image column when truthy. From attributes, the implementation treats the string **`"yes"`**, the string **`"true"`**, or boolean **`true`** (after coercion) as enabled. Prefer **`yes`** / **`no`** for HTML consistency with other web components in this library. |
| `id` | No | Optional identifier (typed on the component; reserved for host integration). |
| `style` | No | Optional inline host style (present on the TypeScript `Component` type; not applied by the current Svelte implementation). |

The component parses **`img`** and **`text`** when they arrive as strings (typical from HTML attributes) via `JSON.parse` inside an effect.

### `img` object

| Field | Required | Description |
|-------|----------|-------------|
| `src` | Yes | Image URL used for the `<img>` element. |
| `alt` | No | Alternate text for the image. |

### `text` object

| Field | Type | Description |
|-------|------|-------------|
| `title` | string | Optional main heading (rendered as **`h1`** in both layouts). |
| `body` | string | Optional body copy (single **`<p>`**). |
| `link` | object | Optional CTA; see [Link object](#link-object). |

### Link object

| Field | Required | Description |
|-------|----------|-------------|
| `label` | Yes (for a visible button) | Button label. A Bulma **`button is-primary`** is rendered only when `label` is set. |
| `key` | No | Logical key forwarded in **`elClick`** when the button is activated (see [Events](#events)). |
| `src` | No | Reserved in the type definition for host apps; **this component does not navigate** or open URLs from `src` automatically. |
| `bgColor` | No | When set, applied inline as **background** and **border** color on the CTA. |
| `textColor` | No | When set, applied inline as **text** color on the CTA. |

## Events

Listen with `addEventListener` or your framework’s event binding.

| Event | `detail` | When it fires |
|-------|----------|----------------|
| `elClick` | `{ key: string }` | When the user activates the CTA **and** `text.link.key` is defined. The `detail.key` matches `text.link.key`. |

If the user clicks the CTA but **`text.link.key`** is missing, the handler logs a console warning (**`"no key"`**) and **does not** dispatch `elClick`. To receive clicks, always set a **`key`** on `text.link` when you rely on this event.

## Styling (Bulma CSS variables)

The shadow tree uses **Bulma 1.x** and shared host baseline styles. Theme the block from the light DOM by setting **`--bulma-*`** variables so they inherit onto the custom element. Documented overrides (from component metadata):

| Variable | Role |
|----------|------|
| `--bulma-section-padding` | Feeds internal **`--hb-spiw-pad`** for host padding and text column insets (see `styles/webcomponent.scss`). |
| `--bulma-primary` | Primary CTA styling for **`button is-primary`** when a link is present. |
| `--bulma-text` | Title and body copy color. |
| `--bulma-text-strong` | Strong heading / emphasis tone where Bulma maps it. |

The host also uses **`min-height: 21.875rem`** and **`display: block`** so the section keeps a stable footprint in page layouts.

## CSS `::part`

Use these part names to style inner surfaces without piercing the shadow root arbitrarily.

| Part | Description |
|------|-------------|
| `mobile_text_content` | Mobile column wrapping title, body, and CTA. |
| `mobile_title` | Mobile headline (`h1`). |
| `mobile_text_body` | Mobile body paragraph. |
| `mobile_link_button` | Mobile primary CTA button. |
| `mobile_image_content` | Mobile figure wrapper around the image. |
| `image_content` | Wide-layout image column surface. |
| `text_content` | Wide-layout text column wrapper (title, body, CTA). |
| `link_button` | Wide-layout primary CTA button. |
| `title` | Wide-layout section headline (`h1`). |
| `text_body` | Wide-layout body paragraph. |

## Slots

None. All structure and copy come from the **`img`** and **`text`** attributes (JSON).

## Minimal HTML example

```html
<hb-site-paragraph-with-image
  img='{"src":"https://example.com/hero.png","alt":"Product"}'
  text='{"title":"Built for clarity","body":"Short supporting copy goes here.","link":{"label":"Learn more","key":"learn-more"}}'
  text_side="right"
  half_space="yes"
></hb-site-paragraph-with-image>
```

### Listening for `elClick`

```html
<script>
  const el = document.querySelector("hb-site-paragraph-with-image");
  el.addEventListener("elClick", (e) => {
    console.log("CTA key:", e.detail.key);
  });
</script>
```

Ensure **`text.link.key`** is set whenever you need this event; the label alone is not enough for dispatch.

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-site-slideshow"></a>

# `hb-site-slideshow` (site-slideshow)

**Category:** content  
**Tags:** content, media  
**Layout:** fullscreen (metadata)  
**Package:** `@htmlbricks/hb-site-slideshow`

## Overview

`hb-site-slideshow` is a full-viewport image slideshow. You supply a list of slides as JSON: each slide has an image URL (`href`) and an optional `caption`. Optional props control the starting slide and autoplay. The default UI includes previous/next controls, a dot strip, and a caption bar when the active slide has a caption. You can replace pieces of that UI with slots, tune appearance with Bulma CSS variables, and listen for slide and hover events from the host page.

## Custom element

```html
<hb-site-slideshow …></hb-site-slideshow>
```

## Data model

Each slide is an object with:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `href` | string | yes | Image URL. Slides without `href` are dropped. |
| `caption` | string | no | If set on the **active** slide, a caption bar is shown at the bottom. |
| `index` | number | no | Logical slide index; if omitted, slides are numbered `0 … n-1` in array order. |

The `data` prop is always an **array** of these objects (see [Attributes](#attributes)).

## Attributes

Web component attributes are **strings**. Use **snake_case** names where the runtime maps them to component props.

| Attribute | Required | Description |
| --- | --- | --- |
| `data` | **yes** | JSON string: array of slide objects (`href`, optional `caption`, optional `index`). |
| `index` | no | Initial slide index as a decimal string (e.g. `"0"`, `"2"`). Out-of-range or invalid values fall back to `0`. |
| `timer` | no | Autoplay interval in **milliseconds**, as a string (e.g. `"3000"`). If omitted or empty, there is no autoplay. Values below `100` are clamped to `100`. While the pointer is over the main slideshow area, autoplay does not advance slides. |
| `id` | no | Passed through for host use (string). |

The TypeScript `Component` type also lists optional `style`; the current implementation does not wire `style` in the component script, so prefer styling via CSS variables and host layout rather than a `style` attribute unless your build layer adds it.

### Boolean and number conventions

For this element, numbers (`index`, `timer`) are passed as their **string** form in HTML. There are no boolean attributes on this tag.

## Behavior

- **Rendering:** If `data` parses to an empty list (or no valid slides), the component shows the text: `no image provided as data`.
- **Navigation:** Previous/next wrap at the ends of the list. Clicking a dot jumps to that slide. Changing the slide dispatches `changeSlide`.
- **Autoplay:** When `timer` is set, slides advance on an interval. Autoplay is suspended while `mouseentered` is true on the slideshow container.
- **Overlay slot:** If you provide the `overlay` slot, hovering still toggles `mouseentered`. When hovered **and** the overlay slot exists, the default slideshow UI is hidden and the overlay slot is shown instead. Leaving the overlay dispatches `changeHover` with `hover: false`.
- **Captions and dots:** If **any** slide has a `caption`, the dot strip is offset upward to leave room for the caption bar (only the active slide’s caption is shown).

## Events

Listen with `addEventListener` or your framework’s binding. Event names are **camelCase** as emitted.

| Event | `detail` | When |
| --- | --- | --- |
| `changeSlide` | `{ index: number }` | Active slide index changed (controls, dots, or autoplay). |
| `changeHover` | `{ index: number; hover: boolean }` | Pointer entered or left the main slideshow container (`hover: true` / `false`). When using the overlay slot, leaving the overlay also emits `changeHover` with `hover: false`. |

## Styling (Bulma)

The component uses Bulma **CSS variables** on `:host` for arrows, dots, caption chrome, and spacing. See the [Bulma CSS variables documentation](https://bulma.io/documentation/features/css-variables/).

### Common CSS custom properties

| Variable | Role |
| --- | --- |
| `--bulma-white` | Previous/next chevrons and caption text on the dimmed bar. |
| `--bulma-border` | Fill for inactive slide dots. |
| `--bulma-text-strong` | Active and hovered dot fill. |
| `--bulma-block-spacing` | Dot strip offset and caption bar rhythm. |
| `--bulma-scheme-invert` | Caption bar tint and arrow hover wash. |
| `--bulma-radius-small` | Corner radius for arrow controls. |

Set these from the light DOM on `hb-site-slideshow` (or ancestors), depending on how your app scopes custom properties.

### CSS parts

| Part | Description |
| --- | --- |
| `dot` | One slide indicator; the current slide has class `is-active`. |
| `caption_container` | Full-width bar at the bottom when the current slide has a `caption`. |
| `caption_content` | Centered caption text inside the bar. |
| `cover_on_images` | Layer above the slide images (`z-index` above images). Default slot content for `cover_on_images` is rendered here. |
| `dots` | Wrapper around the dot strip; the `dots` slot replaces the default generated dots. |

Example (host stylesheet):

```css
hb-site-slideshow::part(dot) {
  /* custom dot sizing, etc. */
}
```

## Slots

| Slot | Description |
| --- | --- |
| `overlay` | When this slot has content, hovering shows this **full-screen** layer instead of the default slideshow UI. |
| `prev` | Previous control; default is a text chevron (`&#10094;`). |
| `next` | Next control; default is a text chevron (`&#10095;`). |
| `dots` | Custom indicators; default renders one `part="dot"` element per slide. |
| `cover_on_images` | Optional markup layered above images inside `part="cover_on_images"`. |

## Examples

### Minimal usage

```html
<hb-site-slideshow
  data='[{"href":"https://example.com/a.jpg","caption":"First"},{"href":"https://example.com/b.jpg"}]'
  index="0"
></hb-site-slideshow>
```

### Autoplay every 6 seconds

```html
<hb-site-slideshow
  data='[{"href":"https://example.com/1.jpg"},{"href":"https://example.com/2.jpg","caption":"Slide 2"}]'
  index="0"
  timer="6000"
></hb-site-slideshow>
```

### Listening for slide changes (vanilla JS)

```html
<hb-site-slideshow
  id="hero"
  data='[{"href":"https://example.com/hero.jpg","caption":"Welcome"}]'
></hb-site-slideshow>
<script>
  document.getElementById("hero").addEventListener("changeSlide", (e) => {
    console.log("slide", e.detail.index);
  });
</script>
```

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).

---

<a id="wc-site-slideshow-horizontal"></a>

# `hb-site-slideshow-horizontal` — integrator guide

**Category:** content · **Tags:** content, media · **Package:** `@htmlbricks/hb-site-slideshow-horizontal`

## Summary

Horizontal slideshow strip that shows a window of image (or video-style) tiles, with previous/next arrows. Slides are **data-driven** (JSON); there are no light DOM slots.

## Behavior

- Accepts a **required** `data` payload: an ordered list of slides. Each slide must include a **`href`** (image URL). Entries without `href` are dropped.
- Slides are sorted by optional **`index`** (ascending), then renumbered internally from `0`.
- The strip shows **`size`** tiles at once when you set `size` as a positive number. If you omit `size`, the count is derived from the viewport: `round(windowInnerWidth / 250)` (minimum practical width still comes from the host).
- **`slide`** is the starting index into the flattened list (also used as the window offset for the arrows). It can be synced from the outside; string values are coerced with `Number(...)`.
- **Previous / next** arrows shift that index and **wrap** across the list so the window is always filled when there are more slides than visible tiles (the implementation may concatenate from the beginning of the list to pad the row).
- **`type`** switches between a plain image caption layout (`images`, default) and a **video-style** row with a play affordance and optional **`duration`** text.

---

## Custom element

```html
<hb-site-slideshow-horizontal></hb-site-slideshow-horizontal>
```

Tag name: **`hb-site-slideshow-horizontal`**

---

## Slide model (`data` items)

Each object in the `data` array can include:

| Field | Required | Description |
| --- | --- | --- |
| `href` | **Yes** | Image URL for the tile. |
| `caption` | No | Shown under the play overlay in `videos` mode, or as an overlay caption in `images` mode when present. |
| `index` | No | Sort hint before display (lower first). |
| `externalLink` | No | If set, clicking the tile opens this URL in a **new** tab (`_blank`). Takes precedence over `link` and `key`. |
| `link` | No | If set (and no `externalLink`), clicking sets **`window.location.href`** to this URL. |
| `key` | No | If set (and neither `externalLink` nor `link`), clicking dispatches the **`slideClick`** custom event with `{ key }`. |
| `duration` | No | In `videos` mode, optional duration string next to the title (exposed via CSS part `video_sub_time`). |

If none of `externalLink`, `link`, or `key` is set, the tile is **non-interactive** (still shows image/caption UI).

---

## Attributes and properties (web component)

Use **snake_case** names. In HTML, **attribute values are strings**: pass JSON as a string for `data`, and numeric fields as their decimal string form.

| Name | Required | Description |
| --- | --- | --- |
| `data` | **Yes** | JSON string: array of slide objects (see above). Invalid JSON logs an error and yields an empty strip (nothing rendered). |
| `type` | No | `"images"` (default) or `"videos"`. |
| `slide` | No | Current / initial slide index (stringified number). |
| `size` | No | Number of visible tiles per row (stringified integer). When omitted, width-based sizing is used. |
| `id` | No | Passed through for identification (standard host usage). |

The authoring **`Component`** type also lists an optional `style` field; the current shadow implementation does **not** apply it—prefer styling via **CSS custom properties** and global layout around the host.

---

## Events

Listen with `addEventListener` or the equivalent in your framework.

| Event | `detail` | When |
| --- | --- | --- |
| `slideClick` | `{ key: string }` | User activates a slide that defines **`key`** and does not use `externalLink` or `link`. |

---

## Styling (Bulma tokens)

The component uses Bulma **CSS variables** on `:host` for spacing and arrow chrome. See [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/).

### Common CSS custom properties

| Variable | Role |
| --- | --- |
| `--bulma-block-spacing` | Vertical margin for the strip and horizontal padding on the arrow hit areas. |
| `--bulma-white` | Color of the arrow glyphs on the gradient fades. |
| `--bulma-scheme-invert` | Base for arrow gradient overlays and hover contrast. |

### CSS shadow parts

Style from outside the shadow root with `::part(...)`.

| Part | Role |
| --- | --- |
| `video_sub_title` | Title line under the play affordance when `type` is `videos`. |
| `video_sub_time` | Duration text beside the title when `duration` is set. |
| `caption_content` | Caption text for image-style slides (`type` `images`). |

### Slots

None—content is entirely driven by the `data` JSON.

---

## Minimal HTML example

```html
<hb-site-slideshow-horizontal
  data='[
    {"href":"https://placehold.co/600x400/111/fff","caption":"One"},
    {"href":"https://placehold.co/600x400/222/fff","caption":"Two"}
  ]'
></hb-site-slideshow-horizontal>
```

### Video-style layout

```html
<hb-site-slideshow-horizontal
  type="videos"
  data='[
    {"href":"https://placehold.co/640x360/000/fff","caption":"Intro","duration":"0:42"}
  ]'
></hb-site-slideshow-horizontal>
```

### Initial index, fixed strip width, and `slideClick`

```html
<hb-site-slideshow-horizontal
  id="strip"
  slide="1"
  size="5"
  data='[
    {"href":"https://placehold.co/400x300/004d40/fff?text=A"},
    {"href":"https://placehold.co/400x300/1b5e20/fff?text=B","key":"b"},
    {"href":"https://placehold.co/400x300/33691e/fff?text=C"}
  ]'
></hb-site-slideshow-horizontal>

<script>
  document.getElementById("strip").addEventListener("slideClick", (e) => {
    console.log("key:", e.detail.key);
  });
</script>
```

---

## TypeScript (authoring)

Props and events for wrappers or Storybook align with `types/webcomponent.type.d.ts`:

- **`Component`**: `id`, `data`, optional `slide`, `type`, `size`, and optional `style` (see note above).
- **`Data`**: slide item shape.
- **`Events`**: `slideClick` with `{ key: string }`.

---

## Behavior notes

- If **`data`** parses to an empty list, or every item lacks `href`, the component **renders nothing** (no arrows, no section).
- Negative **`slide`** values are supported by the internal windowing logic; extremely negative values are normalized toward the start of the list.
- Tiles use pointer interactions on the host page; ensure slide `href` values and link targets meet your accessibility and security policies.

---

<a id="wc-skeleton-component"></a>

# `hb-skeleton-component` — integrator guide

**Category:** data · **Tags:** data, loading · **Package:** `@htmlbricks/hb-skeleton-component` · **Custom element:** `hb-skeleton-component`

## Summary

This package is a **development scaffold**, not a production “skeleton loader” UI. It exists to exercise web-component bindings: it accepts a free-form **`json`** payload, a **`string`** label, and a **`boolean`** flag, renders a small Bulma-styled demo panel (copy of those values plus a primary button), exposes a named **light-DOM slot** for arbitrary markup, and emits a single generic **`event`** for listener wiring demos.

Use it when prototyping tooling, Storybook, or catalog pipelines around `hb-*` elements—**not** as a user-facing loading placeholder.

## Props and attributes

Attributes use **snake_case**. In plain HTML, attribute values are always **strings**; pass JSON as a **JSON string**, and use the usual HTML Bricks conventions for booleans (for example **`yes`** / **`no`**) when setting the `boolean` attribute from markup. From JavaScript you may set **properties** with native types (`boolean`, objects) where your integration supports it.

| Attribute | Required | Description |
|-----------|----------|-------------|
| `string` | Yes (per typings) | Arbitrary string; shown in the panel and used to derive an uppercase line for binding demos. |
| `json` | No | JSON string or (from JS) a parsed object. Invalid or empty string falls back to `{}`. Object values are used as-is. The UI lists **top-level keys** of the parsed object. Default in the implementation is `"{}"`. |
| `boolean` | No | Demo flag. The component treats **`true`**, the strings **`"true"`**, **`"yes"`**, **`"1"`**, and **`""`** as “active” for the displayed boolean line; other values (including **`false`** and **`"no"`**) are inactive. Prefer explicit booleans from JS when testing. |
| `id` | No | Passed to the root inner `<div>` of the demo layout. |
| `style` | No | Present on the TypeScript **`Component`** interface for host styling experiments; the demo markup does not forward it into shadow content. |

## Runtime behavior

- **`string`:** Rendered as-is, and a derived **uppercase** string is shown on a second line.
- **`json`:** Parsed when provided as a string; keys are displayed as a comma-separated list (empty when there are no keys).
- **`boolean`:** Drives the “Boolean:” line via the normalization described above.
- **Button:** “Dispatch Event” dispatches the **`event`** custom event (see below).

## Events

| Event | `detail` |
|-------|----------|
| `event` | `{ test: true }` (logical type: **`{ test: boolean }`** per typings) — generic wiring demo fired from the primary button. |

### Example (vanilla JS)

```js
const el = document.querySelector("hb-skeleton-component");
el.addEventListener("event", (e) => {
  console.log(e.detail); // { test: true }
});
```

## Styling (Bulma)

The shadow tree bundles **Bulma 1.x** (button, base skeleton utilities, animations). Theme it from the light DOM by setting **`--bulma-*`** on an ancestor such as `:root` or `body` so variables apply to the custom element host.

Documented overrides in metadata (`styleSetup` / `extra/docs.ts`):

| Variable | Type | Default | Role |
|----------|------|---------|------|
| `--bulma-primary` | color | `#00d1b2` | Primary button background. |
| `--bulma-text` | color | `#363636` | Body copy, labels, and skeleton-related placeholder tone in demos. |

Additional layout tuning uses **`--bulma-section-padding`** in component SCSS when you want looser or tighter padding around the demo block.

## CSS `::part`

| Part | Role |
|------|------|
| `testpart` | Root demo panel wrapper (`part="testpart"` on the inner scaffold `div`). |

## Slots

| Slot | Description |
|------|-------------|
| `content` | **Light-DOM** slot projected into the scaffold body. Default content in the source is the text **“Default slot content”** when nothing is assigned. |

### Example

```html
<hb-skeleton-component
  id="demo"
  string="hello"
  json='{"a":0,"b":"x"}'
  boolean="no"
>
  <span slot="content">Custom slot markup</span>
</hb-skeleton-component>
```

## TypeScript (authoring types)

Authoring types live in `types/webcomponent.type.d.ts`:

- **`Component`:** `string` (required in the type), optional `id`, `style`, `json`, `boolean`.
- **`Events`:** maps **`event`** to **`{ test: boolean }`**.

## License

**Apache-2.0** (see `LICENSE.md` in package metadata).

---

<a id="wc-stylus-notebook"></a>

# `hb-stylus-notebook` — integrator guide

**Category:** inputs · **Tags:** inputs, drawing · **Package:** `@htmlbricks/hb-stylus-notebook`

**Declared layout size:** fullscreen (see `extra/docs.ts`).

## Summary

`hb-stylus-notebook` is a **notebook shell** around the drawing web component **`hb-stylus-paper`**. It renders a **toolbar** (`part="controller"`) and a **paper region** (`part="paper-container"`) that hosts the nested canvas. The shell wires **mode switching** (draw / select / eraser), **undo/redo**, a **Save** action that exports the current drawing as **JSON** via a download, **load** and **insert** flows using **`hb-input-file`**, and optional restoration of strokes from a **`load_draw`** payload.

**Peer components:** this package expects **`hb-stylus-paper`** and **`hb-input-file`** to be registered (see `extra/docs.ts` → `dependencies`).

## Custom element

```text
hb-stylus-notebook
```

## Attributes (snake_case, string-friendly)

HTML and `setAttribute` only expose **strings**. Serialize objects as **JSON strings**.

| Attribute | Required | Description |
| --- | --- | --- |
| `id` | No | Optional host identifier string. |
| `style` | No | Optional inline style string (reserved in typings; not applied inside `component.wc.svelte` today). |
| `load_draw` | No | JSON string of a saved notebook payload (`TSave`). When the value is a non-empty string, the component attempts `JSON.parse` in an effect. Passed through to `hb-stylus-paper` as a serialized string when set. |

### `load_draw` / save payload shape (`TSave`)

From `types/webcomponent.type.d.ts`:

- **`type`:** `"pdf"` \| `"json"` \| `"png"` \| `"svg"` \| `"jpg"`
- **`draw`:** array of **`IStroke`** objects (strokes, multipaths, eraser, move, etc.)
- **`id`**, **`draw_id`**, **`name`**, **`version`:** strings / numbers as defined in the type file

Strokes include fields such as `type`, `id`, `visible`, `actionIndex`, optional `path`, `pathData`, `color`, `opacity`, bounds `min`/`max`, and related metadata. Treat the typings as the source of truth for exact fields.

## Toolbar and nested paper (runtime behavior)

The **`controller`** part contains:

- **clear** — switches `hb-stylus-paper` to **eraser** mode  
- **select** — **select** mode  
- **draw** — **draw** mode  
- **undo** / **redo** — driven by history index from the paper’s `historyIndex` event  
- **save** — sets an internal `save_as` `{ name: "test", type: "json" }`, then clears it after the paper fires **`save`**; the handler **`downloadObjectAsJson`** saves `{detail}.name + ".json"`  
- **brush** — disabled unless mode is **draw**  
- **load** — `hb-input-file` reads a **text** file and **`JSON.parse`**s it into **`load_draw`**  
- **insert** — `hb-input-file` reads an **image** file as **data URL** and passes **`insert_image`** to the paper (PNG/JPG inferred from the data URL)

The **`paper-container`** part wraps **`hb-stylus-paper`** with props including `mode`, `goto` (history navigation), `save_as`, `load_draw`, `insert_image`, `insert_text`, `background_color` (currently `"green"` in source), and listeners for **`historyIndex`** and **`save`**.

## Events

No custom events are declared for this component (`Events` is `{}` in `types/webcomponent.type.d.ts`).

## Styling

- **Bulma 1.x** is included for the shell; theme with **`--bulma-*`** variables on `:host` or ancestors (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

### CSS custom properties (`styleSetup`)

| Variable | Role |
| --- | --- |
| `--bulma-scheme-main-bis` | Background behind the nested paper in `#paper-container` (falls back in SCSS if unset). |

### CSS parts

| Part | Role |
| --- | --- |
| `controller` | Toolbar row: modes, undo/redo, save, and file inputs. |
| `paper-container` | Region that hosts **`hb-stylus-paper`**. |

### Layout (`styles/webcomponent.scss`)

`:host` is a **fullscreen-sized grid** (`position: absolute; inset: 0; width/height 100%`) with rows **`header`** (toolbar) and **`paper`** (canvas area, `min-height: 0` for correct flex/grid scrolling).

### Slots

None (`htmlSlots` is empty in `extra/docs.ts`).

## Examples

### Minimal

```html
<hb-stylus-notebook></hb-stylus-notebook>
```

### With `load_draw` (attribute must be a JSON string)

```html
<hb-stylus-notebook
  load_draw='{"type":"json","id":"stylus-paper","draw_id":"example","name":"sketch","version":0,"draw":[]}'
></hb-stylus-notebook>
```

Replace the JSON value with a real `TSave` payload exported from your app or from a previous session.

## Related packages

- **`@htmlbricks/hb-stylus-paper`** — drawing surface API (modes, history, save detail, inserts).  
- **`@htmlbricks/hb-input-file`** — file picking for load / insert rows in the toolbar.

## License

See `extra/docs.ts` → `licenses` (Apache-2.0 entry) for catalog metadata.

---

<a id="wc-stylus-paper"></a>

# `hb-stylus-paper` — integrator guide

**Category:** inputs · **Tags:** inputs, drawing · **Package:** `@htmlbricks/hb-stylus-paper` · **Custom element:** `hb-stylus-paper`

## Summary

`hb-stylus-paper` is a freehand drawing surface backed by SVG and [perfect-freehand](https://github.com/steveruizok/perfect-freehand) stroke smoothing. It supports **draw**, **eraser**, and **select** modes, configurable brush `options`, paper `size`, optional `view` (zoom and pan), and serialized payloads for loading drawings, inserting assets, and exporting JSON. Custom events report stroke lifecycle, selection rectangles, history index changes, saves, and load outcomes.

**Layout metadata:** this component is intended for **fullscreen** use in catalog tooling; give the host enough height and width for comfortable drawing.

## Web component conventions

- Attribute names use **snake_case**.
- **Booleans** in HTML use the strings **`yes`** or **`no`** (for example `debug`).
- **Numbers** (for example `pen_opacity`, `goto`) are passed as strings that the implementation parses.
- **Objects** (`options`, `size`, `view`, `load_draw`, `save_as`, `insert_image`, `insert_text`) must be **JSON strings** when set from HTML or `setAttribute`, matching the shapes described below.

## Attributes and props

| Attribute | Required | Description |
|-----------|----------|-------------|
| `id` | No | Logical instance id; echoed on events and in saved payloads. |
| `style` | No | Optional host inline style (typed on the component interface). |
| `draw_id` | No | Stable id for the current drawing session. If omitted, a random value with a timestamp suffix is generated. |
| `background_color` | No | CSS color for the SVG background (default `rgb(255,255,255)`). |
| `pen_color` | No | CSS color used for new strokes (default `rgb(0,0,0)`). |
| `pen_opacity` | No | Stroke opacity; string or number coerced with `Number()` (default `1`). |
| `mode` | No | `"draw"` (default), `"eraser"`, or `"select"`. Leaving **select** clears the current selection highlight state. |
| `debug` | No | `"yes"` shows a small debug readout (pointer type and button state); any other value is treated as **`no`**. |
| `options` | No | JSON object: brush tuning for perfect-freehand. Defaults include `size: 6`, `thinning: 0.7`, `smoothing: 0.5`, `streamline: 0.5`. The implementation merges `simulatePressure` per pointer: **`false`** for `pointerType === "pen"`, otherwise **`true`**, unless overridden in `options`. See [Brush `options`](#brush-options). |
| `size` | No | JSON `TSize`: requires `paperSize` (see [Paper size](#paper-size)); optional `width` / `height` for custom layouts. Default `{ "paperSize": "A4" }`. |
| `view` | No | JSON object: `lockPan`, `lockZoom`, `zoom: { type: "fit" \| "custom", value: number }`, `pan: { x, y }`. Defaults use **fit** zoom and zero pan with pan/zoom unlocked. |
| `goto` | No | History index jump (coerced from string). Negative values reset internal formatting state; values are clamped to the loaded stroke count. After applying, the component emits **`changeIndex`**. |
| `load_draw` | No | JSON **`TSave`** document: `type`, `draw` (array of `IStroke`), `id`, `draw_id`, `name`, `version`, `size`. When version or `draw_id` indicates a new document, strokes replace the canvas and **`drawLoaded`** / **`historyIndex`** fire. |
| `save_as` | No | JSON `{ "name": string, "type": TSaveType }`. Triggers export when strokes exist. **Currently only `type: "json"` is implemented** in the component; other `TSaveType` values log an error and do not emit **`save`**. |
| `insert_image` | No | JSON `{ "name": string, "type": "png" \| "svg" \| "jpg" \| "jpeg", "uri"?: string, "base64"?: string }`. Raster types enqueue an **image** stroke using `base64` as `href`. **`svg` is not fully wired** (handler is a stub). On supported raster paths, **`imageLoaded`** fires with `ok: true`. Unsupported types do not dispatch **`imageLoaded`**. |
| `insert_text` | No | JSON `{ "name": string, "content": string }`. The handler is currently a **stub** (logging only); **`txtLoaded`** still fires with `ok: true`. |

### Paper size

`paperSize` accepts the union defined in `types/webcomponent.type.d.ts`, including ISO sizes (`A0`–`A5`, `B0`–`B6`), North American names (`letter`, `legal`, `tabloid`, …), and **`custom`**. Internal layout logic explicitly maps **A3** and **A4** (default) to millimetre page dimensions for view calculations; other enum values should still be stored in save payloads for round-tripping.

### Brush `options`

Logical fields follow `TPerfectFreeHandOptions`: **`size`** (required in typings when provided), optional **`thinning`**, **`smoothing`**, **`streamline`**, **`easing`**, **`simulatePressure`**, and optional **`start`** / **`end`** taper objects (`taper`, `easing`, `cap`). If `options` is parsed from JSON and omits numeric defaults, the component fills `size`, `thinning`, `smoothing`, and `streamline` from built-in defaults.

## Interaction notes

- **Draw mode:** primary button draws; each completed stroke increments the history index and emits **`endStroke`** then **`historyIndex`**.
- **Eraser:** use **`mode="eraser"`**, or in **draw** mode use stylus **pressure 0** with primary button, or **button 32** (middle / auxiliary), to erase by hitting stroke paths.
- **Select mode:** drag a selection rectangle; strokes fully inside the box are marked selected and a **`selection`** event is emitted. Dragging inside an existing selection can group strokes into a **`multiplestroke`** entry for moving.

## Events

Listen with `addEventListener` or framework bindings. Payloads match `Events` in `types/webcomponent.type.d.ts`.

| Event | `detail` (logical shape) |
|-------|---------------------------|
| `startStroke` | `{ id, draw_id, start: Date, stroke_id, index }` — fired when a drawing gesture begins (twice on the first stroke of a session: once when the session start time is fixed, and again with the stroke start). |
| `endStroke` | `{ id, draw_id, stroke_id, start, end, min, max, pathData, pen_color, index }` — bounding box, SVG path data, and timestamps when the pointer releases after drawing. |
| `selection` | `{ id, draw_id, minX, minY, maxX, maxY, strokes: IStroke[] }` — rectangle in SVG coordinates and strokes whose bounds lie inside the box (only top-level stroke entries, not nested multipaths, in the `strokes` array). |
| `historyIndex` | `{ id, draw_id, index, start_index? }` — history cursor moved (new stroke, erase, load, etc.). |
| `changeIndex` | `{ id, draw_id, index, start_index? }` — emitted after a **`goto`** jump is applied. |
| `save` | **`TSave`** — JSON export: `type`, `draw`, `id`, `draw_id`, `name`, `version`, `size` (only when `save_as.type === "json"` is handled). |
| `drawLoaded` | `{ id, draw_id, index, start_index? }` — after `load_draw` is applied. |
| `imageLoaded` | `{ ok: boolean, error?: string, draw_id, id }` — success after a supported raster `insert_image`; failures for unknown types do not currently emit this event. |
| `txtLoaded` | `{ ok: boolean, error?: string, draw_id, id }` — emitted after `insert_text` is processed (implementation stub). |

**Authoring types:** `Events` also declares **`beginStroke`** (`{ date, id, draw_id }`). The current `component.wc.svelte` implementation **does not dispatch** `beginStroke`; use **`startStroke`** for stroke lifecycle handling.

## Styling (Bulma CSS variables)

The component uses **Bulma 1.x** inside the shadow root. Theme debug chrome by setting these variables on `:host` or an ancestor so they inherit onto the custom element (see `extra/docs.ts`).

| Variable | Role |
|----------|------|
| `--bulma-size-small` | Typography size for the debug banner when `debug="yes"`. |
| `--bulma-scheme-main-bis` | Background for the debug strip above the drawing surface. |
| `--bulma-radius` | Corner radius on the debug panel. |

### CSS `::part`

None.

### Slots

None.

## Types reference

Stroke records (`IStroke`), save payloads (`TSave`, `TSaveType`), brush options (`TPerfectFreeHandOptions`), and the full **`Component`** / **`Events`** maps live in `types/webcomponent.type.d.ts` beside this package.

## Minimal example

```html
<hb-stylus-paper
  mode="draw"
  pen_color="rgb(0,0,0)"
  background_color="rgb(255,255,255)"
  pen_opacity="1"
  debug="no"
></hb-stylus-paper>
```

### Example: brush options and JSON props

```html
<hb-stylus-paper
  mode="draw"
  options='{"size":16,"thinning":0.7,"smoothing":0.5,"streamline":0.5}'
  size='{"paperSize":"A4"}'
></hb-stylus-paper>
```

### Example: export JSON after drawing

Set `save_as` when you want a snapshot (for example from JavaScript after the user clicks Save):

```js
const el = document.querySelector("hb-stylus-paper");
el.setAttribute(
  "save_as",
  JSON.stringify({ name: "sketch-1", type: "json" }),
);
el.addEventListener("save", (e) => {
  console.log(e.detail); // TSave
});
```

Only **`json`** export is implemented in the current build; choose other `TSaveType` values only if a future version adds encoders.

---

<a id="wc-table"></a>

# `hb-table` — integrator guide

**Category:** data · **Tags:** data, table · **Package:** `@htmlbricks/hb-table`

## Summary

`hb-table` is a Bulma-styled **data table** web component. You drive it with JSON **`headers`** and **`rows`**. Each row must include **`_id`**. The table can sort columns, filter from the header row (text, enum, date range), format values (nested keys with dot notation, datetimes via dayjs), copy cell text, highlight rows, and handle row / cell clicks. Optional **global `actions`** and per-row **`_actions`** open **`hb-dialog`** (confirm) or **`hb-dialogform`** (schema-driven edit). Multi-select is optional via **`selectactions`**. The footer embeds **`hb-paginate`** for page size, sort controls, and server-style workflows (`externalfilter`, **`total`**). **`is_loading`** shows Bulma skeleton placeholders while keeping pagination props in sync. Internationalization uses **`i18nlang`**.

### Inner components

The implementation registers and embeds **`hb-paginate`**, **`hb-dialog`**, **`hb-dialogform`**, and **`hb-tooltip`**. Bootstrap Icons are loaded for action icons. Theme behaviour follows Bulma **`--bulma-*`** variables; dark mode follows `prefers-color-scheme` unless you override with **`data-theme`** or **`.theme-light` / `.theme-dark`** on **`html`**, **`body`**, or the **`hb-table`** host (inner layout-only `data-theme` is ignored so nested wrappers do not force light mode). Action icons inherit the button foreground colour.

---

## Custom element

| Tag        | Package              |
| ---------- | -------------------- |
| `hb-table` | `@htmlbricks/hb-table` |

---

## HTML integration rules

- **Attributes** use **snake_case** and are **strings** in plain HTML.
- **String flags:** HTML attributes are always strings — e.g. **`externalfilter`** is **`yes`** / **`no`** (or **`true`** / **`false`**); same idea for **`disablepagination`**, **`add_item`**, **`is_loading`**, **`disable_paginate_sort`**, **`sort_strict_direction`** (the component coerces these at runtime).
- **Arrays / objects** (`rows`, `headers`, `actions`, `selectactions`): pass a **JSON string** on the attribute, or assign parsed objects from JavaScript.
- **Numbers** (`size`, `page`, `pages`, `total`, …): pass numeric values as **strings** in HTML (e.g. `page="0"`).
- **Pagination index:** **`page`** and `pageChange` **`detail.page`** are **zero-based** (first page is **`0`**). Align with APIs that use 1-based pages in your host code.

---

## Attributes and props

| Attribute / prop | Required | Description |
| ---------------- | -------- | ----------- |
| `headers` | yes | JSON array of column definitions (`ITableHeader`). See [Column headers](#column-headers). |
| `rows` | yes | JSON array of row objects. Each row **must** have **`_id`**. Optional **`_actions`**, **`_evidenced`**. |
| `id` | no | Host id; echoed on **`addItem`** as `detail.id`. |
| `style` | no | Host inline style string (typed on component; use as needed). |
| `size` | no | Page size (default **12** in implementation). |
| `page` | no | Current page index, **zero-based**. |
| `pages` | no | Total number of pages (non-negative). |
| `total` | no | Total row count for server-side pagination (passed through to pagination when set). |
| `actions` | no | JSON **`IActionButton[]`** rendered as one shared actions column for every row. |
| `selectactions` | no | JSON array of bulk toolbar actions when multi-select is enabled (`{ name, type, iconOrText }`). |
| `selectrow` | no | When enabled (`yes` / `true`), row clicks dispatch **`clickonrow`** (selection / navigation UX). |
| `enableselect` | no | Toggles multi-select mode (gear); when on, row checkboxes appear if **`selectactions`** has entries. |
| `selected` | no | Row **`_id`** to highlight (`::part(selected-row)`). |
| `externalfilter` | no | String **`yes`** / **`true`** → server mode: header filters still emit events but **client-side** filtering / sorting of `rows` is skipped. Omit the attribute or use **`no`** / **`false`** for local filter + sort on `rows`. |
| `disablepagination` | no | Hides **`hb-paginate`**. |
| `add_item` | no | Shows the **Add** control in the footer toolbar when not in multi-select gear mode. |
| `i18nlang` | no | Language code (metadata lists **`en`**, **`it`**). |
| `page_size_type` | no | **`number`** (free input) or **`select`** (dropdown) for page size UI. |
| `page_size_options` | no | Comma-separated sizes for select mode, e.g. **`10,25,50,100`**. |
| `sort_default` | no | Header **`key`** that should match the initial sort column when using pagination sort UI. |
| `sort_default_label` | no | Label for the “default” sort option in the pagination bar (e.g. “Relevance”). |
| `disable_paginate_sort` | no | Hides sort field / direction controls on **`hb-paginate`**. |
| `sort_strict_direction` | no | When set, sort direction toggles **asc** / **desc** only (no “default” cleared state from column cycling). |
| `sort_direction` | no | Current direction: **`asc`**, **`desc`**, or **`default`**. |
| `is_loading` | no | When **`yes`** / **`true`**, tbody shows Bulma skeleton rows (count from **`size`**, max **100**); footer shows a **`skeleton-block`** over hidden **`hb-paginate`** so **`page` / `pages`** stay wired. |
| `fixed_columns` | no | **`yes`**: `table-layout: fixed`, equal column widths, ellipsis overflow. **`no`** (default): auto table layout. |

---

## Column headers (`ITableHeader`)

Each entry describes one column.

| Field | Type / values | Role |
| ----- | -------------- | ---- |
| `label` | string | Header label. |
| `key` | string | Row field key; supports **dot paths** (e.g. `testnested.nested`). |
| `type` | `string` (default), `datetime`, `enum`, `number`, `ip`, `actions` | Affects formatting, search UI, and sort participation. **`ip`**: values `A.B.C.D` or `A.B.C.D/p` (IPv4, optional CIDR `p` 0–32); client-side sort orders by numeric address then prefix length. |
| `format` | string | For **`datetime`**, dayjs format string (e.g. **`DD MMMM YYYY`**). |
| `search` | boolean | Enables header search (text includes, enum select, or date range for searchable datetime). |
| `click` | boolean | Makes cells dispatch **`cellclick`** when clicked (non-actions columns). |
| `select` | string[] | For **`enum`**, allowed values. |
| `nosort` | boolean | Excludes column from header sort cycling. |
| `sortBy` | `asc` \| `desc` \| `none` | Initial sort indicator on column (internal default **`none`**). |
| `truncateAt` | number | Truncate displayed string length (with `...` when trimmed). |
| `copyTxt` | boolean | Shows copy control; successful copy dispatches **`clipboardCopyText`**. |
| `tooltip` | object | Optional tooltip config for the header (see **`hb-tooltip`** / `TTooltip`). |
| `centerCell` | boolean | When **`true`**, body cells (and skeleton placeholders) for this column use **centered** horizontal text/content. |
| `centerHeader` | boolean | When **`true`**, header content for this column is centered: title row centers the **label** (tooltip + text) in the space beside the sort control; sort stays **left**. Filter rows (`search`) also center controls in the cell. |

**`type: "actions"`:** Renders **`_actions`** for that row (per-row buttons). You usually set **`nosort: true`**. A dummy **`key`** is still required for the column definition.

---

## Rows (`IRow`)

- **`_id`:** string, **required**, stable row identifier (events use it as `itemId` / `rowId`).
- **`_actions`:** optional **`IActionButton[]`** for the **`actions`**-type column.
- **`_evidenced`:** optional boolean; marks the row for emphasised styling.
- Other keys are your data fields, aligned with header **`key`** paths.

---

## Filters (`IFilter`)

Emitted on **`changeFilter`** and used internally when not in **`externalfilter`** mode:

- `key`, `type` (`datetime` \| `string` \| `enum` \| `number` \| `ip`), optional `value`, and for datetime range `start` / `end` (**`Date`** in TS; serialized appropriately in JSON props).

---

## Actions (`IActionButton`)

Used for **`actions`** (global column) and **`_actions`** (per row).

| Field | Description |
| ----- | ----------- |
| `name` | Stable id; appears as **`action`** in events. |
| `type` | **`text`** — `iconOrText` is the label; **`icon`** — Bootstrap Icon **glyph** (name after `bi-`, e.g. **`pencil`**, not **`bi-pencil`**). |
| `iconOrText` | Label or icon name. |
| `btnClass` | Optional Bulma colour token (`primary`, `danger`, `link`, …). **Omitted** → **`is-link`**. Case-insensitive; optional **`btn-`** prefix stripped. Strings starting with **`button `** are passed through (legacy). |
| `btnFill` | If **`true`** / **`yes`**, filled button (no outline). If omitted, **`false`**, or **`no`**, the button is **not** filled and **`is-outlined`** only when the **resolved Bulma theme on this host** is dark (reads `--bulma-scheme-brightness` / `--bulma-hb-def-scheme-brightness` / `--bulma-scheme-main-l` from `getComputedStyle(host)`, then falls back to `data-theme` / `.theme-*` / `prefers-color-scheme`). |
| `disabled` | Disables the control. |
| `confirm` | Opens **`hb-dialog`** with `title`, `content`, `confirmLabel`, optional `denyLabel`, `text`. |
| `edit` | Opens **`hb-dialogform`** with `title`, `schema` (form schema), `confirmLabel`, optional `denyLabel`, `description`, `text`. |
| `tooltip` | Optional tooltip on icon buttons. |

**Click behaviour**

- **Global `actions` column:** **`handleClickOnAction`** — if the button has **`confirm`** or **`edit`**, the modal opens; otherwise **`tableaction`** fires with `{ itemId, action }`.
- **Per-row `_actions`:** **`handleClickOnCustomAction`** always emits **`tableCustomActionClick`** with `{ itemId, action }`. If **`confirm`** / **`edit`** is set, the corresponding dialog opens; there is **no** **`tableaction`** for plain per-row clicks (use **`tableCustomActionClick`** only).

---

## Custom events

Listen on the host element. Names are **case-sensitive** as in the table.

| Event | `detail` (TypeScript `Events`) | Notes |
| ----- | ------------------------------ | ----- |
| `pageChange` | `{ page: number; pages: number }` (see typings) | **Zero-based** `page`. The component dispatches **`detail` with at least `{ page }`**; keep total **`pages`** aligned via the **`pages`** attribute (and your server state) when integrating. |
| `changePageSize` | `{ page_size: number }` | Page size changed; table resets to page **0** internally when not external. |
| `removeFilter` | `{ key: string }` | Filter cleared for `key`. |
| `changeFilter` | `{ filter: IFilter }` | Filter added/updated. |
| `tableCustomActionClick` | `{ itemId: string; action: string }` | Per-row **`_actions`** (distinction from global column). |
| `tableaction` | `{ itemId: string; action: string }` | Global **`actions`** column without confirm/edit. |
| `cellclick` | `{ rowId: string; cellId: string }` | Fired for clickable cells; **`cellId`** is the column header **`key`** (TypeScript `Events` uses `cellId`). |
| `actiononselected` | `{ key: string; selectedItems: string[] }` | Bulk toolbar action (`key` = button `name`). |
| `clickonrow` | `{ itemId: string }` | Row click when **`selectrow`** is enabled. |
| `changeSort` | `{ sortedBy?: string; sortedDirection: "asc" \| "desc" \| "default" }` | Column header sort or pagination sort control. |
| `showConfirmModal` | `{ action: string; detail: { id: string; show: boolean } }` | Confirm dialog visibility. |
| `showConfirmModalForm` | `{ action: string; detail: { id: string; show: boolean } }` | Form dialog visibility. |
| `confirmActionModal` | `{ action: string; id: string; confirm: boolean }` | Confirm dialog result. |
| `confirmActionModalForm` | `{ action: string; id: string; confirm: boolean }` | Form dialog result; **`detail`** merges the dialog payload with the table’s action context (see typings — extra keys may appear). |
| `clipboardCopyText` | `{ text: string }` | After copy-to-clipboard. |
| `addItem` | `{ id: string }` | Add control clicked; `id` is the table’s **`id`** attribute or empty string. |

---

## Slots

| Slot | Purpose |
| ---- | ------- |
| `add-button-content` | Replaces the default **plus** icon for **Add** when **`add_item`** is on. |
| `buttons-container` | Extra toolbar controls before **`hb-paginate`** inside **`part="table-actions"`**. |

---

## CSS parts

| Part | Exposes |
| ---- | ------- |
| `table` | Root `<table>` (headers, body, embedded dialogs). |
| `table-actions` | Footer toolbar: settings, add, bulk actions, **`buttons-container`**, **`hb-paginate`**. |
| `selected-row` | `<tr>` when **`selected`** matches the row **`_id`**. |
| `common-row` | Standard data `<tr>`. |

Toolbar order (conceptually): **settings (gear)** → **Add** (when allowed) → **bulk actions** → **`buttons-container`** → **`hb-paginate`**. Spacing can be tuned with **`--hb-table-toolbar-settings-margin-inline-start`** and **`--hb-paginate-list-gap`** on the host where relevant.

---

## CSS custom properties (host)

Dimensional spacing follows **Bulma rhythm variables** (`--bulma-block-spacing`, `--bulma-size-normal`, `--bulma-control-height`, `--bulma-pagination-item-margin`, …) with **unitless `*-mul` knobs** on the host. Override a `--hb-table-*-mul` to tune density, or set the resolved `--hb-table-*` / `--bulma-table-cell-padding` for full control.

| Variable | Role |
| -------- | ---- |
| `--hb-table-*-mul` | Unitless multipliers (see `extra/docs.ts` `styleSetup.vars` for defaults). |
| `--hb-table-footer-margin-inline-end` | `calc(var(--bulma-block-spacing) * mul)` — footer pagination inline-end margin. |
| `--hb-table-sk-pagination-height` | `calc(var(--bulma-control-height) * mul)` — loading skeleton height. |
| `--hb-table-sk-pagination-min-width` | `min(calc(var(--bulma-size-normal) * mul), 100%)` — skeleton min width while loading. |
| `--hb-table-sk-pagination-offset-inline-end` | `calc(var(--bulma-block-spacing) * mul)` — extra inline-end offset when loading. |
| `--hb-table-toolbar-settings-margin-inline-start` | `calc(var(--bulma-block-spacing) * mul)` — space before the first toolbar control group. |
| `--hb-table-actions-gap` | `calc(var(--bulma-block-spacing) * mul)` — gap in the actions flex row. |
| `--bulma-table-cell-padding` | `calc(var(--bulma-size-normal) * block-mul) calc(var(--bulma-size-normal) * inline-mul)` — cell padding (Bulma token). |
| `--hb-table-cell-line-height` | Unitless `line-height` on `td` / `th` (default `1.1`); lower for denser multi-line cells. |

Additional theming uses Bulma **`--bulma-*`** variables on the host / document as for other `hb-*` components.

---

## Behaviour notes for integrators

1. **Client vs server data:** Without **`externalfilter`**, filters and column sort run against the in-memory **`rows`** you pass. With **`externalfilter`**, use **`changeFilter`**, **`changeSort`**, **`pageChange`**, and **`changePageSize`** to query the server and then assign new **`rows` / `total` / `pages` / `page`**.  
2. **Empty rows while loading:** With client-side pagination, clearing **`rows`** during refresh can leave **`pages`** derived from old state until new data arrives—keep **`page`**, **`pages`**, and **`total`** consistent with your API.  
3. **Multi-select:** Checkboxes appear only when **`selectactions`** is a non-empty array and **`enableselect`** is on. The **Add** button is hidden while the gear multi-select mode is active.  
4. **Examples:** See **`extra/docs.ts`** (`examples` array) for Storybook-style datasets: page size modes, sort defaults, strict direction, loading skeleton, fixed columns, tooltips, truncate/copy, and confirm/form actions.

---

## Minimal examples

**Basic table**

```html
<hb-table
  headers='[{"label":"Title","key":"title"},{"label":"When","key":"time","type":"datetime","format":"DD MMM YYYY"}]'
  rows='[{"_id":"1","title":"Row A","time":"2024-01-01T12:00:00.000Z"}]'
  page="0"
  pages="1"
></hb-table>
```

**Loading state**

```html
<hb-table
  is_loading="yes"
  size="10"
  headers='[{"label":"Title","key":"title"}]'
  rows="[]"
  total="0"
  page="0"
  pages="0"
></hb-table>
```

**Add row entry point**

```html
<hb-table
  id="orders"
  add_item="yes"
  headers='[{"label":"Title","key":"title"}]'
  rows='[{"_id":"1","title":"A"}]'
></hb-table>
<script>
  document.querySelector("hb-table").addEventListener("addItem", (e) => {
    console.log("Create item for table id:", e.detail.id);
  });
</script>
```

---

## Types in this repo

Authoritative TypeScript shapes for props and events live in **`types/webcomponent.type.d.ts`** (`Component`, `Events`, `ITableHeader`, `IRow`, `IActionButton`, `IFilter`). Generated consumer typings are produced on **`npm run build:wc`** (`types/html-elements.d.ts`, `types/svelte-elements.d.ts`).

---

<a id="wc-terms-doc-templates"></a>

# `hb-terms-doc-templates` — integrator guide

**Category:** content · **Tags:** content, compliance · **Package:** `@htmlbricks/hb-terms-doc-templates`

## Summary

`hb-terms-doc-templates` turns structured JSON into long-form legal-style pages: Italian privacy notices (`privacy-doc-italian`) or cookie policies (`cookie-doc-italian`, `cookie-doc-english`, `cookie-doc`). It outputs semantic HTML inside the shadow root (title, numbered chapters, paragraphs, bullet lists, and optional tables via the nested `hb-table` web component). Copy is driven entirely by the `data` payload and optional `i18nlang`; there are no light-DOM slots.

These templates are **starting points for documentation**, not a substitute for legal review.

## Custom element

```html
<hb-terms-doc-templates …></hb-terms-doc-templates>
```

## How it works

1. The host passes a **`data` JSON string** whose root object includes an `id` field that selects the template.
2. On each reactive update, the component parses `data` with `JSON.parse` and **only handles `data` when it is a string**. If `data` is missing, not a string, invalid JSON, or the `id` is not one of the supported values, nothing valid is rendered and the fallback text `invalid doc params` is shown.
3. Supported `id` values:
   - **`privacy-doc-italian`** — Italian privacy policy; input shape `ITPrivacy` (see types).
   - **`cookie-doc-italian`** or **`cookie-doc`** — Italian cookie policy; input shape `CookieContent` (Italian content path; `cookie-doc` is routed the same as `cookie-doc-italian`).
   - **`cookie-doc-english`** — English cookie policy; input shape `CookieContent`.
4. **`i18nlang`** (optional) selects strings from the built-in dictionary (`it`, `en` are registered in metadata). When the language changes, the translator is recreated.
5. **`hb-table`** is registered as a dependency version aligned with this package; chapters may embed a table built from `paragraph.table.headers` and `paragraph.table.rows` with pagination disabled.

Authoritative TypeScript shapes for `data` live in `types/webcomponent.type.d.ts` (`ITPrivacy`, `CookieContent`, nested `CookieRow`, chapters, paragraphs, etc.).

## Attributes (snake_case; HTML values are strings)

| Attribute | Required | Description |
| --- | --- | --- |
| `data` | No (see behavior) | JSON **string** describing the document. Must parse to an object with a supported `id` and the fields expected for that template (site, company, cookies list, privacy admin, and so on). Omitting or leaving invalid shows **`invalid doc params`**. |
| `i18nlang` | No | Language code for UI strings, e.g. `it` or `en`. |
| `id` | No | Optional host identifier string; not used to pick a template (template choice is `data.id`). |
| `style` | No | Present on the `Component` typing for API symmetry; the current implementation does not apply a separate `style` prop beyond normal element styling. |

Nested values inside the JSON (numbers, booleans, arrays) follow normal JSON rules inside the string attribute.

## Events

No custom events are declared for this component (`Events` is `{}` in `types/webcomponent.type.d.ts`).

## Styling

The component uses **Bulma 1.x** content styles in the shadow root. Prefer **`--bulma-*`** variables on `:host` or ancestors (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

### CSS custom properties

| Variable | Purpose |
| --- | --- |
| `--bulma-block-spacing` | Vertical spacing between headings, paragraphs, and lists. |
| `--bulma-content-heading-bottom` | Margin below main chapter titles (`h1` / `h2`). |
| `--bulma-line-height-main` | Line height for paragraphs and list items. |

### CSS parts

| Part | Exposed on | Purpose |
| --- | --- | --- |
| `h1` | Document title | Top-level policy title. |
| `h2` | Chapter heading | Includes numeric index from the template. |
| `p` | Paragraph | Body text inside a chapter. |
| `ul` | List | Wrapper for list blocks. |
| `li` | List item | Single bullet entry. |

Sub-headings rendered as `h3` (when `paragraph.title` is set) do **not** expose a `::part` name.

Embedded `hb-table` nodes have their own theming and parts as documented for that component.

### Slots

None.

## Dependencies

The document renderer pulls in **`hb-table`** for tabular sections. Ensure your bundle or loader includes `hb-table` (and its transitive dependencies) if those chapters appear in your template output.

## Examples

### English cookie policy (minimal empty cookie list)

```html
<hb-terms-doc-templates
  i18nlang="en"
  data='{"id":"cookie-doc-english","site":{"name":"Example","url":"https://example.com","privacyPolicyUri":"https://example.com/privacy","cookiePolicyUri":"https://example.com/cookies"},"company":{"name":"Example Ltd","address":"1 Example Street"},"cookies":[]}'
></hb-terms-doc-templates>
```

### Italian privacy notice (trimmed payload)

Fill in `site`, `company`, `privacyAdmin`, `collectedData`, and optional sections per `ITPrivacy` in `types/webcomponent.type.d.ts`. Example skeleton:

```html
<hb-terms-doc-templates
  i18nlang="it"
  data='{"id":"privacy-doc-italian","site":{"name":"Example","url":"https://example.com","privacyPolicyUri":"https://example.com/privacy","cookiePolicyUri":"https://example.com/cookies"},"company":{"name":"Example Srl","address":"Via Example 1"},"privacyAdmin":{"name":"DPO","email":"privacy@example.com"},"collectedData":{"scopes":[],"dataTypes":[]}}'
></hb-terms-doc-templates>
```

For richer samples (including cookie rows with `storage`, `type`, `durate`, optional `third`), see `extra/docs.ts` (`examples`: `italian`, `cookieit`, `cookieen`, `cookieen_no_data`).

## Programmatic usage note

Because the implementation only parses **`data` when `typeof data === "string"`**, assigning a live **object** to a property in JavaScript will not populate the document until you pass a JSON string (for example `element.setAttribute("data", JSON.stringify(payload))` or an equivalent string property, depending on your wrapper).

## Further reading

- Type definitions: `types/webcomponent.type.d.ts`
- Metadata, Bulma variables, parts, and Storybook-style examples: `extra/docs.ts`
- Template builders: `libs/privacyItContent.ts`, `libs/cookieItContent.ts`, `libs/cookieEnContent.ts`

---

<a id="wc-toast"></a>

# `hb-toast` — integrator guide

**Category:** overlays · **Tags:** overlays, notifications · **Package:** `@htmlbricks/hb-toast` · **Custom element:** `hb-toast`

## Summary

`hb-toast` is a **Bulma-styled** floating notification: a dismissible card with optional status chip, body copy, optional action buttons, and an optional **bottom progress bar**. It is positioned in a corner or edge of the viewport (configurable), uses **Bootstrap Icons** for built-in status icons, and communicates with the host through **`changeVisibility`** and **`action`** custom events.

Use it for transient messages, timed auto-dismiss, upload or task **progress**, or **confirm / cancel** flows without a full modal.

## Props and attributes

Attributes use **snake_case**. In HTML, values are **strings** only: use **`yes`** / **`no`** for visibility, numbers as strings (for example **`timeout="5000"`**), and **`buttons`** as a **JSON array** string. From JavaScript you may assign properties with richer types where your bindings support it (for example a parsed `buttons` array).

| Attribute | Required | Description |
|-----------|----------|-------------|
| `show` | Yes | **`yes`** shows the toast; **`no`** hides it and clears timers. |
| `title` | Yes | Bold header line (also mirrored if the `header_strong` slot is empty). |
| `content` | Yes | Main message HTML is not assumed; plain text is shown in the default body (slot `body` overrides). |
| `img` | Yes (per typings) | String (URL or data URI). Present on the public **`Component`** type and catalog examples; the current implementation does **not** render this value in the shadow tree. Use the **`header_icon`** slot for a custom image or icon. |
| `id` | No | Included in event `detail` for correlation when multiple toasts exist. |
| `style` | No | On the TypeScript interface for host-level styling; not forwarded into inner markup in the current source. |
| `small` | No | Fixed **status chip** text. When set, it overrides automatic status (countdown, progress label, or elapsed time). |
| `level` | No | Semantic tone: **`primary`**, **`secondary`** (link hue), **`success`**, **`danger`**, **`warning`**, **`info`**, **`light`**, **`dark`**. Default **`info`**. Sets **`--hb-toast-accent`** as `hsl(var(--bulma-*-h), …)` (Bulma has no single `var(--bulma-primary)` paint) for start border, icon/status tint, and progress fill; card uses **`--bulma-background`** / **`--bulma-text`**. |
| `position` | No | **`top-left`**, **`top-center`**, **`top-right`**, **`bottom-left`**, **`bottom-center`**, **`bottom-right`**. Default **`bottom-right`**. |
| `timeout` | No | Auto-dismiss delay in **milliseconds** (coerced with `Number`). When set and greater than zero, and **`progress`** is not a valid 0–100 value, the toast closes when the timer elapses and the bar animates from 0% to 100% over that interval. |
| `progress` | No | String parsed as a number **0–100**. When valid, drives the bar width and status text (percentage) unless **`small`** is set. When the value reaches **100%** while visible, the toast **auto-closes** after its fade-out (same path as timeout completion). |
| `buttons` | No | JSON string (or array from JS) of **`TToastButton`** objects: **`type`** **`confirm`** | **`cancel`**; optional **`action`**, **`text`**, **`icon`** (Bootstrap Icons class, e.g. **`bi-check-lg`**), **`themeColor`**, **`color`** (inline background on the button). Defaults supply label, theme, and icon per type. |

### Button objects (`buttons`)

Each entry is merged with defaults:

- **`confirm`:** default text “Confirm”, theme **`success`**, icon **`bi-check-lg`**.
- **`cancel`:** default text “Cancel”, theme **`secondary`** (rendered as Bulma **`is-light`** for secondary), icon **`bi-x-lg`**.

Clicking a button dispatches **`action`** with the button’s **`action`** string, then closes the toast.

## Runtime behavior

- **Visibility:** With **`show="yes"`**, the toast enters with a short fade-in. With **`show="no"`**, timers and intervals are cleared.
- **Close control:** The corner **delete** control calls **`close`**: timers stop, **`show`** becomes **`no`**, and **`changeVisibility`** fires with **`show: false`** (no **`disappear`** flag).
- **Timed dismiss:** If **`timeout`** is a positive number and there is no external numeric **`progress`**, a single timer runs for that many milliseconds, the progress bar fills over the same period, then **`autoClose`** runs: after the fade-out buffer, **`changeVisibility`** fires with **`show: false`** and **`disappear: true`**.
- **Status chip:** If **`small`** is a non-empty string, it is always shown as the chip text. Otherwise: if **`progress`** parses to a number, the chip shows **`{percentage}%`**; if **`timeout`** is positive, the chip shows a **human-readable remaining** countdown (for example “2 minutes 30 seconds remaining”); if neither applies, the chip shows **elapsed** time since open (“now”, then “1 second ago”, and so on). An explicit empty **`small`** suppresses that computed line unless you fill **`header_small`**.
- **Progress bar:** Shown when **`progress`** is a valid percentage **or** when **`timeout`** is positive (timeout mode). External **`progress`** clamps to 0–100; invalid or empty **`progress`** falls back to timeout-driven animation only.
- **Icons in the chip:** Context icons are **`bi-tag`** (custom **`small`**), **`bi-reception-4`** (percentage), **`bi-hourglass-split`** (countdown), **`bi-clock-history`** (elapsed).

## Events

| Event | `detail` |
|-------|----------|
| `changeVisibility` | **`{ id: string; show: boolean; disappear?: boolean }`** — fired when the toast is hidden programmatically, by the user, or after auto-dismiss. **`disappear: true`** is set on **auto** close after timeout or when progress reaches 100%. |
| `action` | **`{ id: string; action?: string }`** — fired when an action button is pressed; **`action`** matches the **`action`** field on the clicked **`TToastButton`**. |

### Example (vanilla JS)

```js
const toast = document.querySelector("hb-toast");

toast.addEventListener("changeVisibility", (e) => {
  console.log(e.detail); // { id, show, disappear? }
});

toast.addEventListener("action", (e) => {
  console.log(e.detail); // { id, action? }
});
```

## Styling (Bulma)

Shadow styles forward **Bulma 1.x** from `styles/bulma.scss`. Theme the host from the light DOM with **`--bulma-*`** variables (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

Documented **`styleSetup`** variables (`extra/docs.ts`):

| Variable | Type | Role |
|----------|------|------|
| `--bulma-column-gap` | number | Edge insets and internal gaps in the toast shell. |
| `--bulma-radius` | number | Corner radius of the floating panel. |
| `--bulma-border-weak` | color | Hairline border and dividers. |
| `--bulma-shadow` | string | Drop shadow for the panel. |
| `--bulma-text` | color | Secondary and meta text. |
| `--bulma-text-strong` | color | Title and strong header tone. |
| `--bulma-block-spacing` | number | Vertical rhythm in header and footer stacks. |

## CSS `::part`

| Part | Description |
|------|-------------|
| `toast` | Outer card: theme background + text; `level` drives `--hb-toast-accent` (border, progress, icons). |
| `status` | Status row under the title: countdown, **`progress`** percentage, **`small`** text, or slot-driven content. |

## Slots

| Slot | Description |
|------|-------------|
| `header_icon` | Leading icon area; default is a **Bootstrap Icons** bell (**`bi-bell`**). |
| `header_strong` | Optional content **before** the **`title`** text in the title line (slot is empty by default; **`title`** still renders). |
| `header_small` | Overrides the default status chip text (same surface as automatic timeout / progress / elapsed). |
| `body` | Main copy; default content is **`content`**. |

## Examples

### Basic open toast

```html
<hb-toast
  show="yes"
  title="Notice"
  content="Hello, world! This is a toast message."
  img=""
  level="info"
></hb-toast>
```

### Timed auto-dismiss (5 seconds)

```html
<hb-toast
  show="yes"
  title="Saved"
  content="Your changes were stored."
  img=""
  timeout="5000"
  level="success"
></hb-toast>
```

### Fixed progress (string 0–100)

```html
<hb-toast
  show="yes"
  title="Upload"
  content="Uploading your file…"
  img=""
  progress="42"
  level="warning"
></hb-toast>
```

### Confirm / cancel (`buttons` as JSON)

```html
<hb-toast
  id="confirm-1"
  show="yes"
  title="Confirm"
  content="Proceed with this action?"
  img=""
  level="light"
  buttons='[{"type":"cancel","action":"dismiss"},{"type":"confirm","action":"proceed","text":"Yes","icon":"bi-check-circle"}]'
></hb-toast>
```

### Custom leading icon (slot)

```html
<hb-toast show="yes" title="Alert" content="Something happened." img="" level="danger">
  <span slot="header_icon" class="icon is-medium">
    <i class="bi bi-exclamation-triangle" aria-hidden="true"></i>
  </span>
</hb-toast>
```

---

<a id="wc-tooltip"></a>

# `hb-tooltip` — integrator guide

**Category:** overlays · **Tags:** overlays, tooltip · **Package:** `@htmlbricks/hb-tooltip`

Web component that wraps your light-DOM content in a trigger region and shows a floating tooltip bubble next to it. Positioning uses [Floating UI](https://floating-ui.com/) (flip, shift, arrow, fixed offset). The bubble is styled like a classic dark tooltip and can be tuned with Bulma CSS variables and an optional inline `style` block inside the `tooltip` JSON.

---

## Overview

- **Default slot:** Put buttons, links, icons, or short text here. The tooltip opens when the user **hovers** or **focuses** the trigger wrapper (not on click alone).
- **`tooltip`:** Pass a **JSON string** with at least `title`. Optional fields control placement, HTML rendering, two-line title/description layout, size limits, and per-tooltip colors.
- **`show`:** When set, visibility is **controlled** from the outside; hover/focus no longer toggles it. Omit `show` for fully interactive mode.
- **`visualizationChange`:** Fires whenever the tooltip becomes visible or hidden, including under programmatic `show`.

Disabled form controls inside the slot do not receive pointer events; the host still receives hover so the tooltip can appear over disabled buttons/inputs.

---

## Custom element

`hb-tooltip`

---

## Attributes

Names are **snake_case**. In HTML, attribute values are always strings.

| Attribute | Required | Description |
| --- | --- | --- |
| `id` | No | Echoed on `visualizationChange` as `detail.id` (empty string if unset). |
| `style` | No | Standard host inline style string. |
| `tooltip` | No | JSON string describing the tooltip (see [Tooltip JSON](#tooltip-json)). Invalid JSON logs a warning and yields no bubble content. |
| `show` | No | Controls visibility when present. Accepted truthy strings: `yes`, `true`, `1`, `on`. Falsy: `no`, `false`, `0`, `off`. When omitted, visibility follows hover/focus only. |

---

## Tooltip JSON

Deserialize the `tooltip` attribute to an object with this shape (TypeScript names as in `types/webcomponent.type.d.ts`):

### Required

| Field | Type | Description |
| --- | --- | --- |
| `title` | `string` | Main text. If `html` is `true`, parsed as HTML (use only with trusted content). If `description` is set, `title` is shown as a bold heading and `description` as body text. |

### Optional (supported by this build)

| Field | Type | Description |
| --- | --- | --- |
| `description` | `string` | Second block below `title` (plain text). Enables two-block layout and a default `max-width` of `200px` unless you override `maxWidth`. |
| `placement` | `"auto" \| "top" \| "bottom" \| "left" \| "right"` | Preferred side relative to the trigger. `"auto"` is treated like `"top"` for positioning. Floating UI may flip/shift to stay in view. |
| `html` | `boolean` | When `true`, `title` is rendered with `{@html ...}` (XSS risk if content is user-controlled). |
| `style` | `TooltipStyle` | Inline look for this instance (see below). |
| `maxWidth` | `string` | CSS `max-width` on the bubble (e.g. `"200px"`). |
| `maxHeight` | `string` | CSS `max-height` on the bubble. |

#### `TooltipStyle`

| Field | Description |
| --- | --- |
| `backgroundColor` | Bubble background (also drives `--tooltip-bg` for the arrow). Default in code: `"black"`. |
| `color` | Text color. Default: `"white"`. |
| `fontSize` | Default: `"0.875rem"`. |
| `padding` | Default: `"0.5rem 1rem"`. |
| `borderRadius` | Default: `"0.25rem"`. |
| `opacity` | Default: `0.9`. |
| `disableDefaultStyle` | When `true`, the component skips its default inline style string so you can style entirely via CSS (you still need layout/position rules as appropriate). |

### Typings only (not read by `component.wc.svelte`)

The `TTooltip` interface also lists `animation`, `delay`, `trigger`, `customClass`, and `offset`. The current implementation always uses hover + focus on the trigger and a fixed 6px popover offset from Floating UI; those fields are **not** applied. Prefer documenting behavior above for integration.

---

## Events

| Event | `detail` |
| --- | --- |
| `visualizationChange` | `{ id: string; show: boolean }` |

Emitted when the tooltip opens (`show: true`) or closes (`show: false`), including when `show` is driven by the attribute.

---

## Slots

| Slot | Description |
| --- | --- |
| *(default)* | Trigger content in the light DOM. Wrapped for hover/focus tooltip behavior. |

---

## Styling

The floating layer uses **`position: fixed`** (Floating UI `strategy: "fixed"`) so it is not clipped by scroll parents such as Bulma **`.table-container`** (`overflow: auto`). Default **`z-index`** is **`45`** via `--hb-tooltip-z-index` (above `--bulma-dropdown-content-z`, below `--bulma-modal-z`; raise on the host if another layer still covers it). The arrow is a small rotated square using the same fill as the bubble.

### CSS custom properties

| Variable | Role |
| --- | --- |
| `--hb-tooltip-z-index` | Stacking order for the floating bubble (default **`45`**). |
| `--tooltip-bg` | Tooltip surface and arrow fill when set; otherwise the arrow falls back to `--bulma-scheme-invert`. |
| `--bulma-scheme-invert` | Default arrow/tone fallback when `--tooltip-bg` is unset. |
| `--bulma-weight-bold` | Font weight for the title line when `description` is used. |
| `--bulma-column-gap` | Space between title and description blocks. |

### CSS parts

None (`styleSetup.parts` is empty).

---

## Examples

### Basic hover tooltip

```html
<hb-tooltip tooltip='{"title":"Help text","placement":"bottom"}'>
  <button type="button">Hover or focus me</button>
</hb-tooltip>
```

### Title and description

```html
<hb-tooltip
  tooltip='{"title":"Volume","description":"Adjusts the output level in percent."}'
>
  <span tabindex="0" class="icon">i</span>
</hb-tooltip>
```

### HTML title (trusted content only)

```html
<hb-tooltip tooltip='{"title":"<strong>Note</strong>: saved automatically.","html":true}'>
  <button type="button">Info</button>
</hb-tooltip>
```

### Programmatic open/close

```html
<hb-tooltip id="tip1" show="yes" tooltip='{"title":"Forced open"}'>
  <span>Trigger</span>
</hb-tooltip>
```

Listen for visibility if you need to sync UI:

```html
<script>
  document.querySelector("hb-tooltip").addEventListener("visualizationChange", (e) => {
    console.log(e.detail.id, e.detail.show);
  });
</script>
```

### Custom colors via JSON `style`

```html
<hb-tooltip
  tooltip='{"title":"Styled","style":{"backgroundColor":"#6f42c1","color":"#ffffff","fontSize":"1rem","padding":"0.5rem 1rem","borderRadius":"0.5rem"}}'
>
  <button type="button">Hover</button>
</hb-tooltip>
```

---

<a id="wc-uploader"></a>

# `hb-uploader` — integrator guide

**Category:** utilities · **Tags:** utilities, files · **Package:** `@htmlbricks/hb-uploader`

Web component that wraps an inner **`hb-dialog`** and runs a **`fetch`** upload when the dialog opens. It shows a **Bulma** `<progress>` bar: **indeterminate** while the request is starting or when byte-level progress is not available, then **determinate** with a percentage when the body is streamed and a known size exists. Upload parameters come from **`fetch_data`** (`url`, `data`, optional `method`, optional `headers`). The component forwards **`modalShow`** from the dialog and emits **`uploadComplete`** or **`uploadError`** when the request finishes or fails.

## Dependencies

- **`hb-dialog`** — modal shell, title slot, and open/close lifecycle. The uploader sets the dialog’s `show` attribute from **`fetch_data.url`** and upload state (see [Behavior](#behavior-and-integration-notes)).

## Custom element

`hb-uploader`

## Attributes and properties

In HTML, attributes are **snake_case** and values are **strings** (project convention: booleans as `yes` / `no` on elements that expose them, objects as **JSON strings**, numbers as string digits). **`fetch_data`** is always provided to the component as serialized JSON when using attributes.

| Name | Required | Description |
| --- | --- | --- |
| `fetch_data` | No (see behavior) | JSON string describing the upload: **`url`** (string), **`data`** (any JSON-serializable value, string, or structure your runtime assigns via properties), optional **`method`** (string; default **`POST`**, uppercased when sent), optional **`headers`** (plain object of string values). If the attribute/property value is a string, the component attempts **`JSON.parse`** inside an effect. |
| `upload_id` | No | Identifier used in **`CustomEvent`** `detail` and passed to the inner dialog as **`id`**. If omitted, it defaults to **`${id}_dialog`** (see `id` below). |
| `id` | No | Host id; when **`upload_id`** is not set, **`upload_id`** becomes **`id + "_dialog"`**. |
| `style` | No | Present in typings for parity with other components; not wired in the current implementation. |

### `fetch_data` shape

| Field | Required | Description |
| --- | --- | --- |
| `url` | Yes, to perform an upload | Request URL passed to **`fetch`**. When the dialog opens with a non-empty URL, the upload runs. |
| `data` | Yes for a real request | Request body source. At runtime (e.g. from script), this may be **`FormData`**, **`Blob`**, **`ArrayBuffer`**, a **typed array**, a **string**, or a plain object (serialized as JSON with default **`Content-Type: application/json`** unless you set **`Content-Type`** in **`headers`**). From HTML attributes alone, use JSON-compatible values inside the JSON string. |
| `method` | No | HTTP method; defaults to **`POST`**. |
| `headers` | No | Extra request headers as a plain object **`{ "Header-Name": "value" }`**. Empty or missing values are skipped when building **`Headers`**. |

## Slots

| Slot | Description |
| --- | --- |
| `title` | Light-DOM content for the dialog title. Default text: **Uploading**. |

## Events

All events are **DOM `CustomEvent`** instances dispatched on **`hb-uploader`**.

| Event | `detail` shape | When it fires |
| --- | --- | --- |
| `modalShow` | `{ id: string; show: boolean }` | Forwarded from the inner **`hb-dialog`** whenever modal visibility changes. |
| `uploadComplete` | `{ completed: boolean; id: string }` | After a **successful** response (**`response.ok`**). |
| `uploadError` | `{ completed: boolean; id: string; error: Error }` | On missing **`fetch_data`**, **`fetch`** failure, non-OK HTTP status, or thrown errors during the upload path. |

Listen with **`addEventListener('uploadComplete', ...)`** or the corresponding **`onuploadcomplete`** style in frameworks that map event names.

## CSS custom properties

These **Bulma** variables theme spacing and typography for the percentage line under the progress bar (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)).

| Variable | Role |
| --- | --- |
| `--bulma-column-gap` | Space between stacked labels and the progress control (default fallback in SCSS: `0.5rem`). |
| `--bulma-size-small` | Supporting text size for the percentage label (fallback: `0.875rem`). |
| `--bulma-text` | Primary label / percentage color (fallback: `#4a4a4a`). |

Progress bar colors use Bulma’s **primary** progress styles from the forwarded **`elements/progress`** module.

## CSS `::part`

None on this host (`styleSetup.parts` is empty). Dialog chrome and any dialog-level parts belong to **`hb-dialog`**.

## Behavior and integration notes

1. **When the upload runs** — On **`modalShow`** with **`show: true`**, if **`fetch_data?.url`** is set, the component starts **`onModalOpened`**: resets **`loaded`**, builds **`Headers`**, resolves a **`Blob`** (or stream) from **`fetch_data.data`**, and calls **`fetch(url, { method, headers, body })`**.

2. **Method** — **`fetch_data.method`** defaults to **`POST`** and is normalized with **`.toUpperCase()`**.

3. **Body and progress** — **`FormData`** is sent as-is; **byte streaming progress is not tracked**, so the UI uses an indeterminate bar until completion, then treats the upload as done. For **string**, **Blob**, **ArrayBuffer**, **ArrayBufferView**, or **JSON-serialized object** bodies with **non-zero** size, the component wraps the blob in a **`ReadableStream`** that updates **`loaded`** / **`total`** for a determinate bar and percentage. **Zero-byte** non-**FormData** bodies skip stream progress; the UI jumps to completed state when the response is OK.

4. **Errors** — Non-OK responses read **`response.text()`** when possible and throw with that text, **`statusText`**, or a status-based message. Errors are shown as plain text in the dialog body; **`uploadError`** is dispatched with the **`Error`**.

5. **Missing configuration** — If **`fetch_data`** is missing when the dialog triggers an upload, the component sets an error message and dispatches **`uploadError`**.

6. **Inner dialog visibility** — The inner **`hb-dialog`** receives **`show="yes"`** when **`fetch_data.url`** is truthy **or** when both **`loaded`** and **`total`** are truthy (so the dialog can remain visible around completion states). Actual **`fetch`** execution is gated on **`fetch_data?.url`** when the modal opens.

7. **Storybook** — **`extra/docs.ts`** maps Storybook actions to **`uploadComplete`**, **`uploadError`**, and **`modalShow`** (same names as runtime `CustomEvent` types).

## Examples

### Minimal HTML (placeholder URL)

Replace the URL with your API endpoint and adjust **`data`** to match your server contract.

```html
<hb-uploader
  fetch_data='{"url":"https://api.example.com/upload","data":{},"method":"POST"}'
  upload_id="my-upload-dialog"
></hb-uploader>
```

### POST with custom headers (JSON `fetch_data`)

```html
<hb-uploader
  upload_id="job-post-1"
  fetch_data='{"url":"https://httpbin.org/post","method":"POST","data":{"batch":"2026-03-30"},"headers":{"X-Upload-Client":"my-app"}}'
></hb-uploader>
```

### Custom title slot

```html
<hb-uploader fetch_data='{"url":"https://api.example.com/v1/files","data":{}}'>
  <span slot="title">Sending files…</span>
</hb-uploader>
```

### Listening for completion in JavaScript

```js
const el = document.querySelector('hb-uploader');
el.addEventListener('uploadComplete', (e) => {
  console.log('upload id:', e.detail.id, 'completed flag:', e.detail.completed);
});
el.addEventListener('uploadError', (e) => {
  console.error(e.detail.error);
});
```

## Package

**`@htmlbricks/hb-uploader`** — ensure your bundle registers **`hb-dialog`** (and this package’s **`main.iife.js`**) per your HTML Bricks / app setup.

---

<a id="wc-vertical-img-txt-archive"></a>

# `hb-vertical-img-txt-archive` — integrator guide

**Category:** content · **Tags:** content, archive · **Package:** `@htmlbricks/hb-vertical-img-txt-archive`

## Summary

`hb-vertical-img-txt-archive` renders a **responsive CSS grid** of archive cards. Each card stacks an **image** above a **title** (`h3`) and **body text** (`p`). Data comes from a single **`collection`** value: either an array (when using the element from JavaScript) or a **JSON string** (typical in HTML).

If `collection` is missing, empty, not an array, or invalid JSON, the component renders **nothing** (no placeholder).

## Custom element

```text
hb-vertical-img-txt-archive
```

## Data model (`collection` items)

Each entry in `collection` matches the `Item` shape in `types/webcomponent.type.d.ts`:

| Field | Required | Description |
| --- | --- | --- |
| `title` | Yes | Card heading; also used as the image `alt` text. |
| `text` | Yes | Supporting paragraph under the title. |
| `image` | Yes | Image URL for the card media. |
| `link` | No | Object `{ type: "tab" \| "page" \| "event"; uri: string }`. When present, the whole card is clickable: the component dispatches **`collectionItemClick`**, opens **`tab`** links in a new window, navigates the document for **`page`**, and only notifies the host for **`event`** (no built-in navigation). |
| `subtitle` | No | Optional metadata (e.g. date or version); not shown in the current markup. |
| `index` | No | Optional ordering hint for consumers; not used for rendering. |

Example item:

```json
{
  "title": "Release notes",
  "subtitle": "v2.4",
  "text": "Changelog highlights.",
  "image": "https://example.com/card.jpg",
  "link": { "type": "page", "uri": "/releases/2-4" }
}
```

## Attributes (snake_case, string values in HTML)

Web component attributes are strings. Objects and arrays must be **JSON-serialized** for `collection`.

| Attribute | Required | Description |
| --- | --- | --- |
| `collection` | Yes | JSON array of items (see above). Whitespace-only or invalid JSON logs an error and yields an empty grid. |
| `id` | No | Optional host id (see `Component` in `types/webcomponent.type.d.ts`). |

The authoring `Component` type also lists optional **`style`** and **`size`**. Layout **column count** in the current implementation is **not** driven by `size`: it is derived from the window width as roughly one column per **~250px** of width, capped by the number of items. Optional `style` may still be useful for host-level styling depending on how your build wires custom-element props; refer to your bundle’s generated typings if you rely on it.

## Layout behavior

- The grid uses `grid-template-columns: repeat(N, auto)` where `N` is computed from `innerWidth` (via `svelte:window`) and the collection length, so the archive **reflows on resize**.
- Cards use full-width images with **`object-fit: cover`** inside the image area.

## Events

| Event | `detail` | When |
| --- | --- | --- |
| `collectionItemClick` | `{ uri: string; link_type?: "tab" \| "page" \| "event" }` | User activates a card that defines **`link`**. |

## Styling (Bulma variables)

The component uses **Bulma 1.x** design tokens on `:host` and in layout (see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/)). Prefer **`--bulma-*`** custom properties for theme alignment.

### CSS custom properties (documented in `extra/docs.ts`)

| Variable | Role |
| --- | --- |
| `--bulma-block-spacing` | Gap between cards in the grid. |
| `--bulma-radius` | Corner radius on the image frame. |
| `--bulma-column-gap` | Space between the image block and text block; also used for spacing under the title. |
| `--bulma-size-5` | Font size for the card title (`h3`). |
| `--bulma-text` | Color for the description paragraph. |
| `--bulma-line-height-main` | Line height for body text. |

Defaults fall back to sensible literals in `styles/webcomponent.scss` if a variable is unset.

### CSS `::part` selectors

| Part | Targets |
| --- | --- |
| `container` | Outer grid wrapper (`part="container"`). |
| `item` | Single card wrapper (`part="item"`). |
| `image` | The `<img>` element (`part="image"`). |
| `title` | The `<h3>` (`part="title"`). |
| `text` | The `<p>` (`part="text"`). |

Example:

```css
hb-vertical-img-txt-archive::part(title) {
  font-weight: 700;
}
```

### Slots

None (`htmlSlots` is empty in `extra/docs.ts`).

## Minimal HTML example

```html
<hb-vertical-img-txt-archive
  collection='[{"title":"One","text":"Short description.","image":"https://placehold.co/300x200","link":{"type":"tab","uri":"https://example.com"}}]'
></hb-vertical-img-txt-archive>
```

Multi-item example (attribute stays on one line in production, or build the string in JS):

```html
<hb-vertical-img-txt-archive
  collection='[{"title":"Alpha","text":"First item.","image":"https://placehold.co/300x200","link":{"type":"event","uri":"open:alpha"}},{"title":"Beta","text":"Second item.","image":"https://placehold.co/300x200","link":{"type":"page","uri":"/beta"}}]'
></hb-vertical-img-txt-archive>
```

## Related files

- Implementation: `component.wc.svelte`
- Authoring types: `types/webcomponent.type.d.ts`
- Metadata, Storybook args, and examples: `extra/docs.ts`
- Styles: `styles/bulma.scss`, `styles/webcomponent.scss`

