kanto-ds 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -4
- package/dist/cdn/kanto.min.js +6619 -0
- package/dist/chunks/{floating-B33g-M_1.js → floating-C9RLa6cZ.js} +2 -2
- package/dist/chunks/floating-C9RLa6cZ.js.map +1 -0
- package/dist/components/forms/kt-date-input/kt-date-input.js +1 -1
- package/dist/components/forms/kt-date-picker/kt-date-picker.js +1 -1
- package/dist/components/forms/kt-input/kt-input.js +1 -1
- package/dist/components/forms/kt-input-menu/kt-input-menu.js +1 -1
- package/dist/components/forms/kt-multi-select/kt-multi-select.js +1 -1
- package/dist/components/forms/kt-select/kt-select.js +1 -1
- package/dist/components/overlays/kt-dropdown/kt-dropdown.js +1 -1
- package/dist/docs/kt-alert.md +52 -0
- package/dist/docs/kt-avatar.md +46 -0
- package/dist/docs/kt-badge.md +104 -0
- package/dist/docs/kt-breadcrumb.md +53 -0
- package/dist/docs/kt-button.md +72 -0
- package/dist/docs/kt-calendar.md +98 -0
- package/dist/docs/kt-card.md +93 -0
- package/dist/docs/kt-chart.md +230 -0
- package/dist/docs/kt-checkbox.md +65 -0
- package/dist/docs/kt-code.md +88 -0
- package/dist/docs/kt-collapsible.md +50 -0
- package/dist/docs/kt-confirm-dialog.md +52 -0
- package/dist/docs/kt-date-input.md +97 -0
- package/dist/docs/kt-date-picker.md +116 -0
- package/dist/docs/kt-drag-drop.md +85 -0
- package/dist/docs/kt-dropdown.md +65 -0
- package/dist/docs/kt-empty-state.md +36 -0
- package/dist/docs/kt-form.md +93 -0
- package/dist/docs/kt-header.md +100 -0
- package/dist/docs/kt-icon.md +68 -0
- package/dist/docs/kt-input-menu.md +90 -0
- package/dist/docs/kt-input.md +137 -0
- package/dist/docs/kt-kbd.md +44 -0
- package/dist/docs/kt-label-input.md +54 -0
- package/dist/docs/kt-meter.md +79 -0
- package/dist/docs/kt-modal.md +62 -0
- package/dist/docs/kt-multi-select.md +115 -0
- package/dist/docs/kt-page-header.md +53 -0
- package/dist/docs/kt-pagination.md +36 -0
- package/dist/docs/kt-progress-bar.md +66 -0
- package/dist/docs/kt-radio-group.md +73 -0
- package/dist/docs/kt-segmented-control.md +70 -0
- package/dist/docs/kt-select.md +80 -0
- package/dist/docs/kt-side-panel.md +59 -0
- package/dist/docs/kt-skeleton.md +46 -0
- package/dist/docs/kt-split-button.md +105 -0
- package/dist/docs/kt-stat.md +57 -0
- package/dist/docs/kt-sub-menu-navigation.md +118 -0
- package/dist/docs/kt-table.md +179 -0
- package/dist/docs/kt-tabs.md +87 -0
- package/dist/docs/kt-textarea.md +59 -0
- package/dist/docs/kt-time-input.md +88 -0
- package/dist/docs/kt-timeline.md +63 -0
- package/dist/docs/kt-toast-container.md +69 -0
- package/dist/docs/kt-toast.md +41 -0
- package/dist/docs/kt-toggle-button-group.md +67 -0
- package/dist/docs/kt-toggle-button.md +52 -0
- package/dist/docs/kt-toggle.md +47 -0
- package/dist/docs/kt-tooltip.md +68 -0
- package/dist/internal/floating.d.ts +4 -0
- package/dist/tokens/colors.css +5 -2
- package/dist/web-types.json +1 -1
- package/package.json +23 -5
- package/dist/chunks/floating-B33g-M_1.js.map +0 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# `<kt-table>`
|
|
2
|
+
|
|
3
|
+
A data table: tri-state sorting, single or multiple selection, paging.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
table.columns = [
|
|
7
|
+
{ key: 'name', label: 'Nom', sortable: true },
|
|
8
|
+
{ key: 'amount', label: 'Amount', sortable: true, align: 'right' },
|
|
9
|
+
{ key: 'status', label: 'Status' },
|
|
10
|
+
];
|
|
11
|
+
table.data = rows;
|
|
12
|
+
table.pageSize = 20;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Sorting
|
|
16
|
+
|
|
17
|
+
Sortable headers are **buttons**, not clickable `<th>`s. A header you can only
|
|
18
|
+
sort with a mouse is a table half the users cannot sort.
|
|
19
|
+
|
|
20
|
+
Each press cycles: unsorted → ascending → descending → unsorted. The third press
|
|
21
|
+
restoring the original order is what makes sorting undoable.
|
|
22
|
+
|
|
23
|
+
Text sorts through `Intl.Collator` with `numeric: true`, in the table's
|
|
24
|
+
`locale`, else the page's `<html lang>`, else the browser's language. Each
|
|
25
|
+
language files accents its own way — "Ångström" next to "Angstrom" in English,
|
|
26
|
+
after "Zeta" in Swedish — and "Entity 2" comes before "Entity 10". Empty values sort to the end whichever way the column points — a
|
|
27
|
+
descending sort should not open with a screen of blanks.
|
|
28
|
+
|
|
29
|
+
`aria-sort` on the header reflects the state.
|
|
30
|
+
|
|
31
|
+
The rows are sorted once per change of `data` or of the order, not on every
|
|
32
|
+
render, so hovering and selecting stay quick on a few thousand rows. That relies
|
|
33
|
+
on `data` being replaced, not edited: assign a new array
|
|
34
|
+
(`table.data = [...rows, added]`) rather than pushing into the old one.
|
|
35
|
+
|
|
36
|
+
## Server-side data
|
|
37
|
+
|
|
38
|
+
With `manual`, the table sorts and pages nothing itself: `data` is the page the
|
|
39
|
+
server sent, shown as it is, and `total-rows` is how many rows the server holds,
|
|
40
|
+
which the pager counts from. Headers and the pager still update `sortKey`,
|
|
41
|
+
`sortDirection` and `page` and fire their events; the application loads what
|
|
42
|
+
they ask for.
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
table.manual = true;
|
|
46
|
+
table.pageSize = 25;
|
|
47
|
+
|
|
48
|
+
async function load() {
|
|
49
|
+
table.loading = true;
|
|
50
|
+
const { rows, total } = await api.customers({
|
|
51
|
+
page: table.page,
|
|
52
|
+
size: table.pageSize,
|
|
53
|
+
sort: table.sortKey,
|
|
54
|
+
direction: table.sortDirection,
|
|
55
|
+
});
|
|
56
|
+
table.data = rows;
|
|
57
|
+
table.totalRows = total;
|
|
58
|
+
table.loading = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
table.addEventListener('kt-sort-change', load);
|
|
62
|
+
table.addEventListener('kt-page-change', load);
|
|
63
|
+
load();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A new sort sends `page` back to 1 and fires only `kt-sort-change` — one event,
|
|
67
|
+
one reload. While `loading`, a table that already shows a page keeps it on
|
|
68
|
+
screen, dimmed and `aria-busy`, so the pager that asked for the next one keeps
|
|
69
|
+
its place and its focus; the loading message only replaces the table before
|
|
70
|
+
anything has loaded.
|
|
71
|
+
|
|
72
|
+
Selection is kept by row identity, so it survives paging. `selectedRows` can
|
|
73
|
+
only return the selected rows of the page on screen: keep the rows themselves
|
|
74
|
+
on your side if you need them across pages.
|
|
75
|
+
|
|
76
|
+
## Thousands of rows
|
|
77
|
+
|
|
78
|
+
`virtual` renders only the rows in view — a few dozen, whatever the length of
|
|
79
|
+
`data` — so five thousand rows scroll as smoothly as fifty, without paging. The
|
|
80
|
+
table then scrolls inside its own box, under a header that stays on top: give
|
|
81
|
+
it a height.
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<kt-table virtual style="height: 480px"></kt-table>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Every row must be the same height. The first one rendered is measured, so
|
|
88
|
+
`compact` and your own padding are followed, but a custom cell taller than the
|
|
89
|
+
rest would put the scroll position out of step with the rows.
|
|
90
|
+
|
|
91
|
+
Sorting and selection work on the data, not on what is rendered: the header box
|
|
92
|
+
selects every row, not only those on screen. `aria-rowcount` and
|
|
93
|
+
`aria-rowindex` let a screen reader announce "row 1,204 of 5,000" with thirty
|
|
94
|
+
rows in the page. A `page-size` takes precedence over `virtual`.
|
|
95
|
+
|
|
96
|
+
## Selection
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<kt-table selectable selection-mode="multiple"></kt-table>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Rows are identified by `id`, then `uid`, then the row object itself. `selected`
|
|
103
|
+
holds those identities; `selectedRows` gives you the rows.
|
|
104
|
+
|
|
105
|
+
Multiple mode adds a select-all box in the header, which goes indeterminate on
|
|
106
|
+
a partial selection.
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
table.addEventListener('kt-selection-change', (e) => {
|
|
110
|
+
console.log(e.detail.selected, e.detail.rows);
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Custom cells
|
|
115
|
+
|
|
116
|
+
`renderCell` returns anything Lit can render — or `undefined` to fall back to
|
|
117
|
+
the raw value, so it only has to handle the columns it cares about.
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
table.renderCell = (row, column) =>
|
|
121
|
+
column.key === 'status'
|
|
122
|
+
? html`<kt-badge variant="category" color="var(--color-success-base)">${row.status}</kt-badge>`
|
|
123
|
+
: undefined;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API
|
|
127
|
+
|
|
128
|
+
| Property | Attribute | Type | Default |
|
|
129
|
+
| --------------- | ---------------- | -------------------------- | ------------------------- |
|
|
130
|
+
| `columns` | — | `KtTableColumn[]` | `[]` |
|
|
131
|
+
| `data` | — | `KtTableRow[]` | `[]` |
|
|
132
|
+
| `selectable` | `selectable` | `boolean` | `false` |
|
|
133
|
+
| `selectionMode` | `selection-mode` | `'single' \| 'multiple'` | `'multiple'` |
|
|
134
|
+
| `selected` | — | `unknown[]` | `[]` |
|
|
135
|
+
| `pageSize` | `page-size` | `number` (0 = no paging) | `0` |
|
|
136
|
+
| `page` | `page` | `number`, from 1 | `1` |
|
|
137
|
+
| `manual` | `manual` | `boolean` | `false` |
|
|
138
|
+
| `totalRows` | `total-rows` | `number` (manual mode) | `0` |
|
|
139
|
+
| `virtual` | `virtual` | `boolean` | `false` |
|
|
140
|
+
| `loading` | `loading` | `boolean` | `false` |
|
|
141
|
+
| `compact` | `compact` | `boolean` | `false` |
|
|
142
|
+
| `sortKey` | `sort-key` | `string \| null` | `null` |
|
|
143
|
+
| `sortDirection` | `sort-direction` | `'asc' \| 'desc' \| null` | `null` |
|
|
144
|
+
| `renderCell` | — | `(row, column) => unknown` | — |
|
|
145
|
+
| `emptyText` | `empty-text` | `string` | `'No data to display'` |
|
|
146
|
+
| `loadingText` | `loading-text` | `string` | `'Loading…'` |
|
|
147
|
+
| `label` | `label` | `string` | `''` |
|
|
148
|
+
| `locale` | `locale` | `string` (BCP 47) | `''` (page, then browser) |
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
interface KtTableColumn {
|
|
152
|
+
key: string;
|
|
153
|
+
label?: string; // falls back to key
|
|
154
|
+
sortable?: boolean;
|
|
155
|
+
align?: 'left' | 'center' | 'right';
|
|
156
|
+
width?: string;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| Getter | Returns |
|
|
161
|
+
| -------------- | ------------------------------- |
|
|
162
|
+
| `sortedRows` | All rows, sorted, before paging |
|
|
163
|
+
| `visibleRows` | The rows currently on screen |
|
|
164
|
+
| `selectedRows` | The selected rows themselves |
|
|
165
|
+
| `totalPages` | Page count |
|
|
166
|
+
|
|
167
|
+
| Event | Detail |
|
|
168
|
+
| --------------------- | -------------------- |
|
|
169
|
+
| `kt-sort-change` | `{ key, direction }` |
|
|
170
|
+
| `kt-selection-change` | `{ selected, rows }` |
|
|
171
|
+
| `kt-row-click` | `{ row, index }` |
|
|
172
|
+
| `kt-page-change` | `{ page }` |
|
|
173
|
+
|
|
174
|
+
| Part | Description |
|
|
175
|
+
| ------------- | ------------- |
|
|
176
|
+
| `table` | The `<table>` |
|
|
177
|
+
| `header-cell` | A `<th>` |
|
|
178
|
+
| `row` | A `<tr>` |
|
|
179
|
+
| `cell` | A `<td>` |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# `<kt-tabs>`
|
|
2
|
+
|
|
3
|
+
An underlined tab bar.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
tabs.tabs = [
|
|
7
|
+
{ value: 'usage', label: 'Usage' },
|
|
8
|
+
{ value: 'api', label: 'API', icon: 'code' },
|
|
9
|
+
{ value: 'theme', label: 'Theme', disabled: true },
|
|
10
|
+
];
|
|
11
|
+
tabs.value = 'usage';
|
|
12
|
+
tabs.addEventListener('kt-change', (e) => show(e.detail.value));
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Against `<kt-segmented-control>`
|
|
16
|
+
|
|
17
|
+
They look similar in a screenshot and mean different things.
|
|
18
|
+
|
|
19
|
+
A **segmented control** picks a _value_ — a date range, a unit, a sort order —
|
|
20
|
+
and belongs inside a form or a toolbar. **Tabs** switch which _region of the
|
|
21
|
+
page_ you are looking at; they read as part of the page structure rather than
|
|
22
|
+
as a control sitting on it.
|
|
23
|
+
|
|
24
|
+
The shapes say so: an inset track with a raised pill, versus a rule with one
|
|
25
|
+
segment underlined. Using the wrong one is not a styling mistake, it is telling
|
|
26
|
+
the reader the wrong thing about what will happen.
|
|
27
|
+
|
|
28
|
+
## Panels
|
|
29
|
+
|
|
30
|
+
`<kt-tabs>` manages selection only — swapping the panel is yours, the same
|
|
31
|
+
contract as the segmented control. Give each tab a `panel` id and the tab
|
|
32
|
+
carries `aria-controls`, so the pairing is announced:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<kt-tabs></kt-tabs>
|
|
36
|
+
<section id="usage-panel" role="tabpanel">…</section>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
tabs.tabs = [{ value: 'usage', label: 'Usage', panel: 'usage-panel' }];
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Loading
|
|
44
|
+
|
|
45
|
+
`loading` renders three placeholder tabs instead of the real ones, so a bar
|
|
46
|
+
waiting on a fetch reserves its height rather than appearing under the content
|
|
47
|
+
already on screen.
|
|
48
|
+
|
|
49
|
+
## Keyboard
|
|
50
|
+
|
|
51
|
+
One tab stop for the whole bar, as a tablist should have.
|
|
52
|
+
|
|
53
|
+
| Key | Does |
|
|
54
|
+
| -------------- | ----------------------------------- |
|
|
55
|
+
| `←` / `→` | Previous / next tab, and selects it |
|
|
56
|
+
| `Home` / `End` | First / last selectable tab |
|
|
57
|
+
|
|
58
|
+
Disabled tabs are skipped, and selection wraps at both ends.
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
| Property | Attribute | Type | Default |
|
|
63
|
+
| ---------- | ---------- | -------------------------- | ------- |
|
|
64
|
+
| `tabs` | — | `KtTab[]` | `[]` |
|
|
65
|
+
| `value` | `value` | `string \| number \| null` | `null` |
|
|
66
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
67
|
+
| `loading` | `loading` | `boolean` | `false` |
|
|
68
|
+
| `label` | `label` | `string` | `''` |
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
interface KtTab {
|
|
72
|
+
value: string | number;
|
|
73
|
+
label?: string; // falls back to String(value)
|
|
74
|
+
icon?: string; // Lucide name, kebab-case
|
|
75
|
+
disabled?: boolean;
|
|
76
|
+
panel?: string; // id of the region it controls
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
| Event | Detail |
|
|
81
|
+
| ----------- | ----------------------------------------------------------------- |
|
|
82
|
+
| `kt-change` | `{ value, tab }` — not fired when the active tab is clicked again |
|
|
83
|
+
|
|
84
|
+
| Part | Description |
|
|
85
|
+
| ------ | ----------- |
|
|
86
|
+
| `base` | The tablist |
|
|
87
|
+
| `tab` | One tab |
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# `<kt-textarea>`
|
|
2
|
+
|
|
3
|
+
A multi-line field, with the same borderless fill and outline states as
|
|
4
|
+
`<kt-input>`.
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<kt-textarea placeholder="Describe the incident..." rows="5"></kt-textarea>
|
|
8
|
+
<kt-textarea maxlength="280" name="bio"></kt-textarea>
|
|
9
|
+
<kt-textarea error="280 characters maximum"></kt-textarea>
|
|
10
|
+
<kt-textarea resize="vertical" placeholder="As long as it needs to be"></kt-textarea>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Resizing
|
|
14
|
+
|
|
15
|
+
Off by default. The grip is drawn by the operating system, lands on the field's
|
|
16
|
+
rounded corner and belongs to no design system — and a field the layout sized is
|
|
17
|
+
not usually the reader's to resize. Set `resize="vertical"` where the content
|
|
18
|
+
genuinely varies and the extra height will not break the screen around it.
|
|
19
|
+
|
|
20
|
+
## The counter
|
|
21
|
+
|
|
22
|
+
Setting `maxlength` shows a `0 / 280` counter, which turns red at the limit.
|
|
23
|
+
It exists because a `maxlength` alone is a silent failure: the browser simply
|
|
24
|
+
stops accepting keystrokes, with nothing on screen to explain why.
|
|
25
|
+
|
|
26
|
+
The counter is `aria-live="polite"`, so it is announced as the user approaches
|
|
27
|
+
the limit rather than on every character.
|
|
28
|
+
|
|
29
|
+
## Form participation
|
|
30
|
+
|
|
31
|
+
Form-associated, like `<kt-input>`: it serialises under its `name`, resets with
|
|
32
|
+
the form, and reports `valueMissing` when `required` and empty.
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
| Property | Attribute | Type | Default |
|
|
37
|
+
| ------------- | ------------- | ---------------------- | -------- |
|
|
38
|
+
| `value` | `value` | `string` | `''` |
|
|
39
|
+
| `name` | `name` | `string` | `''` |
|
|
40
|
+
| `placeholder` | `placeholder` | `string` | `''` |
|
|
41
|
+
| `rows` | `rows` | `number` | `3` |
|
|
42
|
+
| `maxlength` | `maxlength` | `number` | — |
|
|
43
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
44
|
+
| `readonly` | `readonly` | `boolean` | `false` |
|
|
45
|
+
| `required` | `required` | `boolean` | `false` |
|
|
46
|
+
| `error` | `error` | `string` | `''` |
|
|
47
|
+
| `label` | `label` | `string` | `''` |
|
|
48
|
+
| `resize` | `resize` | `'vertical' \| 'none'` | `'none'` |
|
|
49
|
+
|
|
50
|
+
| Event | Detail |
|
|
51
|
+
| ----------- | ------------------- |
|
|
52
|
+
| `kt-input` | `{ value: string }` |
|
|
53
|
+
| `kt-change` | `{ value: string }` |
|
|
54
|
+
|
|
55
|
+
| Part | Description |
|
|
56
|
+
| --------- | ----------------------- |
|
|
57
|
+
| `base` | The field container |
|
|
58
|
+
| `control` | The native `<textarea>` |
|
|
59
|
+
| `counter` | The character counter |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# `<kt-time-input>`
|
|
2
|
+
|
|
3
|
+
A time of day typed into its parts — hours, minutes and, if asked, seconds.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<kt-time-input label="Starts at" name="start" step="15" min="08:00" max="20:00"></kt-time-input>
|
|
7
|
+
<kt-time-input label="Logged at" seconds value="09:15:30"></kt-time-input>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
It shares its keyboard with [`<kt-date-input>`](./kt-date-input.md):
|
|
11
|
+
each part is a segment, typed digit by digit, stepped with the arrows, the
|
|
12
|
+
focus moving on as each fills — `1430` is half past two in the afternoon.
|
|
13
|
+
|
|
14
|
+
## The reader's clock
|
|
15
|
+
|
|
16
|
+
The clock is the one the reader's language reads: `14:30` in London and
|
|
17
|
+
Paris, `02:30 PM` in New York, `14.30` in Copenhagen. `hour-cycle` sets it
|
|
18
|
+
instead — `h12` for hours from 1 to 12 with AM/PM, `h23` for 0 to 23. The
|
|
19
|
+
locale is `locale` if set, else the page's `lang`, else the browser's
|
|
20
|
+
language.
|
|
21
|
+
|
|
22
|
+
On a 12-hour clock the AM/PM segment takes the first letter of either — `a`
|
|
23
|
+
or `p` — or the arrows. The hour reads with it: 12 AM is midnight, 12 PM noon.
|
|
24
|
+
|
|
25
|
+
## The value
|
|
26
|
+
|
|
27
|
+
Whatever the clock shows, the value is the 24-hour time `<input type="time">`
|
|
28
|
+
and a server speak: `14:30`, or `14:30:05` with `seconds`. It is `null` until
|
|
29
|
+
every segment is filled; a segment left at one digit is written out when the
|
|
30
|
+
focus leaves it, `9` becoming `09`. `kt-change` fires once per time, not per
|
|
31
|
+
keystroke.
|
|
32
|
+
|
|
33
|
+
`step` is how many minutes the arrows move by: with `step="15"`, `↑` from
|
|
34
|
+
`09:07` goes to `09:15`. Typed minutes are not rounded.
|
|
35
|
+
|
|
36
|
+
A time before `min` or after `max` puts the field in its error state, the
|
|
37
|
+
message in a tooltip on the alert icon. The value is `null` and the form sees
|
|
38
|
+
a `badInput`.
|
|
39
|
+
|
|
40
|
+
| Key | Does |
|
|
41
|
+
| ------------- | ----------------------------------------------------- |
|
|
42
|
+
| digits | fill the segment in focus |
|
|
43
|
+
| `a` / `p` | AM or PM, on a 12-hour clock |
|
|
44
|
+
| `↑` / `↓` | step the segment, wrapping round the clock |
|
|
45
|
+
| `Home`/`End` | the segment's first or last value |
|
|
46
|
+
| `←` / `→` | the segment before or after |
|
|
47
|
+
| `Backspace` | erases a digit, then steps back to the segment before |
|
|
48
|
+
| `:` `.` space | the next segment |
|
|
49
|
+
|
|
50
|
+
## In a form
|
|
51
|
+
|
|
52
|
+
A form control: it submits the value under `name`, and nothing while the time
|
|
53
|
+
is incomplete. `required` blocks submission while it is empty, `error` shows a
|
|
54
|
+
message, and `form.reset()` puts back the initial value.
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
| Property | Attribute | Type | Default |
|
|
59
|
+
| ----------- | ------------ | -------------------------------- | ------------------ |
|
|
60
|
+
| `value` | `value` | `string \| null` | `null` |
|
|
61
|
+
| `seconds` | `seconds` | `boolean` | `false` |
|
|
62
|
+
| `hourCycle` | `hour-cycle` | `'h12' \| 'h23' \| ''` | `''`, the locale's |
|
|
63
|
+
| `step` | `step` | `number` | `1` |
|
|
64
|
+
| `min` | `min` | `string` | `''` |
|
|
65
|
+
| `max` | `max` | `string` | `''` |
|
|
66
|
+
| `name` | `name` | `string` | `''` |
|
|
67
|
+
| `label` | `label` | `string` | `''` |
|
|
68
|
+
| `locale` | `locale` | `string` | page, then browser |
|
|
69
|
+
| `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
|
|
70
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
71
|
+
| `required` | `required` | `boolean` | `false` |
|
|
72
|
+
| `error` | `error` | `string` | `''` |
|
|
73
|
+
|
|
74
|
+
| Event | Detail |
|
|
75
|
+
| ----------- | --------------------------------------------------------- |
|
|
76
|
+
| `kt-change` | `{ value: string \| null }` — `HH:MM`, `HH:MM:SS`, `null` |
|
|
77
|
+
|
|
78
|
+
| Part | Description |
|
|
79
|
+
| --------- | ---------------------------------------- |
|
|
80
|
+
| `field` | The field |
|
|
81
|
+
| `segment` | An hour, minute, second or AM/PM segment |
|
|
82
|
+
|
|
83
|
+
## Accessibility
|
|
84
|
+
|
|
85
|
+
The field is a `group` named by `label`. Each segment is a `spinbutton` named
|
|
86
|
+
"Hour", "Minute", "Second" or "AM or PM", with its range and value. `Tab`
|
|
87
|
+
walks the segments. On a phone, the number segments bring up the numeric
|
|
88
|
+
keypad.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# `<kt-timeline>`
|
|
2
|
+
|
|
3
|
+
An ordered run of events on a rail.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<kt-timeline>
|
|
7
|
+
<kt-timeline-item heading="Deployed" time="09:24" variant="success" icon="rocket">
|
|
8
|
+
Build 4210 went to production.
|
|
9
|
+
</kt-timeline-item>
|
|
10
|
+
<kt-timeline-item heading="Review requested" time="09:02" icon="eye">
|
|
11
|
+
Two files, forty lines.
|
|
12
|
+
</kt-timeline-item>
|
|
13
|
+
<kt-timeline-item heading="Branch opened" time="08:41"></kt-timeline-item>
|
|
14
|
+
</kt-timeline>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The same shape keeps reappearing — an activity feed, an audit log, a changelog,
|
|
18
|
+
the stages of a delivery, the roles on a résumé — and every time it gets rebuilt
|
|
19
|
+
out of a list, a pseudo-element line and a hand-placed dot.
|
|
20
|
+
|
|
21
|
+
The fiddly part is the rail. It must start at the first dot and stop at the
|
|
22
|
+
last, which a border on the list cannot do: a border runs the full height, so
|
|
23
|
+
the line overshoots below the final event. Here each item draws its own segment
|
|
24
|
+
hanging below its marker, and the last item drops it. The item cannot work out
|
|
25
|
+
on its own that it is last — `:last-child` matches in the light tree, which the
|
|
26
|
+
item's own shadow styles cannot reach — so `<kt-timeline>` reads its slot and
|
|
27
|
+
sets `last` on the final child, again whenever the list changes.
|
|
28
|
+
|
|
29
|
+
Without an `icon` the marker is a 9px dot; with one it is a 24px tinted circle.
|
|
30
|
+
Mixing the two in one list is fine and reads as emphasis — give the events that
|
|
31
|
+
matter an icon and leave the rest as dots.
|
|
32
|
+
|
|
33
|
+
`variant` only colours the marker. As with every status colour in Kanto, it
|
|
34
|
+
never carries the meaning alone: the heading says what happened.
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### `<kt-timeline>`
|
|
39
|
+
|
|
40
|
+
| Property | Attribute | Type | Default |
|
|
41
|
+
| --------- | --------- | --------- | ------- |
|
|
42
|
+
| `compact` | `compact` | `boolean` | `false` |
|
|
43
|
+
|
|
44
|
+
`compact` is passed down to every item, so set it once on the list.
|
|
45
|
+
|
|
46
|
+
### `<kt-timeline-item>`
|
|
47
|
+
|
|
48
|
+
| Property | Attribute | Type | Default |
|
|
49
|
+
| --------- | --------- | ------------------------------------------------------------ | ----------- |
|
|
50
|
+
| `heading` | `heading` | `string` | `''` |
|
|
51
|
+
| `time` | `time` | `string` | `''` |
|
|
52
|
+
| `icon` | `icon` | `string` | `''` |
|
|
53
|
+
| `variant` | `variant` | `neutral \| primary \| success \| warning \| danger \| info` | `'neutral'` |
|
|
54
|
+
| `last` | `last` | `boolean` | `false` |
|
|
55
|
+
|
|
56
|
+
`time` is a free string — `09:24`, `2 days ago`, `June 2026` — because a
|
|
57
|
+
timeline is as often a résumé as it is a log. `last` is managed by the parent;
|
|
58
|
+
set it yourself only if you are laying items out without `<kt-timeline>`.
|
|
59
|
+
|
|
60
|
+
| Slot | Description |
|
|
61
|
+
| --------- | ------------------------------------------- |
|
|
62
|
+
| _default_ | The body of the event |
|
|
63
|
+
| `actions` | Trailing controls, aligned with the heading |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# `<kt-toast-container>` / `toaster`
|
|
2
|
+
|
|
3
|
+
The stack that owns the toasts on screen, and the imperative shortcut to it.
|
|
4
|
+
|
|
5
|
+
## `toaster` — the usual way
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { toaster } from 'kanto-ds';
|
|
9
|
+
|
|
10
|
+
toaster.success('Entity created');
|
|
11
|
+
toaster.error('Could not save', { description: 'Try again in a moment.' });
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
It creates one `<kt-toast-container>` on `<body>` the first time it is called
|
|
15
|
+
and reuses it — including one you already put in your own markup, which it
|
|
16
|
+
adopts rather than duplicating.
|
|
17
|
+
|
|
18
|
+
Defaults that encode the intent: success and info dismiss after 4s, warnings
|
|
19
|
+
after 6s, and **errors never dismiss themselves**. An error the user did not
|
|
20
|
+
happen to be looking at is an error they never saw.
|
|
21
|
+
|
|
22
|
+
## `<kt-toast-container>` — when placement matters
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<kt-toast-container position="top-right" limit="3"></kt-toast-container>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
container.show({ variant: 'success', heading: 'Entity created', duration: 4000 });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The container owns the toasts: it appends them, and removes them when they ask
|
|
33
|
+
to go. A toast that removes itself from a list it does not own is how you end up
|
|
34
|
+
with two on screen and neither leaving.
|
|
35
|
+
|
|
36
|
+
Newest toasts stack towards the screen edge, so the newest is always nearest the
|
|
37
|
+
corner. Past `limit`, the oldest is dropped.
|
|
38
|
+
|
|
39
|
+
**Hovering the stack pauses every countdown**, and leaving it resumes them —
|
|
40
|
+
otherwise a toast expires under the pointer of someone reading it.
|
|
41
|
+
|
|
42
|
+
The container itself does not intercept pointer events; only the toasts inside
|
|
43
|
+
it do, so the rest of the corner stays clickable.
|
|
44
|
+
|
|
45
|
+
## API — `<kt-toast-container>`
|
|
46
|
+
|
|
47
|
+
| Property | Attribute | Type | Default |
|
|
48
|
+
| ---------- | ---------- | -------------------------------------------------------------- | ---------------- |
|
|
49
|
+
| `position` | `position` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `'bottom-right'` |
|
|
50
|
+
| `limit` | `limit` | `number` | `5` |
|
|
51
|
+
|
|
52
|
+
| Method / getter | Description |
|
|
53
|
+
| ---------------- | ----------------------------- |
|
|
54
|
+
| `show(options)` | Raises a toast, returns it |
|
|
55
|
+
| `dismiss(toast)` | Removes one |
|
|
56
|
+
| `clear()` | Removes all |
|
|
57
|
+
| `toasts` | Those on screen, oldest first |
|
|
58
|
+
|
|
59
|
+
## API — `toaster`
|
|
60
|
+
|
|
61
|
+
| Member | Description |
|
|
62
|
+
| ----------------------------------------------- | ------------------------------------ |
|
|
63
|
+
| `success/info/warning/error(heading, options?)` | Raises a toast, returns it |
|
|
64
|
+
| `show(options)` | The general form |
|
|
65
|
+
| `getContainer()` | The container, creating it if needed |
|
|
66
|
+
| `clear()` | Clears the stack |
|
|
67
|
+
| `position` | Where an auto-created container sits |
|
|
68
|
+
|
|
69
|
+
See [`<kt-toast>`](./kt-toast.md) for a single notification.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# `<kt-toast>`
|
|
2
|
+
|
|
3
|
+
A transient notification.
|
|
4
|
+
|
|
5
|
+
For raising one, reach for [`toaster`](./kt-toast-container.md)
|
|
6
|
+
— this page is the element itself.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<kt-toast variant="warning" heading="Approaching your quota" dismissible duration="6000"></kt-toast>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`heading`, not `title`: every element already has a `title`, and shadowing it
|
|
15
|
+
would turn the toast into its own tooltip.
|
|
16
|
+
|
|
17
|
+
Errors are `role="alert"` / `aria-live="assertive"` and interrupt the screen
|
|
18
|
+
reader; everything else is `role="status"` / `polite` and waits its turn.
|
|
19
|
+
|
|
20
|
+
This is the only element in Kanto with a real shadow, and the only one that
|
|
21
|
+
blurs what is behind it. A toast floats above the page, so it says so.
|
|
22
|
+
|
|
23
|
+
## API
|
|
24
|
+
|
|
25
|
+
| Property | Attribute | Type | Default |
|
|
26
|
+
| ------------- | ------------- | ---------------------------------------------------- | --------------- |
|
|
27
|
+
| `variant` | `variant` | `'success' \| 'information' \| 'warning' \| 'error'` | `'information'` |
|
|
28
|
+
| `heading` | `heading` | `string` | `''` |
|
|
29
|
+
| `description` | `description` | `string` | `''` |
|
|
30
|
+
| `dismissible` | `dismissible` | `boolean` | `false` |
|
|
31
|
+
| `duration` | `duration` | `number` (ms, 0 = stays) | `0` |
|
|
32
|
+
|
|
33
|
+
| Method | Description |
|
|
34
|
+
| ---------- | --------------------------- |
|
|
35
|
+
| `close()` | Fires `kt-toast-close` |
|
|
36
|
+
| `pause()` | Freezes the countdown |
|
|
37
|
+
| `resume()` | Resumes it where it stopped |
|
|
38
|
+
|
|
39
|
+
| Event | Fired when |
|
|
40
|
+
| ---------------- | ---------------------------- |
|
|
41
|
+
| `kt-toast-close` | The toast asks to be removed |
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# `<kt-toggle-button-group>`
|
|
2
|
+
|
|
3
|
+
Joins `<kt-toggle-button>`s into one bar and owns their selection.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<kt-toggle-button-group label="View">
|
|
7
|
+
<kt-toggle-button value="list">List</kt-toggle-button>
|
|
8
|
+
<kt-toggle-button value="grid">Grid</kt-toggle-button>
|
|
9
|
+
<kt-toggle-button value="map">Map</kt-toggle-button>
|
|
10
|
+
</kt-toggle-button-group>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
group.addEventListener('kt-change', (e) => console.log(e.detail.value));
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The group owns the selection
|
|
18
|
+
|
|
19
|
+
Buttons are slotted rather than described by an options array, so a label can be
|
|
20
|
+
anything — an icon, a count, a badge.
|
|
21
|
+
|
|
22
|
+
Inside a group a button becomes **controlled**: it no longer flips itself and no
|
|
23
|
+
longer emits. The group reads the click, works out what the selection now is,
|
|
24
|
+
and writes `selected` back down. That is what keeps one press from producing two
|
|
25
|
+
contradictory `kt-change` events.
|
|
26
|
+
|
|
27
|
+
So: listen on the group, never on its buttons.
|
|
28
|
+
|
|
29
|
+
## Single and multiple
|
|
30
|
+
|
|
31
|
+
Single mode behaves like a radio group with a deselect — pressing the active
|
|
32
|
+
button clears it, and `value` is a string or `null`.
|
|
33
|
+
|
|
34
|
+
`multiple` makes it a set of checkboxes drawn together, and `value` becomes an
|
|
35
|
+
array.
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<kt-toggle-button-group multiple orientation="vertical"></kt-toggle-button-group>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Corner rounding belongs to the group, which is the only thing that knows which
|
|
42
|
+
button is first and which is last; it drives `--kt-toggle-button-radius` on each
|
|
43
|
+
child through CSS.
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
| Property | Attribute | Type | Default |
|
|
48
|
+
| ------------- | ------------- | ---------------------------- | -------------- |
|
|
49
|
+
| `value` | — | `string \| string[] \| null` | `null` |
|
|
50
|
+
| `multiple` | `multiple` | `boolean` | `false` |
|
|
51
|
+
| `orientation` | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` |
|
|
52
|
+
| `label` | `label` | `string` | `''` |
|
|
53
|
+
|
|
54
|
+
| Event | Detail |
|
|
55
|
+
| ----------- | ----------- |
|
|
56
|
+
| `kt-change` | `{ value }` |
|
|
57
|
+
|
|
58
|
+
| Slot | Description |
|
|
59
|
+
| --------- | ----------------------------- |
|
|
60
|
+
| _default_ | `<kt-toggle-button>` children |
|
|
61
|
+
|
|
62
|
+
| Part | Description |
|
|
63
|
+
| ------ | ------------------- |
|
|
64
|
+
| `base` | The group container |
|
|
65
|
+
|
|
66
|
+
See [`<kt-toggle-button>`](./kt-toggle-button.md) for the
|
|
67
|
+
button itself.
|