@bitforgehq/ui 0.4.0 → 0.5.0-next.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/CHANGELOG.md +303 -0
- package/README.md +12 -12
- package/ai/MANIFEST.index.json +65 -2
- package/ai/MANIFEST.json +2221 -737
- package/ai/REFERENCE.index.json +161 -0
- package/ai/REFERENCE.json +1336 -27
- package/ai/UI_GUIDE.md +61 -20
- package/ai/components/app-frame.md +1 -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-viewer.md +309 -0
- package/ai/components/media-card.md +478 -0
- package/ai/components/menu-button.md +2 -0
- 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/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-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 +105 -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-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 +10 -3
- package/ai/reference/provide-bf-select.md +20 -0
- package/bin/bf-ui.js +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2649 -606
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +356 -10
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +7531 -3989
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +4151 -130
- package/llms.txt +31 -1
- package/package.json +1 -1
- package/theme.css +60 -0
- package/types/bitforgehq-ui-overlay.d.ts +684 -7
- package/types/bitforgehq-ui-testing.d.ts +434 -6
- package/types/bitforgehq-ui.d.ts +1473 -182
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# bf-image-input
|
|
2
|
+
|
|
3
|
+
Single-image picker with a round or square preview tile and replace or remove
|
|
4
|
+
|
|
5
|
+
`BfImageInputComponent` from `@bitforgehq/ui` · Forms · since 0.5.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { BfImageInputComponent } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-image-input overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-image-input`: a round profile picture bound to a signal, limited to PNG and JPEG
|
|
17
|
+
* up to 2 MB, with a line that names the picked image; then a square company logo.
|
|
18
|
+
*/
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'app-example-image-input-overview',
|
|
21
|
+
imports: [BfImageInputComponent],
|
|
22
|
+
template: `
|
|
23
|
+
<div class="flex flex-col gap-5">
|
|
24
|
+
<bf-image-input
|
|
25
|
+
label="Profile picture"
|
|
26
|
+
helpText="PNG or JPEG, up to 2 MB"
|
|
27
|
+
accept="image/png,image/jpeg"
|
|
28
|
+
[maxSize]="2 * 1024 * 1024"
|
|
29
|
+
[(value)]="avatar"
|
|
30
|
+
/>
|
|
31
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
32
|
+
<bf-image-input label="Company logo" shape="square" helpText="Shown on your invoices" />
|
|
33
|
+
</div>
|
|
34
|
+
`,
|
|
35
|
+
})
|
|
36
|
+
export class ImageInputOverviewExampleComponent {
|
|
37
|
+
readonly avatar = signal<File | null>(null);
|
|
38
|
+
|
|
39
|
+
readonly status = computed(() => {
|
|
40
|
+
const file = this.avatar();
|
|
41
|
+
return file ? `Picked: ${file.name}.` : 'No picture picked.';
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Overview
|
|
47
|
+
|
|
48
|
+
Single-image picker for an avatar, logo or cover: a round or square tile that shows the image
|
|
49
|
+
(or a camera glyph while empty), with the label, help text and Upload/Replace and Remove buttons
|
|
50
|
+
beside it. Clicking the tile or Upload opens the native picker; an image dropped on the tile is
|
|
51
|
+
taken too. `src` shows an image the app already has until the user picks a new one or removes
|
|
52
|
+
it. A pick that breaks `accept` or `maxSize` keeps the current image, shows the reason under the
|
|
53
|
+
buttons and is emitted through `rejected`. It only picks: uploading is the app's job. Bind
|
|
54
|
+
`[(value)]` directly or `[formField]` from a signal form.
|
|
55
|
+
|
|
56
|
+
A single-image picker for an avatar, a logo or a cover: a round or square tile that shows the image (or a camera glyph while empty), with the label, help text and Upload (Replace once there is an image) and Remove buttons beside it. Clicking the tile or Upload opens the native picker; an image dropped on the tile is taken too. `src` shows the image the app already has. It only picks: uploading is the app's job.
|
|
57
|
+
|
|
58
|
+
## When to use
|
|
59
|
+
|
|
60
|
+
Use `bf-image-input` for one picture the user should see while choosing it: a profile picture, a company logo, a cover image. Use [bf-file-input](https://bitforge-ui.web.app/components/file-input) instead for documents, for several files, or for images that are attachments rather than a picture of something (scans, receipts). To only show a person's picture, use [bf-avatar](https://bitforge-ui.web.app/components/avatar).
|
|
61
|
+
|
|
62
|
+
## Basic usage
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { Component, computed, signal } from '@angular/core';
|
|
66
|
+
import { BfImageInputComponent } from '@bitforgehq/ui';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The bf-image-input overview.
|
|
70
|
+
*
|
|
71
|
+
* Live demo of `bf-image-input`: a round profile picture bound to a signal, limited to PNG and JPEG
|
|
72
|
+
* up to 2 MB, with a line that names the picked image; then a square company logo.
|
|
73
|
+
*/
|
|
74
|
+
@Component({
|
|
75
|
+
selector: 'app-example-image-input-overview',
|
|
76
|
+
imports: [BfImageInputComponent],
|
|
77
|
+
template: `
|
|
78
|
+
<div class="flex flex-col gap-5">
|
|
79
|
+
<bf-image-input
|
|
80
|
+
label="Profile picture"
|
|
81
|
+
helpText="PNG or JPEG, up to 2 MB"
|
|
82
|
+
accept="image/png,image/jpeg"
|
|
83
|
+
[maxSize]="2 * 1024 * 1024"
|
|
84
|
+
[(value)]="avatar"
|
|
85
|
+
/>
|
|
86
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
87
|
+
<bf-image-input label="Company logo" shape="square" helpText="Shown on your invoices" />
|
|
88
|
+
</div>
|
|
89
|
+
`,
|
|
90
|
+
})
|
|
91
|
+
export class ImageInputOverviewExampleComponent {
|
|
92
|
+
readonly avatar = signal<File | null>(null);
|
|
93
|
+
|
|
94
|
+
readonly status = computed(() => {
|
|
95
|
+
const file = this.avatar();
|
|
96
|
+
return file ? `Picked: ${file.name}.` : 'No picture picked.';
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`value` is the picked `File`, `null` while none is picked. `accept` defaults to `image/*`. `(changed)` emits the new image (or `null` after Remove) right after a user change; a programmatic write does not emit.
|
|
102
|
+
|
|
103
|
+
## Shapes and sizes
|
|
104
|
+
|
|
105
|
+
`shape="round"` (the default) is a circle for a person; `shape="square"` a rounded square for a logo or a cover. The tile is 64, 96 or 128 px for `sm`, `md` and `lg`; unset, `size` follows `provideBfDefaults()`'s `fieldSize` (`md` by default). The square's radius grows with the tile.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { Component, signal } from '@angular/core';
|
|
109
|
+
import { BfImageInputComponent } from '@bitforgehq/ui';
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Round and square tiles.
|
|
113
|
+
*
|
|
114
|
+
* `shape="round"` (the default) for a person's picture, `shape="square"` for a logo or a cover.
|
|
115
|
+
*/
|
|
116
|
+
@Component({
|
|
117
|
+
selector: 'app-example-image-input-shapes',
|
|
118
|
+
imports: [BfImageInputComponent],
|
|
119
|
+
template: `
|
|
120
|
+
<div class="flex flex-col gap-5">
|
|
121
|
+
<bf-image-input label="Profile picture" [(value)]="avatar" />
|
|
122
|
+
<bf-image-input label="Team logo" shape="square" [(value)]="logo" />
|
|
123
|
+
</div>
|
|
124
|
+
`,
|
|
125
|
+
})
|
|
126
|
+
export class ImageInputShapesExampleComponent {
|
|
127
|
+
protected readonly avatar = signal<File | null>(null);
|
|
128
|
+
protected readonly logo = signal<File | null>(null);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## An image the app already has
|
|
133
|
+
|
|
134
|
+
Bind `[(src)]` to the URL of the saved picture. The tile shows it while `value` is null; a pick shows the new image instead, and Remove clears both `value` and `src`. So on save the app can tell three cases apart:
|
|
135
|
+
|
|
136
|
+
- `value` set: the user picked a new picture; upload it.
|
|
137
|
+
- `value` null and `src` null: the user removed the picture; delete it.
|
|
138
|
+
- `value` null and `src` unchanged: keep the current picture.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { Component, computed, signal } from '@angular/core';
|
|
142
|
+
import { BfImageInputComponent } from '@bitforgehq/ui';
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* An image the app already has.
|
|
146
|
+
*
|
|
147
|
+
* `[(src)]` shows the current picture until the user picks a new one or removes it. Remove sets
|
|
148
|
+
* both `value` and `src` to null, so on save the app can tell the three cases apart: a new picture
|
|
149
|
+
* (`value` set), removed (both null), or keep (`value` null, `src` unchanged).
|
|
150
|
+
*/
|
|
151
|
+
@Component({
|
|
152
|
+
selector: 'app-example-image-input-existing',
|
|
153
|
+
imports: [BfImageInputComponent],
|
|
154
|
+
template: `
|
|
155
|
+
<div class="flex flex-col gap-3">
|
|
156
|
+
<bf-image-input label="Profile picture" [(value)]="picked" [(src)]="currentUrl" />
|
|
157
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ onSave() }}</p>
|
|
158
|
+
</div>
|
|
159
|
+
`,
|
|
160
|
+
})
|
|
161
|
+
export class ImageInputExistingExampleComponent {
|
|
162
|
+
/** The URL the server returned for the saved picture. */
|
|
163
|
+
private readonly savedUrl = 'apple-touch-icon.png';
|
|
164
|
+
protected readonly currentUrl = signal<string | null>(this.savedUrl);
|
|
165
|
+
protected readonly picked = signal<File | null>(null);
|
|
166
|
+
|
|
167
|
+
/** What a save would do. */
|
|
168
|
+
protected readonly onSave = computed(() => {
|
|
169
|
+
const file = this.picked();
|
|
170
|
+
if (file) {
|
|
171
|
+
return `On save: upload ${file.name}.`;
|
|
172
|
+
}
|
|
173
|
+
return this.currentUrl() === null
|
|
174
|
+
? 'On save: delete the current picture.'
|
|
175
|
+
: 'On save: keep the current picture.';
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Rules and rejected images
|
|
181
|
+
|
|
182
|
+
`accept` (native syntax, `image/png,image/jpeg`) and `maxSize` (bytes) decide what is taken; a dropped image is checked against them too. A refused image never enters the value and never makes the field invalid: the current image stays, the reason shows in a line under the buttons ("Too large, max 2 MB") until the next pick or Remove, and `(rejected)` emits a `BfRejectedFile[]` (`file`, `reason` `'type'` or `'size'`, `message`). If several images are dropped at once, the first acceptable one is taken and the others are rejected with the reason `'count'`.
|
|
183
|
+
|
|
184
|
+
## Uploading
|
|
185
|
+
|
|
186
|
+
The control never uploads: it hands the app the `File` the user picked. Upload it on save with the app's own code (a `FormData` posted through `HttpClient` in a data service, a signed URL, a storage SDK), then set `src` to the URL the server returns and `value` back to `null`. Cropping and resizing, when needed, also happen in the app before the upload. With `name` set, the hidden native input carries the picked image, so a plain HTML form post includes it.
|
|
187
|
+
|
|
188
|
+
## With Signal Forms
|
|
189
|
+
|
|
190
|
+
Bind `[formField]` to a `File | null` field. The saved picture (`src`) counts as filled, but `required()` sees only the null `value` and would report the field empty. So set the label's marker with the `REQUIRED` metadata, `metadata(p.avatar, REQUIRED, () => true)`, and add a rule that reads the `src` the app holds: `validate(p.avatar, ({ value }) => value() === null && !this.avatarUrl() ? requiredError({ message: 'Add a profile picture' }) : undefined)`. Without a `src`, `required(p.avatar)` alone is enough.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { Component, signal } from '@angular/core';
|
|
194
|
+
import {
|
|
195
|
+
FormField,
|
|
196
|
+
FormRoot,
|
|
197
|
+
REQUIRED,
|
|
198
|
+
form,
|
|
199
|
+
metadata,
|
|
200
|
+
requiredError,
|
|
201
|
+
validate,
|
|
202
|
+
} from '@angular/forms/signals';
|
|
203
|
+
import { BfButtonComponent, BfImageInputComponent } from '@bitforgehq/ui';
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* A required picture on Signal Forms.
|
|
207
|
+
*
|
|
208
|
+
* The picture the app already has (`src`) fills the field, but `required()` sees only the null
|
|
209
|
+
* `value` and would report it missing. So the `REQUIRED` metadata gives the label its marker, and a
|
|
210
|
+
* `validate()` rule reports a missing picture only while there is neither a picked image nor a
|
|
211
|
+
* `src`.
|
|
212
|
+
*/
|
|
213
|
+
@Component({
|
|
214
|
+
selector: 'app-example-image-input-signal-forms',
|
|
215
|
+
imports: [FormField, FormRoot, BfButtonComponent, BfImageInputComponent],
|
|
216
|
+
template: `
|
|
217
|
+
<form [formRoot]="profile" class="flex flex-col items-start gap-4">
|
|
218
|
+
<bf-image-input label="Profile picture" [formField]="profile.avatar" [(src)]="avatarUrl" />
|
|
219
|
+
<bf-button type="submit">Save profile</bf-button>
|
|
220
|
+
</form>
|
|
221
|
+
`,
|
|
222
|
+
})
|
|
223
|
+
export class ImageInputSignalFormsExampleComponent {
|
|
224
|
+
/** The saved picture's URL; Remove sets it to null. */
|
|
225
|
+
protected readonly avatarUrl = signal<string | null>('apple-touch-icon.png');
|
|
226
|
+
private readonly model = signal<{ avatar: File | null }>({ avatar: null });
|
|
227
|
+
protected readonly profile = form(
|
|
228
|
+
this.model,
|
|
229
|
+
(p) => {
|
|
230
|
+
metadata(p.avatar, REQUIRED, () => true);
|
|
231
|
+
validate(p.avatar, ({ value }) =>
|
|
232
|
+
value() === null && !this.avatarUrl()
|
|
233
|
+
? requiredError({ message: 'Add a profile picture' })
|
|
234
|
+
: undefined,
|
|
235
|
+
);
|
|
236
|
+
},
|
|
237
|
+
{ submission: { action: () => Promise.resolve(undefined) } },
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Read-only and disabled
|
|
243
|
+
|
|
244
|
+
`readonly` shows the image in a plain tile, with no buttons and no drop. `disabled` greys the tile and buttons out, ignores drops and takes them out of the tab order.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { Component } from '@angular/core';
|
|
248
|
+
import { BfImageInputComponent } from '@bitforgehq/ui';
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Read-only and disabled.
|
|
252
|
+
*
|
|
253
|
+
* Read-only shows the picture in a plain tile with no buttons and takes no drop; disabled greys
|
|
254
|
+
* the tile and buttons out and takes them out of the tab order.
|
|
255
|
+
*/
|
|
256
|
+
@Component({
|
|
257
|
+
selector: 'app-example-image-input-states',
|
|
258
|
+
imports: [BfImageInputComponent],
|
|
259
|
+
template: `
|
|
260
|
+
<div class="flex flex-col gap-5">
|
|
261
|
+
<bf-image-input label="Company logo" shape="square" readonly src="apple-touch-icon.png" />
|
|
262
|
+
<bf-image-input label="Profile picture" disabled helpText="Managed by your organisation" />
|
|
263
|
+
</div>
|
|
264
|
+
`,
|
|
265
|
+
})
|
|
266
|
+
export class ImageInputStatesExampleComponent {}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Phones
|
|
270
|
+
|
|
271
|
+
On a phone the tile and Upload open the platform's own picker, which usually offers the photo library and the camera, filtered by `accept`. Dropping is a desktop gesture. Under a coarse pointer the Upload and Remove buttons reach 44 px through an invisible extension; the tile is at least 64 px.
|
|
272
|
+
|
|
273
|
+
## Desktop
|
|
274
|
+
|
|
275
|
+
An image dragged over the tile highlights it (`primary` border, `selected` background) and is taken on drop. The tile takes `surface-muted` on hover, with the pointer cursor; the buttons are `bf-button`'s text variant. The tile shows the focus ring on keyboard focus, and the label takes the focus colour while the tile or a button has focus.
|
|
276
|
+
|
|
277
|
+
## Theming
|
|
278
|
+
|
|
279
|
+
- Tile: 2 px border, dashed `border-strong` while empty and solid `border` with an image; `danger-border` on error; `primary` with a `selected` background while dragging. Empty glyph: `text-subtle`. Read-only: `surface-muted`. Disabled: `surface-sunken` border, `surface-muted` background, `text-disabled` glyph. Under forced colours the border is `CanvasText` (`Highlight` while dragging, `GrayText` disabled).
|
|
280
|
+
- Round tiles are `rounded-full`; square ones `rounded-tile`, `rounded-field` or `rounded-card` with the size.
|
|
281
|
+
- Upload / Replace: the text `bf-button`. Remove: `danger` text, `danger-soft` on hover. The rejection line: `danger`.
|
|
282
|
+
- Label, help and error text come from the `bf-field` wrapper (see its Theming section).
|
|
283
|
+
|
|
284
|
+
## Accessibility
|
|
285
|
+
|
|
286
|
+
The tile is a native `<button>` named "Choose image" (`imageInputChoose`; `ariaLabel` replaces it while there is no visible label) and described by the label, the help and the error paragraph, so a screen reader hears "Choose image, Profile picture". `aria-invalid` and `aria-errormessage` are set once touched and invalid. Upload / Replace and Remove are native buttons described by the label, so "Remove" says what it removes. The preview image is decorative (`alt=""`): the label names what it shows. The native `<input type="file">` is visually hidden and out of the tab order. `required` gives the label its marker; there is no `aria-required`, which is not allowed on a button. The rejection reason shows at once under the buttons, and a polite live region announces it. After Remove, focus returns to the tile. `focus()` focuses the tile; Signal Forms calls it from `focusBoundControl()`.
|
|
287
|
+
|
|
288
|
+
### Keyboard
|
|
289
|
+
|
|
290
|
+
| Key | Where | Action |
|
|
291
|
+
| ------------------------------------------------ | ------------ | ---------------------------------------------------------------- |
|
|
292
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Control | Moves through the tile, Upload / Replace and Remove |
|
|
293
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Tile, Upload | Opens the native file picker |
|
|
294
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Remove | Clears the picked and the existing image; focus goes to the tile |
|
|
295
|
+
|
|
296
|
+
## Inputs
|
|
297
|
+
|
|
298
|
+
| Name | Type | Default | Description |
|
|
299
|
+
| ---------------- | ----------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
300
|
+
| `shape` | `BfImageInputShape` (`'round' \| 'square'`) | `'round'` | The tile's shape: `round` (a circle, the default) or `square` (a rounded square). |
|
|
301
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Tile size: sm 64, md 96, lg 128 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by default). |
|
|
302
|
+
| `accept` | `string` | `'image/*'` | The accepted file types, in the native `accept` syntax (`'image/png,image/jpeg'`); dropped files are checked against it too. |
|
|
303
|
+
| `maxSize` | `number \| undefined` | `undefined` | Largest file size in bytes; a larger image is rejected. Unset allows any size. |
|
|
304
|
+
| `label` | `string` | `''` | Visible label beside the tile. |
|
|
305
|
+
| `ariaLabel` | `string` | `''` | Accessible name of the tile when no visible label is rendered, in place of "Choose image". Ignored when `label` is set. |
|
|
306
|
+
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the tile when no visible label is rendered. Ignored when `label` is set. |
|
|
307
|
+
| `helpText` | `string` | `''` | Helper text under the label. |
|
|
308
|
+
| `errorMessage` | `string` | `''` | Explicit error text; takes precedence over Signal Forms errors. |
|
|
309
|
+
| `disabled` | `boolean` | `false` | Disables the control: the tile and buttons are disabled and drops are ignored. Set by `[formField]`; do not bind it yourself. |
|
|
310
|
+
| `readonly` | `boolean` | `false` | Makes the control read-only: the image shows in a plain tile, with no buttons or drop. Set by `[formField]`; do not bind it yourself. |
|
|
311
|
+
| `required` | `boolean` | `false` | Marks the control required (label marker). A `value` or a `src` fills it; see the docs for a validator that counts `src`. Set by `[formField]`; do not bind it yourself. |
|
|
312
|
+
| `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
313
|
+
| `touched` | `boolean` | `false` | Whether the user has left the field; errors show only once touched. Set by `[formField]`; do not bind it yourself. |
|
|
314
|
+
| `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms; the first message is shown once touched. Set by `[formField]`; do not bind it yourself. |
|
|
315
|
+
| `name` | `string` | `''` | Native name attribute; set, the hidden native file input carries the picked image, so a plain HTML form post includes it. Set by `[formField]`; do not bind it yourself. |
|
|
316
|
+
| `id` | `string` | `generated` | Control id (the tile's); generated when not set. |
|
|
317
|
+
| `value` | `File \| null` | `null` | The picked image; two-way bindable and bound by `[formField]`. Null while none is picked. Two-way: `[(value)]`. |
|
|
318
|
+
| `src` | `string \| null` | `null` | URL of an image the app already has (the current avatar), shown while `value` is null. Remove sets it to null; two-way bindable. Two-way: `[(src)]`. |
|
|
319
|
+
|
|
320
|
+
## Outputs
|
|
321
|
+
|
|
322
|
+
| Name | Payload | Description |
|
|
323
|
+
| ---------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
324
|
+
| `changed` | `File \| null` | Emitted with the new image (or null after Remove) right after a user change, not on a model write. |
|
|
325
|
+
| `rejected` | `BfRejectedFile[]` | Emitted once per pick or drop in which a file broke a rule, with those files. |
|
|
326
|
+
| `focused` | `FocusEvent` | Emitted when the tile gains focus. |
|
|
327
|
+
| `blurred` | `FocusEvent` | Emitted when the tile loses focus. |
|
|
328
|
+
| `touch` | `void` | Emitted on blur of the tile or a button so Signal Forms marks the field touched. Set by `[formField]`; do not bind it yourself. |
|
|
329
|
+
|
|
330
|
+
## Methods
|
|
331
|
+
|
|
332
|
+
### `focus(options?: FocusOptions): void`
|
|
333
|
+
|
|
334
|
+
Focuses the tile; Signal Forms calls it from `focusBoundControl()`. Read-only has no button, so
|
|
335
|
+
nothing is focused.
|
|
336
|
+
|
|
337
|
+
| Parameter | Type | Description |
|
|
338
|
+
| ---------- | -------------- | --------------------------------------------------------------------- |
|
|
339
|
+
| `options?` | `FocusOptions` | Passed on to the native `focus()`, such as `{ preventScroll: true }`. |
|
|
340
|
+
|
|
341
|
+
## Notes
|
|
342
|
+
|
|
343
|
+
### Do
|
|
344
|
+
|
|
345
|
+
- Bind `[(src)]` to the saved picture, and compare `value` and `src` on save to know what changed.
|
|
346
|
+
- Set `accept` and `maxSize` to what the server takes.
|
|
347
|
+
- Use `square` for logos and covers, `round` for people.
|
|
348
|
+
|
|
349
|
+
### Don't
|
|
350
|
+
|
|
351
|
+
- Don't use it for several files or documents; use `bf-file-input`.
|
|
352
|
+
- Don't bind both `[(value)]` and `[formField]`.
|
|
353
|
+
- Don't count on `required()` while a `src` can fill the field; use the `validate()` rule.
|
|
354
|
+
|
|
355
|
+
## Migration notes
|
|
356
|
+
|
|
357
|
+
### 0.5.0
|
|
358
|
+
|
|
359
|
+
New: `bf-image-input`, a single-image picker for an avatar, logo or cover: a `round` or `square`
|
|
360
|
+
tile (`shape`, new type `BfImageInputShape`; 64, 96 or 128 px with `size`) that shows the image or
|
|
361
|
+
a camera glyph, with Upload/Replace and Remove beside it. The value is a `File | null`. `src`, a
|
|
362
|
+
two-way `model()`, shows the image the app already has; Remove clears both `value` and `src`, so
|
|
363
|
+
the app can tell a new picture (`value` set), a removed one (both null) and an unchanged one
|
|
364
|
+
(`src` kept, `value` null) apart. `accept` (default `image/*`) and `maxSize` refuse an image
|
|
365
|
+
without touching the current one: the reason shows under the buttons and `rejected` emits it. It
|
|
366
|
+
only picks; uploading stays with the app. New label keys `imageInputChoose`, `imageInputUpload`,
|
|
367
|
+
`imageInputReplace` and `imageInputRemove`. `@bitforgehq/ui/testing`: new `BfImageInputHarness`
|
|
368
|
+
(`addFile()`, `hasPreview()`, `getPreviewSrc()`, `remove()`, `getRejectedMessage()`).
|