ngwr 14.5.1 → 14.6.0

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.
@@ -16,7 +16,7 @@ import { InjectionToken } from '@angular/core';
16
16
  * Distinct from `Version` exported by `@angular/core`, which reports the
17
17
  * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.
18
18
  */
19
- const NGWR_VERSION = '14.5.1';
19
+ const NGWR_VERSION = '14.6.0';
20
20
 
21
21
  /**
22
22
  * @license
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-version.mjs","sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts","../../../projects/lib/version/ngwr-version.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The ngwr library version string, kept in sync with\n * `projects/lib/package.json` by `scripts/release-prepare.ts`.\n *\n * Prefer this over reading `package.json` at runtime — that requires a JSON\n * import or `require`, neither of which tree-shakes well in Angular builds.\n *\n * Distinct from `Version` exported by `@angular/core`, which reports the\n * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.\n */\nexport const NGWR_VERSION = '14.5.1';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\nimport { NGWR_VERSION } from './version';\n\n/**\n * Injection token holding the ngwr library version string.\n *\n * Default factory returns {@link NGWR_VERSION}. Override it in tests or in\n * apps that want to surface a custom build identifier.\n *\n * @example\n * ```ts\n * @Component({...})\n * export class FooterComponent {\n * readonly version = inject(NGWR_VERSION_TOKEN);\n * }\n * ```\n */\nexport const NGWR_VERSION_TOKEN = new InjectionToken<string>('NGWR_VERSION_TOKEN', {\n providedIn: 'root',\n factory: () => NGWR_VERSION,\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAEH;;;;;;;;;AASG;AACI,MAAM,YAAY,GAAG;;ACjB5B;;;;;AAKG;AAMH;;;;;;;;;;;;;AAaG;MACU,kBAAkB,GAAG,IAAI,cAAc,CAAS,oBAAoB,EAAE;AACjF,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,YAAY;AAC5B,CAAA;;AC5BD;;AAEG;;;;"}
1
+ {"version":3,"file":"ngwr-version.mjs","sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts","../../../projects/lib/version/ngwr-version.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The ngwr library version string, kept in sync with\n * `projects/lib/package.json` by `scripts/release-prepare.ts`.\n *\n * Prefer this over reading `package.json` at runtime — that requires a JSON\n * import or `require`, neither of which tree-shakes well in Angular builds.\n *\n * Distinct from `Version` exported by `@angular/core`, which reports the\n * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.\n */\nexport const NGWR_VERSION = '14.6.0';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\nimport { NGWR_VERSION } from './version';\n\n/**\n * Injection token holding the ngwr library version string.\n *\n * Default factory returns {@link NGWR_VERSION}. Override it in tests or in\n * apps that want to surface a custom build identifier.\n *\n * @example\n * ```ts\n * @Component({...})\n * export class FooterComponent {\n * readonly version = inject(NGWR_VERSION_TOKEN);\n * }\n * ```\n */\nexport const NGWR_VERSION_TOKEN = new InjectionToken<string>('NGWR_VERSION_TOKEN', {\n providedIn: 'root',\n factory: () => NGWR_VERSION,\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAEH;;;;;;;;;AASG;AACI,MAAM,YAAY,GAAG;;ACjB5B;;;;;AAKG;AAMH;;;;;;;;;;;;;AAaG;MACU,kBAAkB,GAAG,IAAI,cAAc,CAAS,oBAAoB,EAAE;AACjF,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,YAAY;AAC5B,CAAA;;AC5BD;;AAEG;;;;"}
@@ -23,6 +23,13 @@
23
23
  user-select: none;
24
24
  }
25
25
 
26
+ // A source the browser could not decode. The `<img>` stays in the DOM, since the
27
+ // next `src` goes through the same element, but some engines draw a broken-image
28
+ // glyph for it — and the failure is the host's to explain, not a stray icon's.
29
+ &--failed &__image {
30
+ display: none;
31
+ }
32
+
26
33
  &__backdrop {
27
34
  position: absolute;
28
35
  inset: 0;
package/llms-full.txt CHANGED
@@ -45,7 +45,7 @@
45
45
  - import: `import { WrAvatar } from 'ngwr/avatar'`
46
46
  - selector: `wr-avatar`
47
47
  - exports: WrAvatar, WrAvatarSize, WrAvatarShape
48
- - Image avatar with a fixed square or rounded shape. Shows a spinner while the image loads.
48
+ - Image avatar with a square, rounded, circle or squircle shape. Projected initials stand in while the image loads and when it fails.
49
49
 
50
50
  ## ngwr/back-top
51
51
  - import: `import { WrBackTop } from 'ngwr/back-top'`
@@ -568,7 +568,7 @@
568
568
  ## ngwr/image-cropper
569
569
  - import: `import { WrImageCropper } from 'ngwr/image-cropper'`
570
570
  - selector: `wr-image-cropper`
571
- - exports: WrImageCropper, WrCropRect, WrCropHandle, WrImageOutputType
571
+ - exports: WrImageCropper, WrCropRect, WrCropHandle, WrImageCropperStatus, WrImageLoadError, WrImageOutputType
572
572
  - DOM-based crop UI. Drag the box to move, drag any of the eight handles to resize. Optional `[aspectRatio]` locks the box shape. `(cropped)` fires with a `Blob` after each drag end — wire to upload or preview.
573
573
 
574
574
  ## ngwr/image-cropper/testing
@@ -840,8 +840,8 @@
840
840
 
841
841
  ## ngwr/select
842
842
  - import: `import { WrSelect } from 'ngwr/select'`
843
- - selector: `wr-option-group`, `wr-option`, `wr-select`
844
- - exports: WrSelect, WrOption, WrOptionGroup, WR_SELECT, WrSelectContext, WrSelectMode, WrSelectSearchLoader, WrSelectTagValidator, WrSelectSize
843
+ - selector: `wr-option-group`, `ng-template[wrOptionLeading]`, `wr-option`, `wr-select`
844
+ - exports: WrSelect, WrOption, WrOptionGroup, WrOptionLeading, WR_SELECT, WrSelectContext, WrOptionLeadingContext, WrOptionLeadingPlacement, WrSelectMode, WrSelectSearchLoader, WrSelectTagValidator, WrSelectSize
845
845
  - Native-like select built on CDK Overlay. A signal-forms native control — it implements `FormValueControl`, so `[formField]` binds straight to its `value` model. `[(value)]` works standalone, and `[(ngModel)]` / reactive forms keep working through [Angular 22's forms bridge](/guides/forms).
846
846
 
847
847
  ## ngwr/select/testing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngwr",
3
- "version": "14.5.1",
3
+ "version": "14.6.0",
4
4
  "bin": {
5
5
  "ngwr-mcp": "mcp/server.js"
6
6
  },
@@ -165,6 +165,7 @@
165
165
  "WrSelect": "ngwr/select",
166
166
  "WrOption": "ngwr/select",
167
167
  "WrOptionGroup": "ngwr/select",
168
+ "WrOptionLeading": "ngwr/select",
168
169
  "WrShinyText": "ngwr/shiny-text",
169
170
  "WrShimmer": "ngwr/shiny-text",
170
171
  "WrSidebar": "ngwr/sidebar",
@@ -120,6 +120,14 @@
120
120
  color: var(--wr-color-text-muted);
121
121
  }
122
122
 
123
+ // The selected option's leading visual on a single-mode button trigger. Never
124
+ // shrinks: the label beside it is the part that truncates.
125
+ &__value-leading {
126
+ display: inline-flex;
127
+ flex: none;
128
+ align-items: center;
129
+ }
130
+
123
131
  &__chevron {
124
132
  --wr-icon-size: 1rem;
125
133
  flex: 0 0 auto;
@@ -268,6 +276,17 @@
268
276
  user-select: none;
269
277
  }
270
278
 
279
+ // A selected option's leading visual — an avatar, an icon. Pulled toward the
280
+ // chip's start edge so a round avatar reads as the chip's cap rather than as a
281
+ // glyph floating in the padding, and spaced from the label by a little more
282
+ // than the chip's own gap.
283
+ &__chip-leading {
284
+ display: inline-flex;
285
+ flex: none;
286
+ align-items: center;
287
+ margin-inline: -0.25rem 0.125rem;
288
+ }
289
+
271
290
  &__chip-label {
272
291
  overflow: hidden;
273
292
  text-overflow: ellipsis;
@@ -424,6 +443,14 @@
424
443
  }
425
444
  }
426
445
 
446
+ // An option's leading visual — a sibling of the label inside the row's flex,
447
+ // so `--wr-option-inline-gap` spaces it like any projected icon.
448
+ &__leading {
449
+ display: inline-flex;
450
+ flex: none;
451
+ align-items: center;
452
+ }
453
+
427
454
  // Search-mode filter — out-of-match options collapse but stay in the
428
455
  // DOM so registration order survives the panel re-open.
429
456
  &--hidden {
@@ -461,6 +488,13 @@
461
488
  position: relative;
462
489
  }
463
490
 
491
+ // A virtual row is a block, not a flex row (one line, so it can ellipsize),
492
+ // so the leading visual takes its spacing as a margin instead of the gap.
493
+ .wr-option__leading {
494
+ vertical-align: middle;
495
+ margin-inline-end: var(--wr-option-inline-gap, 0.5rem);
496
+ }
497
+
464
498
  // Keyboard cursor highlight — the virtualized list is navigated by
465
499
  // aria-activedescendant, so real focus never lands on the row and the tint
466
500
  // is all there was. `--wr-color-hover` is translucent and lands within about
@@ -171,7 +171,7 @@ root barrel for components.
171
171
  | `ngwr/scroll` | `import { WrScroll } from 'ngwr/scroll'` | — |
172
172
  | `ngwr/segmented` | `import { WrSegmented } from 'ngwr/segmented'` | `wr-segmented` |
173
173
  | `ngwr/segmented/testing` | `import { WrSegmentedHarness } from 'ngwr/segmented/testing'` | — |
174
- | `ngwr/select` | `import { WrSelect } from 'ngwr/select'` | `wr-option-group` `wr-option` `wr-select` |
174
+ | `ngwr/select` | `import { WrSelect } from 'ngwr/select'` | `wr-option-group` `ng-template[wrOptionLeading]` `wr-option` `wr-select` |
175
175
  | `ngwr/select/testing` | `import { WrOptionHarness } from 'ngwr/select/testing'` | — |
176
176
  | `ngwr/shiny-text` | `import { WrShinyText } from 'ngwr/shiny-text'` | `[wrShimmer]` `wr-shiny-text` |
177
177
  | `ngwr/shiny-text/testing` | `import { WrShinyTextHarness } from 'ngwr/shiny-text/testing'` | — |
@@ -40,19 +40,25 @@ type WrAvatarSize = number | `${number}` | `${number}px` | `${number}rem`;
40
40
  /**
41
41
  * Image avatar with configurable corner treatment.
42
42
  *
43
- * A spinner is shown while the image loads, then crossfades in.
43
+ * Projected content (typically initials) is the FALLBACK: it shows with no
44
+ * `url`, while the image loads and after it fails, and leaves the paint once the
45
+ * image has loaded. It is projected into a `.wr-avatar__content` wrapper, so a
46
+ * `wr-avatar > …` child selector no longer reaches it. While it is there, no
47
+ * spinner is drawn over it; with nothing projected, the spinner marks the
48
+ * loading state instead. See `alt` for what a screen reader hears.
44
49
  *
45
50
  * @example
46
51
  * ```html
47
52
  * <wr-avatar url="/me.png" alt="Roman" size="3rem" shape="circle" />
48
- * <wr-avatar url="/me.png" alt="Roman" [size]="48" />
53
+ * <wr-avatar [url]="photo" alt="Roman Khegay" [size]="48">RK</wr-avatar>
49
54
  * ```
50
55
  *
51
56
  * @see https://ngwr.dev/reference/components/avatar
52
57
  */
53
58
  declare class WrAvatar {
54
59
  /**
55
- * Image URL. When unset, only projected content (e.g. initials) renders.
60
+ * Image URL. Projected content (e.g. initials) shows while it is unset,
61
+ * loading or broken, and gives way once the image has loaded.
56
62
  *
57
63
  * @default null
58
64
  */
@@ -61,6 +67,11 @@ declare class WrAvatar {
61
67
  * Alt text for the image. Falls back to `avatar.alt`, then `'Avatar'` — pass
62
68
  * the person's name where you have it, since that is what a reader wants.
63
69
  *
70
+ * With an `alt`, projected initials are hidden from assistive technology while
71
+ * an image is in play, so the name is announced once. Without one, the image
72
+ * carries only the generic name, so the initials stay announced beside it —
73
+ * visually hidden once it has loaded, never dropped.
74
+ *
64
75
  * @default null
65
76
  */
66
77
  readonly alt: _angular_core.InputSignal<string | null>;
@@ -85,8 +96,30 @@ declare class WrAvatar {
85
96
  * worth showing instead.
86
97
  */
87
98
  protected readonly failed: _angular_core.WritableSignal<boolean>;
99
+ private readonly img;
88
100
  constructor();
101
+ /**
102
+ * A prerendered `<img>` starts loading when the browser parses the HTML, and
103
+ * `(load)` / `(error)` are only attached at hydration — so on a slow bundle the
104
+ * image settles first, both events go nowhere, and the avatar sits on its
105
+ * fallback over a photo it already has (or names a broken image it never
106
+ * learns about). Only the FIRST render can race: every later `<img>` is
107
+ * created on the client with its listeners in place before `src` is set.
108
+ */
109
+ private adoptSettledImage;
89
110
  protected readonly resolved: _angular_core.Signal<ResolvedCssSize>;
111
+ /**
112
+ * Whether the projected fallback is `aria-hidden`: an `<img>` is in the DOM
113
+ * (loading or loaded) AND the consumer named it. Then the image's `alt` owns
114
+ * the name, announced once and unchanged at the moment the photo arrives.
115
+ * Without a real `alt` the image is only "Avatar", and hiding the initials
116
+ * would drop the one thing that says who this is.
117
+ *
118
+ * The stylesheet keys on the same attribute once `--loaded`: a hidden fallback
119
+ * leaves the paint through `visibility`, a named-by-initials one is clipped and
120
+ * stays in the accessibility tree. While loading it is painted either way.
121
+ */
122
+ protected readonly fallbackHidden: _angular_core.Signal<boolean>;
90
123
  protected readonly cssSize: _angular_core.Signal<string>;
91
124
  protected readonly pxSize: _angular_core.Signal<number | null>;
92
125
  protected readonly classes: _angular_core.Signal<string>;
@@ -72,9 +72,10 @@ interface WrCropWindowBox {
72
72
  * arrives on the `(cropped)` output, which emits a `Blob` after each drag end or
73
73
  * each run of arrow keys, and a consumer who wants a one-off read calls `toBlob()` /
74
74
  * `toDataUrl()` — or `cropRect()` for the geometry — on the component through a
75
- * `viewChild`. An output and three instance members: component API rather than DOM,
76
- * so all of it is outside what a harness can reach at all, which is worth knowing
77
- * rather than discovering.
75
+ * `viewChild`. The same goes for `(loadError)` and `status()`, which is why
76
+ * {@link isFailed} reads the failure off the host class instead. Outputs and instance
77
+ * members are component API rather than DOM, so all of it is outside what a harness
78
+ * can reach at all, which is worth knowing rather than discovering.
78
79
  *
79
80
  * @example
80
81
  * ```ts
@@ -127,6 +128,27 @@ declare class WrImageCropperHarness extends ComponentHarness {
127
128
  * a zero-sized image.
128
129
  */
129
130
  dispatchImageLoad(): Promise<void>;
131
+ /**
132
+ * Fire the image's `error` event — what a browser does with a source it cannot
133
+ * decode, and the only way to get one in a unit test, where nothing is decoded.
134
+ *
135
+ * Afterwards {@link isFailed} is `true` and the component has emitted
136
+ * `(loadError)`; a spec that wants the payload binds that output on its host.
137
+ */
138
+ dispatchImageError(): Promise<void>;
139
+ /**
140
+ * Whether the browser failed to decode the current source.
141
+ *
142
+ * Read off the `wr-image-cropper--failed` host modifier — the part of the failure
143
+ * the DOM shows. It is a third answer beside {@link isEmpty} and {@link isReady},
144
+ * not a combination of them: a failed cropper has a `src` (so it is not empty) and
145
+ * no crop window (so it is not ready), which is also exactly what a cropper still
146
+ * LOADING looks like. Asking those two would call a broken image a slow one.
147
+ *
148
+ * The component renders no message for it, so there is no text to read here; the
149
+ * host's own message is the host's to assert.
150
+ */
151
+ isFailed(): Promise<boolean>;
130
152
  /**
131
153
  * Where the crop window sits over the displayed image, in CSS pixels.
132
154
  *
@@ -18,6 +18,45 @@ interface WrCropRect {
18
18
  type WrCropHandle = 'move' | 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
19
19
  /** Supported output MIME types for {@link WrImageCropper.toBlob}. */
20
20
  type WrImageOutputType = 'image/png' | 'image/jpeg' | 'image/webp';
21
+ /**
22
+ * Where the cropper's source stands.
23
+ *
24
+ * - `empty` — no `src`, so the placeholder is showing.
25
+ * - `loading` — a `src` has been handed to the `<img>` and the browser has not
26
+ * answered yet.
27
+ * - `loaded` — the image decoded. The crop window opens once it also has a
28
+ * measured size, which in a browser is the same moment.
29
+ * - `failed` — the browser could not decode it (a renamed `.heic`, a truncated
30
+ * file, a URL that 404s). `(loadError)` fired with the details.
31
+ */
32
+ type WrImageCropperStatus = 'empty' | 'loading' | 'loaded' | 'failed';
33
+ /**
34
+ * The payload of `(loadError)` — plain strings and numbers, so it can be logged,
35
+ * sent to an error tracker or put into a store as it is.
36
+ *
37
+ * The browser's `error` event carries no reason at all, so none is invented here:
38
+ * what the source DECLARED is the most a host has to work with, and for the common
39
+ * case (a phone photo in a format this browser cannot decode) `type` and `name`
40
+ * are what tell the two apart from a broken upload.
41
+ */
42
+ interface WrImageLoadError {
43
+ /**
44
+ * The URL the `<img>` was given — the `src` string itself, or the object URL
45
+ * made for a `File` / `Blob`. An object URL is revoked as soon as `src` changes,
46
+ * so it identifies the attempt rather than being something to fetch later.
47
+ */
48
+ readonly url: string;
49
+ /** `File.name` for a `File` source; `null` for a `Blob` or a string. */
50
+ readonly name: string | null;
51
+ /**
52
+ * The MIME type a `File` / `Blob` declares — `''` when it declares none, and
53
+ * `null` for a string source. A `File`'s type comes from its extension, so a
54
+ * renamed file declares the type of the name it was given.
55
+ */
56
+ readonly type: string | null;
57
+ /** Size in bytes for a `File` / `Blob`; `null` for a string source. */
58
+ readonly size: number | null;
59
+ }
21
60
 
22
61
  interface RectPx {
23
62
  x: number;
@@ -37,12 +76,18 @@ interface RectPx {
37
76
  * freshly-rendered `Blob` of the cropped region. For one-off reads use
38
77
  * `toBlob()` / `toDataUrl()`.
39
78
  *
79
+ * A source the browser cannot decode fires `(loadError)` once, sets `status()` to
80
+ * `'failed'` and puts `wr-image-cropper--failed` on the host. The component renders
81
+ * no message of its own for it — what went wrong and what to do next is the host's
82
+ * to say.
83
+ *
40
84
  * @example
41
85
  * ```html
42
86
  * <wr-image-cropper
43
87
  * [src]="file"
44
88
  * [aspectRatio]="1"
45
89
  * (cropped)="onBlob($event)"
90
+ * (loadError)="onUnreadable($event)"
46
91
  * />
47
92
  * ```
48
93
  *
@@ -61,8 +106,23 @@ declare class WrImageCropper {
61
106
  readonly outputType: _angular_core.InputSignal<WrImageOutputType>;
62
107
  /** JPEG / WebP quality for `(cropped)` in [0, 1]. @default 0.92 */
63
108
  readonly outputQuality: _angular_core.InputSignal<number>;
109
+ /**
110
+ * Longest side of the exported image, in pixels — for `(cropped)`, `toBlob()` and
111
+ * `toDataUrl()` alike. A crop larger than this is scaled down to fit, keeping its
112
+ * aspect ratio; a smaller one is exported as it is, never upscaled. The crop itself
113
+ * — `cropRect()`, in source pixels — is not affected. `null` (or anything that is
114
+ * not a positive number) exports at the source image's own resolution.
115
+ * @default null
116
+ */
117
+ readonly maxOutputSize: _angular_core.InputSignalWithTransform<number | null, unknown>;
64
118
  /** Emits a Blob after each drag end. */
65
119
  readonly cropped: _angular_core.OutputEmitterRef<Blob>;
120
+ /**
121
+ * Fires once when the browser cannot decode the current `src` — a format it does
122
+ * not support, a broken file, a URL that fails. Nothing is rendered for it; show
123
+ * your own message. A new `src` clears the failure.
124
+ */
125
+ readonly loadError: _angular_core.OutputEmitterRef<WrImageLoadError>;
66
126
  protected readonly imgEl: _angular_core.Signal<ElementRef<HTMLImageElement>>;
67
127
  /** Resolved object URL for `src` (so File / Blob render in `<img>`). */
68
128
  protected readonly emptyText: _angular_core.Signal<string>;
@@ -98,6 +158,13 @@ declare class WrImageCropper {
98
158
  private startPointer;
99
159
  private startRect;
100
160
  private readonly destroyRef;
161
+ private readonly statusState;
162
+ /**
163
+ * Where the source stands: `'empty'`, `'loading'`, `'loaded'` or `'failed'`. Reset
164
+ * to `'loading'` (or `'empty'`) whenever `src` changes, so a failure never outlives
165
+ * the source that caused it.
166
+ */
167
+ readonly status: _angular_core.Signal<WrImageCropperStatus>;
101
168
  /** Resolved crop rect in natural (source) pixel coordinates. */
102
169
  readonly cropRect: _angular_core.Signal<WrCropRect>;
103
170
  constructor();
@@ -113,6 +180,18 @@ declare class WrImageCropper {
113
180
  */
114
181
  private resetGeometry;
115
182
  protected onImageLoad(): void;
183
+ /**
184
+ * The `<img>` could not decode its source.
185
+ *
186
+ * Before this handler existed a failed decode left the component exactly as it
187
+ * looks while loading — an image with no size, no crop window, nothing emitted —
188
+ * so a host had no way to tell "still decoding" from "never going to", and a user
189
+ * who picked a `.heic` the browser cannot read saw an empty box and no reason.
190
+ *
191
+ * Once per source: `status` is reset by a new `src` and by nothing else, so a
192
+ * second `error` for the same source says nothing new and is not re-emitted.
193
+ */
194
+ protected onImageError(): void;
116
195
  /**
117
196
  * Keep `display` on the box the image is actually rendered at.
118
197
  *
@@ -176,11 +255,18 @@ declare class WrImageCropper {
176
255
  toDataUrl(type?: WrImageOutputType, quality?: number): string;
177
256
  /** Re-emit the crop without waiting for a drag (e.g. after a programmatic change). */
178
257
  refresh(): Promise<void>;
258
+ /**
259
+ * The exported image's pixel size: the crop in source pixels, scaled down so its
260
+ * longer side fits `maxOutputSize`. `Math.min(1, …)` is the never-upscale rule — a
261
+ * crop already inside the cap keeps its own size, which is also the whole of the
262
+ * uncapped path.
263
+ */
264
+ private outputSize;
179
265
  private toCanvas;
180
266
  private emitCropped;
181
267
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrImageCropper, never>;
182
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrImageCropper, "wr-image-cropper", never, { "src": { "alias": "src"; "required": false; "isSignal": true; }; "aspectRatio": { "alias": "aspectRatio"; "required": false; "isSignal": true; }; "minWidth": { "alias": "minWidth"; "required": false; "isSignal": true; }; "minHeight": { "alias": "minHeight"; "required": false; "isSignal": true; }; "outputType": { "alias": "outputType"; "required": false; "isSignal": true; }; "outputQuality": { "alias": "outputQuality"; "required": false; "isSignal": true; }; }, { "cropped": "cropped"; }, never, never, true, never>;
268
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrImageCropper, "wr-image-cropper", never, { "src": { "alias": "src"; "required": false; "isSignal": true; }; "aspectRatio": { "alias": "aspectRatio"; "required": false; "isSignal": true; }; "minWidth": { "alias": "minWidth"; "required": false; "isSignal": true; }; "minHeight": { "alias": "minHeight"; "required": false; "isSignal": true; }; "outputType": { "alias": "outputType"; "required": false; "isSignal": true; }; "outputQuality": { "alias": "outputQuality"; "required": false; "isSignal": true; }; "maxOutputSize": { "alias": "maxOutputSize"; "required": false; "isSignal": true; }; }, { "cropped": "cropped"; "loadError": "loadError"; }, never, never, true, never>;
183
269
  }
184
270
 
185
271
  export { WrImageCropper };
186
- export type { WrCropHandle, WrCropRect, WrImageOutputType };
272
+ export type { WrCropHandle, WrCropRect, WrImageCropperStatus, WrImageLoadError, WrImageOutputType };
@@ -51,8 +51,25 @@ declare class WrOptionHarness extends ComponentHarness {
51
51
  static hostSelector: string;
52
52
  /** Build a predicate that narrows the query. */
53
53
  static with(options?: WrOptionHarnessFilters): HarnessPredicate<WrOptionHarness>;
54
- /** The option's label, trimmed. */
54
+ /**
55
+ * The option's text as drawn, trimmed — without its leading visual.
56
+ *
57
+ * A `wrOptionLeading` template renders INSIDE the row, so reading the whole
58
+ * host would hand back an avatar's initials as the first word of the name. It
59
+ * is excluded here for the same reason the select excludes it from the label.
60
+ *
61
+ * This is the drawn text, not the `label` input: an option whose `label` is
62
+ * shorter than what it draws still answers with what it draws. What the select
63
+ * REPORTS for it is on the select harness, as `getValueText()` or
64
+ * `getChipLabels()`.
65
+ */
55
66
  getText(): Promise<string>;
67
+ /**
68
+ * Whether the row is drawing a leading visual right now. Reads the slot the
69
+ * select renders the template into, so it answers `false` for an option that
70
+ * declares a template while the panel is closed — which is the contract.
71
+ */
72
+ hasLeading(): Promise<boolean>;
56
73
  /** Whether the option is currently selected. */
57
74
  isSelected(): Promise<boolean>;
58
75
  /** Whether the option refuses selection. */