@bitforgehq/ui 0.4.0 → 0.5.0-next.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/CHANGELOG.md +350 -0
- package/README.md +12 -12
- package/ai/MANIFEST.index.json +74 -2
- package/ai/MANIFEST.json +3507 -1763
- package/ai/REFERENCE.index.json +189 -0
- package/ai/REFERENCE.json +1617 -139
- package/ai/UI_GUIDE.md +70 -20
- package/ai/components/app-frame.md +1 -1
- package/ai/components/badge.md +41 -1
- package/ai/components/chat-composer.md +357 -0
- package/ai/components/chat-message.md +251 -0
- package/ai/components/chat-thread.md +521 -0
- package/ai/components/checkbox.md +7 -0
- package/ai/components/combobox.md +12 -0
- package/ai/components/date-input.md +9 -0
- package/ai/components/datetime-input.md +2 -0
- package/ai/components/field.md +10 -0
- package/ai/components/file-input.md +498 -0
- package/ai/components/header.md +1 -1
- package/ai/components/image-input.md +368 -0
- package/ai/components/image-tile.md +452 -0
- package/ai/components/image-viewer.md +309 -0
- package/ai/components/media-card.md +478 -0
- package/ai/components/menu-button.md +67 -9
- package/ai/components/menu.md +10 -0
- package/ai/components/multi-combobox.md +7 -0
- package/ai/components/nav-group.md +10 -3
- package/ai/components/nav-item.md +11 -2
- package/ai/components/number-input.md +2 -7
- package/ai/components/pagination.md +59 -11
- package/ai/components/popover.md +13 -0
- package/ai/components/radio-group.md +6 -0
- package/ai/components/segmented.md +14 -2
- package/ai/components/select.md +100 -42
- package/ai/components/sidebar-toggle.md +16 -9
- package/ai/components/sidebar.md +41 -13
- package/ai/components/tab-group.md +18 -2
- package/ai/components/table.md +17 -2
- package/ai/components/theme-menu-button.md +1 -0
- package/ai/components/time-input.md +2 -0
- package/ai/components/toolbar.md +7 -18
- package/ai/reference/bf-chat-composer-harness.md +64 -0
- package/ai/reference/bf-chat-message-role.md +27 -0
- package/ai/reference/bf-chat-message-status.md +28 -0
- package/ai/reference/bf-fade-in-directive.md +18 -0
- package/ai/reference/bf-file-input-harness.md +67 -0
- package/ai/reference/bf-file-input-variant.md +27 -0
- package/ai/reference/bf-image-input-harness.md +67 -0
- package/ai/reference/bf-image-input-shape.md +27 -0
- package/ai/reference/bf-image-tile-aspect.md +28 -0
- package/ai/reference/bf-image-tile-caption-placement.md +27 -0
- package/ai/reference/bf-image-tile-harness.md +70 -0
- package/ai/reference/bf-image-viewer-actions-context.md +29 -0
- package/ai/reference/bf-image-viewer-harness.md +87 -0
- package/ai/reference/bf-image-viewer-image.md +25 -0
- package/ai/reference/bf-image-viewer-options.md +33 -0
- package/ai/reference/bf-image-viewer-origin.md +23 -0
- package/ai/reference/bf-image-viewer-ref.md +55 -0
- package/ai/reference/bf-image-viewer-service.md +32 -0
- package/ai/reference/bf-labels.md +107 -75
- package/ai/reference/bf-media-card-aspect.md +28 -0
- package/ai/reference/bf-media-card-harness.md +66 -0
- package/ai/reference/bf-motion-list-directive.md +22 -0
- package/ai/reference/bf-pagination-position-label.md +22 -0
- package/ai/reference/bf-prose-directive.md +16 -0
- package/ai/reference/bf-rejected-file-reason.md +28 -0
- package/ai/reference/bf-rejected-file.md +28 -0
- package/ai/reference/bf-select-harness.md +14 -5
- package/ai/reference/bf-select-option.md +2 -0
- package/ai/reference/bf-size.md +3 -0
- package/ai/reference/bf_table_sort_select.md +23 -0
- package/ai/reference/provide-bf-labels.md +11 -3
- package/ai/reference/provide-bf-select.md +20 -0
- package/bin/bf-ui.js +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2660 -594
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +414 -10
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +8056 -3993
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +4923 -132
- package/llms.txt +36 -1
- package/package.json +1 -1
- package/theme.css +60 -0
- package/types/bitforgehq-ui-overlay.d.ts +695 -7
- package/types/bitforgehq-ui-testing.d.ts +517 -6
- package/types/bitforgehq-ui.d.ts +1699 -181
|
@@ -72,7 +72,7 @@ Put the group's links in its default slot and its name in `label`. Groups stack
|
|
|
72
72
|
|
|
73
73
|
## In a collapsed sidebar
|
|
74
74
|
|
|
75
|
-
While the sidebar is collapsed to an icon rail the heading
|
|
75
|
+
While the sidebar is collapsed to an icon rail the heading's row closes and the text fades out (on `--bf-duration-layout`, at once under reduced motion), so only the icons show, and it still names the group for screen readers. The heading stays on one line and truncates, so its height never changes while the sidebar's width moves.
|
|
76
76
|
|
|
77
77
|
```ts
|
|
78
78
|
import { Component } from '@angular/core';
|
|
@@ -84,7 +84,7 @@ import { Layers, LayoutList, PanelTop, SquareStack } from 'lucide';
|
|
|
84
84
|
/**
|
|
85
85
|
* Groups in a collapsed sidebar.
|
|
86
86
|
*
|
|
87
|
-
* The headings
|
|
87
|
+
* The headings close and fade out (at once under reduced motion) and still name their groups for screen readers; the items
|
|
88
88
|
* show their icons only.
|
|
89
89
|
*/
|
|
90
90
|
@Component({
|
|
@@ -146,7 +146,7 @@ The group itself is static; its links carry the hover, pressed and focus styles
|
|
|
146
146
|
|
|
147
147
|
## Accessibility
|
|
148
148
|
|
|
149
|
-
The host has `role="group"` and `aria-labelledby` pointing at its heading, so a screen reader announces the group's name when focus enters it ("Workspace, group"). The heading is text, not a heading element, so it does not add to the page outline. Collapsed, the heading is
|
|
149
|
+
The host has `role="group"` and `aria-labelledby` pointing at its heading, so a screen reader announces the group's name when focus enters it ("Workspace, group"). The heading is text, not a heading element, so it does not add to the page outline. Collapsed, the heading is closed to no height and transparent, but stays in the accessibility tree and keeps naming the group.
|
|
150
150
|
|
|
151
151
|
## Inputs
|
|
152
152
|
|
|
@@ -172,6 +172,13 @@ The host has `role="group"` and `aria-labelledby` pointing at its heading, so a
|
|
|
172
172
|
|
|
173
173
|
## Migration notes
|
|
174
174
|
|
|
175
|
+
### 0.5.0
|
|
176
|
+
|
|
177
|
+
Collapsed, the heading's row closes and its text fades out on `--bf-duration-layout` instead of
|
|
178
|
+
switching to `sr-only`; it stays in the accessibility tree and still names the group. The heading
|
|
179
|
+
stays on one line and truncates (it wrapped before), so its height does not change while the
|
|
180
|
+
width moves.
|
|
181
|
+
|
|
175
182
|
### 0.3.1
|
|
176
183
|
|
|
177
184
|
New `bf-nav-group`: a titled group of sidebar links, `role="group"` named by its small heading,
|
|
@@ -109,7 +109,7 @@ The router sets `aria-current="page"` on the current page's link (`routerLinkAct
|
|
|
109
109
|
|
|
110
110
|
## Collapsed
|
|
111
111
|
|
|
112
|
-
In a collapsed `bf-sidebar` the item shows only its icon in a square of its height step. The label stays in the DOM
|
|
112
|
+
In a collapsed `bf-sidebar` the item shows only its icon in a square of its height step: the row keeps its layout, its start padding centres a 20 px icon, and the label fades out while the sidebar narrows, so the icon never moves. The label stays in the DOM, so the link's name does not change, and becomes the link's `title` (the tooltip on hover, kept up to date when the label changes); your own `title` comes back when the sidebar expands. The badge pill shrinks away while a small dot grows on the icon's corner, on the same timing, and the count is still announced. Give every item an icon, or it shows nothing while collapsed. For a styled tooltip that also shows on keyboard focus, add `bfTooltip` from `@bitforgehq/ui/overlay` with the label's text while collapsed (an empty text while expanded shows none) and `bfTooltipPlacement="end"` (see [bf-tooltip](https://bitforge-ui.web.app/components/tooltip)).
|
|
113
113
|
|
|
114
114
|
## Sizes
|
|
115
115
|
|
|
@@ -161,11 +161,12 @@ Hover gives a `surface-muted` fill with the `text` colour and the pointer; press
|
|
|
161
161
|
- Current page (`aria-current="page"`): `primary-soft` with `primary-strong`, hover `primary-soft-hover`, pressed `primary-soft-pressed`; under forced colours a `LinkText` outline.
|
|
162
162
|
- Shape: `rounded-control`; label `font-bold`, `text-label-xs` / `text-label-sm` / `text-label` by size.
|
|
163
163
|
- Badge: `primary` with `on-primary`, `text-micro`; collapsed, a `primary` dot (`CanvasText` under forced colours).
|
|
164
|
+
- Motion: in a sidebar, the padding, the label and the badge move on `--bf-duration-layout` with `ease-md` (none under reduced motion); hover and press colours fade over 150 ms.
|
|
164
165
|
- Focus: `ring` (4 px).
|
|
165
166
|
|
|
166
167
|
## Accessibility
|
|
167
168
|
|
|
168
|
-
The item is the consumer's native `<a>`: exposed as a link, reached with Tab and followed with Enter, its name being its label (plus the badge announcement). The current page carries `aria-current="page"`, which screen readers announce; the fill is not the only cue, as an outline marks it under forced colours. The icon slot is decorative (a `bf-icon` is `aria-hidden` by default). Collapsed, the label is
|
|
169
|
+
The item is the consumer's native `<a>`: exposed as a link, reached with Tab and followed with Enter, its name being its label (plus the badge announcement). The current page carries `aria-current="page"`, which screen readers announce; the fill is not the only cue, as an outline marks it under forced colours. The icon slot is decorative (a `bf-icon` is `aria-hidden` by default). Collapsed, the label is transparent and clipped but still the name, and `title` adds a pointer tooltip only; the badge dot is `aria-hidden`, its count announced through the screen-reader text. In Safari, Tab reaches links only when the user turns on "Press Tab to highlight each item" (Option+Tab otherwise).
|
|
169
170
|
|
|
170
171
|
### Keyboard
|
|
171
172
|
|
|
@@ -200,6 +201,14 @@ The item is the consumer's native `<a>`: exposed as a link, reached with Tab and
|
|
|
200
201
|
|
|
201
202
|
## Migration notes
|
|
202
203
|
|
|
204
|
+
### 0.5.0
|
|
205
|
+
|
|
206
|
+
In a collapsing sidebar the item no longer switches to a centred square: the row keeps its layout
|
|
207
|
+
and its start padding moves so the icon ends centred in the rail without moving, while the label
|
|
208
|
+
fades out (it stays the accessible name; it is no longer `sr-only`). The count pill shrinks away
|
|
209
|
+
while the dot (new element, `[data-badge-dot]`) grows in on the same timing. Each item clips
|
|
210
|
+
what does not fit the rail. No animation under reduced motion or in a top nav.
|
|
211
|
+
|
|
203
212
|
### 0.3.1
|
|
204
213
|
|
|
205
214
|
New `a[bfNavItem]`: one destination of a `bf-sidebar` or `bf-top-nav` on the app's own
|
|
@@ -165,13 +165,8 @@ The control works in Reactive Forms through Angular's Signal Forms interop (no `
|
|
|
165
165
|
```ts
|
|
166
166
|
import { Component } from '@angular/core';
|
|
167
167
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
168
|
-
import {
|
|
169
|
-
|
|
170
|
-
BfCheckboxComponent,
|
|
171
|
-
BfInputComponent,
|
|
172
|
-
BfSelectComponent,
|
|
173
|
-
type BfSelectOption,
|
|
174
|
-
} from '@bitforgehq/ui';
|
|
168
|
+
import { BfButtonComponent, BfCheckboxComponent, BfInputComponent } from '@bitforgehq/ui';
|
|
169
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
175
170
|
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
176
171
|
|
|
177
172
|
/**
|
|
@@ -42,7 +42,9 @@ export class PaginationOverviewExampleComponent {
|
|
|
42
42
|
Page navigation for a long list or table: Previous and Next buttons around the page numbers,
|
|
43
43
|
the current one marked, the hidden runs shown as an ellipsis. `compact` shows "Page 3 of 12"
|
|
44
44
|
between the arrows instead, for narrow screens. The page count comes from `pageCount`, or from
|
|
45
|
-
`total` and `pageSize`; the consumer shows the rows of `page`.
|
|
45
|
+
`total` and `pageSize`; the consumer shows the rows of `page`. A compact pagination with its own
|
|
46
|
+
`positionLabel`, `previousLabel` and `nextLabel` steps through anything numbered, such as the
|
|
47
|
+
versions of a model ("‹ v3 of 7 ›").
|
|
46
48
|
|
|
47
49
|
Pagination splits a long list or table into pages and lets the user move between them: Previous and Next buttons around the page numbers, the current page marked, the first and the last page always in reach, and an ellipsis for each run of pages in between. `bf-pagination` holds only the page number. You bind `[(page)]` and show that page's rows, sliced on the client or fetched from the server. It works the same in both shells: in a desktop web app and in the Capacitor shell.
|
|
48
50
|
|
|
@@ -89,6 +91,41 @@ export class PaginationCompactExampleComponent {
|
|
|
89
91
|
}
|
|
90
92
|
```
|
|
91
93
|
|
|
94
|
+
## Stepping through versions
|
|
95
|
+
|
|
96
|
+
A compact pagination also steps through anything else that is numbered, such as the versions of a model: "‹ v3 of 7 ›". `positionLabel` takes a `BfPaginationPositionLabel`, a function from the page and the count to the position text (`` (page, count) => `v${page} of ${count}` ``); unset, the text comes from `pageOf`. `previousLabel` and `nextLabel` name the arrows ("Previous version") where "Previous page" would be wrong, and `label` names the navigation. Use it instead of a [bf-segmented](https://bitforge-ui.web.app/components/segmented) control once there are more than four versions; the arrows are disabled at the ends, as on any pagination.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { Component, signal } from '@angular/core';
|
|
100
|
+
import { BfPaginationComponent, type BfPaginationPositionLabel } from '@bitforgehq/ui';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A compact pager that steps through the versions of a model, "‹ v3 of 7 ›".
|
|
104
|
+
*
|
|
105
|
+
* `positionLabel` writes the position text; `previousLabel` and `nextLabel` name the arrows.
|
|
106
|
+
*/
|
|
107
|
+
@Component({
|
|
108
|
+
selector: 'app-example-pagination-versions',
|
|
109
|
+
imports: [BfPaginationComponent],
|
|
110
|
+
template: `
|
|
111
|
+
<bf-pagination
|
|
112
|
+
compact
|
|
113
|
+
size="sm"
|
|
114
|
+
label="Versions"
|
|
115
|
+
previousLabel="Previous version"
|
|
116
|
+
nextLabel="Next version"
|
|
117
|
+
[positionLabel]="versionOf"
|
|
118
|
+
[pageCount]="7"
|
|
119
|
+
[(page)]="version"
|
|
120
|
+
/>
|
|
121
|
+
`,
|
|
122
|
+
})
|
|
123
|
+
export class PaginationVersionsExampleComponent {
|
|
124
|
+
readonly version = signal(3);
|
|
125
|
+
protected readonly versionOf: BfPaginationPositionLabel = (page, count) => `v${page} of ${count}`;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
92
129
|
## With a table
|
|
93
130
|
|
|
94
131
|
Put the pagination under the table it pages, aligned to the inline end or centred, and show only the current page's rows: slice them in a `computed()` from `page`, or fetch the page from the server in a `httpResource()` keyed on it. When the user sorts or filters, set `page` back to 1. The [table](https://bitforge-ui.web.app/components/table) page has a paged example.
|
|
@@ -123,16 +160,19 @@ At the first and the last page the arrow is disabled with `aria-disabled="true"`
|
|
|
123
160
|
|
|
124
161
|
## Inputs
|
|
125
162
|
|
|
126
|
-
| Name
|
|
127
|
-
|
|
|
128
|
-
| `pageCount`
|
|
129
|
-
| `total`
|
|
130
|
-
| `pageSize`
|
|
131
|
-
| `siblings`
|
|
132
|
-
| `compact`
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
163
|
+
| Name | Type | Default | Description |
|
|
164
|
+
| --------------- | ----------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `pageCount` | `number` | `0` | The number of pages; wins over `total` and `pageSize` when above 0. |
|
|
166
|
+
| `total` | `number` | `0` | The number of items, used with `pageSize` while `pageCount` is 0. |
|
|
167
|
+
| `pageSize` | `number` | `10` | Items per page, used with `total` while `pageCount` is 0. |
|
|
168
|
+
| `siblings` | `number` | `1` | How many pages to show on each side of the current one. |
|
|
169
|
+
| `compact` | `boolean` | `false` | Shows "Page 3 of 12" between the arrows instead of the page buttons, for narrow screens. |
|
|
170
|
+
| `positionLabel` | `BfPaginationPositionLabel \| undefined` | `undefined` | Writes the compact position text from the page and the count, such as "v3 of 7"; unset uses `provideBfLabels()`'s `pageOf` ("Page 3 of 12"). Only used with `compact`. |
|
|
171
|
+
| `previousLabel` | `string` | `''` | Accessible name of the previous button, such as "Previous version"; empty falls back to `provideBfLabels()`'s `previousPage` ("Previous page"). |
|
|
172
|
+
| `nextLabel` | `string` | `''` | Accessible name of the next button, such as "Next version"; empty falls back to `provideBfLabels()`'s `nextPage` ("Next page"). |
|
|
173
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Button height step: sm 32, md 40, lg 48 px (44 px hit area under a coarse pointer); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
174
|
+
| `label` | `string` | `''` | Accessible name of the navigation; empty falls back to `provideBfLabels()`'s `pagination` ("Pagination"). |
|
|
175
|
+
| `page` | `number` | `1` | The current page, 1-based; two-way bindable. The buttons set it; show the rows of this page. Two-way: `[(page)]`. |
|
|
136
176
|
|
|
137
177
|
## Methods
|
|
138
178
|
|
|
@@ -159,6 +199,14 @@ Goes to a page: sets `page` when it is another page within 1…count; else does
|
|
|
159
199
|
|
|
160
200
|
## Migration notes
|
|
161
201
|
|
|
202
|
+
### 0.5.0
|
|
203
|
+
|
|
204
|
+
New `positionLabel` input (new type `BfPaginationPositionLabel`, `(page, count) => string`) that
|
|
205
|
+
writes a compact pagination's position text, such as "v3 of 7"; unset it is the `pageOf` label as
|
|
206
|
+
before. New `previousLabel` and `nextLabel` inputs name the arrows ("Previous version"); empty
|
|
207
|
+
they fall back to `previousPage` and `nextPage`. Together they make a compact pager for stepping
|
|
208
|
+
through versions.
|
|
209
|
+
|
|
162
210
|
### 0.3.1
|
|
163
211
|
|
|
164
212
|
New `bf-pagination` in `@bitforgehq/ui`, page navigation for both shells: a `nav` (named by
|
package/ai/components/popover.md
CHANGED
|
@@ -207,6 +207,9 @@ in the Capacitor shell a `bf-sheet` suits phone screens better.
|
|
|
207
207
|
`Shift+Tab` before the first control closes it onto the trigger.
|
|
208
208
|
- `Escape` goes to the popover first: inside a dialog, the first `Escape` closes the popover and
|
|
209
209
|
the next one the dialog.
|
|
210
|
+
- Motion: the panel grows out of the trigger as it opens (a fade, a slight scale and a 4 px slide,
|
|
211
|
+
180 ms) and shrinks back as it closes (130 ms). Focus and `aria-expanded` change at once; the
|
|
212
|
+
closing panel is inert while it fades. Under `prefers-reduced-motion` it only fades.
|
|
210
213
|
|
|
211
214
|
### Keyboard
|
|
212
215
|
|
|
@@ -238,6 +241,16 @@ in the Capacitor shell a `bf-sheet` suits phone screens better.
|
|
|
238
241
|
|
|
239
242
|
## Migration notes
|
|
240
243
|
|
|
244
|
+
### 0.5.0
|
|
245
|
+
|
|
246
|
+
The panel animates open and closed. It grows out of the trigger (a fade, a scale from 96 % and a
|
|
247
|
+
4 px slide away from the trigger, 180 ms, ease-out; transform origin on the trigger's corner,
|
|
248
|
+
following a flip above or below) and shrinks back on close (130 ms). Focus, `aria-expanded` and
|
|
249
|
+
`bfPopoverClosed` still change at once; the closing panel stays in the overlay container, inert
|
|
250
|
+
and click-through, until its exit has played, and opening again during the exit replaces it. Under
|
|
251
|
+
`prefers-reduced-motion` it only fades. Where the Web Animations API is missing or stubbed
|
|
252
|
+
(jsdom), the panel is still removed at once.
|
|
253
|
+
|
|
241
254
|
### 0.3.1
|
|
242
255
|
|
|
243
256
|
New `[bfPopoverTriggerFor]` (`BfPopoverTriggerDirective`, exported as `bfPopoverTrigger`) and the
|
|
@@ -302,6 +302,7 @@ Hovering an unselected, enabled option darkens its ring (`text-muted`) with the
|
|
|
302
302
|
- Pressed (an option held down): its ring border turns `text`; not while disabled or read-only.
|
|
303
303
|
- Disabled: ring border `surface-sunken`, background `surface-muted`; option label `text-disabled`, description `text-muted`.
|
|
304
304
|
- Read-only: ring background `surface-muted`, no hover, default cursor.
|
|
305
|
+
- Motion: the dot grows in and shrinks out over 150 ms on `ease-md` (a fade only under reduced motion); an error message fades in (`animate-message-in`).
|
|
305
306
|
- Legend: `text-muted`. Error text: `danger`. Focus ring: `ring`.
|
|
306
307
|
|
|
307
308
|
The legend, the options and the error text sit 6 px apart; each ring sits 10 px from its label.
|
|
@@ -374,6 +375,11 @@ Focuses the checked radio, else the first enabled one; Signal Forms calls it fro
|
|
|
374
375
|
|
|
375
376
|
## Migration notes
|
|
376
377
|
|
|
378
|
+
### 0.5.0
|
|
379
|
+
|
|
380
|
+
The selected option's dot grows in and the previous one shrinks away (150 ms on `ease-md`);
|
|
381
|
+
under reduced motion they only fade. An error message fades in when it appears.
|
|
382
|
+
|
|
377
383
|
### 0.3.0
|
|
378
384
|
|
|
379
385
|
New exported `BfRadioGroupOrientation` (`'vertical' | 'horizontal'`), the type of `orientation`
|
|
@@ -104,6 +104,8 @@ export class SegmentedBasicExampleComponent {
|
|
|
104
104
|
`value` is `null` while no segment is selected; the first enabled segment then owns the tab stop.
|
|
105
105
|
`(changed)` emits the chosen value after a user change.
|
|
106
106
|
|
|
107
|
+
The selected pill slides to the newly chosen segment, changing width as it goes (200 ms, `--bf-duration-layout`); it lands in place at once on the first render and after a resize, and always under `prefers-reduced-motion`.
|
|
108
|
+
|
|
107
109
|
## Typed values
|
|
108
110
|
|
|
109
111
|
The control is generic over the option value: numbers, ids and objects bind directly.
|
|
@@ -330,7 +332,7 @@ Hovering an inactive segment darkens its label to `text`, and the active one tak
|
|
|
330
332
|
## Theming
|
|
331
333
|
|
|
332
334
|
- Track: `surface-sunken`.
|
|
333
|
-
- Active segment: `surface` / `text` with the `shadow-segment` elevation and a 1.5 px inset `primary-strong` outline (≥ 3:1 on `surface` and the track, so the selection does not rest on the fill), hover `surface-muted` (under `forced-colors`, `Highlight` / `HighlightText` with a `CanvasText` outline). Inactive: `text-muted`, hover `text`. Pressed (editable only): `surface-pressed` with `text` (the selected segment keeps `Highlight` under `forced-colors`).
|
|
335
|
+
- Active segment: `surface` / `text` with the `shadow-segment` elevation and a 1.5 px inset `primary-strong` outline (≥ 3:1 on `surface` and the track, so the selection does not rest on the fill), hover `surface-muted` (under `forced-colors`, `Highlight` / `HighlightText` with a `CanvasText` outline). Inactive: `text-muted`, hover `text`. Pressed (editable only): `surface-pressed` with `text` (the selected segment keeps `Highlight` under `forced-colors`). The pill slides over `--bf-duration-layout` with the `ease-md` curve.
|
|
334
336
|
- Read-only: track `surface-muted`, no hover, default cursor. Error: the track gets a 1.5 px `danger-border` ring and the message below is `danger`.
|
|
335
337
|
- Focus ring: `ring`, drawn past a 2 px `surface` offset so it sits on `surface` (≥ 3:1) rather than on the track. Disabled segments: `text-disabled` with `cursor-not-allowed`; an unselected one takes the `surface-sunken` fill, the selected one keeps its `surface` pill without the shadow, outlined in `text-disabled` (never opacity).
|
|
336
338
|
- Shape (track and segments): `rounded-control` (`--radius-control`, `9999px` by default).
|
|
@@ -339,7 +341,7 @@ Segments are at least 24/32/40 px tall inside the track's 4 px padding. Under a
|
|
|
339
341
|
|
|
340
342
|
## Accessibility
|
|
341
343
|
|
|
342
|
-
`role="radiogroup"` with `aria-label` from `label` (else `ariaLabel`, or `aria-labelledby` from `ariaLabelledby`); each segment is a `<button role="radio">` with `aria-checked` mirroring selection. Only the selected (or first enabled) segment sits in the tab order — the rest carry `tabindex="-1"` — and ArrowRight/Down, ArrowLeft/Up, Home and End move the selection and focus like a native radio group, matching platform expectations. `disabled` segments are skipped by keyboard navigation and get the native attribute. The group carries `aria-required`, and once touched and invalid `aria-invalid="true"` with `aria-describedby` and `aria-errormessage` pointing at the error text. Read-only: `aria-readonly="true"` on the group; the tab stop stays focusable and selection by pointer or keyboard is ignored. `focus()` focuses the tab-stop segment; Signal Forms calls it from `focusBoundControl()`. Under forced colours (Windows High Contrast) the selected segment opts out of the adjustment (`forced-color-adjust: none`) and paints `Highlight`/`HighlightText` itself with a `CanvasText` selected outline, focus outline and focus ring (on a `Canvas` offset) and no shadow, so it paints system colours only and so no text backplate hides its label; a disabled selected segment's outline is `GrayText` instead; the others repaint in `ButtonText`.
|
|
344
|
+
`role="radiogroup"` with `aria-label` from `label` (else `ariaLabel`, or `aria-labelledby` from `ariaLabelledby`); each segment is a `<button role="radio">` with `aria-checked` mirroring selection. Only the selected (or first enabled) segment sits in the tab order — the rest carry `tabindex="-1"` — and ArrowRight/Down, ArrowLeft/Up, Home and End move the selection and focus like a native radio group, matching platform expectations. `disabled` segments are skipped by keyboard navigation and get the native attribute. The group carries `aria-required`, and once touched and invalid `aria-invalid="true"` with `aria-describedby` and `aria-errormessage` pointing at the error text. Read-only: `aria-readonly="true"` on the group; the tab stop stays focusable and selection by pointer or keyboard is ignored. The sliding pill is one decorative `aria-hidden` element of the track. `focus()` focuses the tab-stop segment; Signal Forms calls it from `focusBoundControl()`. Under forced colours (Windows High Contrast) the selected segment opts out of the adjustment (`forced-color-adjust: none`) and paints `Highlight`/`HighlightText` itself with a `CanvasText` selected outline, focus outline and focus ring (on a `Canvas` offset) and no shadow, so it paints system colours only and so no text backplate hides its label; a disabled selected segment's outline is `GrayText` instead; the others repaint in `ButtonText`.
|
|
343
345
|
|
|
344
346
|
### Keyboard
|
|
345
347
|
|
|
@@ -422,6 +424,16 @@ missing, or it is already selected.
|
|
|
422
424
|
|
|
423
425
|
## Migration notes
|
|
424
426
|
|
|
427
|
+
### 0.5.0
|
|
428
|
+
|
|
429
|
+
The selected segment's pill (surface fill, `shadow-segment`, `primary-strong` outline,
|
|
430
|
+
`Highlight` under forced colours) is now one indicator element of the track that slides to the
|
|
431
|
+
newly chosen segment, changing width as it goes (`--bf-duration-layout`, `ease-md`). The first
|
|
432
|
+
placement and a resize land at once, and so does everything under `prefers-reduced-motion`. The
|
|
433
|
+
selected segment's hover and pressed fills move with it. Until it has been measured (jsdom,
|
|
434
|
+
server render, a hidden control) the segment keeps painting its own pill. `bf-theme-switch`
|
|
435
|
+
slides the same way. The indicator is `aria-hidden`; roles, keyboard and focus are unchanged.
|
|
436
|
+
|
|
425
437
|
### 0.3.0
|
|
426
438
|
|
|
427
439
|
**Breaking:** typed values. `BfSegmentedComponent<T = string>` is generic over the option value
|
package/ai/components/select.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# bf-select
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Single-choice dropdown with a styled list, native under touch, with Signal Forms binding
|
|
4
4
|
|
|
5
|
-
`BfSelectComponent` from `@bitforgehq/ui` · Forms · since 0.1.0
|
|
5
|
+
`BfSelectComponent` from `@bitforgehq/ui/overlay` · Forms · since 0.1.0
|
|
6
6
|
|
|
7
7
|
## Example
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import { Component, computed, signal } from '@angular/core';
|
|
11
|
-
import { BfButtonComponent
|
|
11
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
12
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The bf-select overview.
|
|
@@ -91,11 +92,16 @@ export class SelectOverviewExampleComponent {
|
|
|
91
92
|
|
|
92
93
|
## Overview
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
Single-choice dropdown styled like the text field, with a placeholder and Signal Forms binding.
|
|
96
|
+
Under a mouse it opens a styled list (the combobox's) that follows the select-only combobox
|
|
97
|
+
pattern: the arrow keys, `Home`, `End`, `PageUp`/`PageDown` and typeahead move through it,
|
|
98
|
+
`Enter`, `Space` or `Tab` pick. Under a coarse pointer (touch) it renders the platform's native
|
|
99
|
+
select instead. Generic over the option value (a string by default); `null` means nothing is
|
|
100
|
+
selected. Moved from `@bitforgehq/ui` to `@bitforgehq/ui/overlay` in 0.5.0.
|
|
97
101
|
|
|
98
|
-
A single-choice dropdown
|
|
102
|
+
A single-choice dropdown styled like the text field, with a CSS chevron. Under a mouse it opens a styled list, the same panel as [bf-combobox](https://bitforge-ui.web.app/components/combobox) without the search: rounded, floating, with a check mark on the chosen option, exactly as wide as the field. Under a coarse pointer (`matchMedia('(pointer: coarse)')`, a phone or a Capacitor app) it renders the native `<select>` with the same label, help and error text instead, so a phone opens its own picker. Closed, both look the same.
|
|
103
|
+
|
|
104
|
+
Import `BfSelectComponent` from `@bitforgehq/ui/overlay` (it needs `@angular/cdk` for the list: `bun add @angular/cdk rxjs`). The list opens in the CDK overlay container, so it is never clipped by a scrolling parent and works inside a `BfDialogService` dialog. `BfSelectOption` is exported from both `@bitforgehq/ui` and `@bitforgehq/ui/overlay`. Before 0.5.0 the select lived in `@bitforgehq/ui`; see [Migration notes](#migration-notes).
|
|
99
105
|
|
|
100
106
|
## When to use
|
|
101
107
|
|
|
@@ -105,7 +111,7 @@ Use `bf-select` to choose one value from a list too long to show at once: a coun
|
|
|
105
111
|
|
|
106
112
|
```ts
|
|
107
113
|
import { Component, signal } from '@angular/core';
|
|
108
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
114
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
109
115
|
|
|
110
116
|
/** A select of projects with a placeholder until one is picked. */
|
|
111
117
|
@Component({
|
|
@@ -143,7 +149,7 @@ string conversion. `BfSelectOption` defaults to `string`; type it for anything e
|
|
|
143
149
|
|
|
144
150
|
```ts
|
|
145
151
|
import { Component, signal } from '@angular/core';
|
|
146
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
152
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
147
153
|
|
|
148
154
|
/** A select of numeric team ids: the bound value is a `number | null`. */
|
|
149
155
|
@Component({
|
|
@@ -171,7 +177,7 @@ instance (reloaded from a server), pass `compareWith`:
|
|
|
171
177
|
```ts
|
|
172
178
|
import { Component, signal } from '@angular/core';
|
|
173
179
|
import { FormField, form } from '@angular/forms/signals';
|
|
174
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
180
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
175
181
|
|
|
176
182
|
interface Team {
|
|
177
183
|
id: number;
|
|
@@ -202,10 +208,12 @@ export class SelectCompareWithExampleComponent {
|
|
|
202
208
|
}
|
|
203
209
|
```
|
|
204
210
|
|
|
205
|
-
`compareWith` is called as `compareWith(optionValue, value)`.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
mapped back by position, so the model always gets the option's real
|
|
211
|
+
`compareWith` is called as `compareWith(optionValue, value)`. With `name` set, a form post carries
|
|
212
|
+
the chosen option's key: a string, number or boolean option value as text (`"42"` for `42`), an
|
|
213
|
+
object option's index. Under a mouse a hidden input carries it; under touch it is the native
|
|
214
|
+
`<option value>`. A choice is mapped back by position, so the model always gets the option's real
|
|
215
|
+
value. In tests, pick options by label with `BfSelectHarness` from `@bitforgehq/ui/testing`, which
|
|
216
|
+
drives both modes. An option whose value is `null` is not supported: `null` means nothing is selected, so such an
|
|
209
217
|
option never shows as selected.
|
|
210
218
|
|
|
211
219
|
Migrating from 0.2: "nothing selected" is `null` (it was `''`). Type bound signals and fields
|
|
@@ -217,7 +225,7 @@ Migrating from 0.2: "nothing selected" is `null` (it was `''`). Type bound signa
|
|
|
217
225
|
```ts
|
|
218
226
|
import { Component, signal } from '@angular/core';
|
|
219
227
|
import { FormField, form } from '@angular/forms/signals';
|
|
220
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
228
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
221
229
|
|
|
222
230
|
/** A select bound to a Signal Forms field. */
|
|
223
231
|
@Component({
|
|
@@ -240,15 +248,14 @@ Reactive Forms, bind a `FormControl<T | null>` through `[formControl]` or `formC
|
|
|
240
248
|
|
|
241
249
|
## Read-only
|
|
242
250
|
|
|
243
|
-
`readonly` shows the chosen option on the muted surface and blocks changes.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
changes. The select stays in the tab order. Signal Forms binds it from `readonly()` rules.
|
|
251
|
+
`readonly` shows the chosen option on the muted surface and blocks changes. The field stays in the
|
|
252
|
+
tab order, but a click or a key never opens the list. Under touch, the platform picker can still
|
|
253
|
+
open, because the browser gives no press to cancel; a choice made there snaps back to the bound
|
|
254
|
+
value, so the value never changes. Signal Forms binds it from `readonly()` rules.
|
|
248
255
|
|
|
249
256
|
```ts
|
|
250
257
|
import { Component } from '@angular/core';
|
|
251
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
258
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
252
259
|
|
|
253
260
|
/** A read-only select. */
|
|
254
261
|
@Component({
|
|
@@ -273,7 +280,7 @@ of the two. Both are ignored while `label` is set.
|
|
|
273
280
|
|
|
274
281
|
```ts
|
|
275
282
|
import { Component, signal } from '@angular/core';
|
|
276
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
283
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
277
284
|
|
|
278
285
|
/** A small toolbar select named by `ariaLabel`. */
|
|
279
286
|
@Component({
|
|
@@ -298,7 +305,7 @@ export class SelectUnlabelledExampleComponent {
|
|
|
298
305
|
|
|
299
306
|
```ts
|
|
300
307
|
import { Component, signal } from '@angular/core';
|
|
301
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
308
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
302
309
|
|
|
303
310
|
/** The sm, md and lg select sizes. */
|
|
304
311
|
@Component({
|
|
@@ -329,7 +336,8 @@ Once the control is touched it shows the first error as text: the error's own `m
|
|
|
329
336
|
```ts
|
|
330
337
|
import { Component, signal } from '@angular/core';
|
|
331
338
|
import { FormField, FormRoot, form, required } from '@angular/forms/signals';
|
|
332
|
-
import { BfButtonComponent
|
|
339
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
340
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
333
341
|
|
|
334
342
|
/**
|
|
335
343
|
* A required select with the default message.
|
|
@@ -367,36 +375,61 @@ export class SelectValidationExampleComponent {
|
|
|
367
375
|
}
|
|
368
376
|
```
|
|
369
377
|
|
|
378
|
+
## In a table
|
|
379
|
+
|
|
380
|
+
A stacked `bf-table` with sortable columns shows a "Sort by" select above its rows. Add `provideBfSelect()` from `@bitforgehq/ui/overlay` to the app's providers (`ApplicationConfig.providers`) so that select is the styled `bf-select` too; without it, the table, which lives in the cdk-free `@bitforgehq/ui`, falls back to the native one. `provideBfSelect()` imports `bf-select` (and the CDK overlay it uses) into the bundle that holds the app config, usually the initial one; when the stacked tables sit behind lazy routes, add it to the `providers` of a route in a lazily loaded routes file instead, or provide `{ provide: BF_TABLE_SORT_SELECT, useValue: BfSelectComponent }` on the component that holds the table, as this site's stacked table example does.
|
|
381
|
+
|
|
382
|
+
## Migration notes
|
|
383
|
+
|
|
384
|
+
### 0.5.0
|
|
385
|
+
|
|
386
|
+
- `bf-select` moved from `@bitforgehq/ui` to `@bitforgehq/ui/overlay`: change `import { BfSelectComponent } from '@bitforgehq/ui'` to `import { BfSelectComponent } from '@bitforgehq/ui/overlay'`. The selector, inputs, outputs and Signal Forms binding are unchanged.
|
|
387
|
+
- The overlay entry needs `@angular/cdk` (and `rxjs`): `bun add @angular/cdk rxjs`.
|
|
388
|
+
- `BfSelectOption` stays importable from `@bitforgehq/ui`, and is also exported from `@bitforgehq/ui/overlay`.
|
|
389
|
+
- Add `provideBfSelect()` to the app's providers so a stacked `bf-table`'s "Sort by" control is the styled select (see [In a table](#in-a-table)); without it, that control stays native.
|
|
390
|
+
- Under a mouse the control is no longer a `<select>`: `checkValidity()` and the browser's own "required" popup are not part of the styled mode. Enforce `required` through Signal Forms (`required()`) or your own check; `aria-required` still announces it. A form post keeps working through a hidden input when `name` is set.
|
|
391
|
+
- Tests that set a `<select>`'s value directly break under a mouse; pick by label with `BfSelectHarness` (`selectOption()`, `getValueLabel()`, the new `isOpen()`), which drives both modes alike: `getOptionLabels()` reads a read-only or disabled select's options too, and `selectOption()` on a read-only select changes nothing.
|
|
392
|
+
|
|
370
393
|
## Desktop
|
|
371
394
|
|
|
372
|
-
Hover darkens the border (`text-muted`, `danger` in the error state) with the pointer cursor; a read-only select keeps the default cursor. Hover styles apply only on devices that can hover, so a tap never leaves one stuck. The `primary` border and the focus ring show on every focus, as on the text fields.
|
|
395
|
+
Hover darkens the border (`text-muted`, `danger` in the error state) with the pointer cursor; a read-only select keeps the default cursor. Hover styles apply only on devices that can hover, so a tap never leaves one stuck. The `primary` border and the focus ring show on every focus, as on the text fields. A click on the field opens the list below it (above it when there is no room below), with the chosen option active; the list follows the field while it is open, and a click outside closes it. The pointer moving over an option makes it the active one. Options are 32, 36 or 44 px tall with the field's size. Fields default to `md` (40 px); `provideBfDefaults({ fieldSize: 'sm' })` gives 32 px selects for toolbars and filter bars ([density guide](https://bitforge-ui.web.app/density)).
|
|
373
396
|
|
|
374
397
|
## Theming
|
|
375
398
|
|
|
376
399
|
- Border: `border-strong` (the control boundary, ≥ 3:1), switching to `danger-border` on error; focus: `primary` border with a `ring` ring.
|
|
377
400
|
- Hover (pointer cursor): border `text-muted`, or `danger` in the error state.
|
|
378
401
|
- Disabled: `surface-sunken` border, `surface-muted` background, `text-disabled` text. Read-only: `surface-muted` background, default cursor. Placeholder text: `text-muted` (≥ 4.5:1). Chevron: `text-subtle`.
|
|
402
|
+
- List: `bg-surface`, a 1 px `border-border`, `rounded-card`, `shadow-floating`; the active option `surface-muted`, the chosen option `primary-text` with a check mark, a disabled one `text-disabled`. In forced colours the active option takes the system highlight.
|
|
379
403
|
- Label, help and error text come from the `bf-field` wrapper (see its Theming section).
|
|
380
404
|
|
|
381
405
|
## Accessibility
|
|
382
406
|
|
|
383
|
-
|
|
407
|
+
Under a mouse the field follows the [select-only combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/) pattern: it is a focusable `role="combobox"` with `aria-haspopup="listbox"`, `aria-expanded`, and while open `aria-controls` (the listbox) and `aria-activedescendant` (the active option). Focus never leaves the field. The list is a `listbox` named like the field, its options `role="option"` with `aria-selected` and `aria-disabled`. `required` is announced with `aria-required`.
|
|
408
|
+
|
|
409
|
+
Under touch it is a native `<select>`, so it opens the platform's own picker and stays fully keyboard-operable. The `placeholder`, when set, is a disabled option so it can be shown but never chosen.
|
|
410
|
+
|
|
411
|
+
In both modes `bf-field` renders the label; the control wires `aria-describedby` to the help paragraph and the error paragraph under it (both while shown, help first) and, while it is invalid, `aria-errormessage` to the error paragraph; `aria-invalid` is set once touched and invalid (an explicit `errorMessage` shows immediately). With a visible `label` the label names the control alone: the native select through its `<label for>`, the styled field (a div, which a `<label for>` cannot name) through `aria-labelledby` pointing at the label's id, as in the APG example. `ariaLabel` and `ariaLabelledby` become `aria-label` and `aria-labelledby` only while there is no visible `label`. A hidden input carrying `name` posts nothing while the select is disabled. Read-only: `aria-readonly="true"`, the control stays focusable and the value never changes. Disabled: `aria-disabled="true"` (the native `disabled` under touch) and out of the tab order. `focus()` focuses the field, or the native select under touch; Signal Forms calls it from `focusBoundControl()`.
|
|
412
|
+
|
|
413
|
+
Motion (styled mode): the list grows out of the field as it opens (a fade, a slight scale and a 4 px slide, 180 ms) and fades back as it closes (130 ms). The field's state changes at once and the closing list is inert. Under `prefers-reduced-motion` the list only fades.
|
|
384
414
|
|
|
385
415
|
### Keyboard
|
|
386
416
|
|
|
387
|
-
|
|
417
|
+
Under a mouse (the styled list). While `readonly`, no key opens the list or changes the value; <kbd>Tab</kbd> still moves on.
|
|
388
418
|
|
|
389
|
-
| Key
|
|
390
|
-
|
|
|
391
|
-
| <kbd>
|
|
392
|
-
| <kbd>
|
|
393
|
-
| <kbd>
|
|
394
|
-
| <kbd>
|
|
395
|
-
| <kbd>
|
|
396
|
-
| <kbd>
|
|
397
|
-
| <kbd>
|
|
398
|
-
| <kbd>
|
|
399
|
-
|
|
419
|
+
| Key | Where | Action |
|
|
420
|
+
| ------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
421
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> / <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Field | Opens the list on the chosen option (else the first enabled one); <kbd>Alt</kbd>+<kbd>ArrowDown</kbd> too |
|
|
422
|
+
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Open list | Next / previous enabled option, wrapping at the ends |
|
|
423
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | Open list | First / last enabled option |
|
|
424
|
+
| <kbd>PageUp</kbd> / <kbd>PageDown</kbd> | Open list | Moves ten enabled options up / down, stopping at the first / last |
|
|
425
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Open list | Picks the active option and closes the list |
|
|
426
|
+
| <kbd>Tab</kbd> | Open list | Picks the active option, closes the list and moves focus on |
|
|
427
|
+
| <kbd>Escape</kbd> | Open list | Closes the list without changing the value (a dialog underneath stays open) |
|
|
428
|
+
| <kbd>A</kbd>–<kbd>Z</kbd>, <kbd>0</kbd>–<kbd>9</kbd> | Field | Typeahead: opens the list and makes the next option whose label starts with the typed text active (case and accents ignored) |
|
|
429
|
+
|
|
430
|
+
Typed keys within half a second form one search ("New Y"); the same letter typed again cycles through the options starting with it. Typeahead and the arrows only move the active option: the value changes on <kbd>Enter</kbd>, <kbd>Space</kbd>, <kbd>Tab</kbd> or a click, so `changed` fires once per pick. The pointer moving over an option makes it the active one, so a following <kbd>Tab</kbd> picks it, as in the combobox.
|
|
431
|
+
|
|
432
|
+
Under touch (the native select) the keys are the browser's own and differ slightly between operating systems: <kbd>Space</kbd> (or <kbd>Alt</kbd>+<kbd>ArrowDown</kbd> / <kbd>F4</kbd> on Windows and Linux) opens the picker, the arrows, <kbd>Home</kbd> / <kbd>End</kbd> and typeahead choose, <kbd>Escape</kbd> closes it.
|
|
400
433
|
|
|
401
434
|
## Inputs
|
|
402
435
|
|
|
@@ -408,7 +441,7 @@ The select is a native `<select>`, so these are the browser's keys and differ sl
|
|
|
408
441
|
| `label` | `string` | `''` | Visible label above the control. |
|
|
409
442
|
| `ariaLabel` | `string` | `''` | Accessible name when no visible label is rendered (e.g. a table filter). Ignored when `label` is set. |
|
|
410
443
|
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when `label` is set. |
|
|
411
|
-
| `placeholder` | `string` | `''` |
|
|
444
|
+
| `placeholder` | `string` | `''` | Muted text shown while nothing is selected. |
|
|
412
445
|
| `helpText` | `string` | `''` | Helper text under the control. |
|
|
413
446
|
| `errorMessage` | `string` | `''` | Explicit error text; takes precedence over Signal Forms errors. |
|
|
414
447
|
| `disabled` | `boolean` | `false` | Disables the control. Set by `[formField]`; do not bind it yourself. |
|
|
@@ -417,7 +450,7 @@ The select is a native `<select>`, so these are the browser's keys and differ sl
|
|
|
417
450
|
| `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
418
451
|
| `touched` | `boolean` | `false` | Whether the user has left the field. Set by `[formField]`; do not bind it yourself. |
|
|
419
452
|
| `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
420
|
-
| `name` | `string` | `''` |
|
|
453
|
+
| `name` | `string` | `''` | Form name: of a hidden input carrying the selected option's key, or of the native select. Set by `[formField]`; do not bind it yourself. |
|
|
421
454
|
| `id` | `string` | `generated` | Control id; generated when not set. |
|
|
422
455
|
| `value` | `T \| null` | `null` | The selected option's value, `null` while nothing is selected; two-way bindable and bound by `[formField]`. Two-way: `[(value)]`. |
|
|
423
456
|
|
|
@@ -434,7 +467,8 @@ The select is a native `<select>`, so these are the browser's keys and differ sl
|
|
|
434
467
|
|
|
435
468
|
### `focus(options?: FocusOptions): void`
|
|
436
469
|
|
|
437
|
-
Focuses the native select; Signal Forms calls it from
|
|
470
|
+
Focuses the field, or the native select under a coarse pointer; Signal Forms calls it from
|
|
471
|
+
`focusBoundControl()`.
|
|
438
472
|
|
|
439
473
|
| Parameter | Type | Description |
|
|
440
474
|
| ---------- | -------------- | --------------------------------------------------------------------- |
|
|
@@ -445,7 +479,8 @@ Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
|
|
|
445
479
|
### Do
|
|
446
480
|
|
|
447
481
|
- Provide a `placeholder` when no option is a sensible default, and make the field required.
|
|
448
|
-
- Keep option labels short; long
|
|
482
|
+
- Keep option labels short; a long label is truncated in the field.
|
|
483
|
+
- Add `provideBfSelect()` once when a page has a stacked, sortable `bf-table`: in the app config, or on the lazy route that holds the table, so the styled select and the CDK overlay stay out of the initial bundle.
|
|
449
484
|
|
|
450
485
|
### Don't
|
|
451
486
|
|
|
@@ -453,6 +488,29 @@ Focuses the native select; Signal Forms calls it from `focusBoundControl()`.
|
|
|
453
488
|
|
|
454
489
|
## Migration notes
|
|
455
490
|
|
|
491
|
+
### 0.5.0
|
|
492
|
+
|
|
493
|
+
**Breaking:** `bf-select` moved from `@bitforgehq/ui` to `@bitforgehq/ui/overlay` (see
|
|
494
|
+
Migration). Under a fine pointer it now opens a styled list, the combobox's panel without the
|
|
495
|
+
search, exactly as wide as the field: a focusable `role="combobox"` field following the APG
|
|
496
|
+
select-only combobox pattern (Enter, Space, the arrows and Alt+ArrowDown open; the arrows,
|
|
497
|
+
Home/End and PageUp/PageDown move; Enter, Space, Tab or a click pick; Escape closes without
|
|
498
|
+
closing a dialog underneath). Typeahead (500 ms buffer, case and accents ignored) moves the active
|
|
499
|
+
option, open or closed, and never commits on its own. Under a coarse pointer
|
|
500
|
+
(`matchMedia('(pointer: coarse)')`, followed live) it renders the native `<select>` as before;
|
|
501
|
+
on the server it renders the styled field. With `name` set, a hidden input carries the chosen
|
|
502
|
+
option's key, so plain form posts keep working (none while disabled). With a visible `label` the
|
|
503
|
+
field is named by `aria-labelledby` pointing at the label (a `<label for>` cannot name a div);
|
|
504
|
+
`ariaLabel` and `ariaLabelledby` still apply only without one. Under forced colours a disabled
|
|
505
|
+
field shows `GrayText`. `@bitforgehq/ui/testing`: `BfSelectHarness` drives both modes by label,
|
|
506
|
+
and has a new `isOpen()`; in both modes `getOptionLabels()` reads a read-only or disabled
|
|
507
|
+
select's options, and `selectOption()` on a read-only select changes nothing.
|
|
508
|
+
|
|
509
|
+
The styled list animates open and closed with the popover's motion (180 ms in, 130 ms out). The
|
|
510
|
+
field's `aria-expanded` and focus change at once; the closing list is inert and keeps the options
|
|
511
|
+
it showed until it is gone. Under `prefers-reduced-motion` it only fades. The native select under
|
|
512
|
+
touch is unchanged.
|
|
513
|
+
|
|
456
514
|
### 0.3.0
|
|
457
515
|
|
|
458
516
|
**Breaking:** typed values. `BfSelectComponent<T = string>` is generic over the option value
|