@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/bundle/index.d.ts +153 -6
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +495 -23
- package/bundle/index.js.map +1 -1
- package/bundle/style.css +80 -0
- package/dist/index.d.ts +148 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +473 -1
- package/dist/index.js.map +1 -1
- package/dist/style.css +80 -0
- package/docs-user/README.md +1 -0
- package/docs-user/form.md +108 -0
- package/docs-user/tabs.md +105 -2
- package/package.json +1 -1
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
|
+
}
|
package/docs-user/README.md
CHANGED
|
@@ -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.
|
|
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
|
-
| `
|
|
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.
|