@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/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 (always present; hosts the native `<input>` or the user-rendered content) |
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
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.5.2",
4
+ "version": "0.5.4",
5
5
  "description": "",
6
6
  "author": "",
7
7
  "license": "ISC",