ngwr 14.6.0 → 14.7.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.6.0';
19
+ const NGWR_VERSION = '14.7.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.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;;;;"}
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.7.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;;;;"}
@@ -199,8 +199,14 @@
199
199
  color: var(--wr-input-group-affix);
200
200
  cursor: pointer;
201
201
  transition: color var(--wr-transition-base);
202
-
203
- --wr-icon-size: 1rem;
202
+ // The eye is a bare `.wr-icon__svg`, which is `1em`, and a `<button>` does
203
+ // not inherit its font-size — so it drew the browser's control font (about
204
+ // 13.33px) with no reset and the surrounding text's size under `ngwr/reset`.
205
+ // Inheriting the size makes `1em` the text around the field in every app.
206
+ // The size only, not the `font` shorthand, which would also hand the button
207
+ // the group's line-height. `--wr-icon-size` only drives `<wr-icon>` hosts, so
208
+ // the declaration that used to sit here drove nothing.
209
+ font-size: inherit;
204
210
 
205
211
  &:hover:not(:disabled) {
206
212
  color: var(--wr-color-primary);
@@ -132,7 +132,13 @@
132
132
  touch-action: none;
133
133
 
134
134
  &__close {
135
- --wr-icon-size: 1.25rem;
135
+ // A bare `.wr-icon__svg` in a `<button>`: `1em`, and a button inherits no
136
+ // font-size — so the ✕ came out at the browser's control font, about
137
+ // 13.33px, inside a 36px circle, and at the surrounding text's size under
138
+ // `ngwr/reset`. Inheriting the size gives the same glyph either way. The
139
+ // size only, not the `font` shorthand. `--wr-icon-size` was never going to
140
+ // do it: only a `<wr-icon>` host reads that hook.
141
+ font-size: inherit;
136
142
  position: absolute;
137
143
  top: 1rem;
138
144
  inset-inline-end: 1rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngwr",
3
- "version": "14.6.0",
3
+ "version": "14.7.0",
4
4
  "bin": {
5
5
  "ngwr-mcp": "mcp/server.js"
6
6
  },
@@ -129,7 +129,6 @@
129
129
  }
130
130
 
131
131
  &__chevron {
132
- --wr-icon-size: 1rem;
133
132
  flex: 0 0 auto;
134
133
  color: var(--wr-color-on-surface-muted);
135
134
  transition: transform var(--wr-transition-base);
@@ -179,6 +178,22 @@
179
178
  }
180
179
  }
181
180
 
181
+ // A leading visual inside a search trigger. The FIELD owns the leading
182
+ // padding here (see `--search` above), so the inset has to move with the
183
+ // visual — otherwise the avatar sits on the border. Only the trigger's own
184
+ // 0.5rem `gap` is then left between the two, which is the spacing the button
185
+ // trigger already has between `__value-leading` and `__value`.
186
+ &__trigger--search &__value-leading {
187
+ padding-inline-start: calc(var(--wr-select-padding-x) * var(--wr-density-x, 1));
188
+ }
189
+
190
+ // …and the field drops the inset it just handed over. Keyed on the sibling
191
+ // rather than on a modifier class so the two can never be set apart: the
192
+ // padding moves exactly when the element it moved to exists.
193
+ &__value-leading + &__search-input {
194
+ padding-inline-start: 0;
195
+ }
196
+
182
197
  &__search-input {
183
198
  flex: 1 1 auto;
184
199
  min-width: 0;
@@ -294,7 +309,6 @@
294
309
  }
295
310
 
296
311
  &__chip-remove {
297
- --wr-icon-size: 0.875rem;
298
312
  display: inline-flex;
299
313
  align-items: center;
300
314
  justify-content: center;
@@ -320,7 +334,6 @@
320
334
  }
321
335
 
