@ixfx/components 0.5.1 → 0.5.3

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/dist/style.css CHANGED
@@ -141,3 +141,83 @@
141
141
  ixfx-grouped-item-lister {
142
142
  display: block;
143
143
  }
144
+ ixfx-form {
145
+ display: block;
146
+ }
147
+
148
+ ixfx-form-group {
149
+ display: block;
150
+ container-type: inline-size;
151
+ }
152
+
153
+ ixfx-form-group fieldset {
154
+ gap: var(--space-m) var(--space-l);
155
+ margin: 0;
156
+ border: 0;
157
+ grid-template-columns: 1fr;
158
+ min-inline-size: 0;
159
+ margin-block-start: var(--space-m);
160
+ margin-block-end: var(--space-m);
161
+ padding: 0;
162
+ display: grid;
163
+ }
164
+
165
+ ixfx-form-group fieldset > legend:empty {
166
+ display: none;
167
+ }
168
+
169
+ ixfx-form-group fieldset > legend {
170
+ font-weight: var(--font-weight-bold);
171
+ grid-column: 1 / -1;
172
+ margin-block-start: var(--space-m);
173
+ margin-block-end: var(--space-s);
174
+ padding: 0;
175
+ }
176
+
177
+ ixfx-form-group fieldset > :not(ixfx-form-part) {
178
+ grid-column: 1 / -1;
179
+ }
180
+
181
+ ixfx-form-part {
182
+ grid-column: 1 / -1;
183
+ grid-template-columns: 1fr;
184
+ align-items: start;
185
+ display: grid;
186
+ }
187
+
188
+ ixfx-form-part > label {
189
+ font-size: var(--ixfx-form-label-font-size, var(--text-s));
190
+ font-weight: var(--ixfx-form-label-font-weight, inherit);
191
+ font-family: var(--ixfx-form-label-font-family, inherit);
192
+ color: var(--ixfx-form-label-color, inherit);
193
+ text-transform: var(--ixfx-form-label-text-transform, uppercase);
194
+ padding-bottom: var(--space-xs);
195
+ grid-area: 1 / 1;
196
+ }
197
+
198
+ ixfx-form-part > input, ixfx-form-part > select, ixfx-form-part > textarea {
199
+ grid-area: 2 / 1;
200
+ }
201
+
202
+ @container (width >= 30rem) {
203
+ ixfx-form-group fieldset {
204
+ grid-template-columns: max-content 1fr;
205
+ }
206
+
207
+ ixfx-form-part {
208
+ grid-column: 1 / -1;
209
+ grid-template-columns: subgrid;
210
+ align-items: baseline;
211
+ display: grid;
212
+ }
213
+
214
+ ixfx-form-part > label {
215
+ padding-right: var(--space-xs);
216
+ grid-area: 1 / 1;
217
+ padding-bottom: 0;
218
+ }
219
+
220
+ ixfx-form-part > input, ixfx-form-part > select, ixfx-form-part > textarea {
221
+ grid-area: 1 / 2;
222
+ }
223
+ }
@@ -12,6 +12,7 @@ This directory contains documentation for each component and some architectural
12
12
  - [crumbs](./crumbs.md)
13
13
  - [data-display](./data-display.md)
14
14
  - [editable-label](./editable-label.md)
15
+ - [form](./form.md)
15
16
  - [grouped-item-lister](./grouped-item-lister.md)
16
17
  - [icons](./icons.md)
17
18
  - [incr-search](./incr-search.md)
