ngwr 9.0.1 → 9.1.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.
- package/date-picker/styles/_index.scss +62 -0
- package/drawer/styles/_index.scss +23 -0
- package/fesm2022/ngwr-date-picker.mjs +509 -1
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +85 -12
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +340 -6
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +5 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +5 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +1 -1
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +221 -38
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/llms-full.txt +11 -11
- package/llms.txt +12 -6
- package/package.json +21 -5
- package/schematics/use/symbol-map.json +5 -0
- package/types/ngwr-date-picker.d.ts +167 -1
- package/types/ngwr-dialog.d.ts +71 -8
- package/types/ngwr-drawer.d.ts +225 -6
- package/types/ngwr-select.d.ts +115 -9
- package/types/ngwr-version.d.ts +1 -1
package/fesm2022/ngwr-select.mjs
CHANGED
|
@@ -3,9 +3,9 @@ import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
|
3
3
|
import { TemplatePortal } from '@angular/cdk/portal';
|
|
4
4
|
import { isPlatformBrowser } from '@angular/common';
|
|
5
5
|
import * as i0 from '@angular/core';
|
|
6
|
-
import { InjectionToken, input, inject, ElementRef, computed, effect, DestroyRef, ViewEncapsulation, Component,
|
|
6
|
+
import { InjectionToken, input, inject, ElementRef, computed, effect, DestroyRef, ViewEncapsulation, Component, model, output, signal, contentChildren, viewChild, TemplateRef, ViewContainerRef, PLATFORM_ID, forwardRef } from '@angular/core';
|
|
7
7
|
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
|
-
import {
|
|
8
|
+
import { skip, debounce, timer, switchMap, of, isObservable, from, tap, finalize } from 'rxjs';
|
|
9
9
|
import { useI18nText, useI18nFormatter } from 'ngwr/i18n';
|
|
10
10
|
import { WR_OVERLAY, WR_RESPONSIVE_OVERLAYS, wrPresentAsSheet } from 'ngwr/overlay';
|
|
11
11
|
|
|
@@ -69,14 +69,14 @@ class WrOption {
|
|
|
69
69
|
active = computed(() => this.parent?.activeOptionId() === this.id, /* @ts-ignore */
|
|
70
70
|
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
71
71
|
/**
|
|
72
|
-
* @internal —
|
|
72
|
+
* @internal — searchable selects only. True when the parent has a query that
|
|
73
73
|
* the option's text content does not match (case-insensitive substring).
|
|
74
74
|
* Hidden options stay in the DOM so registration order survives but
|
|
75
75
|
* collapse via CSS.
|
|
76
76
|
*/
|
|
77
77
|
hidden = computed(() => {
|
|
78
78
|
const parent = this.parent;
|
|
79
|
-
if (!parent?.
|
|
79
|
+
if (!parent?.isSearchable() || !parent.clientFilter())
|
|
80
80
|
return false;
|
|
81
81
|
const q = parent.searchQuery().trim().toLowerCase();
|
|
82
82
|
if (!q)
|
|
@@ -160,6 +160,11 @@ let listboxUid = 0;
|
|
|
160
160
|
* - `'tag'` — free-text + chips with `[separators]` / `[validate]` /
|
|
161
161
|
* `[allowDuplicates]`. Replaces `<wr-chips-input>`.
|
|
162
162
|
*
|
|
163
|
+
* `[searchable]` is orthogonal to the mode — it adds the type-ahead filter to
|
|
164
|
+
* `single` / `multi` without changing the value shape, which is how you get a
|
|
165
|
+
* searchable multi-select. Server-side search hangs off the `(searchChange)` output
|
|
166
|
+
* plus `[options]` / `[loading]`.
|
|
167
|
+
*
|
|
163
168
|
* A signal-forms native control: it implements `FormValueControl<unknown>`, so
|
|
164
169
|
* `[formField]` binds straight to its `value` model — no
|
|
165
170
|
* `ControlValueAccessor` in between. `[(value)]` works standalone, and classic
|
|
@@ -179,6 +184,16 @@ let listboxUid = 0;
|
|
|
179
184
|
* <wr-option value="ts">TypeScript</wr-option>
|
|
180
185
|
* <wr-option value="ng">Angular</wr-option>
|
|
181
186
|
* </wr-select>
|
|
187
|
+
*
|
|
188
|
+
* <!-- Multi + type-ahead, options from a store -->
|
|
189
|
+
* <wr-select
|
|
190
|
+
* mode="multi"
|
|
191
|
+
* searchable
|
|
192
|
+
* [options]="results()"
|
|
193
|
+
* [loading]="pending()"
|
|
194
|
+
* [(value)]="categories"
|
|
195
|
+
* (searchChange)="store.search($event)"
|
|
196
|
+
* />
|
|
182
197
|
* ```
|
|
183
198
|
*
|
|
184
199
|
* The legacy `[multi]="true"` boolean is still accepted but is now an
|
|
@@ -240,6 +255,34 @@ class WrSelect {
|
|
|
240
255
|
/** Convenience — `effectiveMode() === 'search'`. Exposed via context for options. */
|
|
241
256
|
isSearch = computed(() => this.effectiveMode() === 'search', /* @ts-ignore */
|
|
242
257
|
...(ngDevMode ? [{ debugName: "isSearch" }] : /* istanbul ignore next */ []));
|
|
258
|
+
/**
|
|
259
|
+
* Add a type-ahead filter without changing the value shape — the missing
|
|
260
|
+
* multi-with-search combination, since `[mode]` picks exactly one shape:
|
|
261
|
+
*
|
|
262
|
+
* ```html
|
|
263
|
+
* <wr-select mode="multi" searchable [(value)]="categories">…</wr-select>
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* On `multi` the trigger keeps its chips and grows an inline text field (the
|
|
267
|
+
* shape `tag` mode already uses); on `single` it is equivalent to
|
|
268
|
+
* `mode="search"`. Every search input (`[options]`, `[loader]`,
|
|
269
|
+
* `[debounceMs]`, `[minChars]`, `[virtualScroll]`, the `(searchChange)` output, …)
|
|
270
|
+
* applies either way. Ignored in `tag` mode, which owns its own input.
|
|
271
|
+
* @default false
|
|
272
|
+
*/
|
|
273
|
+
searchable = input(false, { ...(ngDevMode ? { debugName: "searchable" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
274
|
+
/**
|
|
275
|
+
* Filtering is active — either `mode="search"` or `[searchable]`. Exposed via
|
|
276
|
+
* context so each `<wr-option>` can self-hide non-matching rows.
|
|
277
|
+
*/
|
|
278
|
+
isSearchable = computed(() => this.isSearch() || (this.searchable() && !this.isTag()), /* @ts-ignore */
|
|
279
|
+
...(ngDevMode ? [{ debugName: "isSearchable" }] : /* istanbul ignore next */ []));
|
|
280
|
+
/** The trigger *is* the search input: `mode="search"`, or a searchable single. */
|
|
281
|
+
isSearchTrigger = computed(() => this.isSearch() || (this.searchable() && !this.hasChips()), /* @ts-ignore */
|
|
282
|
+
...(ngDevMode ? [{ debugName: "isSearchTrigger" }] : /* istanbul ignore next */ []));
|
|
283
|
+
/** Chips plus an inline search field — a searchable multi-select. */
|
|
284
|
+
hasChipSearch = computed(() => this.searchable() && this.isMulti(), /* @ts-ignore */
|
|
285
|
+
...(ngDevMode ? [{ debugName: "hasChipSearch" }] : /* istanbul ignore next */ []));
|
|
243
286
|
/** WrSelectContext — resolved multi-selection flag for child options. */
|
|
244
287
|
multi = computed(() => this.effectiveMode() === 'multi' || this.effectiveMode() === 'tag', /* @ts-ignore */
|
|
245
288
|
...(ngDevMode ? [{ debugName: "multi" }] : /* istanbul ignore next */ []));
|
|
@@ -250,10 +293,14 @@ class WrSelect {
|
|
|
250
293
|
}, /* @ts-ignore */
|
|
251
294
|
...(ngDevMode ? [{ debugName: "hasChips" }] : /* istanbul ignore next */ []));
|
|
252
295
|
/**
|
|
253
|
-
*
|
|
296
|
+
* The live search query. Empty string = no filter. Exposed via context so
|
|
254
297
|
* each `<wr-option>` can self-hide non-matching rows.
|
|
298
|
+
*
|
|
299
|
+
* Two-way bindable, so the query can be owned or reset from outside:
|
|
300
|
+
* `[(searchQuery)]="query"`. For server-side search prefer the debounced
|
|
301
|
+
* {@link searchChange} output — `searchQueryChange` fires on every keystroke.
|
|
255
302
|
*/
|
|
256
|
-
searchQuery =
|
|
303
|
+
searchQuery = model('', /* @ts-ignore */
|
|
257
304
|
...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
|
|
258
305
|
// Search-mode-only inputs (ignored in other modes)
|
|
259
306
|
/**
|
|
@@ -274,6 +321,22 @@ class WrSelect {
|
|
|
274
321
|
*/
|
|
275
322
|
loader = input(null, /* @ts-ignore */
|
|
276
323
|
...(ngDevMode ? [{ debugName: "loader" }] : /* istanbul ignore next */ []));
|
|
324
|
+
/**
|
|
325
|
+
* The option list is already scoped to the query upstream — skip the built-in
|
|
326
|
+
* client-side label filter. Set it when `[options]` is fed from a server via
|
|
327
|
+
* the {@link searchChange} output; the async `[loader]` path implies it.
|
|
328
|
+
*
|
|
329
|
+
* Without this, a server that ranks or fuzzy-matches (returning rows whose
|
|
330
|
+
* labels don't literally contain the query) would have those rows hidden
|
|
331
|
+
* again on the client. @default false
|
|
332
|
+
*/
|
|
333
|
+
serverSearch = input(false, { ...(ngDevMode ? { debugName: "serverSearch" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
334
|
+
/**
|
|
335
|
+
* Whether the built-in label filter runs. Exposed via context so each
|
|
336
|
+
* `<wr-option>` knows whether to self-hide. @internal
|
|
337
|
+
*/
|
|
338
|
+
clientFilter = computed(() => !this.loader() && !this.serverSearch(), /* @ts-ignore */
|
|
339
|
+
...(ngDevMode ? [{ debugName: "clientFilter" }] : /* istanbul ignore next */ []));
|
|
277
340
|
/** Search mode: debounce (ms) applied to the loader. @default 250 */
|
|
278
341
|
debounceMs = input(250, { ...(ngDevMode ? { debugName: "debounceMs" } : /* istanbul ignore next */ {}), transform: (v) => Math.max(0, Number(v) || 0) });
|
|
279
342
|
/** Search mode: minimum query length before the panel opens / loader fires. @default 0 */
|
|
@@ -314,12 +377,45 @@ class WrSelect {
|
|
|
314
377
|
...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
|
|
315
378
|
resolvedNoResults = useI18nText(this.noResultsText, 'select.noResults', 'No results');
|
|
316
379
|
resolvedLoading = useI18nText(this.loadingText, 'select.loading', 'Loading…');
|
|
380
|
+
/**
|
|
381
|
+
* Debounced search query, gated to searchable selects. Emits on every settled
|
|
382
|
+
* change — including the empty string when the field is cleared — so a
|
|
383
|
+
* store-backed option list can stay in sync and reset itself. `[debounceMs]`
|
|
384
|
+
* controls the delay; `[minChars]` does NOT gate it, for the same reason the
|
|
385
|
+
* `[loader]` path clears its results below that threshold.
|
|
386
|
+
*
|
|
387
|
+
* This is the hook for server-side search: dispatch on `(searchChange)`, feed
|
|
388
|
+
* the results back through `[options]` (plus `[serverSearch]`), and drive the
|
|
389
|
+
* panel's progress row with `[loading]`. Prefer it over the model's raw
|
|
390
|
+
* `(searchQueryChange)`, which fires on every keystroke.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```html
|
|
394
|
+
* <wr-select
|
|
395
|
+
* mode="search"
|
|
396
|
+
* serverSearch
|
|
397
|
+
* [options]="results()"
|
|
398
|
+
* [loading]="pending()"
|
|
399
|
+
* (searchChange)="store.search($event)"
|
|
400
|
+
* />
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
searchChange = output();
|
|
404
|
+
/**
|
|
405
|
+
* Show the panel's progress row. Independent of `[loader]`, which raises and
|
|
406
|
+
* lowers its own flag — use this when the options come from a store fed by
|
|
407
|
+
* the {@link searchChange} output. @default false
|
|
408
|
+
*/
|
|
409
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
317
410
|
/** Search mode: results returned by the async loader. @internal */
|
|
318
411
|
loadedOptions = signal([], /* @ts-ignore */
|
|
319
412
|
...(ngDevMode ? [{ debugName: "loadedOptions" }] : /* istanbul ignore next */ []));
|
|
320
|
-
/**
|
|
321
|
-
|
|
322
|
-
...(ngDevMode ? [{ debugName: "
|
|
413
|
+
/** True while the async `[loader]` is in flight. @internal */
|
|
414
|
+
loaderLoading = signal(false, /* @ts-ignore */
|
|
415
|
+
...(ngDevMode ? [{ debugName: "loaderLoading" }] : /* istanbul ignore next */ []));
|
|
416
|
+
/** Either flag shows the panel's progress row. @internal */
|
|
417
|
+
isLoading = computed(() => this.loading() || this.loaderLoading(), /* @ts-ignore */
|
|
418
|
+
...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
323
419
|
// Tag-mode-only inputs (ignored in other modes)
|
|
324
420
|
/**
|
|
325
421
|
* Tag mode: keys / characters that commit the current draft into a chip.
|
|
@@ -404,7 +500,11 @@ class WrSelect {
|
|
|
404
500
|
const list = this.registry();
|
|
405
501
|
return arr.map(v => {
|
|
406
502
|
const found = list.find(o => o.value === v);
|
|
407
|
-
|
|
503
|
+
// Fall back to `displayWith` (defaults to `String`) rather than `String`
|
|
504
|
+
// directly: a searchable multi fed from `[options]` has no registered
|
|
505
|
+
// `<wr-option>` for a virtualized or unmounted row, and object items
|
|
506
|
+
// would otherwise render as "[object Object]".
|
|
507
|
+
return { value: v, label: found?.getLabel() ?? this.displayWith()(v) };
|
|
408
508
|
});
|
|
409
509
|
}, /* @ts-ignore */
|
|
410
510
|
...(ngDevMode ? [{ debugName: "selectedChips" }] : /* istanbul ignore next */ []));
|
|
@@ -442,8 +542,10 @@ class WrSelect {
|
|
|
442
542
|
parts.push('wr-select--multi');
|
|
443
543
|
if (this.isTag())
|
|
444
544
|
parts.push('wr-select--tag');
|
|
445
|
-
if (this.
|
|
545
|
+
if (this.isSearchTrigger())
|
|
446
546
|
parts.push('wr-select--search');
|
|
547
|
+
if (this.hasChipSearch())
|
|
548
|
+
parts.push('wr-select--searchable');
|
|
447
549
|
if (this.isDisabled())
|
|
448
550
|
parts.push('wr-select--disabled');
|
|
449
551
|
return parts.join(' ');
|
|
@@ -466,11 +568,11 @@ class WrSelect {
|
|
|
466
568
|
* `dynamicOptions()` narrowed to the current search query. `dynamicOptions()`
|
|
467
569
|
* is intentionally UNFILTERED (projected `<wr-option>` self-hide via CSS), so
|
|
468
570
|
* the virtual path — which renders plain rows that can't self-hide — filters
|
|
469
|
-
* here. Loader results are already query-scoped upstream.
|
|
571
|
+
* here. Loader / `serverSearch` results are already query-scoped upstream.
|
|
470
572
|
*/
|
|
471
573
|
filteredDynamicOptions = computed(() => {
|
|
472
574
|
const items = this.dynamicOptions();
|
|
473
|
-
if (this.
|
|
575
|
+
if (!this.clientFilter())
|
|
474
576
|
return items;
|
|
475
577
|
const q = this.searchQuery().trim().toLowerCase();
|
|
476
578
|
if (!q)
|
|
@@ -485,7 +587,7 @@ class WrSelect {
|
|
|
485
587
|
* so their DOM-derived labels / selection still work).
|
|
486
588
|
*/
|
|
487
589
|
virtualActive = computed(() => this.virtualScroll() &&
|
|
488
|
-
this.
|
|
590
|
+
this.isSearchable() &&
|
|
489
591
|
this.projectedOptions().length === 0 &&
|
|
490
592
|
this.filteredDynamicOptions().length > 0, /* @ts-ignore */
|
|
491
593
|
...(ngDevMode ? [{ debugName: "virtualActive" }] : /* istanbul ignore next */ []));
|
|
@@ -568,7 +670,7 @@ class WrSelect {
|
|
|
568
670
|
if (this.virtualActive())
|
|
569
671
|
return this.filteredDynamicOptions().length;
|
|
570
672
|
const list = this.registry();
|
|
571
|
-
if (!this.
|
|
673
|
+
if (!this.isSearchable() || !this.clientFilter())
|
|
572
674
|
return list.length;
|
|
573
675
|
const q = this.searchQuery().trim().toLowerCase();
|
|
574
676
|
if (!q)
|
|
@@ -583,7 +685,7 @@ class WrSelect {
|
|
|
583
685
|
...(ngDevMode ? [{ debugName: "visibleCount" }] : /* istanbul ignore next */ []));
|
|
584
686
|
/** Search mode: show "no results" when the panel has nothing to offer. */
|
|
585
687
|
hasNoResults = computed(() => {
|
|
586
|
-
if (!this.
|
|
688
|
+
if (!this.isSearchable() || !this.open() || this.isLoading())
|
|
587
689
|
return false;
|
|
588
690
|
if (this.searchQuery().trim().length < this.minChars())
|
|
589
691
|
return false;
|
|
@@ -592,7 +694,7 @@ class WrSelect {
|
|
|
592
694
|
...(ngDevMode ? [{ debugName: "hasNoResults" }] : /* istanbul ignore next */ []));
|
|
593
695
|
/** Search mode: is this option hidden by the current query? @internal */
|
|
594
696
|
isOptionHidden(label) {
|
|
595
|
-
if (!this.
|
|
697
|
+
if (!this.isSearchable() || !this.clientFilter())
|
|
596
698
|
return false;
|
|
597
699
|
const q = this.searchQuery().trim().toLowerCase();
|
|
598
700
|
if (!q)
|
|
@@ -600,7 +702,7 @@ class WrSelect {
|
|
|
600
702
|
return !label.toLowerCase().includes(q);
|
|
601
703
|
}
|
|
602
704
|
panelTpl = viewChild.required('panelTpl', { ...(ngDevMode ? { debugName: "panelTpl" } : /* istanbul ignore next */ {}), read: TemplateRef });
|
|
603
|
-
/**
|
|
705
|
+
/** The inline input inside a chip trigger — tag mode or a searchable multi. @internal */
|
|
604
706
|
tagInputEl = viewChild('tagInput', /* @ts-ignore */
|
|
605
707
|
...(ngDevMode ? [{ debugName: "tagInputEl" }] : /* istanbul ignore next */ []));
|
|
606
708
|
/** Search-mode inline input. Present only when `mode="search"`. @internal */
|
|
@@ -612,6 +714,37 @@ class WrSelect {
|
|
|
612
714
|
focusSearchInput() {
|
|
613
715
|
this.searchInputEl()?.nativeElement.focus();
|
|
614
716
|
}
|
|
717
|
+
// Chip-trigger input dispatch. One inline input, two owners: tag mode
|
|
718
|
+
// commits a draft into chips, a searchable multi filters the panel.
|
|
719
|
+
onChipsInput(event) {
|
|
720
|
+
if (this.hasChipSearch())
|
|
721
|
+
this.onSearchInput(event);
|
|
722
|
+
else
|
|
723
|
+
this.onTagInput(event);
|
|
724
|
+
}
|
|
725
|
+
onChipsKeydown(event) {
|
|
726
|
+
if (this.hasChipSearch())
|
|
727
|
+
this.onSearchKey(event);
|
|
728
|
+
else
|
|
729
|
+
this.onTagKeydown(event);
|
|
730
|
+
}
|
|
731
|
+
onChipsFocus() {
|
|
732
|
+
// Tag mode has no panel to open.
|
|
733
|
+
if (this.hasChipSearch())
|
|
734
|
+
this.onSearchFocus();
|
|
735
|
+
}
|
|
736
|
+
onChipsPaste(event) {
|
|
737
|
+
// Splitting a paste into chips is tag-mode behaviour; a search query is
|
|
738
|
+
// pasted as-is.
|
|
739
|
+
if (!this.hasChipSearch())
|
|
740
|
+
this.onTagPaste(event);
|
|
741
|
+
}
|
|
742
|
+
onChipsBlur() {
|
|
743
|
+
if (this.hasChipSearch())
|
|
744
|
+
this.touch.emit();
|
|
745
|
+
else
|
|
746
|
+
this.onTagBlur();
|
|
747
|
+
}
|
|
615
748
|
// Search-mode handlers
|
|
616
749
|
onSearchInput(event) {
|
|
617
750
|
if (this.isDisabled())
|
|
@@ -620,11 +753,14 @@ class WrSelect {
|
|
|
620
753
|
this.searchQuery.set(value);
|
|
621
754
|
if (!this.open())
|
|
622
755
|
this.open.set(true);
|
|
623
|
-
// A new filter re-
|
|
624
|
-
//
|
|
756
|
+
// A new filter invalidates every index, so re-seed the cursor onto the first
|
|
757
|
+
// option that still matches. This must happen on BOTH paths: on the
|
|
758
|
+
// projected-`<wr-option>` path the registry keeps filtered-out options (they
|
|
759
|
+
// only self-hide via CSS), so a stale `activeIndex` would leave the cursor on
|
|
760
|
+
// a row the user can no longer see — and Enter would select it.
|
|
761
|
+
this.activeIndex.set(this.firstEnabled());
|
|
625
762
|
if (this.virtualActive()) {
|
|
626
763
|
this.scrollTop.set(0);
|
|
627
|
-
this.activeIndex.set(this.firstEnabled());
|
|
628
764
|
this.vlistElement?.scrollTo({ top: 0 });
|
|
629
765
|
}
|
|
630
766
|
}
|
|
@@ -655,6 +791,23 @@ class WrSelect {
|
|
|
655
791
|
return;
|
|
656
792
|
}
|
|
657
793
|
}
|
|
794
|
+
// Space belongs to the text field, not to the listbox. The button trigger
|
|
795
|
+
// uses it to pick the active option, but here swallowing it would make a
|
|
796
|
+
// multi-word query ("dump trucks") impossible to type.
|
|
797
|
+
if (event.key === ' ')
|
|
798
|
+
return;
|
|
799
|
+
// Backspace on an empty query drops the last chip, matching tag mode. The
|
|
800
|
+
// button-trigger path only does this while closed, and a chip-search input
|
|
801
|
+
// keeps the panel open, so it never reaches that branch.
|
|
802
|
+
if (event.key === 'Backspace' && this.hasChipSearch() && this.searchQuery() === '' && this.hasSelection()) {
|
|
803
|
+
const chips = this.selectedChips();
|
|
804
|
+
const last = chips[chips.length - 1];
|
|
805
|
+
if (last) {
|
|
806
|
+
event.preventDefault();
|
|
807
|
+
this.removeChip(last.value, event);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
658
811
|
// Otherwise route through the same keyboard plumbing as the button trigger.
|
|
659
812
|
this.onTriggerKey(event);
|
|
660
813
|
}
|
|
@@ -686,23 +839,43 @@ class WrSelect {
|
|
|
686
839
|
this.closeOverlay();
|
|
687
840
|
// Reset the search query when the panel closes so a re-open
|
|
688
841
|
// doesn't carry a stale filter.
|
|
689
|
-
if (this.
|
|
842
|
+
if (this.isSearchable())
|
|
690
843
|
this.searchQuery.set('');
|
|
691
844
|
}
|
|
692
845
|
});
|
|
693
|
-
//
|
|
846
|
+
// One debounced query stream feeds both the async loader and the public
|
|
847
|
+
// `searchChange` output, so they fire on exactly the same cadence.
|
|
848
|
+
// `debounce(() => timer(...))` rather than `debounceTime(this.debounceMs())`:
|
|
849
|
+
// this runs in the constructor, where inputs aren't bound yet, so reading
|
|
850
|
+
// the input eagerly would pin the delay to the default and ignore
|
|
851
|
+
// `[debounceMs]` forever.
|
|
852
|
+
// `skip(1)` sits BEFORE the debounce so it drops the mount-time replay of the
|
|
853
|
+
// empty query and nothing else. Downstream of `debounce` it would instead
|
|
854
|
+
// drop the first *settled* emission — and when a fast typist's first
|
|
855
|
+
// keystrokes coalesce with that initial `''`, the dropped emission is their
|
|
856
|
+
// query, not the replay.
|
|
857
|
+
const debouncedQuery = toObservable(this.searchQuery).pipe(skip(1), debounce(() => timer(this.debounceMs())));
|
|
858
|
+
debouncedQuery.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(query => {
|
|
859
|
+
if (this.isSearchable())
|
|
860
|
+
this.searchChange.emit(query);
|
|
861
|
+
});
|
|
862
|
+
// Async loader pipeline (searchable selects only). `switchMap` cancels
|
|
694
863
|
// in-flight requests when a new keystroke arrives.
|
|
695
|
-
|
|
696
|
-
.pipe(
|
|
864
|
+
debouncedQuery
|
|
865
|
+
.pipe(switchMap(query => {
|
|
697
866
|
const loader = this.loader();
|
|
698
|
-
if (!loader || !this.
|
|
699
|
-
this.
|
|
867
|
+
if (!loader || !this.isSearchable() || query.length < this.minChars()) {
|
|
868
|
+
this.loaderLoading.set(false);
|
|
700
869
|
return of([]);
|
|
701
870
|
}
|
|
702
|
-
this.
|
|
871
|
+
this.loaderLoading.set(true);
|
|
703
872
|
const result = loader(query);
|
|
704
873
|
const source = isObservable(result) ? result : from(Promise.resolve(result));
|
|
705
|
-
|
|
874
|
+
// A loader can hand back a long-lived observable (a store selector,
|
|
875
|
+
// say) that emits and never completes — so the first value lowers the
|
|
876
|
+
// flag. `finalize` still covers completion, errors and cancellation,
|
|
877
|
+
// including a loader that completes without emitting.
|
|
878
|
+
return source.pipe(tap(() => this.loaderLoading.set(false)), finalize(() => this.loaderLoading.set(false)));
|
|
706
879
|
}), takeUntilDestroyed(this.destroyRef))
|
|
707
880
|
.subscribe(items => this.loadedOptions.set(items));
|
|
708
881
|
// Resolve the single-mode trigger label whenever the value or
|
|
@@ -723,7 +896,7 @@ class WrSelect {
|
|
|
723
896
|
// Search mode: a value picked from a virtualized (or since-unmounted) row
|
|
724
897
|
// has no registered `<wr-option>`, so derive its label from `displayWith`
|
|
725
898
|
// — the same function that rendered the row.
|
|
726
|
-
this.selectedLabel.set(v != null && this.
|
|
899
|
+
this.selectedLabel.set(v != null && this.isSearchable() ? this.displayWith()(v) : null);
|
|
727
900
|
});
|
|
728
901
|
// Wire the virtual scroll listener + viewport/row measurement while the panel
|
|
729
902
|
// is open and virtualized. Re-runs when the panel opens/closes or the virtual
|
|
@@ -832,8 +1005,10 @@ class WrSelect {
|
|
|
832
1005
|
return;
|
|
833
1006
|
if (this.hasChips()) {
|
|
834
1007
|
this.value.set([]);
|
|
1008
|
+
// `isSearchTrigger()`, not `isSearch()` — a searchable single renders the
|
|
1009
|
+
// same × and would otherwise fall through to the no-op below.
|
|
835
1010
|
}
|
|
836
|
-
else if (this.
|
|
1011
|
+
else if (this.isSearchTrigger()) {
|
|
837
1012
|
this.value.set(null);
|
|
838
1013
|
this.searchQuery.set('');
|
|
839
1014
|
}
|
|
@@ -934,8 +1109,13 @@ class WrSelect {
|
|
|
934
1109
|
return;
|
|
935
1110
|
}
|
|
936
1111
|
// Multi mode: Backspace on closed trigger removes last chip. Tag
|
|
937
|
-
// mode has its own input handler — never reaches this branch.
|
|
938
|
-
|
|
1112
|
+
// mode has its own input handler — never reaches this branch. A
|
|
1113
|
+
// searchable multi does, so only claim Backspace once its query is empty
|
|
1114
|
+
// — otherwise it would eat a chip and a character at once.
|
|
1115
|
+
if (event.key === 'Backspace' &&
|
|
1116
|
+
this.isMulti() &&
|
|
1117
|
+
this.hasSelection() &&
|
|
1118
|
+
(!this.hasChipSearch() || this.searchQuery() === '')) {
|
|
939
1119
|
event.preventDefault();
|
|
940
1120
|
const chips = this.selectedChips();
|
|
941
1121
|
const last = chips[chips.length - 1];
|
|
@@ -976,7 +1156,10 @@ class WrSelect {
|
|
|
976
1156
|
break;
|
|
977
1157
|
}
|
|
978
1158
|
const list = this.registry();
|
|
979
|
-
|
|
1159
|
+
// Filtered-out options stay registered (they only self-hide via CSS), so
|
|
1160
|
+
// the hidden check is load-bearing: without it Enter can commit a row
|
|
1161
|
+
// that is not on screen.
|
|
1162
|
+
if (idx >= 0 && idx < list.length && !list[idx].disabled && !this.isOptionHidden(list[idx].getLabel())) {
|
|
980
1163
|
const id = list[idx].id;
|
|
981
1164
|
const el = this.overlayRef?.overlayElement.querySelector(`#${CSS.escape(id)}`);
|
|
982
1165
|
el?.click();
|
|
@@ -1122,13 +1305,13 @@ class WrSelect {
|
|
|
1122
1305
|
this.overlayRef = null;
|
|
1123
1306
|
}
|
|
1124
1307
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1125
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: WrSelect, isStandalone: true, selector: "wr-select", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, loader: { classPropertyName: "loader", publicName: "loader", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, freeText: { classPropertyName: "freeText", publicName: "freeText", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxItems: { classPropertyName: "maxItems", publicName: "maxItems", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, providers: [
|
|
1308
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: WrSelect, isStandalone: true, selector: "wr-select", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, loader: { classPropertyName: "loader", publicName: "loader", isSignal: true, isRequired: false, transformFunction: null }, serverSearch: { classPropertyName: "serverSearch", publicName: "serverSearch", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, freeText: { classPropertyName: "freeText", publicName: "freeText", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxItems: { classPropertyName: "maxItems", publicName: "maxItems", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchQuery: "searchQueryChange", searchChange: "searchChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, providers: [
|
|
1126
1309
|
{
|
|
1127
1310
|
provide: WR_SELECT,
|
|
1128
1311
|
// eslint-disable-next-line @angular-eslint/no-forward-ref
|
|
1129
1312
|
useExisting: forwardRef(() => WrSelect),
|
|
1130
1313
|
},
|
|
1131
|
-
], queries: [{ propertyName: "projectedOptions", predicate: WrOption, isSignal: true }], viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "tagInputEl", first: true, predicate: ["tagInput"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isSearch()) {\n <!-- Search mode: a real text input drives a filtered overlay panel.\n The wrapper isn't itself focusable \u2014 the input is the tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"touch.emit()\"\n />\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n} @else if (isTag()) {\n <!-- Tag mode: a focusable input lives inside, so the wrapper itself\n only exists to host chips and forward whitespace clicks to the\n input. Keyboard users tab straight into the input \u2014 no need for a\n separate role. The click handler is a mouse-only convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n [value]=\"draft()\"\n [disabled]=\"isDisabled() || atCapacity()\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n (input)=\"onTagInput($event)\"\n (keydown)=\"onTagKeydown($event)\"\n (paste)=\"onTagPaste($event)\"\n (blur)=\"onTagBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"size() === 'sm'\"\n [class.wr-select-panel--lg]=\"size() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n >\n @if (loading()) {\n <div class=\"wr-select-panel__loading\" aria-busy=\"true\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\">{{ resolvedNoResults() }}</div>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
1314
|
+
], queries: [{ propertyName: "projectedOptions", predicate: WrOption, isSignal: true }], viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "tagInputEl", first: true, predicate: ["tagInput"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"touch.emit()\"\n />\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled() || (isTag() && atCapacity())\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"size() === 'sm'\"\n [class.wr-select-panel--lg]=\"size() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n >\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\" aria-busy=\"true\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\">{{ resolvedNoResults() }}</div>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
1132
1315
|
}
|
|
1133
1316
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrSelect, decorators: [{
|
|
1134
1317
|
type: Component,
|
|
@@ -1138,8 +1321,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
1138
1321
|
// eslint-disable-next-line @angular-eslint/no-forward-ref
|
|
1139
1322
|
useExisting: forwardRef(() => WrSelect),
|
|
1140
1323
|
},
|
|
1141
|
-
], template: "@if (isSearch()) {\n <!-- Search mode: a real text input drives a filtered overlay panel.\n The wrapper isn't itself focusable \u2014 the input is the tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"touch.emit()\"\n />\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n} @else if (isTag()) {\n <!-- Tag mode: a focusable input lives inside, so the wrapper itself\n only exists to host chips and forward whitespace clicks to the\n input. Keyboard users tab straight into the input \u2014 no need for a\n separate role. The click handler is a mouse-only convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n [value]=\"draft()\"\n [disabled]=\"isDisabled() || atCapacity()\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n (input)=\"onTagInput($event)\"\n (keydown)=\"onTagKeydown($event)\"\n (paste)=\"onTagPaste($event)\"\n (blur)=\"onTagBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"size() === 'sm'\"\n [class.wr-select-panel--lg]=\"size() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n >\n @if (loading()) {\n <div class=\"wr-select-panel__loading\" aria-busy=\"true\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\">{{ resolvedNoResults() }}</div>\n }\n </div>\n</ng-template>\n" }]
|
|
1142
|
-
}], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], loader: [{ type: i0.Input, args: [{ isSignal: true, alias: "loader", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], freeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "freeText", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], noResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], maxItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxItems", required: false }] }], maxTagCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTagCount", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], projectedOptions: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => WrOption), { isSignal: true }] }], panelTpl: [{ type: i0.ViewChild, args: ['panelTpl', { ...{ read: TemplateRef }, isSignal: true }] }], tagInputEl: [{ type: i0.ViewChild, args: ['tagInput', { isSignal: true }] }], searchInputEl: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
1324
|
+
], template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"touch.emit()\"\n />\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled() || (isTag() && atCapacity())\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.value, $event)\"\n (keydown.enter)=\"removeChip(chip.value, $event)\"\n (keydown.space)=\"removeChip(chip.value, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n (keydown.enter)=\"clearAll($event)\"\n (keydown.space)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"size() === 'sm'\"\n [class.wr-select-panel--lg]=\"size() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n >\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\" aria-busy=\"true\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\">{{ resolvedNoResults() }}</div>\n }\n </div>\n</ng-template>\n" }]
|
|
1325
|
+
}], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }, { type: i0.Output, args: ["searchQueryChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], loader: [{ type: i0.Input, args: [{ isSignal: true, alias: "loader", required: false }] }], serverSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSearch", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], freeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "freeText", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], noResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], maxItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxItems", required: false }] }], maxTagCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTagCount", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], projectedOptions: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => WrOption), { isSignal: true }] }], panelTpl: [{ type: i0.ViewChild, args: ['panelTpl', { ...{ read: TemplateRef }, isSignal: true }] }], tagInputEl: [{ type: i0.ViewChild, args: ['tagInput', { isSignal: true }] }], searchInputEl: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
1143
1326
|
|
|
1144
1327
|
/**
|
|
1145
1328
|
* @license
|