@ixfx/components 0.5.2 → 0.5.4
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/bundle/index.d.ts +97 -2
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +348 -30
- package/bundle/index.js.map +1 -1
- package/dist/index.d.ts +97 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +326 -8
- package/dist/index.js.map +1 -1
- package/docs-user/tabs.md +95 -1
- package/docs-user/vertical-list.md +44 -4
- package/package.json +1 -1
package/docs-user/tabs.md
CHANGED
|
@@ -261,4 +261,98 @@ Manages multiple `ixfx-tab-panel` children, selecting one at a time.
|
|
|
261
261
|
| `getSelectedElement()` | — | `TabPanelElement \| undefined` | Get currently selected panel |
|
|
262
262
|
| `selectPanel(id)` | `string` | `boolean` | Select panel by ID; returns true if found |
|
|
263
263
|
| `selectTab(nameOrTitle)` | `string` | `boolean` | Select a tab as if it was clicked: activates the matching header (via the list associated with `syncWithList`) and shows its panel. `nameOrTitle` can be a tab's label text (case-insensitive) or the panel id its `for` attribute points at. Returns true if a panel was found |
|
|
264
|
-
| `syncWithList(el)` | `TabListElement` | `void` | Associates this panels element with a tab list, syncing panel selection to it. Must be called before `selectTab()` if the tab header should be activated |
|
|
264
|
+
| `syncWithList(el)` | `TabListElement` | `void` | Associates this panels element with a tab list, syncing panel selection to it. Must be called before `selectTab()` if the tab header should be activated |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## `TabController`
|
|
269
|
+
|
|
270
|
+
A class that wires an `ixfx-tab-list` to an `ixfx-tab-panels` container. It keeps track of the tabs, mirrors the selected header into the panels, and offers an API to select, add, remove, rename and reorder tabs.
|
|
271
|
+
|
|
272
|
+
### Setup
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
import { TabController } from '@ixfx/components';
|
|
276
|
+
|
|
277
|
+
const controller = new TabController(
|
|
278
|
+
document.querySelector('ixfx-tab-list'),
|
|
279
|
+
document.querySelector('ixfx-tab-panels'),
|
|
280
|
+
{
|
|
281
|
+
onSelect: (tab, previous) => console.log(`selected ${tab.id}`),
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
controller.connect();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Call `connect()` to start wiring; `disconnect()` removes listeners, observers and commands.
|
|
288
|
+
|
|
289
|
+
### Options
|
|
290
|
+
|
|
291
|
+
| Option | Type | Description |
|
|
292
|
+
|--------|------|-------------|
|
|
293
|
+
| `commands` | `CommandRegistry \| undefined` | When provided, a command is registered per tab (id = tab id). Invoking `commands.invoke(id)` selects the tab |
|
|
294
|
+
| `onSelect` | `(tab, previous) => void` | Fired after a tab is selected (click or programmatic) |
|
|
295
|
+
| `onAdd` | `(tab) => void` | Fired after a tab is added |
|
|
296
|
+
| `onRemove` | `(tab) => void` | Fired after a tab is removed |
|
|
297
|
+
| `onReorder` | `(tabs) => void` | Fired after tabs are reordered |
|
|
298
|
+
|
|
299
|
+
### Methods
|
|
300
|
+
|
|
301
|
+
| Method | Argument | Returns | Description |
|
|
302
|
+
|--------|----------|---------|-------------|
|
|
303
|
+
| `connect()` | — | `void` | Wire the list and panels, register commands |
|
|
304
|
+
| `disconnect()` | — | `void` | Remove listeners, observers and commands |
|
|
305
|
+
| `selectTab(sel)` | `string \| TabListItemElement` | `boolean` | Select a tab as if it was clicked. `sel` may be a tab id, the panel id its `for` points at, its label text (case-insensitive), or the item element |
|
|
306
|
+
| `selectNext()` | — | `boolean` | Select the next tab (wraps) |
|
|
307
|
+
| `selectPrevious()` | — | `boolean` | Select the previous tab (wraps) |
|
|
308
|
+
| `getSelected()` | — | `Tab \| undefined` | Currently selected tab |
|
|
309
|
+
| `getTabs()` | — | `Tab[]` | All managed tabs |
|
|
310
|
+
| `getTab(id)` | `string` | `Tab \| undefined` | Tab by id |
|
|
311
|
+
| `addTab(opts)` | `TabAddOptions` | `Tab` | Add a tab (item + panel) |
|
|
312
|
+
| `removeTab(sel)` | `string \| TabListItemElement` | `boolean` | Remove a tab; selects an adjacent tab if the removed one was selected |
|
|
313
|
+
| `renameTab(sel, label)` | `string \| TabListItemElement, string` | `boolean` | Change a tab's label |
|
|
314
|
+
| `setTabIcon(sel, iconName?)` | `string \| TabListItemElement, string \| undefined` | `boolean` | Set or clear a tab's icon |
|
|
315
|
+
|
|
316
|
+
A `Tab` is `{ id, forId, item, panel, label, iconName? }` where `item` is the `ixfx-tab-list-item` and `panel` the `ixfx-tab-panel`.
|
|
317
|
+
|
|
318
|
+
### `TabAddOptions`
|
|
319
|
+
|
|
320
|
+
| Option | Type | Default | Description |
|
|
321
|
+
|--------|------|---------|-------------|
|
|
322
|
+
| `label` | `string` | *(required)* | Tab label |
|
|
323
|
+
| `id` | `string` | derived from label | id for the tab item |
|
|
324
|
+
| `forId` | `string` | derived from label | id for the panel (and the item's `for`) |
|
|
325
|
+
| `iconName` | `string` | — | Icon to display |
|
|
326
|
+
| `description` | `string` | — | Tooltip description |
|
|
327
|
+
| `closeable` | `boolean` | `true` | Show the close button |
|
|
328
|
+
| `toolbar` | `HTMLElement[]` | — | Elements to slot into `slot="toolbar"` |
|
|
329
|
+
| `content` | `Node \| string \| (panel) => void` | — | Panel content |
|
|
330
|
+
| `select` | `boolean` | `true` | Select the new tab immediately |
|
|
331
|
+
| `index` | `number` | append | Insertion index among existing tabs |
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
controller.addTab({
|
|
335
|
+
label: 'Console',
|
|
336
|
+
iconName: 'debug',
|
|
337
|
+
content: (panel) => { panel.append('Console output'); },
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Keyboard shortcuts via commands
|
|
342
|
+
|
|
343
|
+
Pass a `CommandRegistry` to the options and bind shortcuts with `KeyboardManager`. Command ids equal the tab ids:
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
import { CommandRegistry, KeyboardManager } from '@clinth/ui-commands';
|
|
347
|
+
|
|
348
|
+
const commands = new CommandRegistry();
|
|
349
|
+
const controller = new TabController(list, panels, { commands });
|
|
350
|
+
|
|
351
|
+
const keyboard = new KeyboardManager();
|
|
352
|
+
keyboard.bind({ key: '1', modifiers: new Set(['alt']), commandId: 'tab-home' });
|
|
353
|
+
keyboard.bind({ key: '2', modifiers: new Set(['alt']), commandId: 'tab-profile' });
|
|
354
|
+
keyboard.onKeyEvent((commandId) => commands.invoke(commandId));
|
|
355
|
+
keyboard.attach(document.body);
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Commands are unregistered when their tab is removed or the controller disconnects.
|
|
@@ -127,14 +127,14 @@ Switching `interactionMode` always clears the current selection.
|
|
|
127
127
|
| `'implicit'` | Click always replaces selection. No modifier support. |
|
|
128
128
|
| `'standard'` | Plain click replaces selection; Ctrl/Cmd+click toggles; Shift+click range-selects (multiple mode only); Cmd+A selects all |
|
|
129
129
|
| `'vscode'` | Like `standard` for clicks; Shift+Arrow extends the range from the keyboard anchor |
|
|
130
|
-
| `'checked'` | Checkbox column on the left of every row; only that column is clickable and it spans the full row height so a tall card can still be toggled with a single click on the left; Space toggles the focused row. See [Custom checkbox](#custom-checkbox) for styling |
|
|
130
|
+
| `'checked'` | Checkbox column on the left of every row; only that column is clickable and it spans the full row height so a tall card can still be toggled with a single click on the left; Space toggles the focused row. Rows marked `not-checked` get no checkbox and can't be checked (see [Non-checkable rows](#non-checkable-rows)). See [Custom checkbox](#custom-checkbox) for styling |
|
|
131
131
|
| `'manual'` | No automatic selection; drive state entirely via `select()` / `deselect()` / `clearSelection()` |
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
135
135
|
## Custom checkbox
|
|
136
136
|
|
|
137
|
-
In `checked` mode every row gets a checkbox column on the left. The column is always a **full-height click target** (the wrapper uses `align-self: stretch`), so a single click anywhere on the left side of a tall card toggles it — you never have to precisely hit a small checkbox. Clicks on the row body (the text or custom card content) do **not** toggle, and the cursor is `default` there so the affordance is clear.
|
|
137
|
+
In `checked` mode every row gets a checkbox column on the left — except rows marked [`not-checked`](#non-checkable-rows), which get no checkbox and can't be checked. The column is always a **full-height click target** (the wrapper uses `align-self: stretch`), so a single click anywhere on the left side of a tall card toggles it — you never have to precisely hit a small checkbox. Clicks on the row body (the text or custom card content) do **not** toggle, and the cursor is `default` there so the affordance is clear.
|
|
138
138
|
|
|
139
139
|
By default the column contains a native `<input type="checkbox">`. The visible square is small, but the entire column (~20–24px wide, full row height) is clickable.
|
|
140
140
|
|
|
@@ -189,9 +189,44 @@ list.checkboxRenderer = {
|
|
|
189
189
|
|
|
190
190
|
Because the wrapper still spans the full row height and owns the click handler, the empty unchecked area is just as clickable as the visible tick — even though there is no border, background, or other visual indicator of where the column is.
|
|
191
191
|
|
|
192
|
+
### Non-checkable rows (`not-checked`)
|
|
193
|
+
|
|
194
|
+
Sometimes a list needs rows that are informational rather than selectable — headers, separators, "new document" placeholders. Mark an item with the `not-checked` attribute to opt it out in `checked` mode:
|
|
195
|
+
|
|
196
|
+
```html
|
|
197
|
+
<ixfx-vertical-list interaction-mode="checked">
|
|
198
|
+
<li not-checked>Recent files</li>
|
|
199
|
+
<li>Document A</li>
|
|
200
|
+
<li>Image 01.png</li>
|
|
201
|
+
</ixfx-vertical-list>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
A `not-checked` row:
|
|
205
|
+
|
|
206
|
+
- shows **no checkbox column** (nothing is rendered on its left),
|
|
207
|
+
- can **not be selected, toggled, or range-selected** — click, Space, `select()`, `selectMany()`, and `Cmd/Ctrl+A` all skip it,
|
|
208
|
+
- is still **visible and navigable** — the cursor moves onto it, and it still fires `list-item-click` and `list-activate` (e.g. Enter).
|
|
209
|
+
|
|
210
|
+
Outside `checked` mode the attribute has no effect, so the same item remains fully selectable in other interaction modes.
|
|
211
|
+
|
|
212
|
+
The attribute works on both plain `<li>` items and `data-list-item` custom elements (on the custom element itself, before it is wrapped).
|
|
213
|
+
|
|
214
|
+
When adding items programmatically, pass `notChecked: true` to `addItem()` instead of setting the attribute yourself:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
list.addItem('Header', { notChecked: true });
|
|
218
|
+
list.addItem('Document A');
|
|
219
|
+
|
|
220
|
+
// Or set the attribute directly on an element
|
|
221
|
+
const header = document.createElement('li');
|
|
222
|
+
header.textContent = 'Header';
|
|
223
|
+
header.setAttribute('not-checked', '');
|
|
224
|
+
list.addItem(header);
|
|
225
|
+
```
|
|
226
|
+
|
|
192
227
|
### How it works under the hood
|
|
193
228
|
|
|
194
|
-
In `checked` mode each row has a `<span class="ixfx-list-checkbox" role="checkbox" part="checkbox">` prepended as the first child:
|
|
229
|
+
In `checked` mode each checkable row has a `<span class="ixfx-list-checkbox" role="checkbox" part="checkbox">` prepended as the first child:
|
|
195
230
|
|
|
196
231
|
- When `checkboxRenderer` is set, your template is rendered into the span (via lit's `render()`) and re-rendered on every state change.
|
|
197
232
|
- When `checkboxRenderer` is **not** set, the span contains a real `<input type="checkbox">` with `pointer-events: none` — the span owns the click so there is exactly one toggle per click.
|
|
@@ -239,6 +274,9 @@ list.selectAll();
|
|
|
239
274
|
list.addItem('New item');
|
|
240
275
|
list.addItem(myElement);
|
|
241
276
|
|
|
277
|
+
// In checked mode, add a row that shows no checkbox and can't be checked
|
|
278
|
+
list.addItem('Header', { notChecked: true });
|
|
279
|
+
|
|
242
280
|
// Remove a specific element
|
|
243
281
|
list.removeItem(element);
|
|
244
282
|
|
|
@@ -277,6 +315,8 @@ The component reflects state onto list items as data attributes. Use these in CS
|
|
|
277
315
|
| `data-selected` | The item is in the current selection |
|
|
278
316
|
| `data-checked` | Set in `checked` mode; mirrors `data-selected` |
|
|
279
317
|
|
|
318
|
+
The `not-checked` attribute is user-set, not reflected: it opts a row out of `checked` mode (see [Non-checkable rows](#non-checkable-rows)).
|
|
319
|
+
|
|
280
320
|
---
|
|
281
321
|
|
|
282
322
|
## Events
|
|
@@ -431,7 +471,7 @@ ixfx-vertical-list {
|
|
|
431
471
|
|---|---|
|
|
432
472
|
| `list` | The `<ul>` scroll container |
|
|
433
473
|
| `search-overlay` | The floating search input overlay |
|
|
434
|
-
| `checkbox` | The per-row checkbox column wrapper in `checked` mode (
|
|
474
|
+
| `checkbox` | The per-row checkbox column wrapper in `checked` mode (present on checkable rows; hosts the native `<input>` or the user-rendered content) |
|
|
435
475
|
|
|
436
476
|
Use `::part()` to style these from outside the component's shadow DOM:
|
|
437
477
|
|