@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
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# bf-image-viewer
|
|
2
|
+
|
|
3
|
+
Full-screen image viewer that zooms from a thumbnail and steps through a set
|
|
4
|
+
|
|
5
|
+
`BfImageViewerComponent` from `@bitforgehq/ui/overlay` · Overlays · since 0.5.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
Component,
|
|
12
|
+
ElementRef,
|
|
13
|
+
Injector,
|
|
14
|
+
type TemplateRef,
|
|
15
|
+
inject,
|
|
16
|
+
signal,
|
|
17
|
+
viewChild,
|
|
18
|
+
viewChildren,
|
|
19
|
+
} from '@angular/core';
|
|
20
|
+
import { BfActionDirective, BfButtonComponent, BfMediaCardComponent } from '@bitforgehq/ui';
|
|
21
|
+
import {
|
|
22
|
+
type BfImageViewerActionsContext,
|
|
23
|
+
type BfImageViewerImage,
|
|
24
|
+
BfImageViewerService,
|
|
25
|
+
} from '@bitforgehq/ui/overlay';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The bf-image-viewer overview.
|
|
29
|
+
*
|
|
30
|
+
* Live demo of `BfImageViewerService`: a grid of `bf-media-card` thumbnails opens the viewer on
|
|
31
|
+
* the clicked one, zooming out of its picture. The arrow keys or the side buttons step through the set, and closing zooms
|
|
32
|
+
* back into the thumbnail of the image shown. The "Use as cover" action closes the viewer and
|
|
33
|
+
* keeps the pick.
|
|
34
|
+
*/
|
|
35
|
+
@Component({
|
|
36
|
+
selector: 'app-example-image-viewer-overview',
|
|
37
|
+
imports: [BfActionDirective, BfButtonComponent, BfMediaCardComponent],
|
|
38
|
+
template: `
|
|
39
|
+
<div class="flex flex-col gap-3">
|
|
40
|
+
<div class="grid grid-cols-2 gap-3 @md:grid-cols-5">
|
|
41
|
+
@for (image of images; track image.src; let i = $index) {
|
|
42
|
+
<bf-media-card #card aspect="1/1" [src]="image.src" [alt]="image.label">
|
|
43
|
+
<button bfAction type="button" class="text-label text-start" (click)="open(i)">
|
|
44
|
+
{{ image.caption ?? 'Untitled' }}
|
|
45
|
+
</button>
|
|
46
|
+
</bf-media-card>
|
|
47
|
+
}
|
|
48
|
+
</div>
|
|
49
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<ng-template #actions let-image let-ref="ref">
|
|
53
|
+
<bf-button variant="primary" size="sm" (clicked)="useAsCover(image); ref.close()">
|
|
54
|
+
Use as cover
|
|
55
|
+
</bf-button>
|
|
56
|
+
</ng-template>
|
|
57
|
+
`,
|
|
58
|
+
})
|
|
59
|
+
export class ImageViewerOverviewExampleComponent {
|
|
60
|
+
private readonly viewer = inject(BfImageViewerService);
|
|
61
|
+
private readonly injector = inject(Injector);
|
|
62
|
+
|
|
63
|
+
protected readonly images: readonly BfImageViewerImage[] = [
|
|
64
|
+
{ src: 'media/skyline.svg', label: 'Neon skyline at dusk', caption: 'Starfall, concept 1' },
|
|
65
|
+
{ src: 'media/forest.svg', label: 'Pine forest under a pale sun', caption: 'Rootbound' },
|
|
66
|
+
{ src: 'media/harbour.svg', label: 'Harbour at sunset', caption: 'Tidewalk' },
|
|
67
|
+
{ src: 'media/desert.svg', label: 'Dunes under an orange sun' },
|
|
68
|
+
{ src: 'media/glacier.svg', label: 'Glacier under a small sun', caption: 'Coldlight' },
|
|
69
|
+
];
|
|
70
|
+
protected readonly status = signal('No cover picked.');
|
|
71
|
+
|
|
72
|
+
private readonly cards = viewChildren<BfMediaCardComponent, ElementRef<HTMLElement>>('card', {
|
|
73
|
+
read: ElementRef,
|
|
74
|
+
});
|
|
75
|
+
private readonly actions =
|
|
76
|
+
viewChild.required<TemplateRef<BfImageViewerActionsContext>>('actions');
|
|
77
|
+
|
|
78
|
+
protected open(index: number): void {
|
|
79
|
+
this.viewer.open({
|
|
80
|
+
images: this.images,
|
|
81
|
+
index,
|
|
82
|
+
// Any image's thumbnail, so closing zooms into the one shown.
|
|
83
|
+
origin: (i) => this.cards()[i]?.nativeElement.querySelector('img'),
|
|
84
|
+
actions: this.actions(),
|
|
85
|
+
injector: this.injector,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
protected useAsCover(image: BfImageViewerImage): void {
|
|
90
|
+
this.status.set(`Cover: ${image.label}.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Overview
|
|
96
|
+
|
|
97
|
+
The full-screen image viewer (lightbox) `BfImageViewerService.open()` renders: one image large
|
|
98
|
+
over a dark scrim, with a counter, previous and next buttons, a close button, an optional
|
|
99
|
+
caption and actions. It opens with a zoom out of the clicked thumbnail and steps with a
|
|
100
|
+
cross-fade. Apps do not place it in templates; open it with the service.
|
|
101
|
+
|
|
102
|
+
A full-screen image viewer (a lightbox) from `@bitforgehq/ui/overlay` (needs `@angular/cdk`, no Capacitor). `BfImageViewerService.open()` shows one image large over a dark scrim and steps through a set: previous and next buttons, the arrow keys, a counter ("2 of 5"), the caption and optional actions. It zooms out of the thumbnail that was clicked and back into it, and returns a `BfImageViewerRef`.
|
|
103
|
+
|
|
104
|
+
## When to use
|
|
105
|
+
|
|
106
|
+
Use `BfImageViewerService` to look at a picture larger than the page shows it, such as a card's cover, a gallery or the results of an image generator, and to step through a set of them. Use [BfDialogService](https://bitforge-ui.web.app/api/bf-dialog-service) instead for a picture that comes with a form or a decision, and [bf-media-card](https://bitforge-ui.web.app/components/media-card) for the thumbnails themselves.
|
|
107
|
+
|
|
108
|
+
## Basic usage
|
|
109
|
+
|
|
110
|
+
Call `open()` with the `images` (each a `src`, a `label`, which becomes the image's `alt`, and an optional `caption`) and the `index` to show first. Pass the clicked thumbnail as `origin`, or a function that returns the thumbnail of any image (`(index) => thumbnails[index]`), so closing after a few steps zooms into the right one. Actions under the image (a download link, a "Use this one" button) come from an `actions` template, whose context is `{ $implicit: image, index, ref }`; inject `BfImageViewerRef` or use `ref` to close or step from it.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import {
|
|
114
|
+
Component,
|
|
115
|
+
ElementRef,
|
|
116
|
+
Injector,
|
|
117
|
+
type TemplateRef,
|
|
118
|
+
inject,
|
|
119
|
+
signal,
|
|
120
|
+
viewChild,
|
|
121
|
+
viewChildren,
|
|
122
|
+
} from '@angular/core';
|
|
123
|
+
import { BfActionDirective, BfButtonComponent, BfMediaCardComponent } from '@bitforgehq/ui';
|
|
124
|
+
import {
|
|
125
|
+
type BfImageViewerActionsContext,
|
|
126
|
+
type BfImageViewerImage,
|
|
127
|
+
BfImageViewerService,
|
|
128
|
+
} from '@bitforgehq/ui/overlay';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The bf-image-viewer overview.
|
|
132
|
+
*
|
|
133
|
+
* Live demo of `BfImageViewerService`: a grid of `bf-media-card` thumbnails opens the viewer on
|
|
134
|
+
* the clicked one, zooming out of its picture. The arrow keys or the side buttons step through the set, and closing zooms
|
|
135
|
+
* back into the thumbnail of the image shown. The "Use as cover" action closes the viewer and
|
|
136
|
+
* keeps the pick.
|
|
137
|
+
*/
|
|
138
|
+
@Component({
|
|
139
|
+
selector: 'app-example-image-viewer-overview',
|
|
140
|
+
imports: [BfActionDirective, BfButtonComponent, BfMediaCardComponent],
|
|
141
|
+
template: `
|
|
142
|
+
<div class="flex flex-col gap-3">
|
|
143
|
+
<div class="grid grid-cols-2 gap-3 @md:grid-cols-5">
|
|
144
|
+
@for (image of images; track image.src; let i = $index) {
|
|
145
|
+
<bf-media-card #card aspect="1/1" [src]="image.src" [alt]="image.label">
|
|
146
|
+
<button bfAction type="button" class="text-label text-start" (click)="open(i)">
|
|
147
|
+
{{ image.caption ?? 'Untitled' }}
|
|
148
|
+
</button>
|
|
149
|
+
</bf-media-card>
|
|
150
|
+
}
|
|
151
|
+
</div>
|
|
152
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<ng-template #actions let-image let-ref="ref">
|
|
156
|
+
<bf-button variant="primary" size="sm" (clicked)="useAsCover(image); ref.close()">
|
|
157
|
+
Use as cover
|
|
158
|
+
</bf-button>
|
|
159
|
+
</ng-template>
|
|
160
|
+
`,
|
|
161
|
+
})
|
|
162
|
+
export class ImageViewerOverviewExampleComponent {
|
|
163
|
+
private readonly viewer = inject(BfImageViewerService);
|
|
164
|
+
private readonly injector = inject(Injector);
|
|
165
|
+
|
|
166
|
+
protected readonly images: readonly BfImageViewerImage[] = [
|
|
167
|
+
{ src: 'media/skyline.svg', label: 'Neon skyline at dusk', caption: 'Starfall, concept 1' },
|
|
168
|
+
{ src: 'media/forest.svg', label: 'Pine forest under a pale sun', caption: 'Rootbound' },
|
|
169
|
+
{ src: 'media/harbour.svg', label: 'Harbour at sunset', caption: 'Tidewalk' },
|
|
170
|
+
{ src: 'media/desert.svg', label: 'Dunes under an orange sun' },
|
|
171
|
+
{ src: 'media/glacier.svg', label: 'Glacier under a small sun', caption: 'Coldlight' },
|
|
172
|
+
];
|
|
173
|
+
protected readonly status = signal('No cover picked.');
|
|
174
|
+
|
|
175
|
+
private readonly cards = viewChildren<BfMediaCardComponent, ElementRef<HTMLElement>>('card', {
|
|
176
|
+
read: ElementRef,
|
|
177
|
+
});
|
|
178
|
+
private readonly actions =
|
|
179
|
+
viewChild.required<TemplateRef<BfImageViewerActionsContext>>('actions');
|
|
180
|
+
|
|
181
|
+
protected open(index: number): void {
|
|
182
|
+
this.viewer.open({
|
|
183
|
+
images: this.images,
|
|
184
|
+
index,
|
|
185
|
+
// Any image's thumbnail, so closing zooms into the one shown.
|
|
186
|
+
origin: (i) => this.cards()[i]?.nativeElement.querySelector('img'),
|
|
187
|
+
actions: this.actions(),
|
|
188
|
+
injector: this.injector,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected useAsCover(image: BfImageViewerImage): void {
|
|
193
|
+
this.status.set(`Cover: ${image.label}.`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Stepping and the ref
|
|
199
|
+
|
|
200
|
+
The ref's `index` is a signal of the shown image. `next()` and `previous()` wrap around at both ends, `goTo(index)` jumps (a negative index counts from the end), and `close()` closes; `afterClosed` resolves once the viewer has left the page, and `closed()` turns true. A viewer of one image shows no counter and no step buttons.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { Component, Injector, inject, signal } from '@angular/core';
|
|
204
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
205
|
+
import { BfImageViewerService } from '@bitforgehq/ui/overlay';
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* One image, no thumbnail.
|
|
209
|
+
*
|
|
210
|
+
* Without an `origin` the viewer fades in and out; with a single image it shows no counter and
|
|
211
|
+
* no step buttons. `afterClosed` resolves once it has gone.
|
|
212
|
+
*/
|
|
213
|
+
@Component({
|
|
214
|
+
selector: 'app-example-image-viewer-single',
|
|
215
|
+
imports: [BfButtonComponent],
|
|
216
|
+
template: `
|
|
217
|
+
<div class="flex flex-col items-start gap-3">
|
|
218
|
+
<bf-button variant="tonal" (clicked)="open()">View the key art</bf-button>
|
|
219
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
220
|
+
</div>
|
|
221
|
+
`,
|
|
222
|
+
})
|
|
223
|
+
export class ImageViewerSingleExampleComponent {
|
|
224
|
+
private readonly viewer = inject(BfImageViewerService);
|
|
225
|
+
private readonly injector = inject(Injector);
|
|
226
|
+
protected readonly status = signal('');
|
|
227
|
+
|
|
228
|
+
protected async open(): Promise<void> {
|
|
229
|
+
const ref = this.viewer.open({
|
|
230
|
+
images: [
|
|
231
|
+
{ src: 'media/glacier.svg', label: 'Glacier under a small sun', caption: 'Key art' },
|
|
232
|
+
],
|
|
233
|
+
injector: this.injector,
|
|
234
|
+
});
|
|
235
|
+
this.status.set('Viewer open.');
|
|
236
|
+
await ref.afterClosed;
|
|
237
|
+
this.status.set('Viewer closed.');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Motion
|
|
243
|
+
|
|
244
|
+
The viewer opens with a zoom: the image's frame starts on the thumbnail's box, cropped as the thumbnail crops it, and grows to its place in 260 ms on the overlay curve (`--ease-ios`), while the scrim and the controls fade in. With no `origin`, or one that is off screen, it fades in instead. Closing zooms back into the thumbnail of the shown image when it is still on screen, else fades out. Stepping cross-fades the images. Under `prefers-reduced-motion` the viewer only fades, in 150 ms, and the images appear without a fade.
|
|
245
|
+
|
|
246
|
+
## Loading and failing
|
|
247
|
+
|
|
248
|
+
While a large image loads, the viewer shows its thumbnail (the `origin` image, when there is one) with a spinner over it, and the frame already has the thumbnail's aspect ratio; the image fades in over it when it arrives (`bfFadeIn`). An image that fails to load shows the `imageLoadFailed` label ("The image could not be loaded") in its place.
|
|
249
|
+
|
|
250
|
+
## Desktop
|
|
251
|
+
|
|
252
|
+
Built for desktop and touch alike: the image fills the space between the top bar (counter and close) and the caption row, at its own aspect ratio. The previous and next buttons sit on the image's sides and the close button at the top end, each a `tonal` `bf-icon-button` that follows `provideBfDefaults()`; their hover tint and focus ring are the button's. A click on the scrim or beside the image closes the viewer; a click on the image, the caption or the actions does not. The page behind it is inert and does not scroll.
|
|
253
|
+
|
|
254
|
+
## Theming
|
|
255
|
+
|
|
256
|
+
- The viewer is a `.dark` sub-tree, so its text and buttons take their dark-mode tokens in both colour schemes; the scrim is `bg-scrim/90`.
|
|
257
|
+
- Counter `text-label`, caption `text-body`, failure message `text-body text-text-secondary`.
|
|
258
|
+
- Buttons: `tonal` `bf-icon-button`s with lucide `ChevronLeft`, `ChevronRight` (mirrored under `dir="rtl"`) and `X`; the spinner is a `lg` `bf-spinner`.
|
|
259
|
+
- Motion: `OVERLAY_DURATION` (260 ms) and `OVERLAY_EASING` from the overlay core, `OVERLAY_REDUCED_DURATION` (150 ms) under reduced motion; the images fade on `duration-slow`.
|
|
260
|
+
|
|
261
|
+
## Accessibility
|
|
262
|
+
|
|
263
|
+
The viewer is a modal `role="dialog"` named by `ariaLabel` or the `imageViewer` label ("Image viewer"), with `aria-modal="true"`. On open, focus moves to the close button and is trapped in the viewer; on close it returns to the element that opened it. The page behind it is `inert`. Every image has its `label` as `alt`, the step buttons are named by `imageViewerPrevious` and `imageViewerNext`, and after each step a polite live region announces the position and the label ("3 of 5, Harbour at sunset"); the counter itself is the `imageViewerPosition` label. The service is a root singleton: pass `injector: inject(Injector)` so a feature-level `provideBfLabels()` names its parts.
|
|
264
|
+
|
|
265
|
+
### Keyboard
|
|
266
|
+
|
|
267
|
+
| Key | Action |
|
|
268
|
+
| ------------------------------------------------ | ----------------------------------------------------------- |
|
|
269
|
+
| <kbd>ArrowRight</kbd> / <kbd>ArrowLeft</kbd> | Next / previous image, wrapping (swapped under `dir="rtl"`) |
|
|
270
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | First / last image |
|
|
271
|
+
| <kbd>Escape</kbd> | Closes the viewer |
|
|
272
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Next / previous control, wrapping inside the viewer |
|
|
273
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Activates the focused button |
|
|
274
|
+
|
|
275
|
+
## Notes
|
|
276
|
+
|
|
277
|
+
### Do
|
|
278
|
+
|
|
279
|
+
- Give every image a `label` that says what it shows; it is all a screen reader gets of the picture.
|
|
280
|
+
- Pass `origin` (a function for a gallery), so the image visibly comes from and returns to its thumbnail.
|
|
281
|
+
- Keep actions to one or two buttons that act on the shown image.
|
|
282
|
+
|
|
283
|
+
### Don't
|
|
284
|
+
|
|
285
|
+
- Don't open the viewer for a picture that already fills the page.
|
|
286
|
+
- Don't put a form in the actions; open a dialog for that.
|
|
287
|
+
|
|
288
|
+
## Migration notes
|
|
289
|
+
|
|
290
|
+
### 0.5.0
|
|
291
|
+
|
|
292
|
+
New: `BfImageViewerService` in `@bitforgehq/ui/overlay`, a full-screen image viewer (lightbox).
|
|
293
|
+
`open({ images: [{ src, label, caption? }], index?, origin?, actions?, ariaLabel?,
|
|
294
|
+
closeOnNavigation?, injector? })` (new types `BfImageViewerOptions`, `BfImageViewerImage`,
|
|
295
|
+
`BfImageViewerOrigin`, `BfImageViewerActionsContext`) renders `bf-image-viewer` over a dark scrim
|
|
296
|
+
and returns a `BfImageViewerRef` (`index()`, `next()`, `previous()`, `goTo()`, `close()`,
|
|
297
|
+
`afterClosed`, `closed()`). Previous and next buttons, the arrow keys (mirrored under rtl), Home
|
|
298
|
+
and End step through the set, wrapping at both ends; a counter shows "2 of 5" and a polite live
|
|
299
|
+
region announces each step. It is a modal dialog: focus moves to its close button and is trapped,
|
|
300
|
+
`Escape`, the close button and a click on the scrim close it, the page behind is `inert`, and
|
|
301
|
+
focus returns to the opener. The `actions` template (context `{ $implicit: image, index, ref }`)
|
|
302
|
+
sits beside the caption. It zooms out of `origin` (the clicked thumbnail, or a function returning
|
|
303
|
+
any image's thumbnail) with a 260 ms FLIP on the overlay curve and zooms back into it on close
|
|
304
|
+
when it is on screen, else fades; images cross-fade on a step; reduced motion only fades. While a
|
|
305
|
+
large image loads it shows the thumbnail and a spinner, then fades the image in. New labels
|
|
306
|
+
`imageViewer`, `imageViewerPrevious`, `imageViewerNext` and `imageViewerPosition`.
|
|
307
|
+
`@bitforgehq/ui/testing`: new `BfImageViewerHarness` (`getIndex()`, `getCount()`,
|
|
308
|
+
`getCounterText()`, `getSrc()`, `getAlt()`, `getCaption()`, `next()`, `previous()`,
|
|
309
|
+
`pressKey()`, `close()`, `loadImage()`, `failImage()`, `isLoaded()`, `hasFailed()`).
|