322
336
  &__clear {
323
- --wr-icon-size: 0.875rem;
324
337
  // Never shrink: the chips box is allowed to grow into the whole row, so the
325
338
  // × has to hold its own size against it.
326
339
  flex: 0 0 auto;
@@ -568,7 +581,6 @@
568
581
 
569
582
  .wr-select__chip-remove,
570
583
  .wr-select__clear {
571
- --wr-icon-size: 1.125rem;
572
584
  width: 2rem;
573
585
  height: 2rem;
574
586
  }
@@ -376,6 +376,28 @@
376
376
  display: inline-flex;
377
377
  align-items: center;
378
378
  gap: 0.375rem;
379
+
380
+ // The spacing of the header's controls, and it moves for a COARSE pointer
381
+ // only. A column that is both `sortable` and `filterable` puts two 12px
382
+ // controls in this row 0.375rem apart, so their centres are 18px apart —
383
+ // while the touch halo each one grows below is 24px wide and centred on its
384
+ // control. Two 24px boxes with centres 18px apart OVERLAP by 6px, and the
385
+ // later one in the DOM (the filter) wins the hit test, so the sort button
386
+ // would hand over a strip of the slack it just gained. 0.75rem puts the
387
+ // centres 24px apart, which is the closest the two halos can come: at rest
388
+ // they abut exactly, and a filter showing a count pill is 32px wide, which
389
+ // only separates them further. A fine pointer keeps the 0.375rem this row
390
+ // has always drawn.
391
+ //
392
+ // This gap is also the whole of what a COARSE-pointer `target-size` run
393
+ // measures. axe never sees the halo — an `::after` enters no rect, so it
394
+ // reads 12x12 either way — but 24px between centres satisfies WCAG 2.5.8's
395
+ // spacing exception exactly, and the pair passes. Narrow this and a coarse
396
+ // run reports both nodes again; `table-header-target.spec.ts` pins the
397
+ // value here so a PR catches it without a browser.
398
+ @media (pointer: coarse) {
399
+ gap: 0.75rem;
400
+ }
379
401
  }
380
402
 
381
403
  &__title {
@@ -398,6 +420,24 @@
398
420
  // reads it, and the rule here used to set it for nothing.
399
421
  font-size: inherit;
400
422
  line-height: 0;
423
+ // The pointer target, widened for a COARSE pointer and for nothing else.
424
+ // WCAG 2.5.8 asks 24x24 of every control, a mouse included, and this glyph
425
+ // is 12x12: a lone one passes on the criterion's spacing exception, and a
426
+ // column carrying both this button and a filter trigger 6px away does not.
427
+ // A real 24px box on every pointer is what satisfies the criterion outright,
428
+ // and it was written, measured and REFUSED — it moves the caret and the
429
+ // funnel apart on a mouse, and the maintainer chose to keep the geometry
430
+ // 14.6.0 draws. So the coarse half ships, the fine-pointer failure is a
431
+ // decided trade recorded in AGENTS.md and in `scripts/contrast-baseline.json`,
432
+ // and the table's docs page says so to the reader. Do not re-report it.
433
+ //
434
+ // 24px rather than the mixin's 44px default, which is a real constraint and
435
+ // not a preference: the halo is centred on the control, so 44 reaches 22px
436
+ // either side and swallows the filter trigger beside it — the mixin's own
437
+ // docblock warns off tightly-packed rows for exactly this. 24 is the
438
+ // criterion's number, and `__th-inner` opens its gap under the same media
439
+ // query so the two halos cannot overlap.
440
+ @include theme.touch-target($size: 24px);
401
441
  }
402
442
 
403
443
  &__tr {
@@ -810,6 +850,13 @@
810
850
  // count pill beside the funnel — 10px type with no line-height of its own —
811
851
  // grows from 12px to 20px tall under it.
812
852
  font-size: inherit;
853
+ // The same coarse-pointer target as the sort button beside it, where the
854
+ // whole reasoning — why 24 and not 44, and why a fine pointer keeps the
855
+ // 12px glyph — is written out. It goes on the TRIGGER and never on the
856
+ // `<wr-table-filter>` host: the halo belongs to the element that owns the
857
+ // click, and a tap landing on the host's own pseudo-element would target
858
+ // the host and open nothing.
859
+ @include theme.touch-target($size: 24px);
813
860
  }
