npm package: @mmlogic/components
Description: Stencil.js web component library for dynamic forms and virtual-scroll data tables
Author: Mosterd
Latest version: 0.1.9

# @mmlogic/components

A **Stencil.js v4** web component library for rendering dynamic forms and paginated data tables from API descriptors. Components are framework-agnostic and work in any HTML/JS environment. An Angular output target is included.

## Installation

```bash
npm install @mmlogic/components
```

## Usage

### Vanilla HTML / JS

```html
<script type="module" src="node_modules/@mmlogic/components/dist/mosterd-components/mosterd-components.esm.js"></script>

<mrd-form id="my-form"></mrd-form>

<script>
  const form = document.getElementById('my-form');
  form.layout = { /* ClientLayout descriptor */ };
  form.values = { name: 'Alice' };
  form.addEventListener('mrdSubmit', e => console.log(e.detail));
</script>
```

### Via CDN (unpkg)

```html
<script type="module" src="https://unpkg.com/@mmlogic/components/dist/mosterd-components/mosterd-components.esm.js"></script>
```

### Angular

```ts
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { defineCustomElements } from '@mmlogic/components/loader';

defineCustomElements();

@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
export class AppModule {}
```

---

## Components

### `<mrd-form>`

Renders a complete form from a layout descriptor. Handles validation, RTL layout (Arabic locale), and emits the submitted values.

```html
<mrd-form id="form" locale="nl-NL"></mrd-form>
```

```js
form.layout = {
  title: 'New order',
  items: [
    { type: 'FIELD', field: { name: 'name', label: 'Name', dataType: 'TEXT', required: true } },
    { type: 'FIELD', field: { name: 'amount', label: 'Amount', dataType: 'CURRENCY' } },
  ]
};
form.addEventListener('mrdSubmit', e => console.log(e.detail.values));
form.addEventListener('mrdCancel', () => console.log('cancelled'));
```

**Props**

| Prop | Type | Description |
|---|---|---|
| `layout` | `ClientLayout` | Form descriptor with title and items |
| `values` | `Record<string, any>` | Initial field values |
| `locale` | `string` | BCP 47 locale (default: `navigator.language`) |
| `disabled` | `boolean` | Disables all inputs |

**Events**

| Event | Detail | Description |
|---|---|---|
| `mrdSubmit` | `{ values: Record<string, any> }` | Form submitted and valid |
| `mrdCancel` | — | Cancel button clicked |

---

### `<mrd-table>`

A virtual-scroll paginated table. Only the visible rows (plus a configurable buffer) are rendered in the DOM. Pages are fetched on demand via an event.

```html
<mrd-table id="table"></mrd-table>
```

```js
const table = document.getElementById('table');

table.columns       = view.values;       // TableColumn[] from dashboard API
table.totalElements = page.totalElements;
table.pageSize      = page.size;
table.tableHeight   = 500;               // px, fixed container height
table.locale        = 'nl-NL';
table.defaultSort   = view.defaultSort ?? '';

table.addEventListener('mrdLoadPage', async (e) => {
  const { page, sort } = e.detail;
  const url      = `${dataHref}?page=${page}${sort ? '&sort=' + sort : ''}`;
  const result   = await fetch(url, { headers: { Authorization: `Bearer ${token}` } }).then(r => r.json());
  const rows     = Object.values(result._embedded ?? {})[0] ?? [];
  await table.setPage(page, rows);
});

await table.init();
await table.setPage(0, page0Rows); // inject pre-fetched page 0
```

**Props**

| Prop | Type | Default | Description |
|---|---|---|---|
| `columns` | `TableColumn[]` | `[]` | Column definitions |
| `totalElements` | `number` | `0` | Total records (`0` = non-paginated mode using `rows` prop) |
| `pageSize` | `number` | `20` | Records per page |
| `rowHeight` | `number` | `36` | Row height in px |
| `tableHeight` | `number` | `500` | Container height in px |
| `locale` | `string` | `navigator.language` | Locale for cell formatting |
| `defaultSort` | `string` | `''` | Initial sort, e.g. `"timestamp,desc"` or `"name"` |
| `rows` | `Record[]` | `[]` | Rows for non-paginated mode |

**Events**

| Event | Detail | Description |
|---|---|---|
| `mrdLoadPage` | `{ page: number; sort: string }` | Fired when a page needs to be fetched |

**Methods**

| Method | Description |
|---|---|
| `init()` | Reset state and render window; call after props are set and before `setPage(0, …)` |
| `setPage(page, rows)` | Inject fetched rows for a page number |

**Column sorting**

Click any column header to sort. Clicking again toggles ASC ↔ DESC. The active column is highlighted and shows ▲ / ▼. The `sort` value in the `mrdLoadPage` event is the raw query-param value (`"fieldname"` or `"fieldname,desc"`).

---

### Field components

Individual input components can be used standalone. Each emits `mrdChange` and `mrdBlur`.

| Component | DataType | Description |
|---|---|---|
| `<mrd-text-field>` | `TEXT` | Single-line text input |
| `<mrd-textarea-field>` | `TEXTBLOCK` | Rich text editor (Quill, lazy-loaded) |
| `<mrd-number-field>` | `INTEGER`, `DECIMAL`, `PERCENTAGE` | Numeric input |
| `<mrd-currency-field>` | `CURRENCY` | Amount + ISO-4217 currency selector |
| `<mrd-boolean-field>` | `BOOLEAN` | Toggle switch |
| `<mrd-date-field>` | `DATE` | Date picker |
| `<mrd-datetime-field>` | `DATETIME` | Date + time picker |
| `<mrd-time-field>` | `TIME` | Time picker |
| `<mrd-email-field>` | `EMAIL` | Email input with validation |
| `<mrd-hyperlink-field>` | `HYPERLINK` | URL input with validation |
| `<mrd-list-field>` | `LIST` | Single dropdown or multi-checkbox |
| `<mrd-file-field>` | `FILE` | Drag-and-drop file upload |
| `<mrd-image-field>` | `IMAGE` | Drag-and-drop image upload with preview |
| `<mrd-relation-field>` | `RELATION` | Search (event-driven) or dropdown |

**Common props for field components**

| Prop | Type | Description |
|---|---|---|
| `name` | `string` | Field name, included in `mrdChange` detail |
| `label` | `string` | Visible label |
| `value` | `any` | Current value |
| `required` | `boolean` | Shows validation error when empty on blur |
| `disabled` | `boolean` | Disables the input |
| `locale` | `string` | BCP 47 locale for formatting |

**Events for field components**

| Event | Detail |
|---|---|
| `mrdChange` | `{ name: string; value: any }` |
| `mrdBlur` | `{ name: string; value: any }` |

---

## Theming

All visual properties are CSS custom properties. Override them on `:root` or any ancestor element:

```css
:root {
  --mrd-color-primary:      #2563eb;
  --mrd-color-neutral-100:  #f3f4f6;
  --mrd-border-radius:      0.375rem;
  --mrd-font-size-sm:       0.875rem;
  /* see full list in src/global/variables.scss */
}
```

---

## TypeScript types

Types are exported from the package root:

```ts
import type { TableColumn } from '@mmlogic/components/dist/types/utils/cell-renderer';
import type { ClientLayout, ClientLayoutItem } from '@mmlogic/components/dist/types/types/client-layout';
```

---

## License

MIT