@@ -0,0 +1,108 @@
1
+ # Form layout components
2
+
3
+ Primitives for typical form layout with responsive label positioning and data binding.
4
+
5
+ ---
6
+
7
+ ## `ixfx-form`
8
+
9
+ Main container for form sections. Provides `getValues()` and `setValues()` methods for data binding.
10
+
11
+ ```html
12
+ <ixfx-form>
13
+ <ixfx-form-group label="Section A">
14
+ <p>Preamble text for section A...</p>
15
+ <ixfx-form-part label="Some text">
16
+ <input type="text" name="input1" />
17
+ </ixfx-form-part>
18
+ <ixfx-form-part label="Some other text">
19
+ <input type="text" name="input2" />
20
+ </ixfx-form-part>
21
+ </ixfx-form-group>
22
+ </ixfx-form>
23
+ ```
24
+
25
+ ### Properties / attributes
26
+
27
+ `ixfx-form` has no attributes — it is a pure layout and data-binding container.
28
+
29
+ ### Methods
30
+
31
+ | Method | Returns | Description |
32
+ |--------|---------|-------------|
33
+ | `getValues()` | `Record<string, unknown>` | Collects values from all named inputs within the form |
34
+ | `setValues(data)` | `void` | Applies values to named inputs. Keys absent from `data` are left untouched. |
35
+
36
+ ### Value collection rules
37
+
38
+ - Native `<input type=radio>` sharing a `name` → value of the checked one
39
+ - Native/checked-bearing checkbox → boolean
40
+ - `<select multiple>` → array of selected values
41
+ - Everything else with a `.value` property → used as-is
42
+
43
+ ---
44
+
45
+ ## `ixfx-form-group`
46
+
47
+ A section within the form, backed by a `<fieldset>`/`<legend>` for proper accessibility semantics.
48
+
49
+ ```html
50
+ <ixfx-form-group label="Personal Information">
51
+ <ixfx-form-part label="Full Name">
52
+ <input type="text" name="name" />
53
+ </ixfx-form-part>
54
+ <ixfx-form-part label="Email">
55
+ <input type="email" name="email" />
56
+ </ixfx-form-part>
57
+ </ixfx-form-group>
58
+ ```
59
+
60
+ ### Properties / attributes
61
+
62
+ | Attribute / Property | Type | Default | Description |
63
+ |---------------------|--------|---------|-------------|
64
+ | `label` | string | `''` | Section heading text |
65
+
66
+ ---
67
+
68
+ ## `ixfx-form-part`
69
+
70
+ Wraps a form control with a label. Uses light DOM so the `<label for>` natively associates with the slotted input.
71
+
72
+ ```html
73
+ <ixfx-form-part label="Username">
74
+ <input type="text" name="username" />
75
+ </ixfx-form-part>
76
+ ```
77
+
78
+ ### Properties / attributes
79
+
80
+ | Attribute / Property | Type | Default | Description |
81
+ |---------------------|--------|---------|-------------|
82
+ | `label` | string | `''` | Label text displayed for the control |
83
+
84
+ ### How it works
85
+
86
+ `ixfx-form-part` generates an `<label for="...">` element that is associated with the first control-like child (`<input>`, `<select>`, `<textarea>`, or element with a `name` attribute). The association works natively because both elements live in the same light DOM tree — no shadow boundary to work around.
87
+
88
+ An `id` is generated for the control if it doesn't already have one.
89
+
90
+ ---
91
+
92
+ ## Responsive behaviour
93
+
94
+ The layout switches between two modes based on available width (via CSS container queries):
95
+
96
+ **Narrow (< 30rem):** Label appears above the input, single-column stacked layout.
97
+
98
+ **Wide (≥ 30rem):** Label appears to the left of the input, two-column grid. All labels in a group share the width of the widest label (right-justified).
99
+
100
+ Resize the container to see the transition.
101
+
102
+ ---
103
+
104
+ ## TypeScript types
105
+
106
+ ```ts
107
+ import type { FormElement, FormGroupElement, FormPartElement } from 'ixfxfun-components';
108
+ ```
package/docs-user/tabs.md CHANGED
@@ -27,10 +27,18 @@ Wire them together:
27
27
  const tabList = document.querySelector('ixfx-tab-list');
28
28
  const tabPanels = document.querySelector('ixfx-tab-panels');
29
29
  tabList.addEventListener('change', e => {
30
- tabPanels.selectPanel(e.detail.selected);
30
+ tabPanels.selectPanel(e.detail.for);
31
31
  });
32
32
  ```
33
33
 
34
+ Select a tab programmatically — same effect as clicking it (header + content):
35
+
36
+ ```js
37
+ tabPanels.syncWithList(tabList);
38
+ tabPanels.selectTab('Home'); // by label text
39
+ tabPanels.selectTab('panel-home'); // by the panel id a tab's `for` points at
40
+ ```
41
+
34
42
  ---
35
43
 
36
44
  ## `ixfx-tab-list`
@@ -252,4 +260,99 @@ Manages multiple `ixfx-tab-panel` children, selecting one at a time.
252
260
  |---------------------|----------|---------|----------------------------------|
253
261
  | `getSelectedElement()` | — | `TabPanelElement \| undefined` | Get currently selected panel |
254
262
  | `selectPanel(id)` | `string` | `boolean` | Select panel by ID; returns true if found |
255
- | `syncWithList(el)` | `TabListElement` | `void` | Sync panel selection to tab list |
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 |
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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.5.1",
4
+ "version": "0.5.3",
5
5
  "description": "",
6
6
  "author": "",
7
7
  "license": "ISC",