814
861
 
815
862
  &__count {
@@ -822,6 +869,14 @@
822
869
  background: var(--wr-color-primary);
823
870
  color: var(--wr-color-primary-contrast);
824
871
  font-size: 0.625rem; // 10px — intentional micro-count badge, below --wr-text-xs
872
+ // The pill has no padding on the block axis, so its height IS its line box,
873
+ // and leaving that at `normal` handed the box to whatever the page
874
+ // inherited: 12px in an app with no reset, 20px under `ngwr/reset`, whose
875
+ // `font: inherit` on a `<button>` brings the header's 1.25rem down with the
876
+ // font-size. A ratio of its own makes it 12px in both — and a ratio rather
877
+ // than a length, so a consumer who enlarges the numeral gets a pill that
878
+ // grows with it.
879
+ line-height: 1.2;
825
880
  font-weight: 600;
826
881
  }
827
882
 
@@ -205,8 +205,15 @@ $toast-colors: (
205
205
  transition:
206
206
  color var(--wr-transition-base),
207
207
  background var(--wr-transition-base);
208
-
209
- --wr-icon-size: 0.875rem;
208
+ // The glyph is a bare `.wr-icon__svg` — `1em` — and a `<button>` does not
209
+ // inherit its font-size, so it drew the browser's control font (about
210
+ // 13.33px) with no reset and the surrounding text's size under `ngwr/reset`.
211
+ // Inheriting the size makes `1em` the toast's own text in every app; only a
212
+ // `<wr-icon>` host reads `--wr-icon-size`, so the hook never applied here.
213
+ // The SIZE only, never the `font` shorthand: that would also bring the
214
+ // surrounding line-height, which is what stretched the table's filter count
215
+ // pill from 12px to 20px when it was tried there.
216
+ font-size: inherit;
210
217
 
211
218
  &:hover {
212
219
  color: var(--wr-color-on-surface);
@@ -323,7 +330,6 @@ $toast-colors: (
323
330
  width: 2rem;
324
331
  height: 2rem;
325
332
  padding: 0;
326
- --wr-icon-size: 1.125rem;
327
333
  }
328
334
 
329
335
  .wr-toast-host__close-all {
@@ -249,7 +249,6 @@
249
249
  }
250
250
 
251
251
  &__chevron {
252
- --wr-icon-size: 1rem;
253
252
  flex: 0 0 auto;
254
253
  color: var(--wr-color-on-surface-muted);
255
254
  transition: transform var(--wr-transition-base);
@@ -260,7 +259,6 @@
260
259
  }
261
260
 
262
261
  &__clear {
263
- --wr-icon-size: 0.875rem;
264
262
  display: inline-flex;
265
263
  align-items: center;
266
264
  justify-content: center;
@@ -329,7 +327,6 @@
329
327
  }
330
328
 
331
329
  &__chip-remove {
332
- --wr-icon-size: 0.875rem;
333
330
  display: inline-flex;
334
331
  align-items: center;
335
332
  justify-content: center;
@@ -47,6 +47,19 @@ type WrAvatarSize = number | `${number}` | `${number}px` | `${number}rem`;
47
47
  * spinner is drawn over it; with nothing projected, the spinner marks the
48
48
  * loading state instead. See `alt` for what a screen reader hears.
49
49
  *
50
+ * Projected initials take the text size around the avatar, capped at
51
+ * `--wr-avatar-initials-scale` (`0.375`) of the box, so they shrink only where
52
+ * they would not fit: a 16px avatar in a 14px chip draws 6px letters, while a
53
+ * `6rem` one keeps the surrounding size up to 36px and is capped only past it.
54
+ * Anything projected that is sized in `em` follows the cap too — a `<wr-icon>`
55
+ * at its default `1.2em` included — so pin a `px` or `rem` size on the projected
56
+ * element, or override the hook, to keep it. A `font-size` rule on the avatar
57
+ * itself still wins, unless it sits in a cascade layer (Tailwind v4's utilities
58
+ * do), which ngwr's unlayered stylesheet beats whatever the selector.
59
+ *
60
+ * The initials are inert: `pointer-events: none` hands a click on them to whatever
61
+ * holds the avatar, and `user-select: none` keeps them out of a text selection.
62
+ *
50
63
  * @example
51
64
  * ```html
52
65
  * <wr-avatar url="/me.png" alt="Roman" size="3rem" shape="circle" />
@@ -84,7 +97,8 @@ declare class WrAvatar {
84
97
  */
85
98
  readonly shape: _angular_core.InputSignal<WrAvatarShape>;
86
99
  /**
87
- * Box size. See {@link WrAvatarSize} for accepted values.
100
+ * Box size. See {@link WrAvatarSize} for accepted values. The resolved value is
101
+ * published on the host as `--wr-avatar-size`.
88
102
  *
89
103
  * @default '6rem'
90
104
  */
@@ -157,6 +157,12 @@ declare class WrImageCropper {
157
157
  private active;
158
158
  private startPointer;
159
159
  private startRect;
160
+ /** The element the live drag set pointer capture on, so the end releases that one. */
161
+ private captureTarget;
162
+ /** The pointer the live drag belongs to. Every other pointer's events are ignored. */
163
+ private pointerId;
164
+ /** How far past each side of the crop the live resize was pressed — see `onPointerMove`. */
165
+ private overhang;
160
166
  private readonly destroyRef;
161
167
  private readonly statusState;
162
168
  /**
@@ -217,8 +223,50 @@ declare class WrImageCropper {
217
223
  private initialCrop;
218
224
  protected readonly handles: readonly WrCropHandle[];
219
225
  protected onPointerDown(handle: WrCropHandle, event: PointerEvent): void;
226
+ /**
227
+ * A resize ENDS when the pointer leaves the image, exactly as `pointerup` would end it:
228
+ * the move that carried it out is applied — clamped to the image like any other — and
229
+ * then the drag is over. Nothing after that resumes it or changes the crop, pointer
230
+ * back over the image or not, until a new `pointerdown`. A move drag is not affected:
231
+ * its size is fixed, so it simply rests against the edge.
232
+ *
233
+ * The move that leaves is applied rather than dropped because pointer moves are
234
+ * SAMPLES. Dropped, a quick drag toward a corner would stop wherever its last sample
235
+ * inside happened to fall — a single move from a handle to past the image would change
236
+ * nothing at all — and even a slow one would stop a fraction of a pixel short of the
237
+ * edge. A drag that leaves across one edge still ends there: heading for a corner at a
238
+ * slant that crosses the bottom first, the crop's right edge stays where it was then.
239
+ *
240
+ * "The image" is the `<img>`'s own box — the bounds `applyMove` / `applyResize` clamp
241
+ * the crop to — with its edges counting as inside, since the crop may reach them. The
242
+ * box is re-measured on every move and compared with `clientX` / `clientY`: both are
243
+ * viewport coordinates, so a scroll mid-drag cannot put them out of step. The moves
244
+ * outside reach this handler at all only because the drag holds pointer capture.
245
+ *
246
+ * One allowance, fixed at `pointerdown`: the box is widened on each side by however far
247
+ * past that side of the CROP the press landed — at most as far as the pressed handle
248
+ * reaches past it. Half of an edge handle, and three quarters of a corner handle, lies
249
+ * outside the crop, so a press there already has the pointer ahead of the edge it drags.
250
+ * Measured without the allowance, that pointer would leave the image while the crop was
251
+ * still short of the image's edge by the same distance, with nothing able to close the
252
+ * gap; and a handle resting on the image's edge, pressed on its outer part, would begin
253
+ * its drag already outside. A press on a handle's inner part widens nothing.
254
+ */
220
255
  protected onPointerMove(event: PointerEvent): void;
256
+ /**
257
+ * Ends the drag — for `pointerup`, `pointercancel`, a capture lost to something else,
258
+ * and a resize whose pointer left the image. Once per drag: the capture released here
259
+ * fires `lostpointercapture` afterwards, and by then there is nothing left to end.
260
+ *
261
+ * Only for the pointer that started it. A second finger is refused a drag of its own at
262
+ * `pointerdown`, but its moves and its lifting reach these handlers all the same, and
263
+ * would otherwise drive or end the first finger's drag.
264
+ */
221
265
  protected onPointerUp(event: PointerEvent): void;
266
+ /** How far past each side of the crop window the pointer is, in CSS pixels. */
267
+ private pastCrop;
268
+ /** Whether the pointer is on the image, edges and the press's `overhang` included. */
269
+ private withinImage;
222
270
  /** Whether a key has moved the crop since the last `keyup`. */
223
271
  private keyboardDirty;
224
272
  /**
@@ -44,7 +44,11 @@ type WrSelectSize = 'sm' | 'md' | 'lg';
44
44
  *
45
45
  * - `'option'` — the row in the open panel.
46
46
  * - `'chip'` — a selected chip on a `mode="multi"` trigger.
47
- * - `'value'` — the selected value on a single-mode button trigger.
47
+ * - `'value'` — the selected value on a single-mode trigger: beside the label on
48
+ * a button trigger, and before the input on a search-shaped one while that
49
+ * input is showing the label. One placement rather than two, because it is one
50
+ * surface — the same size is right on both, and splitting it would ask every
51
+ * consumer to name a case they do not have.
48
52
  */
49
53
  type WrOptionLeadingPlacement = 'option' | 'chip' | 'value';
50
54
  /** Template context of `<ng-template wrOptionLeading>`. */
@@ -572,10 +576,38 @@ declare class WrSelect implements FormValueControl<unknown>, WrSelectContext {
572
576
  private leadingFor;
573
577
  /**
574
578
  * The leading visual beside the selected value on a single-mode BUTTON trigger
575
- * — one instance, for the one selected value. The search-shaped triggers do not
576
- * draw one: their selection is an `<input>`'s text, which cannot hold markup.
579
+ * — one instance, for the one selected value. A search-shaped trigger draws the
580
+ * same visual through {@link searchLeading}, on a narrower condition.
577
581
  */
578
582
  protected readonly selectedLeading: _angular_core.Signal<TemplateRef<WrOptionLeadingContext<unknown>> | null>;
583
+ /**
584
+ * The same visual on a SEARCH-shaped trigger (`mode="search"`, a searchable
585
+ * single), drawn before the input — the surface an assignee picker actually
586
+ * uses, and the one that lost its avatar the moment the trigger became a field.
587
+ *
588
+ * The condition is {@link searchShowsValue} rather than `hasSelection()`, and
589
+ * the two part company on every open panel: focusing the field replaces the
590
+ * label with the live query, which starts empty, so what is on screen is the
591
+ * placeholder. An avatar left standing there names a value the field no longer
592
+ * shows, and then sits in front of whatever the user types next — the visual
593
+ * would be answering a different question from the text beside it. It comes
594
+ * back with the label, which is one signal flipping rather than two things
595
+ * kept in step: committing a choice clears the query and closes the panel.
596
+ *
597
+ * Nothing is instantiated for a search select with no selection, panel open or
598
+ * shut — `selectedLabel()` is `null` there, and this returns `null` with it.
599
+ *
600
+ * The COST of that rule, stated because it is a consumer's to weigh: stepping
601
+ * aside is a destroy, so opening the picker and dismissing it unchanged
602
+ * re-creates the visual — a template built on an `<img>` re-mounts once per
603
+ * round trip, and re-requests unless the cache answers. Kept rather than
604
+ * traded for a hidden-but-alive node: the panel's own rows already create and
605
+ * destroy one apiece on the same gesture, so holding this one back buys a
606
+ * fraction of that, and a node retained while it must not be seen is a second
607
+ * piece of state to keep in step with the field. Pinned in `option.spec.ts`
608
+ * ("re-creates it after a round trip"), so a future change here has to say so.
609
+ */
610
+ protected readonly searchLeading: _angular_core.Signal<TemplateRef<WrOptionLeadingContext<unknown>> | null>;
579
611
  /**
580
612
  * Trigger label for single mode. Multi reads `selectedChips` instead.
581
613
  *
@@ -748,6 +780,26 @@ declare class WrSelect implements FormValueControl<unknown>, WrSelectContext {
748
780
  protected onSearchFocus(): void;
749
781
  protected onSearchBlur(): void;
750
782
  protected onSearchKey(event: KeyboardEvent): void;
783
+ /**
784
+ * Whether the search field is showing the SELECTED VALUE rather than a query.
785
+ * `searchDisplay` takes its text off this, and `searchLeading` the visual that
786
+ * goes before it, so the two can never disagree about which of the two things
787
+ * the field is holding.
788
+ *
789
+ * FOCUS as well as open, and that second term is the whole `[minChars]` story.
790
+ * Backspacing under the threshold closes the panel, and falling back to the
791
+ * selected label the moment `open()` goes false rewrote the field out from
792
+ * under someone who was still typing — "sma" -> "sm" showed an empty box.
793
+ *
794
+ * A NON-EMPTY query, though: picking an option clears it and leaves focus in
795
+ * the field, and there the label is what belongs on screen.
796
+ *
797
+ * Only the search-shaped trigger reads it. A searchable MULTI binds its input
798
+ * to `searchQuery()` directly and shows its selection as chips, which carry
799
+ * their own leading visuals whatever the field holds.
800
+ * @internal
801
+ */
802
+ protected readonly searchShowsValue: _angular_core.Signal<boolean>;
751
803
  /**
752
804
  * Display text for the search input. Shows the selected option's
753
805
  * label when collapsed, or the live query while typing / panel open.
@@ -997,9 +1049,14 @@ declare class WrOptionGroup {
997
1049
  * - in an option row, while the panel is OPEN — destroyed again when it closes,
998
1050
  * and under `virtualScroll` only for the rows in the rendered window;
999
1051
  * - in a selected chip on a `mode="multi"` trigger, one per visible chip;
1000
- * - beside the selected value on a single-mode BUTTON trigger — one copy. The
1001
- * search-shaped triggers (`mode="search"`, `[searchable]`) do not draw it: their
1002
- * selection is the text of an `<input>`, which cannot hold markup.
1052
+ * - beside the selected value on a single-mode BUTTON trigger — one copy;
1053
+ * - before the input on a SEARCH-shaped trigger (`mode="search"`, a searchable
1054
+ * single), while that input is showing the selected label. It goes beside the
1055
+ * field rather than into it — an `<input>` holds a string — and it steps aside
1056
+ * while a query is on screen, panel open or half-typed, because the value it
1057
+ * names is not what the field is showing then. Picking an option brings it
1058
+ * back with the label. Stepping aside is a DESTROY, like a closing panel's
1059
+ * rows, so an `<img>`-backed visual re-mounts once per open-and-dismiss.
1003
1060
  *
1004
1061
  * Every copy sits in an `aria-hidden` wrapper and is skipped when the option's
1005
1062
  * text is read, so the trigger label, the chip, the search filter and the
@@ -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
- declare const NGWR_VERSION = "14.6.0";
19
+ declare const NGWR_VERSION = "14.7.0";
20
20
 
21
21
  /**
22
22
  * @license