@uni-design-system/uni-angular 7.2.0 → 8.0.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, effect, viewChild, afterNextRender, ViewChild, booleanAttribute, viewChildren } from '@angular/core';
|
|
2
|
+
import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, effect, viewChild, afterNextRender, ViewChild, afterRenderEffect, booleanAttribute, viewChildren } from '@angular/core';
|
|
3
3
|
import { injectGlobal, css, keyframes } from '@emotion/css';
|
|
4
|
-
import { UniThemes, LightTheme, toTypefaces, createThemeFromPalette, Z_INDEX, fadeIn, fadeOut, expandFadeIn, collapseFadeOut, removeInputPlatformStyling, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
|
|
4
|
+
import { UniThemes, LightTheme, toTypefaces, parseTheme, formatThemeIssues, hydrateTheme, createThemeFromPalette, Z_INDEX, fadeIn, fadeOut, EXPAND_DEFAULT_SPEED, expandDuration, expandFadeIn, collapseFadeOut, removeInputPlatformStyling, HOVER_OR_KEYBOARD_FOCUS, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
|
|
5
5
|
import { NgClass, NgTemplateOutlet, CommonModule } from '@angular/common';
|
|
6
6
|
|
|
7
7
|
let nextUniqueId = 0;
|
|
@@ -50,6 +50,112 @@ function motionSafe(styles) {
|
|
|
50
50
|
return { '@media (prefers-reduced-motion: no-preference)': styles };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The keyboard and ARIA bookkeeping shared by every combobox-style popup:
|
|
55
|
+
* open state, the active option index, and the `aria-activedescendant` id
|
|
56
|
+
* wiring. Deliberately owns no markup and no filtering — each control renders
|
|
57
|
+
* its own options and decides what a suggestion *is*.
|
|
58
|
+
*
|
|
59
|
+
* Extracted because three controls need the identical contract
|
|
60
|
+
* (`uni-search-input`, `uni-tag-input`, and the multi-select upgrade), and the
|
|
61
|
+
* parts that silently drift between hand-rolled copies all live here: the
|
|
62
|
+
* wrap-around arithmetic, Home/End, and keeping the active id in sync with the
|
|
63
|
+
* option list.
|
|
64
|
+
*
|
|
65
|
+
* `Enter` and `Escape` are deliberately *not* handled: what they mean depends
|
|
66
|
+
* on the control (submit a search, commit a typed token, clear the field), and
|
|
67
|
+
* `activeIndex()` is all a caller needs to decide.
|
|
68
|
+
*/
|
|
69
|
+
class ListboxNavigation {
|
|
70
|
+
config;
|
|
71
|
+
/** Id for the `role="listbox"` element; wire as `aria-controls`. */
|
|
72
|
+
listboxId;
|
|
73
|
+
_open = signal(false, ...(ngDevMode ? [{ debugName: "_open" }] : /* istanbul ignore next */ []));
|
|
74
|
+
_activeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeIndex" }] : /* istanbul ignore next */ []));
|
|
75
|
+
wrap;
|
|
76
|
+
/** Whether the popup is showing. Also false when there is nothing to show. */
|
|
77
|
+
open = computed(() => this._open() && this.config.count() > 0, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
78
|
+
/** Index of the highlighted option, or -1 when none is active. */
|
|
79
|
+
activeIndex = computed(() => {
|
|
80
|
+
const index = this._activeIndex();
|
|
81
|
+
// A shrinking option list must never leave the active id dangling.
|
|
82
|
+
return index < this.config.count() ? index : -1;
|
|
83
|
+
}, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
84
|
+
/** Value for `aria-activedescendant`, or null when nothing is active. */
|
|
85
|
+
activeDescendantId = computed(() => {
|
|
86
|
+
const index = this.activeIndex();
|
|
87
|
+
return this.open() && index >= 0 ? this.optionId(index) : null;
|
|
88
|
+
}, ...(ngDevMode ? [{ debugName: "activeDescendantId" }] : /* istanbul ignore next */ []));
|
|
89
|
+
constructor(config) {
|
|
90
|
+
this.config = config;
|
|
91
|
+
this.listboxId = uniqueId(config.idPrefix ?? 'uni-listbox');
|
|
92
|
+
this.wrap = config.wrap ?? true;
|
|
93
|
+
}
|
|
94
|
+
/** Stable per-option id, for `role="option"` elements. */
|
|
95
|
+
optionId(index) {
|
|
96
|
+
return `${this.listboxId}-option-${index}`;
|
|
97
|
+
}
|
|
98
|
+
show() {
|
|
99
|
+
this._open.set(true);
|
|
100
|
+
}
|
|
101
|
+
/** Close and drop the active option, so reopening starts clean. */
|
|
102
|
+
hide() {
|
|
103
|
+
this._open.set(false);
|
|
104
|
+
this._activeIndex.set(-1);
|
|
105
|
+
}
|
|
106
|
+
setActive(index) {
|
|
107
|
+
this._activeIndex.set(index);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Handle ArrowDown / ArrowUp / Home / End, opening the popup if needed.
|
|
111
|
+
* Returns true when the key was consumed, so a caller can fall through to
|
|
112
|
+
* its own handling for everything else.
|
|
113
|
+
*/
|
|
114
|
+
navigate(event) {
|
|
115
|
+
const count = this.config.count();
|
|
116
|
+
if (count === 0)
|
|
117
|
+
return false;
|
|
118
|
+
const target = this.nextIndex(event.key, count);
|
|
119
|
+
if (target === null)
|
|
120
|
+
return false;
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
this._open.set(true);
|
|
123
|
+
this._activeIndex.set(target);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
nextIndex(key, count) {
|
|
127
|
+
const current = this.activeIndex();
|
|
128
|
+
switch (key) {
|
|
129
|
+
case 'ArrowDown':
|
|
130
|
+
return this.step(current, 1, count);
|
|
131
|
+
case 'ArrowUp':
|
|
132
|
+
// Opening with ArrowUp lands on the last option, matching menus.
|
|
133
|
+
return current < 0 ? count - 1 : this.step(current, -1, count);
|
|
134
|
+
case 'Home':
|
|
135
|
+
return 0;
|
|
136
|
+
case 'End':
|
|
137
|
+
return count - 1;
|
|
138
|
+
default:
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
step(current, delta, count) {
|
|
143
|
+
const next = current + delta;
|
|
144
|
+
if (this.wrap)
|
|
145
|
+
return (next + count) % count;
|
|
146
|
+
return Math.min(Math.max(next, 0), count - 1);
|
|
147
|
+
}
|
|
148
|
+
/** Close when focus leaves the control entirely (not on internal moves). */
|
|
149
|
+
closeOnFocusOut(event) {
|
|
150
|
+
const next = event.relatedTarget;
|
|
151
|
+
const container = event.currentTarget;
|
|
152
|
+
if (!next || !container?.contains(next))
|
|
153
|
+
this.hide();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Factory mirroring the signal-API style used elsewhere in the CDK. */
|
|
157
|
+
const createListboxNavigation = (config) => new ListboxNavigation(config);
|
|
158
|
+
|
|
53
159
|
/** Returns a document-unique dashed-ident usable as a CSS `anchor-name`. */
|
|
54
160
|
function newAnchorName() {
|
|
55
161
|
return `--${uniqueId('uni-anchor')}`;
|
|
@@ -648,10 +754,19 @@ class ThemeService {
|
|
|
648
754
|
static CUSTOM_KEY = 'CustomTheme';
|
|
649
755
|
/** localStorage key holding the serialized brand palette config. */
|
|
650
756
|
static PALETTE_KEY = 'uni-custom-palette';
|
|
651
|
-
themes = inject(UNI_THEMES);
|
|
652
757
|
localStorage = inject(LocalStorageService);
|
|
653
|
-
|
|
654
|
-
|
|
758
|
+
/**
|
|
759
|
+
* The live theme registry: the injected {@link UNI_THEMES} map (validated
|
|
760
|
+
* at construction) plus anything added via {@link registerTheme} — including
|
|
761
|
+
* the custom brand theme, which registers under {@link CUSTOM_KEY} so it is
|
|
762
|
+
* an ordinary, selectable entry.
|
|
763
|
+
*/
|
|
764
|
+
registry = signal({}, ...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
|
|
765
|
+
_theme = signal(LightTheme, ...(ngDevMode ? [{ debugName: "_theme" }] : /* istanbul ignore next */ []));
|
|
766
|
+
/** The active theme. Write via {@link setTheme}/{@link selectTheme} — every write is validated. */
|
|
767
|
+
theme = this._theme.asReadonly();
|
|
768
|
+
/** Selectable themes, derived from the registry — reactive to registration. */
|
|
769
|
+
themeOptions = computed(() => Object.entries(this.registry()).map(([value, theme]) => ({ label: theme.name, value })), ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
|
|
655
770
|
components = computed(() => this.theme().components, ...(ngDevMode ? [{ debugName: "components" }] : /* istanbul ignore next */ []));
|
|
656
771
|
component = (componentName) => computed(() => this.components()[componentName] || {});
|
|
657
772
|
colors = computed(() => this.theme().colors, ...(ngDevMode ? [{ debugName: "colors" }] : /* istanbul ignore next */ []));
|
|
@@ -677,9 +792,21 @@ class ThemeService {
|
|
|
677
792
|
}
|
|
678
793
|
}
|
|
679
794
|
`;
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
795
|
+
// Seed the registry from the injected map, excluding (with reasons) any
|
|
796
|
+
// theme that fails the structural contract — a malformed theme would
|
|
797
|
+
// otherwise render as silent `undefined` CSS.
|
|
798
|
+
const injected = inject(UNI_THEMES);
|
|
799
|
+
const registry = {};
|
|
800
|
+
for (const [key, candidate] of Object.entries(injected)) {
|
|
801
|
+
const result = parseTheme(candidate);
|
|
802
|
+
if (result.success) {
|
|
803
|
+
registry[key] = result.theme;
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
console.warn(`Uni theme '${key}' rejected: ${formatThemeIssues(result.issues)}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
this.registry.set(registry);
|
|
683
810
|
// Rehydrate a custom brand theme if one is active — this is what lets a
|
|
684
811
|
// palette built in one story reskin every other story: each story spins up
|
|
685
812
|
// a fresh ThemeService, and it reads the persisted config here on init.
|
|
@@ -689,14 +816,75 @@ class ThemeService {
|
|
|
689
816
|
this.applyPalette(savedPalette, false);
|
|
690
817
|
}
|
|
691
818
|
else {
|
|
692
|
-
this.selectTheme(savedTheme || Object.keys(
|
|
819
|
+
this.selectTheme(savedTheme || Object.keys(registry)[0] || 'base');
|
|
693
820
|
}
|
|
694
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* Activate a registered theme. Returns false — touching nothing, persisting
|
|
824
|
+
* nothing — when the name is unknown (previously this silently kept the old
|
|
825
|
+
* theme while still recording the bad key).
|
|
826
|
+
*/
|
|
695
827
|
selectTheme(themeName) {
|
|
828
|
+
const theme = this.registry()[themeName];
|
|
829
|
+
if (!theme)
|
|
830
|
+
return false;
|
|
831
|
+
// Registered themes were validated at registration; set directly.
|
|
832
|
+
this._theme.set(theme);
|
|
696
833
|
this.selectedThemeKey.set(themeName);
|
|
697
|
-
if (this.themes[themeName])
|
|
698
|
-
this.theme.set(this.themes[themeName]);
|
|
699
834
|
this.localStorage.setItem('theme', themeName);
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Validate and activate a theme without registering it. The result carries
|
|
839
|
+
* acceptance or the complete list of rejection reasons; on rejection the
|
|
840
|
+
* active theme is unchanged.
|
|
841
|
+
*/
|
|
842
|
+
setTheme(input) {
|
|
843
|
+
const result = this.accept(input);
|
|
844
|
+
if (result.success) {
|
|
845
|
+
this._theme.set(result.theme);
|
|
846
|
+
this.selectedThemeKey.set(result.theme.id);
|
|
847
|
+
}
|
|
848
|
+
return result;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Validate and add a theme to the registry (keyed by its `id`), making it
|
|
852
|
+
* selectable and listed in {@link themeOptions}. On rejection the registry
|
|
853
|
+
* is unchanged and the result lists every reason.
|
|
854
|
+
*/
|
|
855
|
+
registerTheme(input, opts) {
|
|
856
|
+
const result = this.accept(input);
|
|
857
|
+
if (result.success) {
|
|
858
|
+
const theme = result.theme;
|
|
859
|
+
this.registry.update((themes) => ({ ...themes, [theme.id]: theme }));
|
|
860
|
+
if (opts?.select)
|
|
861
|
+
this.selectTheme(theme.id);
|
|
862
|
+
}
|
|
863
|
+
return result;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* The gate every externally-supplied theme passes: validate, then restore
|
|
867
|
+
* any built-in icons the payload left out. Themes that travel as JSON (the
|
|
868
|
+
* MCP theme tools, a theme registry) omit the built-in icon set — it is
|
|
869
|
+
* ~71% of the bytes and every consumer already ships it — so hydration
|
|
870
|
+
* applies the same `{...BaseIcons, ...icons}` contract `createTheme` uses:
|
|
871
|
+
* the theme's own icons win, built-ins fill the rest.
|
|
872
|
+
*/
|
|
873
|
+
accept(input) {
|
|
874
|
+
const result = parseTheme(input);
|
|
875
|
+
return result.success ? { ...result, theme: hydrateTheme(result.theme) } : result;
|
|
876
|
+
}
|
|
877
|
+
/** Remove a registered theme; falls back to the first remaining theme if it was active. */
|
|
878
|
+
unregisterTheme(id) {
|
|
879
|
+
if (!(id in this.registry()))
|
|
880
|
+
return;
|
|
881
|
+
this.registry.update((themes) => {
|
|
882
|
+
const { [id]: _removed, ...rest } = themes;
|
|
883
|
+
return rest;
|
|
884
|
+
});
|
|
885
|
+
if (this.selectedThemeKey() === id) {
|
|
886
|
+
this.selectTheme(Object.keys(this.registry())[0] || 'base');
|
|
887
|
+
}
|
|
700
888
|
}
|
|
701
889
|
/** The live brand palette config, when a custom theme is active. */
|
|
702
890
|
customPalette = signal(null, ...(ngDevMode ? [{ debugName: "customPalette" }] : /* istanbul ignore next */ []));
|
|
@@ -708,18 +896,24 @@ class ThemeService {
|
|
|
708
896
|
*/
|
|
709
897
|
applyPalette(config, persist = true) {
|
|
710
898
|
this.customPalette.set(config);
|
|
711
|
-
|
|
899
|
+
// Registering (rather than only setting) makes 'Your Brand' an ordinary
|
|
900
|
+
// registry entry: it appears in themeOptions/uni-theme-switch and can be
|
|
901
|
+
// switched away from and back to.
|
|
902
|
+
const result = this.registerTheme(createThemeFromPalette({ ...config, id: ThemeService.CUSTOM_KEY, name: 'Your Brand' }));
|
|
903
|
+
if (!result.success)
|
|
904
|
+
return;
|
|
905
|
+
this._theme.set(result.theme);
|
|
712
906
|
this.selectedThemeKey.set(ThemeService.CUSTOM_KEY);
|
|
713
907
|
if (persist) {
|
|
714
908
|
this.localStorage.setItem(ThemeService.PALETTE_KEY, config);
|
|
715
909
|
this.localStorage.setItem('theme', ThemeService.CUSTOM_KEY);
|
|
716
910
|
}
|
|
717
911
|
}
|
|
718
|
-
/** Drop the custom brand theme and fall back to the first
|
|
912
|
+
/** Drop the custom brand theme and fall back to the first registered theme. */
|
|
719
913
|
clearCustomPalette() {
|
|
720
914
|
this.customPalette.set(null);
|
|
721
915
|
this.localStorage.removeItem(ThemeService.PALETTE_KEY);
|
|
722
|
-
this.
|
|
916
|
+
this.unregisterTheme(ThemeService.CUSTOM_KEY);
|
|
723
917
|
}
|
|
724
918
|
selectedThemeName = computed(() => this.theme().name, ...(ngDevMode ? [{ debugName: "selectedThemeName" }] : /* istanbul ignore next */ []));
|
|
725
919
|
selectedThemeKey = signal('', ...(ngDevMode ? [{ debugName: "selectedThemeKey" }] : /* istanbul ignore next */ []));
|
|
@@ -3308,6 +3502,13 @@ class UniDropdownComponent extends BaseComponent {
|
|
|
3308
3502
|
popoverId = uniqueId('uni-dropdown');
|
|
3309
3503
|
paddingVertical = input(...(ngDevMode ? [undefined, { debugName: "paddingVertical" }] : /* istanbul ignore next */ []));
|
|
3310
3504
|
paddingHorizontal = input(...(ngDevMode ? [undefined, { debugName: "paddingHorizontal" }] : /* istanbul ignore next */ []));
|
|
3505
|
+
// Per-instance panel-chrome overrides; undefined falls back to the theme's
|
|
3506
|
+
// `dropdown` options, so hosts like uni-menu can restyle their panel
|
|
3507
|
+
// without forking the shared dropdown entry.
|
|
3508
|
+
border = input(...(ngDevMode ? [undefined, { debugName: "border" }] : /* istanbul ignore next */ []));
|
|
3509
|
+
borderRadius = input(...(ngDevMode ? [undefined, { debugName: "borderRadius" }] : /* istanbul ignore next */ []));
|
|
3510
|
+
shadow = input(...(ngDevMode ? [undefined, { debugName: "shadow" }] : /* istanbul ignore next */ []));
|
|
3511
|
+
color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : /* istanbul ignore next */ []));
|
|
3311
3512
|
dropdownShowing = output();
|
|
3312
3513
|
dropdownHiding = output();
|
|
3313
3514
|
dropdownRef;
|
|
@@ -3432,17 +3633,17 @@ class UniDropdownComponent extends BaseComponent {
|
|
|
3432
3633
|
}
|
|
3433
3634
|
}
|
|
3434
3635
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
3435
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDropdownComponent, isStandalone: true, selector: "uni-dropdown", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, ariaHasPopup: { classPropertyName: "ariaHasPopup", publicName: "ariaHasPopup", isSignal: true, isRequired: false, transformFunction: null }, paddingVertical: { classPropertyName: "paddingVertical", publicName: "paddingVertical", isSignal: true, isRequired: false, transformFunction: null }, paddingHorizontal: { classPropertyName: "paddingHorizontal", publicName: "paddingHorizontal", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dropdownShowing: "dropdownShowing", dropdownHiding: "dropdownHiding" }, providers: [{ provide: COMPONENT_NAME, useValue: 'dropdown' }], viewQueries: [{ propertyName: "dropdownRef", first: true, predicate: ["dropdown"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
|
|
3636
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDropdownComponent, isStandalone: true, selector: "uni-dropdown", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, ariaHasPopup: { classPropertyName: "ariaHasPopup", publicName: "ariaHasPopup", isSignal: true, isRequired: false, transformFunction: null }, paddingVertical: { classPropertyName: "paddingVertical", publicName: "paddingVertical", isSignal: true, isRequired: false, transformFunction: null }, paddingHorizontal: { classPropertyName: "paddingHorizontal", publicName: "paddingHorizontal", isSignal: true, isRequired: false, transformFunction: null }, border: { classPropertyName: "border", publicName: "border", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dropdownShowing: "dropdownShowing", dropdownHiding: "dropdownHiding" }, providers: [{ provide: COMPONENT_NAME, useValue: 'dropdown' }], viewQueries: [{ propertyName: "dropdownRef", first: true, predicate: ["dropdown"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
|
|
3436
3637
|
<!-- 1. The native 'popover' attribute brings it to the top layer with native light-dismiss -->
|
|
3437
3638
|
<div #dropdown popover="auto" [id]="popoverId" [class]="dropdownClass()">
|
|
3438
3639
|
<div
|
|
3439
3640
|
box-layout
|
|
3440
|
-
[border]="componentOptions().border"
|
|
3441
|
-
[borderRadius]="componentOptions().borderRadius"
|
|
3641
|
+
[border]="border() ?? componentOptions().border"
|
|
3642
|
+
[borderRadius]="borderRadius() ?? componentOptions().borderRadius"
|
|
3442
3643
|
[paddingVertical]="paddingVertical()"
|
|
3443
3644
|
[paddingHorizontal]="paddingHorizontal()"
|
|
3444
|
-
[color]="componentOptions().color"
|
|
3445
|
-
[shadow]="componentOptions().shadow"
|
|
3645
|
+
[color]="color() ?? componentOptions().color"
|
|
3646
|
+
[shadow]="shadow() ?? componentOptions().shadow"
|
|
3446
3647
|
>
|
|
3447
3648
|
<ng-content></ng-content>
|
|
3448
3649
|
</div>
|
|
@@ -3460,12 +3661,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3460
3661
|
<div #dropdown popover="auto" [id]="popoverId" [class]="dropdownClass()">
|
|
3461
3662
|
<div
|
|
3462
3663
|
box-layout
|
|
3463
|
-
[border]="componentOptions().border"
|
|
3464
|
-
[borderRadius]="componentOptions().borderRadius"
|
|
3664
|
+
[border]="border() ?? componentOptions().border"
|
|
3665
|
+
[borderRadius]="borderRadius() ?? componentOptions().borderRadius"
|
|
3465
3666
|
[paddingVertical]="paddingVertical()"
|
|
3466
3667
|
[paddingHorizontal]="paddingHorizontal()"
|
|
3467
|
-
[color]="componentOptions().color"
|
|
3468
|
-
[shadow]="componentOptions().shadow"
|
|
3668
|
+
[color]="color() ?? componentOptions().color"
|
|
3669
|
+
[shadow]="shadow() ?? componentOptions().shadow"
|
|
3469
3670
|
>
|
|
3470
3671
|
<ng-content></ng-content>
|
|
3471
3672
|
</div>
|
|
@@ -3473,13 +3674,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3473
3674
|
`,
|
|
3474
3675
|
providers: [{ provide: COMPONENT_NAME, useValue: 'dropdown' }],
|
|
3475
3676
|
}]
|
|
3476
|
-
}], propDecorators: { trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], ariaHasPopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaHasPopup", required: false }] }], paddingVertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingVertical", required: false }] }], paddingHorizontal: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingHorizontal", required: false }] }], dropdownShowing: [{ type: i0.Output, args: ["dropdownShowing"] }], dropdownHiding: [{ type: i0.Output, args: ["dropdownHiding"] }], dropdownRef: [{
|
|
3677
|
+
}], propDecorators: { trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], ariaHasPopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaHasPopup", required: false }] }], paddingVertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingVertical", required: false }] }], paddingHorizontal: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingHorizontal", required: false }] }], border: [{ type: i0.Input, args: [{ isSignal: true, alias: "border", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }], shadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "shadow", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], dropdownShowing: [{ type: i0.Output, args: ["dropdownShowing"] }], dropdownHiding: [{ type: i0.Output, args: ["dropdownHiding"] }], dropdownRef: [{
|
|
3477
3678
|
type: ViewChild,
|
|
3478
3679
|
args: ['dropdown', { static: true }]
|
|
3479
3680
|
}] } });
|
|
3480
3681
|
|
|
3481
|
-
class UniExpandComponent {
|
|
3682
|
+
class UniExpandComponent extends BaseComponent {
|
|
3482
3683
|
collapsed = model(true, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
|
|
3684
|
+
/**
|
|
3685
|
+
* Exact per-instance duration in seconds. Bypasses size-aware scaling —
|
|
3686
|
+
* an explicit number means the consumer wants that number. Omitted, the
|
|
3687
|
+
* duration derives from the `expand` theme options' `transitionSpeed`
|
|
3688
|
+
* scaled by content height (see `duration`).
|
|
3689
|
+
*/
|
|
3690
|
+
transitionSpeed = input(...(ngDevMode ? [undefined, { debugName: "transitionSpeed" }] : /* istanbul ignore next */ []));
|
|
3483
3691
|
/** Referenced by the controlling toggle's aria-controls. */
|
|
3484
3692
|
regionId = uniqueId('uni-expand');
|
|
3485
3693
|
/**
|
|
@@ -3488,12 +3696,49 @@ class UniExpandComponent {
|
|
|
3488
3696
|
* animations only after the first render, i.e. on real state changes.
|
|
3489
3697
|
*/
|
|
3490
3698
|
ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
3699
|
+
contentRef = viewChild('content', ...(ngDevMode ? [{ debugName: "contentRef" }] : /* istanbul ignore next */ []));
|
|
3700
|
+
/**
|
|
3701
|
+
* Last observed content height. Retained after collapse (the observer
|
|
3702
|
+
* disconnects but the signal keeps its value) so the leave animation and
|
|
3703
|
+
* the next reveal are timed to the real size. Until the first measurement
|
|
3704
|
+
* lands — one frame into the very first reveal — the duration falls back
|
|
3705
|
+
* to the unscaled token; CSS remaps animation progress when
|
|
3706
|
+
* `animation-duration` updates, so the retime is imperceptible that early.
|
|
3707
|
+
*/
|
|
3708
|
+
contentHeight = signal(undefined, ...(ngDevMode ? [{ debugName: "contentHeight" }] : /* istanbul ignore next */ []));
|
|
3491
3709
|
constructor() {
|
|
3710
|
+
super();
|
|
3492
3711
|
afterNextRender(() => this.ready.set(true));
|
|
3712
|
+
afterRenderEffect((onCleanup) => {
|
|
3713
|
+
const content = this.contentRef()?.nativeElement;
|
|
3714
|
+
if (!content)
|
|
3715
|
+
return;
|
|
3716
|
+
const observer = new ResizeObserver(() => this.contentHeight.set(content.scrollHeight));
|
|
3717
|
+
observer.observe(content);
|
|
3718
|
+
onCleanup(() => observer.disconnect());
|
|
3719
|
+
});
|
|
3493
3720
|
}
|
|
3494
3721
|
toggle() {
|
|
3495
3722
|
this.collapsed.update((collapsed) => !collapsed);
|
|
3496
3723
|
}
|
|
3724
|
+
/**
|
|
3725
|
+
* The resolved duration in seconds: the `transitionSpeed` input verbatim,
|
|
3726
|
+
* or the `expand` theme options' `transitionSpeed` scaled by content height
|
|
3727
|
+
* (`expandDuration` — √-of-height, clamped) so short regions stay snappy
|
|
3728
|
+
* and tall ones aren't rushed. The public sync hook: Expand Area binds this
|
|
3729
|
+
* to its toggle so the chevron rotates on the region's clock, and consumers
|
|
3730
|
+
* can bind adjacent styling the same way
|
|
3731
|
+
* (`[style.transition-duration]="expand.duration() + 's'"`).
|
|
3732
|
+
*/
|
|
3733
|
+
duration = computed(() => {
|
|
3734
|
+
const override = this.transitionSpeed();
|
|
3735
|
+
if (override !== undefined)
|
|
3736
|
+
return override;
|
|
3737
|
+
const speed = this.componentOptions().transitionSpeed ?? EXPAND_DEFAULT_SPEED;
|
|
3738
|
+
const height = this.contentHeight();
|
|
3739
|
+
return height === undefined ? speed : expandDuration(height, speed);
|
|
3740
|
+
}, ...(ngDevMode ? [{ debugName: "duration" }] : /* istanbul ignore next */ []));
|
|
3741
|
+
cssDuration = computed(() => `${this.duration()}s`, ...(ngDevMode ? [{ debugName: "cssDuration" }] : /* istanbul ignore next */ []));
|
|
3497
3742
|
/**
|
|
3498
3743
|
* A custom element is `display: inline` by default, which would lay the
|
|
3499
3744
|
* animated grid out as a block-in-inline box and distort the revealed
|
|
@@ -3519,23 +3764,29 @@ class UniExpandComponent {
|
|
|
3519
3764
|
* decorations that legitimately paint outside the region (focus rings,
|
|
3520
3765
|
* offset outlines). Angular removes a leaving node on the next frame when it
|
|
3521
3766
|
* detects no animation, so nothing hangs when the guard strips them.
|
|
3767
|
+
*
|
|
3768
|
+
* The classes carry the default duration; the animated div's
|
|
3769
|
+
* `[style.animation-duration]` binding overrides it with the resolved
|
|
3770
|
+
* `duration`, so the classes stay static while timing tracks the theme and
|
|
3771
|
+
* the content's size through signals alone.
|
|
3522
3772
|
*/
|
|
3523
3773
|
expandAnimation = css(motionSafe({
|
|
3524
3774
|
overflow: 'hidden',
|
|
3525
|
-
animation: `${this.expand} ease-in
|
|
3775
|
+
animation: `${this.expand} ease-in-out ${EXPAND_DEFAULT_SPEED}s`,
|
|
3526
3776
|
}));
|
|
3527
3777
|
collapseAnimation = css(motionSafe({
|
|
3528
3778
|
overflow: 'hidden',
|
|
3529
|
-
animation: `${this.collapse} ease-in
|
|
3779
|
+
animation: `${this.collapse} ease-in-out ${EXPAND_DEFAULT_SPEED}s`,
|
|
3530
3780
|
}));
|
|
3531
3781
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3532
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniExpandComponent, isStandalone: true, selector: "uni-expand", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "attr.id": "regionId", "class": "hostClassName" } }, ngImport: i0, template: `@if (!collapsed()) {
|
|
3782
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniExpandComponent, isStandalone: true, selector: "uni-expand", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, transitionSpeed: { classPropertyName: "transitionSpeed", publicName: "transitionSpeed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "attr.id": "regionId", "class": "hostClassName" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'expand' }], viewQueries: [{ propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `@if (!collapsed()) {
|
|
3533
3783
|
<div
|
|
3534
3784
|
[animate.enter]="ready() ? expandAnimation : ''"
|
|
3535
3785
|
[animate.leave]="collapseAnimation"
|
|
3536
3786
|
[class]="expandClassName"
|
|
3787
|
+
[style.animation-duration]="cssDuration()"
|
|
3537
3788
|
>
|
|
3538
|
-
<div [class]="contentClassName">
|
|
3789
|
+
<div #content [class]="contentClassName">
|
|
3539
3790
|
<ng-content></ng-content>
|
|
3540
3791
|
</div>
|
|
3541
3792
|
</div>
|
|
@@ -3547,13 +3798,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3547
3798
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3548
3799
|
selector: 'uni-expand',
|
|
3549
3800
|
imports: [],
|
|
3801
|
+
providers: [{ provide: COMPONENT_NAME, useValue: 'expand' }],
|
|
3550
3802
|
template: `@if (!collapsed()) {
|
|
3551
3803
|
<div
|
|
3552
3804
|
[animate.enter]="ready() ? expandAnimation : ''"
|
|
3553
3805
|
[animate.leave]="collapseAnimation"
|
|
3554
3806
|
[class]="expandClassName"
|
|
3807
|
+
[style.animation-duration]="cssDuration()"
|
|
3555
3808
|
>
|
|
3556
|
-
<div [class]="contentClassName">
|
|
3809
|
+
<div #content [class]="contentClassName">
|
|
3557
3810
|
<ng-content></ng-content>
|
|
3558
3811
|
</div>
|
|
3559
3812
|
</div>
|
|
@@ -3563,7 +3816,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3563
3816
|
'[class]': 'hostClassName',
|
|
3564
3817
|
},
|
|
3565
3818
|
}]
|
|
3566
|
-
}], ctorParameters: () => [], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }] } });
|
|
3819
|
+
}], ctorParameters: () => [], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], transitionSpeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "transitionSpeed", required: false }] }], contentRef: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
|
|
3567
3820
|
|
|
3568
3821
|
/**
|
|
3569
3822
|
* UniExpandComponent Barrel File
|
|
@@ -3571,182 +3824,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3571
3824
|
* This file exports all public-facing elements of the expand component.
|
|
3572
3825
|
*/
|
|
3573
3826
|
|
|
3574
|
-
class UniTooltipComponent extends BaseComponent {
|
|
3575
|
-
elRef = inject(ElementRef);
|
|
3576
|
-
renderer = inject(Renderer2);
|
|
3577
|
-
timer = useTimer();
|
|
3578
|
-
isMouseInside = signal(false, ...(ngDevMode ? [{ debugName: "isMouseInside" }] : /* istanbul ignore next */ []));
|
|
3579
|
-
/** Whether the bubble is currently shown (or fading out). */
|
|
3580
|
-
visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
3581
|
-
tooltipId = uniqueId('uni-tooltip');
|
|
3582
|
-
anchorName = newAnchorName();
|
|
3583
|
-
hoverDelay = input(500, ...(ngDevMode ? [{ debugName: "hoverDelay" }] : /* istanbul ignore next */ []));
|
|
3584
|
-
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
3585
|
-
placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
3586
|
-
inlineText = input(false, ...(ngDevMode ? [{ debugName: "inlineText" }] : /* istanbul ignore next */ []));
|
|
3587
|
-
/** @deprecated The tooltip renders in the native top layer; ignored. */
|
|
3588
|
-
appendToBody = input(false, ...(ngDevMode ? [{ debugName: "appendToBody" }] : /* istanbul ignore next */ []));
|
|
3589
|
-
tipRef = viewChild.required('tip');
|
|
3590
|
-
constructor() {
|
|
3591
|
-
super();
|
|
3592
|
-
effect(() => {
|
|
3593
|
-
const timerActive = this.timer.isActive();
|
|
3594
|
-
const mouseInside = this.isMouseInside();
|
|
3595
|
-
// If the timer finished and the mouse is still inside, show tooltip
|
|
3596
|
-
if (!timerActive && mouseInside) {
|
|
3597
|
-
this.showTooltip();
|
|
3598
|
-
}
|
|
3599
|
-
// If the mouse left and the timer is not running, hide tooltip
|
|
3600
|
-
else if (!mouseInside) {
|
|
3601
|
-
this.hideTooltip();
|
|
3602
|
-
}
|
|
3603
|
-
});
|
|
3604
|
-
// A tooltip must be reachable by keyboard (WCAG 1.4.13): when the
|
|
3605
|
-
// projected content has no focusable element, the host itself joins
|
|
3606
|
-
// the tab sequence. The bubble is always in the DOM, so the describedby
|
|
3607
|
-
// relationship is wired once, on the element that receives focus.
|
|
3608
|
-
afterNextRender(() => {
|
|
3609
|
-
const host = this.elRef.nativeElement;
|
|
3610
|
-
if (!host.querySelector(FOCUSABLE_SELECTOR) && !host.matches(FOCUSABLE_SELECTOR)) {
|
|
3611
|
-
this.renderer.setAttribute(host, 'tabindex', '0');
|
|
3612
|
-
}
|
|
3613
|
-
this.renderer.setAttribute(resolveFocusTarget(host), 'aria-describedby', this.tooltipId);
|
|
3614
|
-
});
|
|
3615
|
-
}
|
|
3616
|
-
onFocusIn(event) {
|
|
3617
|
-
// Only keyboard-driven focus shows the tooltip immediately; mouse
|
|
3618
|
-
// interaction keeps the hover-delay behavior.
|
|
3619
|
-
if (event.target.matches(':focus-visible')) {
|
|
3620
|
-
this.showTooltip();
|
|
3621
|
-
}
|
|
3622
|
-
}
|
|
3623
|
-
onFocusOut() {
|
|
3624
|
-
if (!this.isMouseInside()) {
|
|
3625
|
-
this.hideTooltip();
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
onEscape(event) {
|
|
3629
|
-
if (this.visible()) {
|
|
3630
|
-
// Dismiss only the tooltip, not an enclosing dialog/popover
|
|
3631
|
-
event.stopPropagation();
|
|
3632
|
-
this.hideTooltip();
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
toggleTooltip() {
|
|
3636
|
-
if (this.visible()) {
|
|
3637
|
-
this.hideTooltip();
|
|
3638
|
-
}
|
|
3639
|
-
else {
|
|
3640
|
-
this.showTooltip();
|
|
3641
|
-
}
|
|
3642
|
-
}
|
|
3643
|
-
mouseenter() {
|
|
3644
|
-
this.isMouseInside.set(true);
|
|
3645
|
-
this.timer.start(this.hoverDelay());
|
|
3646
|
-
}
|
|
3647
|
-
mouseleave() {
|
|
3648
|
-
this.isMouseInside.set(false);
|
|
3649
|
-
this.timer.stop();
|
|
3650
|
-
}
|
|
3651
|
-
showTooltip() {
|
|
3652
|
-
if (this.visible())
|
|
3653
|
-
return;
|
|
3654
|
-
const tip = this.tipRef().nativeElement;
|
|
3655
|
-
tip.showPopover();
|
|
3656
|
-
this.renderer.setAttribute(tip, 'fade', 'in');
|
|
3657
|
-
this.visible.set(true);
|
|
3658
|
-
}
|
|
3659
|
-
hideTooltip() {
|
|
3660
|
-
if (!this.visible())
|
|
3661
|
-
return;
|
|
3662
|
-
this.renderer.setAttribute(this.tipRef().nativeElement, 'fade', 'out');
|
|
3663
|
-
}
|
|
3664
|
-
onAnimationEnd(event) {
|
|
3665
|
-
if (event.animationName.includes(this.tooltipFadeOut)) {
|
|
3666
|
-
this.tipRef().nativeElement.hidePopover();
|
|
3667
|
-
this.visible.set(false);
|
|
3668
|
-
}
|
|
3669
|
-
}
|
|
3670
|
-
className = computed(() => css({
|
|
3671
|
-
display: 'inline-flex',
|
|
3672
|
-
anchorName: this.anchorName,
|
|
3673
|
-
}, this.inlineText() && {
|
|
3674
|
-
cursor: 'help',
|
|
3675
|
-
textDecoration: 'underline',
|
|
3676
|
-
textDecorationStyle: 'dotted',
|
|
3677
|
-
}), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
3678
|
-
tooltipFadeIn = keyframes({ ...fadeIn });
|
|
3679
|
-
tooltipFadeOut = keyframes({ ...fadeOut });
|
|
3680
|
-
tooltipClassName = computed(() => css([
|
|
3681
|
-
{
|
|
3682
|
-
...this.theme.colorPair(this.componentOptions().color),
|
|
3683
|
-
...this.theme.radius(this.componentOptions().borderRadius),
|
|
3684
|
-
...this.theme.boxShadow(this.componentOptions().shadow),
|
|
3685
|
-
...this.theme.typeface(this.componentOptions().typeface),
|
|
3686
|
-
border: 'none',
|
|
3687
|
-
padding: 5,
|
|
3688
|
-
width: 'max-content',
|
|
3689
|
-
...anchorStyles(this.anchorName, this.placement(), { mainAxis: 6 }),
|
|
3690
|
-
'&[fade="in"]': {
|
|
3691
|
-
animation: `${this.tooltipFadeIn} ease-in 350ms`,
|
|
3692
|
-
},
|
|
3693
|
-
'&[fade="out"]': {
|
|
3694
|
-
animation: `${this.tooltipFadeOut} ease-in 350ms`,
|
|
3695
|
-
},
|
|
3696
|
-
},
|
|
3697
|
-
]), ...(ngDevMode ? [{ debugName: "tooltipClassName" }] : /* istanbul ignore next */ []));
|
|
3698
|
-
arrowClassName = computed(() => css({
|
|
3699
|
-
...this.theme.colorPair(this.componentOptions().color),
|
|
3700
|
-
...anchorArrowStyles(this.placement()),
|
|
3701
|
-
}), ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
|
|
3702
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3703
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: UniTooltipComponent, isStandalone: true, selector: "uni-tooltip", inputs: { hoverDelay: { classPropertyName: "hoverDelay", publicName: "hoverDelay", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, inlineText: { classPropertyName: "inlineText", publicName: "inlineText", isSignal: true, isRequired: false, transformFunction: null }, appendToBody: { classPropertyName: "appendToBody", publicName: "appendToBody", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "toggleTooltip()", "mouseenter": "mouseenter()", "mouseleave": "mouseleave()", "focusin": "onFocusIn($event)", "focusout": "onFocusOut()", "keydown.escape": "onEscape($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }], viewQueries: [{ propertyName: "tipRef", first: true, predicate: ["tip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `<ng-content></ng-content
|
|
3704
|
-
><span
|
|
3705
|
-
#tip
|
|
3706
|
-
popover="manual"
|
|
3707
|
-
role="tooltip"
|
|
3708
|
-
[id]="tooltipId"
|
|
3709
|
-
[class]="tooltipClassName()"
|
|
3710
|
-
(mouseenter)="isMouseInside.set(true)"
|
|
3711
|
-
(mouseleave)="isMouseInside.set(false)"
|
|
3712
|
-
(animationend)="onAnimationEnd($event)"
|
|
3713
|
-
>{{ label() }}<span [class]="arrowClassName()"></span
|
|
3714
|
-
></span>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3715
|
-
}
|
|
3716
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, decorators: [{
|
|
3717
|
-
type: Component,
|
|
3718
|
-
args: [{
|
|
3719
|
-
selector: 'uni-tooltip',
|
|
3720
|
-
imports: [],
|
|
3721
|
-
// The bubble lives declaratively in the template as a manual popover: the
|
|
3722
|
-
// top layer escapes any overflow context (which made appendToBody obsolete)
|
|
3723
|
-
// and native CSS anchor positioning keeps it attached to the host.
|
|
3724
|
-
template: `<ng-content></ng-content
|
|
3725
|
-
><span
|
|
3726
|
-
#tip
|
|
3727
|
-
popover="manual"
|
|
3728
|
-
role="tooltip"
|
|
3729
|
-
[id]="tooltipId"
|
|
3730
|
-
[class]="tooltipClassName()"
|
|
3731
|
-
(mouseenter)="isMouseInside.set(true)"
|
|
3732
|
-
(mouseleave)="isMouseInside.set(false)"
|
|
3733
|
-
(animationend)="onAnimationEnd($event)"
|
|
3734
|
-
>{{ label() }}<span [class]="arrowClassName()"></span
|
|
3735
|
-
></span>`,
|
|
3736
|
-
providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }],
|
|
3737
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3738
|
-
host: {
|
|
3739
|
-
'[class]': 'className()',
|
|
3740
|
-
'(click)': 'toggleTooltip()',
|
|
3741
|
-
'(mouseenter)': 'mouseenter()',
|
|
3742
|
-
'(mouseleave)': 'mouseleave()',
|
|
3743
|
-
'(focusin)': 'onFocusIn($event)',
|
|
3744
|
-
'(focusout)': 'onFocusOut()',
|
|
3745
|
-
'(keydown.escape)': 'onEscape($event)',
|
|
3746
|
-
},
|
|
3747
|
-
}]
|
|
3748
|
-
}], ctorParameters: () => [], propDecorators: { hoverDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverDelay", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], inlineText: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlineText", required: false }] }], appendToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendToBody", required: false }] }], tipRef: [{ type: i0.ViewChild, args: ['tip', { isSignal: true }] }] } });
|
|
3749
|
-
|
|
3750
3827
|
/**
|
|
3751
3828
|
* Trigger for a {@link UniExpandComponent} region.
|
|
3752
3829
|
*
|
|
@@ -3765,13 +3842,19 @@ class UniExpandToggleComponent {
|
|
|
3765
3842
|
label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
3766
3843
|
/** Muted qualifier beside the label ("for POs & custom orders"). Needs `label`. */
|
|
3767
3844
|
sublabel = input(...(ngDevMode ? [undefined, { debugName: "sublabel" }] : /* istanbul ignore next */ []));
|
|
3845
|
+
/**
|
|
3846
|
+
* Rotation duration in seconds. Expand Area binds the region's resolved
|
|
3847
|
+
* `duration` here so chevron and reveal share one clock even when the
|
|
3848
|
+
* region is size-scaled or overridden per instance.
|
|
3849
|
+
*/
|
|
3850
|
+
transitionSpeed = input(...(ngDevMode ? [undefined, { debugName: "transitionSpeed" }] : /* istanbul ignore next */ []));
|
|
3851
|
+
/** Fallback clock when no `transitionSpeed` is bound: the `expand` theme options' `transitionSpeed`. */
|
|
3852
|
+
expandOptions = inject(ThemeService).getComponentOptions('expand');
|
|
3853
|
+
speed = computed(() => this.transitionSpeed() ?? this.expandOptions().transitionSpeed ?? EXPAND_DEFAULT_SPEED, ...(ngDevMode ? [{ debugName: "speed" }] : /* istanbul ignore next */ []));
|
|
3768
3854
|
/**
|
|
3769
3855
|
* The glyph rotates, never the host.
|
|
3770
3856
|
*
|
|
3771
|
-
* The host is
|
|
3772
|
-
* `anchor-name` on its own element, nested inside ours — so rotating the
|
|
3773
|
-
* host swings the anchor through the turn and the bubble visibly bobs. The
|
|
3774
|
-
* host is also taller than the glyph (an inline-level box reserves baseline
|
|
3857
|
+
* The host is taller than the glyph (an inline-level box reserves baseline
|
|
3775
3858
|
* descender space), so spinning it about its own centre walks the glyph
|
|
3776
3859
|
* off-centre. `uni-icon` is a centred square sized to the glyph, which makes
|
|
3777
3860
|
* it the only box here that rotates symmetrically. It also keeps a label
|
|
@@ -3780,7 +3863,9 @@ class UniExpandToggleComponent {
|
|
|
3780
3863
|
glyphStyles = computed(() => ({
|
|
3781
3864
|
'& uni-icon': {
|
|
3782
3865
|
flexShrink: 0,
|
|
3783
|
-
...motionSafe({
|
|
3866
|
+
...motionSafe({
|
|
3867
|
+
transition: `transform ${this.speed()}s ease-in-out`,
|
|
3868
|
+
}),
|
|
3784
3869
|
transform: this.collapsed() ? 'rotate(-180deg)' : 'rotate(0)',
|
|
3785
3870
|
},
|
|
3786
3871
|
}), ...(ngDevMode ? [{ debugName: "glyphStyles" }] : /* istanbul ignore next */ []));
|
|
@@ -3817,7 +3902,7 @@ class UniExpandToggleComponent {
|
|
|
3817
3902
|
this.collapsed.update((collapsed) => !collapsed);
|
|
3818
3903
|
}
|
|
3819
3904
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3820
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniExpandToggleComponent, isStandalone: true, selector: "uni-expand-toggle", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, ariaControls: { classPropertyName: "ariaControls", publicName: "ariaControls", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, sublabel: { classPropertyName: "sublabel", publicName: "sublabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "class": "className()", "attr.toggled": "collapsed() || null" } }, ngImport: i0, template: `
|
|
3905
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniExpandToggleComponent, isStandalone: true, selector: "uni-expand-toggle", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, ariaControls: { classPropertyName: "ariaControls", publicName: "ariaControls", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, sublabel: { classPropertyName: "sublabel", publicName: "sublabel", isSignal: true, isRequired: false, transformFunction: null }, transitionSpeed: { classPropertyName: "transitionSpeed", publicName: "transitionSpeed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "class": "className()", "attr.toggled": "collapsed() || null" } }, ngImport: i0, template: `
|
|
3821
3906
|
@if (label()) {
|
|
3822
3907
|
<button
|
|
3823
3908
|
type="button"
|
|
@@ -3835,25 +3920,23 @@ class UniExpandToggleComponent {
|
|
|
3835
3920
|
</span>
|
|
3836
3921
|
</button>
|
|
3837
3922
|
} @else {
|
|
3838
|
-
<
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
</button>
|
|
3848
|
-
</uni-tooltip>
|
|
3923
|
+
<button
|
|
3924
|
+
icon-button
|
|
3925
|
+
iconName="chevronUp"
|
|
3926
|
+
(click)="toggle()"
|
|
3927
|
+
[attr.aria-expanded]="!collapsed()"
|
|
3928
|
+
[attr.aria-controls]="ariaControls() || null"
|
|
3929
|
+
>
|
|
3930
|
+
{{ collapsed() ? 'Expand' : 'Collapse' }}
|
|
3931
|
+
</button>
|
|
3849
3932
|
}
|
|
3850
|
-
`, isInline: true, dependencies: [{ kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }
|
|
3933
|
+
`, isInline: true, dependencies: [{ kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3851
3934
|
}
|
|
3852
3935
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandToggleComponent, decorators: [{
|
|
3853
3936
|
type: Component,
|
|
3854
3937
|
args: [{
|
|
3855
3938
|
selector: 'uni-expand-toggle',
|
|
3856
|
-
imports: [UniIconComponent, UniIconButtonComponent, UniTextComponent
|
|
3939
|
+
imports: [UniIconComponent, UniIconButtonComponent, UniTextComponent],
|
|
3857
3940
|
template: `
|
|
3858
3941
|
@if (label()) {
|
|
3859
3942
|
<button
|
|
@@ -3872,17 +3955,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3872
3955
|
</span>
|
|
3873
3956
|
</button>
|
|
3874
3957
|
} @else {
|
|
3875
|
-
<
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
</button>
|
|
3885
|
-
</uni-tooltip>
|
|
3958
|
+
<button
|
|
3959
|
+
icon-button
|
|
3960
|
+
iconName="chevronUp"
|
|
3961
|
+
(click)="toggle()"
|
|
3962
|
+
[attr.aria-expanded]="!collapsed()"
|
|
3963
|
+
[attr.aria-controls]="ariaControls() || null"
|
|
3964
|
+
>
|
|
3965
|
+
{{ collapsed() ? 'Expand' : 'Collapse' }}
|
|
3966
|
+
</button>
|
|
3886
3967
|
}
|
|
3887
3968
|
`,
|
|
3888
3969
|
host: {
|
|
@@ -3891,7 +3972,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3891
3972
|
},
|
|
3892
3973
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3893
3974
|
}]
|
|
3894
|
-
}], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], ariaControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaControls", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], sublabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sublabel", required: false }] }] } });
|
|
3975
|
+
}], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], ariaControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaControls", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], sublabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sublabel", required: false }] }], transitionSpeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "transitionSpeed", required: false }] }] } });
|
|
3895
3976
|
|
|
3896
3977
|
class UniExpandAreaComponent {
|
|
3897
3978
|
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
@@ -3927,6 +4008,7 @@ class UniExpandAreaComponent {
|
|
|
3927
4008
|
style="display: inline-flex; margin: -2px"
|
|
3928
4009
|
[collapsed]="initCollapsed()"
|
|
3929
4010
|
[ariaControls]="expand.regionId"
|
|
4011
|
+
[transitionSpeed]="expand.duration()"
|
|
3930
4012
|
/>
|
|
3931
4013
|
</div>
|
|
3932
4014
|
|
|
@@ -3936,7 +4018,7 @@ class UniExpandAreaComponent {
|
|
|
3936
4018
|
</div>
|
|
3937
4019
|
</uni-expand>
|
|
3938
4020
|
</div>
|
|
3939
|
-
`, isInline: true, dependencies: [{ kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniExpandToggleComponent, selector: "uni-expand-toggle", inputs: ["collapsed", "ariaControls", "label", "sublabel"], outputs: ["collapsedChange"] }, { kind: "component", type: UniExpandComponent, selector: "uni-expand", inputs: ["collapsed"], outputs: ["collapsedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4021
|
+
`, isInline: true, dependencies: [{ kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniExpandToggleComponent, selector: "uni-expand-toggle", inputs: ["collapsed", "ariaControls", "label", "sublabel", "transitionSpeed"], outputs: ["collapsedChange"] }, { kind: "component", type: UniExpandComponent, selector: "uni-expand", inputs: ["collapsed", "transitionSpeed"], outputs: ["collapsedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3940
4022
|
}
|
|
3941
4023
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandAreaComponent, decorators: [{
|
|
3942
4024
|
type: Component,
|
|
@@ -3968,6 +4050,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3968
4050
|
style="display: inline-flex; margin: -2px"
|
|
3969
4051
|
[collapsed]="initCollapsed()"
|
|
3970
4052
|
[ariaControls]="expand.regionId"
|
|
4053
|
+
[transitionSpeed]="expand.duration()"
|
|
3971
4054
|
/>
|
|
3972
4055
|
</div>
|
|
3973
4056
|
|
|
@@ -4274,32 +4357,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4274
4357
|
* This file exports all public-facing elements of the json-view component.
|
|
4275
4358
|
*/
|
|
4276
4359
|
|
|
4277
|
-
|
|
4360
|
+
const isDivider = (item) => 'divider' in item;
|
|
4361
|
+
|
|
4362
|
+
class UniMenuItemComponent {
|
|
4363
|
+
theme = inject(ThemeService);
|
|
4278
4364
|
_elementRef = inject(ElementRef);
|
|
4279
|
-
display = input('flex', ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
|
|
4280
|
-
flexDirection = input('row', ...(ngDevMode ? [{ debugName: "flexDirection" }] : /* istanbul ignore next */ []));
|
|
4281
|
-
alignItems = input('center', ...(ngDevMode ? [{ debugName: "alignItems" }] : /* istanbul ignore next */ []));
|
|
4282
|
-
paddingHorizontal = input('md', ...(ngDevMode ? [{ debugName: "paddingHorizontal" }] : /* istanbul ignore next */ []));
|
|
4283
|
-
gap = input('md', ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
|
|
4284
4365
|
label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4285
4366
|
template = input(...(ngDevMode ? [undefined, { debugName: "template" }] : /* istanbul ignore next */ []));
|
|
4286
4367
|
context = input(...(ngDevMode ? [undefined, { debugName: "context" }] : /* istanbul ignore next */ []));
|
|
4287
4368
|
symbolName = input(...(ngDevMode ? [undefined, { debugName: "symbolName" }] : /* istanbul ignore next */ []));
|
|
4288
4369
|
active = input(...(ngDevMode ? [undefined, { debugName: "active" }] : /* istanbul ignore next */ []));
|
|
4289
|
-
|
|
4370
|
+
/** Tone routed through the theme's `menuItem` variants (e.g. 'warn'). */
|
|
4371
|
+
variant = input(...(ngDevMode ? [undefined, { debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
4372
|
+
/** Per-instance override of the theme's `menuItem.hoverColor`. */
|
|
4373
|
+
hoverColor = input(...(ngDevMode ? [undefined, { debugName: "hoverColor" }] : /* istanbul ignore next */ []));
|
|
4290
4374
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4375
|
+
options = computed(() => this.theme.getComponentOptions('menuItem')(), ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
4376
|
+
activeSymbol = computed(() => this.options().activeSymbol, ...(ngDevMode ? [{ debugName: "activeSymbol" }] : /* istanbul ignore next */ []));
|
|
4377
|
+
/** Typography comes from the host's themed typeface; the span only lays out. */
|
|
4378
|
+
LabelClassName = css({ display: 'block', whiteSpace: 'nowrap' });
|
|
4379
|
+
menuItemClassName = computed(() => {
|
|
4380
|
+
const options = this.options();
|
|
4381
|
+
const variant = this.variant();
|
|
4382
|
+
const variantStyle = variant
|
|
4383
|
+
? this.theme.component('menuItem')().variants?.[variant]
|
|
4384
|
+
: undefined;
|
|
4385
|
+
const transitionSpeed = options.transitionSpeed ?? 0;
|
|
4386
|
+
return css([
|
|
4387
|
+
{
|
|
4388
|
+
display: 'flex',
|
|
4389
|
+
flexDirection: 'row',
|
|
4390
|
+
alignItems: 'center',
|
|
4391
|
+
boxSizing: 'border-box',
|
|
4392
|
+
cursor: 'pointer',
|
|
4393
|
+
height: options.height,
|
|
4394
|
+
...this.theme.horizontalPadding(options.paddingHorizontal),
|
|
4395
|
+
...this.theme.gap(options.gap),
|
|
4396
|
+
...this.theme.radius(options.borderRadius),
|
|
4397
|
+
...this.theme.typeface(options.typeface),
|
|
4398
|
+
...this.theme.color(options.textColor),
|
|
4399
|
+
// Roving focus highlights items the same way hover does — but only
|
|
4400
|
+
// when focus is keyboard-driven. `onOpened()` focuses an item on every
|
|
4401
|
+
// open, including pointer opens, and plain `:focus` would paint that
|
|
4402
|
+
// as a highlight the mouse user never asked for, reading as a
|
|
4403
|
+
// preselected item. `:focus-visible` excludes programmatic focus that
|
|
4404
|
+
// follows a click while still matching keyboard navigation.
|
|
4405
|
+
[HOVER_OR_KEYBOARD_FOCUS]: {
|
|
4406
|
+
...this.theme.colorPair(this.hoverColor() ?? options.hoverColor),
|
|
4407
|
+
outline: 'none',
|
|
4408
|
+
},
|
|
4409
|
+
'&[aria-disabled="true"]': {
|
|
4410
|
+
color: this.theme.colors()['on-disabled-surface'],
|
|
4411
|
+
pointerEvents: 'none',
|
|
4412
|
+
},
|
|
4300
4413
|
},
|
|
4301
|
-
|
|
4302
|
-
|
|
4414
|
+
transitionSpeed > 0 && { transition: `all ${transitionSpeed}s ease` },
|
|
4415
|
+
// Variant tones override the base look. A variant that restyles the
|
|
4416
|
+
// highlight must key it with HOVER_OR_KEYBOARD_FOCUS — Emotion merges by
|
|
4417
|
+
// exact selector text, so a variant spelling it `&:hover, &:focus`
|
|
4418
|
+
// would both miss the override and reintroduce the phantom highlight.
|
|
4419
|
+
{ ...variantStyle },
|
|
4420
|
+
]);
|
|
4421
|
+
}, ...(ngDevMode ? [{ debugName: "menuItemClassName" }] : /* istanbul ignore next */ []));
|
|
4303
4422
|
/** Host element, used by Menu for roving-focus bookkeeping. */
|
|
4304
4423
|
get host() {
|
|
4305
4424
|
return this._elementRef.nativeElement;
|
|
@@ -4307,21 +4426,22 @@ class UniMenuItemComponent extends UniBoxComponent {
|
|
|
4307
4426
|
focus() {
|
|
4308
4427
|
this._elementRef.nativeElement.focus();
|
|
4309
4428
|
}
|
|
4310
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuItemComponent, deps:
|
|
4311
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMenuItemComponent, isStandalone: true, selector: "[uni-menu-item], [menu-item]", inputs: {
|
|
4429
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4430
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMenuItemComponent, isStandalone: true, selector: "[uni-menu-item], [menu-item]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, symbolName: { classPropertyName: "symbolName", publicName: "symbolName", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, hoverColor: { classPropertyName: "hoverColor", publicName: "hoverColor", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem", "tabindex": "-1" }, properties: { "attr.aria-disabled": "disabled() ? 'true' : null", "attr.aria-current": "active() ? 'true' : null", "class": "menuItemClassName()" } }, ngImport: i0, template: "@let tpl = template();\n@let symbol = symbolName();\n@if (symbol) {\n <uni-symbol [name]=\"symbol\"></uni-symbol>\n}\n<div box-layout [grow]=\"1\">\n @if (tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"context()\"></ng-container>\n } @else {\n <span [class]=\"LabelClassName\">{{ label() }}</span>\n }\n</div>\n@let activeSym = activeSymbol();\n@if (active() && activeSym) {\n <uni-symbol [name]=\"activeSym\"></uni-symbol>\n}\n", dependencies: [{ kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4312
4431
|
}
|
|
4313
4432
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuItemComponent, decorators: [{
|
|
4314
4433
|
type: Component,
|
|
4315
|
-
args: [{ selector: '[uni-menu-item], [menu-item]', imports: [
|
|
4434
|
+
args: [{ selector: '[uni-menu-item], [menu-item]', imports: [UniSymbolComponent, UniBoxComponent, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
4316
4435
|
role: 'menuitem',
|
|
4317
4436
|
tabindex: '-1',
|
|
4318
4437
|
'[attr.aria-disabled]': "disabled() ? 'true' : null",
|
|
4319
4438
|
'[attr.aria-current]': "active() ? 'true' : null",
|
|
4320
4439
|
'[class]': 'menuItemClassName()',
|
|
4321
|
-
}, template: "@let tpl = template();\n@let symbol = symbolName();\n@if (symbol) {\n <uni-symbol [name]=\"symbol\"></uni-symbol>\n}\n<div box-layout [grow]=\"1\">\n @if (tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"context()\"></ng-container>\n } @else {\n <span
|
|
4322
|
-
}], propDecorators: {
|
|
4440
|
+
}, template: "@let tpl = template();\n@let symbol = symbolName();\n@if (symbol) {\n <uni-symbol [name]=\"symbol\"></uni-symbol>\n}\n<div box-layout [grow]=\"1\">\n @if (tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"context()\"></ng-container>\n } @else {\n <span [class]=\"LabelClassName\">{{ label() }}</span>\n }\n</div>\n@let activeSym = activeSymbol();\n@if (active() && activeSym) {\n <uni-symbol [name]=\"activeSym\"></uni-symbol>\n}\n" }]
|
|
4441
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], symbolName: [{ type: i0.Input, args: [{ isSignal: true, alias: "symbolName", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], hoverColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverColor", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
4323
4442
|
|
|
4324
4443
|
class UniMenuComponent {
|
|
4444
|
+
theme = inject(ThemeService);
|
|
4325
4445
|
// Modern Signal Inputs for perfect Zoneless tracking
|
|
4326
4446
|
menuItems = input.required(...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
|
|
4327
4447
|
activeItem = input(...(ngDevMode ? [undefined, { debugName: "activeItem" }] : /* istanbul ignore next */ []));
|
|
@@ -4329,6 +4449,21 @@ class UniMenuComponent {
|
|
|
4329
4449
|
// Modern Signal Output
|
|
4330
4450
|
menuItemClicked = output();
|
|
4331
4451
|
TriggerClassName = css({ display: 'inline-block' });
|
|
4452
|
+
/** Type guard for the template: dividers render as separators, not items. */
|
|
4453
|
+
isDivider = isDivider;
|
|
4454
|
+
menuOptions = computed(() => this.theme.getComponentOptions('menu')(), ...(ngDevMode ? [{ debugName: "menuOptions" }] : /* istanbul ignore next */ []));
|
|
4455
|
+
menuClassName = computed(() => css({ minWidth: this.menuOptions().minWidth }), ...(ngDevMode ? [{ debugName: "menuClassName" }] : /* istanbul ignore next */ []));
|
|
4456
|
+
dividerClassName = computed(() => {
|
|
4457
|
+
const { dividerBorder, dividerSpacing } = this.menuOptions();
|
|
4458
|
+
// Read spacing directly: getSpacing('none') yields the string 'none',
|
|
4459
|
+
// which is invalid as a margin and must resolve to 0 instead.
|
|
4460
|
+
const spacing = this.theme.spacing()[dividerSpacing ?? 'none'];
|
|
4461
|
+
return css({
|
|
4462
|
+
...this.theme.borderTop(dividerBorder),
|
|
4463
|
+
marginBlock: spacing,
|
|
4464
|
+
marginInline: spacing,
|
|
4465
|
+
});
|
|
4466
|
+
}, ...(ngDevMode ? [{ debugName: "dividerClassName" }] : /* istanbul ignore next */ []));
|
|
4332
4467
|
itemComponents = viewChildren(UniMenuItemComponent, ...(ngDevMode ? [{ debugName: "itemComponents" }] : /* istanbul ignore next */ []));
|
|
4333
4468
|
dropdownComponent = viewChild(UniDropdownComponent, ...(ngDevMode ? [{ debugName: "dropdownComponent" }] : /* istanbul ignore next */ []));
|
|
4334
4469
|
/** Which item receives focus when the menu opens (ArrowUp opens onto the last item). */
|
|
@@ -4340,7 +4475,7 @@ class UniMenuComponent {
|
|
|
4340
4475
|
});
|
|
4341
4476
|
}, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
4342
4477
|
handleMenuItemClick(item, dropdown) {
|
|
4343
|
-
if (item.action) {
|
|
4478
|
+
if (!isDivider(item) && item.action) {
|
|
4344
4479
|
item.action();
|
|
4345
4480
|
}
|
|
4346
4481
|
this.menuItemClicked.emit(item);
|
|
@@ -4437,7 +4572,12 @@ class UniMenuComponent {
|
|
|
4437
4572
|
[trigger]="trigger"
|
|
4438
4573
|
[placement]="placement()"
|
|
4439
4574
|
ariaHasPopup="menu"
|
|
4440
|
-
|
|
4575
|
+
[color]="menuOptions().color"
|
|
4576
|
+
[border]="menuOptions().border"
|
|
4577
|
+
[borderRadius]="menuOptions().borderRadius"
|
|
4578
|
+
[shadow]="menuOptions().shadow"
|
|
4579
|
+
[paddingVertical]="menuOptions().paddingVertical"
|
|
4580
|
+
[paddingHorizontal]="menuOptions().paddingHorizontal"
|
|
4441
4581
|
#dropdown
|
|
4442
4582
|
(dropdownShowing)="onOpened()"
|
|
4443
4583
|
>
|
|
@@ -4445,23 +4585,29 @@ class UniMenuComponent {
|
|
|
4445
4585
|
the focused item (menu-item hosts carry tabindex="-1"), and
|
|
4446
4586
|
Enter/Space activation is dispatched from onMenuKeydown. -->
|
|
4447
4587
|
<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
|
|
4448
|
-
<div role="menu" (keydown)="onMenuKeydown($event, dropdown)">
|
|
4588
|
+
<div role="menu" [class]="menuClassName()" (keydown)="onMenuKeydown($event, dropdown)">
|
|
4449
4589
|
@for (item of menuItems(); track item) {
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4590
|
+
@if (isDivider(item)) {
|
|
4591
|
+
<div role="separator" [class]="dividerClassName()"></div>
|
|
4592
|
+
} @else {
|
|
4593
|
+
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->
|
|
4594
|
+
<div
|
|
4595
|
+
menu-item
|
|
4596
|
+
[label]="item.label"
|
|
4597
|
+
[symbolName]="item.symbolName"
|
|
4598
|
+
[active]="activeItem() === item"
|
|
4599
|
+
[template]="item.template"
|
|
4600
|
+
[context]="item.context"
|
|
4601
|
+
[variant]="item.variant"
|
|
4602
|
+
[disabled]="item.disabled ?? false"
|
|
4603
|
+
(click)="handleMenuItemClick(item, dropdown)"
|
|
4604
|
+
></div>
|
|
4605
|
+
}
|
|
4460
4606
|
}
|
|
4461
4607
|
</div>
|
|
4462
4608
|
</uni-dropdown>
|
|
4463
4609
|
}
|
|
4464
|
-
`, isInline: true, dependencies: [{ kind: "component", type: UniMenuItemComponent, selector: "[uni-menu-item], [menu-item]", inputs: ["
|
|
4610
|
+
`, isInline: true, dependencies: [{ kind: "component", type: UniMenuItemComponent, selector: "[uni-menu-item], [menu-item]", inputs: ["label", "template", "context", "symbolName", "active", "variant", "hoverColor", "disabled"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "color"], outputs: ["dropdownShowing", "dropdownHiding"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4465
4611
|
}
|
|
4466
4612
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuComponent, decorators: [{
|
|
4467
4613
|
type: Component,
|
|
@@ -4482,7 +4628,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4482
4628
|
[trigger]="trigger"
|
|
4483
4629
|
[placement]="placement()"
|
|
4484
4630
|
ariaHasPopup="menu"
|
|
4485
|
-
|
|
4631
|
+
[color]="menuOptions().color"
|
|
4632
|
+
[border]="menuOptions().border"
|
|
4633
|
+
[borderRadius]="menuOptions().borderRadius"
|
|
4634
|
+
[shadow]="menuOptions().shadow"
|
|
4635
|
+
[paddingVertical]="menuOptions().paddingVertical"
|
|
4636
|
+
[paddingHorizontal]="menuOptions().paddingHorizontal"
|
|
4486
4637
|
#dropdown
|
|
4487
4638
|
(dropdownShowing)="onOpened()"
|
|
4488
4639
|
>
|
|
@@ -4490,18 +4641,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4490
4641
|
the focused item (menu-item hosts carry tabindex="-1"), and
|
|
4491
4642
|
Enter/Space activation is dispatched from onMenuKeydown. -->
|
|
4492
4643
|
<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
|
|
4493
|
-
<div role="menu" (keydown)="onMenuKeydown($event, dropdown)">
|
|
4644
|
+
<div role="menu" [class]="menuClassName()" (keydown)="onMenuKeydown($event, dropdown)">
|
|
4494
4645
|
@for (item of menuItems(); track item) {
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4646
|
+
@if (isDivider(item)) {
|
|
4647
|
+
<div role="separator" [class]="dividerClassName()"></div>
|
|
4648
|
+
} @else {
|
|
4649
|
+
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->
|
|
4650
|
+
<div
|
|
4651
|
+
menu-item
|
|
4652
|
+
[label]="item.label"
|
|
4653
|
+
[symbolName]="item.symbolName"
|
|
4654
|
+
[active]="activeItem() === item"
|
|
4655
|
+
[template]="item.template"
|
|
4656
|
+
[context]="item.context"
|
|
4657
|
+
[variant]="item.variant"
|
|
4658
|
+
[disabled]="item.disabled ?? false"
|
|
4659
|
+
(click)="handleMenuItemClick(item, dropdown)"
|
|
4660
|
+
></div>
|
|
4661
|
+
}
|
|
4505
4662
|
}
|
|
4506
4663
|
</div>
|
|
4507
4664
|
</uni-dropdown>
|
|
@@ -4578,11 +4735,38 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
|
|
|
4578
4735
|
// --- CONFIGURATION ---
|
|
4579
4736
|
options = input.required(...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
4580
4737
|
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
4738
|
+
/**
|
|
4739
|
+
* What the field is for, e.g. "Fruits". Rendered visually hidden inside the
|
|
4740
|
+
* trigger so its accessible name reads "Fruits, Apple, Pear" — without it a
|
|
4741
|
+
* screen reader announces only the current selection, with no clue which
|
|
4742
|
+
* field it belongs to.
|
|
4743
|
+
*/
|
|
4744
|
+
label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4745
|
+
/** Debounce for the filter box, matching the library's input debounce. */
|
|
4746
|
+
debounceTime = input(200, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ []));
|
|
4747
|
+
srOnly = css(visuallyHidden);
|
|
4581
4748
|
query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
4749
|
+
queryTimer;
|
|
4582
4750
|
filteredOptions = computed(() => {
|
|
4583
4751
|
const filterText = this.query().toLowerCase();
|
|
4584
4752
|
return this.options().filter((opt) => opt.label.toLowerCase().includes(filterText));
|
|
4585
4753
|
}, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
|
|
4754
|
+
optionRefs = viewChildren('optionRow', ...(ngDevMode ? [{ debugName: "optionRefs" }] : /* istanbul ignore next */ []));
|
|
4755
|
+
/**
|
|
4756
|
+
* Roving focus over the options. The shared helper owns the index
|
|
4757
|
+
* arithmetic — wrapping, Home/End, and never pointing past a list the
|
|
4758
|
+
* filter has narrowed — the same contract `uni-search-input` and
|
|
4759
|
+
* `uni-tag-input` use, so the keys behave identically across all three.
|
|
4760
|
+
*/
|
|
4761
|
+
list = createListboxNavigation({
|
|
4762
|
+
count: () => this.filteredOptions().length,
|
|
4763
|
+
idPrefix: 'uni-multi-select',
|
|
4764
|
+
});
|
|
4765
|
+
/** Announced with the selection so the count is not left to guesswork. */
|
|
4766
|
+
selectionSummary = computed(() => {
|
|
4767
|
+
const count = this.value().length;
|
|
4768
|
+
return count === 0 ? 'none selected' : `${count} selected`;
|
|
4769
|
+
}, ...(ngDevMode ? [{ debugName: "selectionSummary" }] : /* istanbul ignore next */ []));
|
|
4586
4770
|
// Derived display string
|
|
4587
4771
|
selectedLabelsText = computed(() => {
|
|
4588
4772
|
const selections = this.value();
|
|
@@ -4622,7 +4806,26 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
|
|
|
4622
4806
|
},
|
|
4623
4807
|
}), ...(ngDevMode ? [{ debugName: "searchInputClass" }] : /* istanbul ignore next */ []));
|
|
4624
4808
|
handleQueryInput(event) {
|
|
4625
|
-
|
|
4809
|
+
const text = event.target.value;
|
|
4810
|
+
// Debounced so a long option list is not re-filtered on every keystroke.
|
|
4811
|
+
clearTimeout(this.queryTimer);
|
|
4812
|
+
this.queryTimer = setTimeout(() => this.query.set(text), this.debounceTime());
|
|
4813
|
+
}
|
|
4814
|
+
/**
|
|
4815
|
+
* Arrow keys walk the options from anywhere in the panel, including the
|
|
4816
|
+
* filter box — previously the only way in was to Tab through every
|
|
4817
|
+
* checkbox.
|
|
4818
|
+
*/
|
|
4819
|
+
onPanelKeydown(event) {
|
|
4820
|
+
if (!this.list.navigate(event))
|
|
4821
|
+
return;
|
|
4822
|
+
const row = this.optionRefs()[this.list.activeIndex()]?.nativeElement;
|
|
4823
|
+
row?.querySelector('input, [tabindex]')?.focus();
|
|
4824
|
+
}
|
|
4825
|
+
/** Keeps the active index in step when focus lands on a row by pointer. */
|
|
4826
|
+
onOptionFocus(index) {
|
|
4827
|
+
this.list.show();
|
|
4828
|
+
this.list.setActive(index);
|
|
4626
4829
|
}
|
|
4627
4830
|
selectAll() {
|
|
4628
4831
|
if (this.disabled())
|
|
@@ -4657,7 +4860,7 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
|
|
|
4657
4860
|
});
|
|
4658
4861
|
}
|
|
4659
4862
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4660
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div
|
|
4863
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], viewQueries: [{ propertyName: "optionRefs", predicate: ["optionRow"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled()\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "color"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniStackComponent, selector: "[uni-stack-layout], [stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniRowComponent, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4661
4864
|
}
|
|
4662
4865
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, decorators: [{
|
|
4663
4866
|
type: Component,
|
|
@@ -4672,8 +4875,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4672
4875
|
UniSymbolComponent,
|
|
4673
4876
|
UniRowComponent,
|
|
4674
4877
|
UniInputBoxComponent,
|
|
4675
|
-
], providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div
|
|
4676
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
|
|
4878
|
+
], providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled()\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n" }]
|
|
4879
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], optionRefs: [{ type: i0.ViewChildren, args: ['optionRow', { isSignal: true }] }] } });
|
|
4677
4880
|
|
|
4678
4881
|
class UniNotificationBadgeComponent extends BaseComponent {
|
|
4679
4882
|
count = input(undefined, ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
@@ -5438,71 +5641,59 @@ class UniSearchInputComponent extends BaseComponent {
|
|
|
5438
5641
|
search = output();
|
|
5439
5642
|
suggestionSelected = output();
|
|
5440
5643
|
field = viewChild.required(UniDebounceInputComponent);
|
|
5441
|
-
listboxId = uniqueId('uni-search-listbox');
|
|
5442
|
-
listOpen = signal(false, ...(ngDevMode ? [{ debugName: "listOpen" }] : /* istanbul ignore next */ []));
|
|
5443
|
-
activeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
5444
5644
|
visibleSuggestions = computed(() => this.suggestions().slice(0, this.componentOptions().maxSuggestions ?? 8), ...(ngDevMode ? [{ debugName: "visibleSuggestions" }] : /* istanbul ignore next */ []));
|
|
5445
|
-
|
|
5446
|
-
|
|
5645
|
+
/** Shared combobox bookkeeping: open state, active option, ARIA ids. */
|
|
5646
|
+
list = createListboxNavigation({
|
|
5647
|
+
count: () => this.visibleSuggestions().length,
|
|
5648
|
+
idPrefix: 'uni-search-listbox',
|
|
5649
|
+
});
|
|
5650
|
+
listboxId = this.list.listboxId;
|
|
5447
5651
|
hasQuery = computed(() => !!this.field()?.value(), ...(ngDevMode ? [{ debugName: "hasQuery" }] : /* istanbul ignore next */ []));
|
|
5448
5652
|
handleChange(value) {
|
|
5449
|
-
this.
|
|
5450
|
-
this.
|
|
5653
|
+
this.list.show();
|
|
5654
|
+
this.list.setActive(-1);
|
|
5451
5655
|
this.change.emit(value);
|
|
5452
5656
|
}
|
|
5453
5657
|
submit() {
|
|
5454
|
-
const active = this.activeIndex();
|
|
5455
|
-
if (this.
|
|
5658
|
+
const active = this.list.activeIndex();
|
|
5659
|
+
if (this.list.open() && active >= 0) {
|
|
5456
5660
|
this.select(this.visibleSuggestions()[active]);
|
|
5457
5661
|
return;
|
|
5458
5662
|
}
|
|
5459
|
-
this.
|
|
5663
|
+
this.list.hide();
|
|
5460
5664
|
this.search.emit(this.field().value() ?? '');
|
|
5461
5665
|
}
|
|
5462
5666
|
select(suggestion) {
|
|
5463
5667
|
this.field().value.set(suggestion);
|
|
5464
|
-
this.
|
|
5465
|
-
this.activeIndex.set(-1);
|
|
5668
|
+
this.list.hide();
|
|
5466
5669
|
this.suggestionSelected.emit(suggestion);
|
|
5467
5670
|
this.search.emit(suggestion);
|
|
5468
5671
|
}
|
|
5469
5672
|
clear() {
|
|
5470
5673
|
this.field().clear();
|
|
5471
|
-
this.
|
|
5472
|
-
this.activeIndex.set(-1);
|
|
5674
|
+
this.list.hide();
|
|
5473
5675
|
this.field().focus();
|
|
5474
5676
|
}
|
|
5475
5677
|
onKeydown(event) {
|
|
5678
|
+
// Arrows and Home/End belong to the shared listbox contract.
|
|
5679
|
+
if (this.list.navigate(event))
|
|
5680
|
+
return;
|
|
5476
5681
|
switch (event.key) {
|
|
5477
5682
|
case 'Enter':
|
|
5478
5683
|
event.preventDefault();
|
|
5479
5684
|
this.submit();
|
|
5480
5685
|
break;
|
|
5481
|
-
case 'ArrowDown':
|
|
5482
|
-
case 'ArrowUp': {
|
|
5483
|
-
const count = this.visibleSuggestions().length;
|
|
5484
|
-
if (count === 0)
|
|
5485
|
-
return;
|
|
5486
|
-
event.preventDefault();
|
|
5487
|
-
this.listOpen.set(true);
|
|
5488
|
-
const delta = event.key === 'ArrowDown' ? 1 : -1;
|
|
5489
|
-
this.activeIndex.set((this.activeIndex() + delta + count) % count);
|
|
5490
|
-
break;
|
|
5491
|
-
}
|
|
5492
5686
|
case 'Escape':
|
|
5493
|
-
|
|
5494
|
-
|
|
5687
|
+
// Escape backs out one layer at a time: close the list, then clear.
|
|
5688
|
+
if (this.list.open())
|
|
5689
|
+
this.list.hide();
|
|
5495
5690
|
else if (this.hasQuery())
|
|
5496
5691
|
this.clear();
|
|
5497
5692
|
break;
|
|
5498
5693
|
}
|
|
5499
5694
|
}
|
|
5500
5695
|
onFocusOut(event) {
|
|
5501
|
-
|
|
5502
|
-
const next = event.relatedTarget;
|
|
5503
|
-
if (!next || !event.currentTarget.contains(next)) {
|
|
5504
|
-
this.listOpen.set(false);
|
|
5505
|
-
}
|
|
5696
|
+
this.list.closeOnFocusOut(event);
|
|
5506
5697
|
}
|
|
5507
5698
|
className = computed(() => css({
|
|
5508
5699
|
display: 'block',
|
|
@@ -5544,11 +5735,11 @@ class UniSearchInputComponent extends BaseComponent {
|
|
|
5544
5735
|
});
|
|
5545
5736
|
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
5546
5737
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSearchInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
5547
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSearchInputComponent, isStandalone: true, selector: "uni-search-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change", search: "search", suggestionSelected: "suggestionSelected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], viewQueries: [{ propertyName: "field", first: true, predicate: UniDebounceInputComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"
|
|
5738
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSearchInputComponent, isStandalone: true, selector: "uni-search-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change", search: "search", suggestionSelected: "suggestionSelected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], viewQueries: [{ propertyName: "field", first: true, predicate: UniDebounceInputComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul [id]=\"listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniDebounceInputComponent, selector: "uni-debounce-input", inputs: ["inputName", "inputId", "debounceTime", "label", "placeholder", "disabled", "role", "ariaExpanded", "ariaControls", "ariaActivedescendant"], outputs: ["change"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5548
5739
|
}
|
|
5549
5740
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSearchInputComponent, decorators: [{
|
|
5550
5741
|
type: Component,
|
|
5551
|
-
args: [{ selector: 'uni-search-input', imports: [UniDebounceInputComponent, UniIconButtonComponent, UniSymbolComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], host: { '[class]': 'className()' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"
|
|
5742
|
+
args: [{ selector: 'uni-search-input', imports: [UniDebounceInputComponent, UniIconButtonComponent, UniSymbolComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], host: { '[class]': 'className()' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul [id]=\"listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n" }]
|
|
5552
5743
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], change: [{ type: i0.Output, args: ["change"] }], search: [{ type: i0.Output, args: ["search"] }], suggestionSelected: [{ type: i0.Output, args: ["suggestionSelected"] }], field: [{ type: i0.ViewChild, args: [i0.forwardRef(() => UniDebounceInputComponent), { isSignal: true }] }] } });
|
|
5553
5744
|
|
|
5554
5745
|
/**
|
|
@@ -5618,11 +5809,11 @@ class UniSelectComponent {
|
|
|
5618
5809
|
pointerEvents: 'none' /* Crucial for clicking through */,
|
|
5619
5810
|
});
|
|
5620
5811
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5621
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <option [value]=\"i\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5812
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option [value]=\"i\" [selected]=\"currentSelectedIndex() === i.toString()\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5622
5813
|
}
|
|
5623
5814
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, decorators: [{
|
|
5624
5815
|
type: Component,
|
|
5625
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-select', imports: [UniInputBoxComponent, UniSymbolComponent], template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <option [value]=\"i\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n" }]
|
|
5816
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-select', imports: [UniInputBoxComponent, UniSymbolComponent], template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option [value]=\"i\" [selected]=\"currentSelectedIndex() === i.toString()\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n" }]
|
|
5626
5817
|
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
5627
5818
|
|
|
5628
5819
|
/**
|
|
@@ -6279,24 +6470,180 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
6279
6470
|
* This file exports all public-facing elements of the sort-header component.
|
|
6280
6471
|
*/
|
|
6281
6472
|
|
|
6282
|
-
|
|
6473
|
+
/**
|
|
6474
|
+
* Compact chip for categories, states, filters and tokens.
|
|
6475
|
+
*
|
|
6476
|
+
* Two orthogonal style axes: `variant` picks the colour role and `tone` picks
|
|
6477
|
+
* the archetype (soft / solid / outline). Both live in the theme's `tag` entry,
|
|
6478
|
+
* so a theme restyles every chip in the app without touching markup.
|
|
6479
|
+
*
|
|
6480
|
+
* Structurally a chip is **body + trailing action as siblings**, never nested
|
|
6481
|
+
* buttons: an interactive chip whose body is a `<button>` cannot contain the
|
|
6482
|
+
* remove `<button>` (invalid HTML, and the inner control becomes unreachable
|
|
6483
|
+
* for keyboard users). Both stay independently operable.
|
|
6484
|
+
*/
|
|
6485
|
+
class UniTagComponent extends BaseComponent {
|
|
6486
|
+
// Presentation. `variant` comes from BaseComponent; chips default to `md`
|
|
6487
|
+
// rather than the library-wide `lg`, since they sit inside dense content.
|
|
6488
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
6489
|
+
tone = input('soft', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
6283
6490
|
label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6284
6491
|
value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
}
|
|
6293
|
-
|
|
6294
|
-
|
|
6492
|
+
/** Truncation budget, e.g. `'14ch'`. A number is treated as px. */
|
|
6493
|
+
maxWidth = input(...(ngDevMode ? [undefined, { debugName: "maxWidth" }] : /* istanbul ignore next */ []));
|
|
6494
|
+
// Lead convenience inputs. Anything richer goes in the `[tag-lead]` slot.
|
|
6495
|
+
avatarSrc = input(...(ngDevMode ? [undefined, { debugName: "avatarSrc" }] : /* istanbul ignore next */ []));
|
|
6496
|
+
/** Initials fallback when `avatarSrc` is absent or fails to load. */
|
|
6497
|
+
avatarName = input(...(ngDevMode ? [undefined, { debugName: "avatarName" }] : /* istanbul ignore next */ []));
|
|
6498
|
+
/** Theme icon primitive — the preferred glyph path. */
|
|
6499
|
+
iconName = input(...(ngDevMode ? [undefined, { debugName: "iconName" }] : /* istanbul ignore next */ []));
|
|
6500
|
+
/** Material Symbols ligature, for glyphs the theme's icon set doesn't carry. */
|
|
6501
|
+
symbolName = input(...(ngDevMode ? [undefined, { debugName: "symbolName" }] : /* istanbul ignore next */ []));
|
|
6502
|
+
/** Status dot in the current colour. */
|
|
6503
|
+
dot = input(false, ...(ngDevMode ? [{ debugName: "dot" }] : /* istanbul ignore next */ []));
|
|
6504
|
+
// Behaviour
|
|
6505
|
+
removable = input(false, ...(ngDevMode ? [{ debugName: "removable" }] : /* istanbul ignore next */ []));
|
|
6506
|
+
interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
6507
|
+
/**
|
|
6508
|
+
* Toggle state. Left undefined the chip carries no `aria-pressed` at all —
|
|
6509
|
+
* an interactive chip is not always a toggle (inside a tag input it is
|
|
6510
|
+
* focusable so it can be removed), and announcing "not pressed" on a
|
|
6511
|
+
* recipient chip is worse than announcing nothing.
|
|
6512
|
+
*/
|
|
6513
|
+
selected = input(undefined, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
6514
|
+
invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
6515
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
6516
|
+
/** Accessible-name override for the remove button. */
|
|
6517
|
+
removeLabel = input(...(ngDevMode ? [undefined, { debugName: "removeLabel" }] : /* istanbul ignore next */ []));
|
|
6518
|
+
/**
|
|
6519
|
+
* Tab position of the chip's controls. A composite that owns its own
|
|
6520
|
+
* roving focus — `uni-tag-input`, where the whole field is one tab stop —
|
|
6521
|
+
* passes `-1` so Tab does not walk through every chip to reach the next
|
|
6522
|
+
* control.
|
|
6523
|
+
*/
|
|
6524
|
+
controlTabIndex = input(0, ...(ngDevMode ? [{ debugName: "controlTabIndex" }] : /* istanbul ignore next */ []));
|
|
6525
|
+
removed = output();
|
|
6526
|
+
activated = output();
|
|
6527
|
+
/** A disabled chip wears the theme's `disabled` role, whatever its variant. */
|
|
6528
|
+
resolvedVariant = computed(() => this.disabled() ? 'disabled' : this.variant(), ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
|
|
6529
|
+
themeStyle = computed(() => this.theme.componentStyle('tag', this.resolvedVariant(), this.size())(), ...(ngDevMode ? [{ debugName: "themeStyle" }] : /* istanbul ignore next */ []));
|
|
6530
|
+
/** Lead elements derive from the chip height, so no second size token. */
|
|
6531
|
+
leadSize = computed(() => {
|
|
6532
|
+
const height = Number(this.themeStyle()['height'] ?? 24);
|
|
6533
|
+
return Number.isFinite(height) ? Math.max(height - 6, 0) : 18;
|
|
6534
|
+
}, ...(ngDevMode ? [{ debugName: "leadSize" }] : /* istanbul ignore next */ []));
|
|
6535
|
+
/** Remove glyph, proportional to the chip rather than to the icon-button. */
|
|
6536
|
+
removeGlyphSize = computed(() => Math.max(Math.round(this.leadSize() * 0.8), 10), ...(ngDevMode ? [{ debugName: "removeGlyphSize" }] : /* istanbul ignore next */ []));
|
|
6537
|
+
initials = computed(() => (this.avatarName() ?? '')
|
|
6538
|
+
.split(/\s+/)
|
|
6539
|
+
.filter(Boolean)
|
|
6540
|
+
.slice(0, 2)
|
|
6541
|
+
.map((part) => part[0]?.toUpperCase() ?? '')
|
|
6542
|
+
.join(''), ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
|
|
6543
|
+
hostClass = computed(() => {
|
|
6544
|
+
const options = this.componentOptions();
|
|
6545
|
+
return `${css([
|
|
6546
|
+
this.theme.radius(options.borderRadius),
|
|
6547
|
+
{ ...this.theme.typeface(options.typeface) },
|
|
6548
|
+
// Theme `fixed` + variant (incl. its nested `&.tone-*` rules) + size.
|
|
6549
|
+
{ ...this.themeStyle() },
|
|
6550
|
+
{ ...this.theme.gap(options.gap) },
|
|
6551
|
+
{
|
|
6552
|
+
boxSizing: 'border-box',
|
|
6553
|
+
maxWidth: this.maxWidth(),
|
|
6554
|
+
// The chip is not a widget; only its sub-controls take focus.
|
|
6555
|
+
'& > button': this.theme.focusRing(),
|
|
6556
|
+
// The remove control is the trailing counterpart of the lead, so it
|
|
6557
|
+
// sizes from the chip too. Left at the icon-button's own `sm` size it
|
|
6558
|
+
// is 22px inside a 24px chip — and taller than an `sm` chip entirely.
|
|
6559
|
+
'& > button[uni-icon-button]': {
|
|
6560
|
+
flex: 'none',
|
|
6561
|
+
width: this.leadSize(),
|
|
6562
|
+
minWidth: this.leadSize(),
|
|
6563
|
+
height: this.leadSize(),
|
|
6564
|
+
minHeight: this.leadSize(),
|
|
6565
|
+
padding: 0,
|
|
6566
|
+
fontSize: this.removeGlyphSize(),
|
|
6567
|
+
// uni-icon writes width/height as *inline* styles, sized from the
|
|
6568
|
+
// icon-button's own `sm` token (18px — wider than an `sm` chip).
|
|
6569
|
+
// Only !important can reach past an inline style to keep the glyph
|
|
6570
|
+
// proportional to the chip.
|
|
6571
|
+
'& uni-icon': {
|
|
6572
|
+
width: `${this.removeGlyphSize()}px !important`,
|
|
6573
|
+
height: `${this.removeGlyphSize()}px !important`,
|
|
6574
|
+
},
|
|
6575
|
+
},
|
|
6576
|
+
},
|
|
6577
|
+
this.invalid() && {
|
|
6578
|
+
// Colour alone cannot carry "this entry is malformed" (WCAG 1.4.1).
|
|
6579
|
+
textDecoration: 'underline dashed',
|
|
6580
|
+
textUnderlineOffset: 3,
|
|
6581
|
+
},
|
|
6582
|
+
this.disabled() && { pointerEvents: 'none' },
|
|
6583
|
+
])} tone-${this.tone()}`;
|
|
6584
|
+
}, ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
|
|
6585
|
+
/** Truncating label; `title` exposes the full text when a budget is set. */
|
|
6586
|
+
labelClass = css({
|
|
6587
|
+
overflow: 'hidden',
|
|
6588
|
+
textOverflow: 'ellipsis',
|
|
6589
|
+
whiteSpace: 'nowrap',
|
|
6590
|
+
});
|
|
6591
|
+
bodyClass = computed(() => css([
|
|
6592
|
+
{
|
|
6593
|
+
display: 'inline-flex',
|
|
6594
|
+
alignItems: 'center',
|
|
6595
|
+
minWidth: 0,
|
|
6596
|
+
// The body inherits the chip's own colours in every case.
|
|
6597
|
+
font: 'inherit',
|
|
6598
|
+
color: 'inherit',
|
|
6599
|
+
background: 'none',
|
|
6600
|
+
border: 0,
|
|
6601
|
+
padding: 0,
|
|
6602
|
+
...this.theme.gap(this.componentOptions().gap),
|
|
6603
|
+
},
|
|
6604
|
+
this.interactive() && { cursor: 'pointer' },
|
|
6605
|
+
]), ...(ngDevMode ? [{ debugName: "bodyClass" }] : /* istanbul ignore next */ []));
|
|
6606
|
+
leadClass = computed(() => css({
|
|
6607
|
+
flex: 'none',
|
|
6608
|
+
display: 'inline-flex',
|
|
6609
|
+
alignItems: 'center',
|
|
6610
|
+
justifyContent: 'center',
|
|
6611
|
+
overflow: 'hidden',
|
|
6612
|
+
width: this.leadSize(),
|
|
6613
|
+
height: this.leadSize(),
|
|
6614
|
+
borderRadius: '50%',
|
|
6615
|
+
fontSize: Math.max(Math.round(this.leadSize() * 0.5), 8),
|
|
6616
|
+
// Initials sit on a wash of the current ink so they read on any tone.
|
|
6617
|
+
backgroundColor: this.avatarSrc() ? undefined : 'rgba(0, 0, 0, 0.12)',
|
|
6618
|
+
'& img': { width: '100%', height: '100%', objectFit: 'cover' },
|
|
6619
|
+
}), ...(ngDevMode ? [{ debugName: "leadClass" }] : /* istanbul ignore next */ []));
|
|
6620
|
+
dotClass = computed(() => css({
|
|
6621
|
+
flex: 'none',
|
|
6622
|
+
width: Math.max(Math.round(this.leadSize() / 3), 6),
|
|
6623
|
+
height: Math.max(Math.round(this.leadSize() / 3), 6),
|
|
6624
|
+
borderRadius: '50%',
|
|
6625
|
+
backgroundColor: 'currentColor',
|
|
6626
|
+
}), ...(ngDevMode ? [{ debugName: "dotClass" }] : /* istanbul ignore next */ []));
|
|
6627
|
+
remove() {
|
|
6628
|
+
if (this.disabled())
|
|
6629
|
+
return;
|
|
6630
|
+
this.removed.emit(this.value());
|
|
6631
|
+
}
|
|
6632
|
+
activate() {
|
|
6633
|
+
if (this.disabled())
|
|
6634
|
+
return;
|
|
6635
|
+
this.activated.emit(this.value());
|
|
6636
|
+
}
|
|
6637
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
6638
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagComponent, isStandalone: true, selector: "uni-tag", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, avatarName: { classPropertyName: "avatarName", publicName: "avatarName", isSignal: true, isRequired: false, transformFunction: null }, iconName: { classPropertyName: "iconName", publicName: "iconName", isSignal: true, isRequired: false, transformFunction: null }, symbolName: { classPropertyName: "symbolName", publicName: "symbolName", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, controlTabIndex: { classPropertyName: "controlTabIndex", publicName: "controlTabIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed", activated: "activated" }, host: { properties: { "class": "hostClass()", "attr.aria-invalid": "invalid() ? 'true' : null" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tag' }], usesInheritance: true, ngImport: i0, template: "<!-- Lead + label. Shared so the static and interactive bodies stay identical. -->\n<ng-template #body>\n <ng-content select=\"[tag-lead]\"></ng-content>\n\n @if (selected() && componentOptions().selectedIcon; as selectedIcon) {\n <uni-icon [name]=\"selectedIcon\" [size]=\"leadSize()\" />\n } @else if (avatarSrc(); as src) {\n <span [class]=\"leadClass()\" aria-hidden=\"true\">\n <img [src]=\"src\" alt=\"\" />\n </span>\n } @else if (initials(); as text) {\n <span [class]=\"leadClass()\" aria-hidden=\"true\">{{ text }}</span>\n } @else if (iconName(); as icon) {\n <uni-icon [name]=\"icon\" [size]=\"leadSize()\" />\n } @else if (symbolName(); as symbol) {\n <uni-symbol [name]=\"symbol\" [style.font-size.px]=\"leadSize()\" />\n } @else if (dot()) {\n <span [class]=\"dotClass()\" aria-hidden=\"true\"></span>\n }\n\n @if (label(); as text) {\n <span [class]=\"labelClass\" [attr.title]=\"maxWidth() ? text : null\">{{ text }}</span>\n } @else {\n <span [class]=\"labelClass\"><ng-content></ng-content></span>\n }\n</ng-template>\n\n<!-- An interactive chip's body is the button; a static chip is plain text with\n no role and no tab stop, so screen readers read it as content. -->\n@if (interactive()) {\n <button\n type=\"button\"\n [class]=\"bodyClass()\"\n [disabled]=\"disabled()\"\n [attr.aria-pressed]=\"selected() ?? null\"\n [attr.tabindex]=\"controlTabIndex()\"\n (click)=\"activate()\"\n >\n <ng-container [ngTemplateOutlet]=\"body\"></ng-container>\n </button>\n} @else {\n <span [class]=\"bodyClass()\">\n <ng-container [ngTemplateOutlet]=\"body\"></ng-container>\n </span>\n}\n\n@if (removable()) {\n <button\n uni-icon-button\n variant=\"ghost\"\n size=\"sm\"\n [iconName]=\"componentOptions().removeIcon ?? 'close'\"\n [disable]=\"disabled()\"\n [attr.tabindex]=\"controlTabIndex()\"\n (click)=\"remove()\"\n >\n {{ removeLabel() ?? 'Remove ' + (label() ?? '') }}\n </button>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6295
6639
|
}
|
|
6296
6640
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagComponent, decorators: [{
|
|
6297
6641
|
type: Component,
|
|
6298
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tag', imports: [
|
|
6299
|
-
|
|
6642
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tag', imports: [NgTemplateOutlet, UniIconComponent, UniIconButtonComponent, UniSymbolComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tag' }], host: {
|
|
6643
|
+
'[class]': 'hostClass()',
|
|
6644
|
+
'[attr.aria-invalid]': "invalid() ? 'true' : null",
|
|
6645
|
+
}, template: "<!-- Lead + label. Shared so the static and interactive bodies stay identical. -->\n<ng-template #body>\n <ng-content select=\"[tag-lead]\"></ng-content>\n\n @if (selected() && componentOptions().selectedIcon; as selectedIcon) {\n <uni-icon [name]=\"selectedIcon\" [size]=\"leadSize()\" />\n } @else if (avatarSrc(); as src) {\n <span [class]=\"leadClass()\" aria-hidden=\"true\">\n <img [src]=\"src\" alt=\"\" />\n </span>\n } @else if (initials(); as text) {\n <span [class]=\"leadClass()\" aria-hidden=\"true\">{{ text }}</span>\n } @else if (iconName(); as icon) {\n <uni-icon [name]=\"icon\" [size]=\"leadSize()\" />\n } @else if (symbolName(); as symbol) {\n <uni-symbol [name]=\"symbol\" [style.font-size.px]=\"leadSize()\" />\n } @else if (dot()) {\n <span [class]=\"dotClass()\" aria-hidden=\"true\"></span>\n }\n\n @if (label(); as text) {\n <span [class]=\"labelClass\" [attr.title]=\"maxWidth() ? text : null\">{{ text }}</span>\n } @else {\n <span [class]=\"labelClass\"><ng-content></ng-content></span>\n }\n</ng-template>\n\n<!-- An interactive chip's body is the button; a static chip is plain text with\n no role and no tab stop, so screen readers read it as content. -->\n@if (interactive()) {\n <button\n type=\"button\"\n [class]=\"bodyClass()\"\n [disabled]=\"disabled()\"\n [attr.aria-pressed]=\"selected() ?? null\"\n [attr.tabindex]=\"controlTabIndex()\"\n (click)=\"activate()\"\n >\n <ng-container [ngTemplateOutlet]=\"body\"></ng-container>\n </button>\n} @else {\n <span [class]=\"bodyClass()\">\n <ng-container [ngTemplateOutlet]=\"body\"></ng-container>\n </span>\n}\n\n@if (removable()) {\n <button\n uni-icon-button\n variant=\"ghost\"\n size=\"sm\"\n [iconName]=\"componentOptions().removeIcon ?? 'close'\"\n [disable]=\"disabled()\"\n [attr.tabindex]=\"controlTabIndex()\"\n (click)=\"remove()\"\n >\n {{ removeLabel() ?? 'Remove ' + (label() ?? '') }}\n </button>\n}\n" }]
|
|
6646
|
+
}], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], avatarName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarName", required: false }] }], iconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconName", required: false }] }], symbolName: [{ type: i0.Input, args: [{ isSignal: true, alias: "symbolName", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], controlTabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlTabIndex", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
|
|
6300
6647
|
|
|
6301
6648
|
/**
|
|
6302
6649
|
* UniTagComponent Barrel File
|
|
@@ -6304,6 +6651,392 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
6304
6651
|
* This file exports all public-facing elements of the tag component.
|
|
6305
6652
|
*/
|
|
6306
6653
|
|
|
6654
|
+
/** Loose address check — deliberately permissive, matching what mail clients accept. */
|
|
6655
|
+
const EMAIL = /^[^\s@,;]+@[^\s@,;.]+\.[^\s@,;]+$/;
|
|
6656
|
+
/** `Name <a@b.com>` / `"Name" <a@b.com>` → the address, plus the display name. */
|
|
6657
|
+
const unwrapAddress = (raw) => {
|
|
6658
|
+
const match = raw.match(/^\s*"?([^"<]*?)"?\s*<([^>]+)>\s*$/);
|
|
6659
|
+
if (!match)
|
|
6660
|
+
return { value: raw.trim() };
|
|
6661
|
+
const label = match[1].trim();
|
|
6662
|
+
return { value: match[2].trim(), ...(label ? { label } : {}) };
|
|
6663
|
+
};
|
|
6664
|
+
class UniTagInputComponent extends BaseComponent {
|
|
6665
|
+
// --- Signal Forms block (explicit per AGENTS.md, not a base class) --------
|
|
6666
|
+
value = model([], ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
6667
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
6668
|
+
touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
6669
|
+
invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
6670
|
+
dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
6671
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
6672
|
+
ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
6673
|
+
// --- Configuration -------------------------------------------------------
|
|
6674
|
+
/** Accessible name for the field, e.g. "To". */
|
|
6675
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6676
|
+
placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
6677
|
+
/** `email` wires an address validator, paste parser and space separator. */
|
|
6678
|
+
preset = input('text', ...(ngDevMode ? [{ debugName: "preset" }] : /* istanbul ignore next */ []));
|
|
6679
|
+
separators = input([',', ';'], ...(ngDevMode ? [{ debugName: "separators" }] : /* istanbul ignore next */ []));
|
|
6680
|
+
commitOnBlur = input(true, ...(ngDevMode ? [{ debugName: "commitOnBlur" }] : /* istanbul ignore next */ []));
|
|
6681
|
+
allowDuplicates = input(false, ...(ngDevMode ? [{ debugName: "allowDuplicates" }] : /* istanbul ignore next */ []));
|
|
6682
|
+
max = input(...(ngDevMode ? [undefined, { debugName: "max" }] : /* istanbul ignore next */ []));
|
|
6683
|
+
validate = input(...(ngDevMode ? [undefined, { debugName: "validate" }] : /* istanbul ignore next */ []));
|
|
6684
|
+
parse = input(...(ngDevMode ? [undefined, { debugName: "parse" }] : /* istanbul ignore next */ []));
|
|
6685
|
+
// Chip presentation, forwarded to uni-tag.
|
|
6686
|
+
tagVariant = input('primary', ...(ngDevMode ? [{ debugName: "tagVariant" }] : /* istanbul ignore next */ []));
|
|
6687
|
+
tagTone = input('soft', ...(ngDevMode ? [{ debugName: "tagTone" }] : /* istanbul ignore next */ []));
|
|
6688
|
+
tagSize = input(...(ngDevMode ? [undefined, { debugName: "tagSize" }] : /* istanbul ignore next */ []));
|
|
6689
|
+
// --- Autocomplete (same contract as uni-search-input: the app filters) ----
|
|
6690
|
+
suggestions = input([], ...(ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []));
|
|
6691
|
+
/** Debounced text the app should filter `suggestions` from. */
|
|
6692
|
+
query = output();
|
|
6693
|
+
debounceTime = input(250, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ []));
|
|
6694
|
+
// --- Events --------------------------------------------------------------
|
|
6695
|
+
added = output();
|
|
6696
|
+
removed = output();
|
|
6697
|
+
rejected = output();
|
|
6698
|
+
inputRef = viewChild.required('field');
|
|
6699
|
+
chipRefs = viewChildren('chip', ...(ngDevMode ? [{ debugName: "chipRefs" }] : /* istanbul ignore next */ []));
|
|
6700
|
+
/** Uncommitted text in the field. */
|
|
6701
|
+
draft = signal('', ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
|
|
6702
|
+
/** Index of the focused chip, or -1 when focus is in the text input. */
|
|
6703
|
+
focusedChip = signal(-1, ...(ngDevMode ? [{ debugName: "focusedChip" }] : /* istanbul ignore next */ []));
|
|
6704
|
+
/** Announcement for the status region; add/remove are otherwise silent. */
|
|
6705
|
+
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
6706
|
+
hintId = uniqueId('uni-tag-input-hint');
|
|
6707
|
+
srOnly = css(visuallyHidden);
|
|
6708
|
+
queryTimer;
|
|
6709
|
+
visibleSuggestions = computed(() => {
|
|
6710
|
+
const taken = new Set(this.value().map((item) => item.value));
|
|
6711
|
+
return this.suggestions()
|
|
6712
|
+
.filter((suggestion) => this.allowDuplicates() || !taken.has(suggestion.value))
|
|
6713
|
+
.slice(0, this.componentOptions().maxSuggestions ?? 8);
|
|
6714
|
+
}, ...(ngDevMode ? [{ debugName: "visibleSuggestions" }] : /* istanbul ignore next */ []));
|
|
6715
|
+
/** Shared combobox bookkeeping — identical contract to uni-search-input. */
|
|
6716
|
+
list = createListboxNavigation({
|
|
6717
|
+
count: () => this.visibleSuggestions().length,
|
|
6718
|
+
idPrefix: 'uni-tag-listbox',
|
|
6719
|
+
});
|
|
6720
|
+
showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
6721
|
+
separatorKeys = computed(() => this.preset() === 'email' ? [...this.separators(), ' '] : this.separators(), ...(ngDevMode ? [{ debugName: "separatorKeys" }] : /* istanbul ignore next */ []));
|
|
6722
|
+
describedBy = computed(() => [this.ariaDescribedBy(), this.hintId].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
6723
|
+
labelOf(item) {
|
|
6724
|
+
return item.label ?? item.value;
|
|
6725
|
+
}
|
|
6726
|
+
// --- Committing ----------------------------------------------------------
|
|
6727
|
+
/** Split pasted text into candidate tokens. */
|
|
6728
|
+
parseRaw(raw) {
|
|
6729
|
+
const custom = this.parse();
|
|
6730
|
+
if (custom)
|
|
6731
|
+
return custom(raw);
|
|
6732
|
+
const pattern = this.preset() === 'email' ? /[,;\n\t]+/ : new RegExp(`[${this.separators().map((s) => `\\${s}`).join('')}\n\t]+`);
|
|
6733
|
+
return raw.split(pattern);
|
|
6734
|
+
}
|
|
6735
|
+
isValid(candidate) {
|
|
6736
|
+
const custom = this.validate();
|
|
6737
|
+
if (custom)
|
|
6738
|
+
return custom(candidate);
|
|
6739
|
+
return this.preset() === 'email' ? EMAIL.test(candidate) : true;
|
|
6740
|
+
}
|
|
6741
|
+
/**
|
|
6742
|
+
* Turn typed text into a chip. Invalid entries are kept and flagged rather
|
|
6743
|
+
* than dropped; duplicates and over-max are refused with a reason.
|
|
6744
|
+
*/
|
|
6745
|
+
commit(raw) {
|
|
6746
|
+
const trimmed = raw.trim();
|
|
6747
|
+
if (!trimmed)
|
|
6748
|
+
return false;
|
|
6749
|
+
const { value: candidate, label } = this.preset() === 'email' ? unwrapAddress(trimmed) : { value: trimmed, label: undefined };
|
|
6750
|
+
if (!candidate)
|
|
6751
|
+
return false;
|
|
6752
|
+
const current = this.value();
|
|
6753
|
+
if (!this.allowDuplicates() && current.some((item) => item.value === candidate)) {
|
|
6754
|
+
this.reject(candidate, 'duplicate');
|
|
6755
|
+
return false;
|
|
6756
|
+
}
|
|
6757
|
+
const max = this.max();
|
|
6758
|
+
if (max !== undefined && current.length >= max) {
|
|
6759
|
+
this.reject(candidate, 'max');
|
|
6760
|
+
return false;
|
|
6761
|
+
}
|
|
6762
|
+
const match = this.suggestions().find((suggestion) => suggestion.value === candidate);
|
|
6763
|
+
const item = {
|
|
6764
|
+
value: candidate,
|
|
6765
|
+
...(label || match?.label ? { label: label ?? match?.label } : {}),
|
|
6766
|
+
...(match?.avatarSrc ? { avatarSrc: match.avatarSrc } : {}),
|
|
6767
|
+
...(this.isValid(candidate) ? {} : { invalid: true }),
|
|
6768
|
+
};
|
|
6769
|
+
this.value.update((items) => [...items, item]);
|
|
6770
|
+
this.added.emit(item);
|
|
6771
|
+
this.announce(`${this.labelOf(item)} added. ${this.value().length} ${this.countNoun()}.`);
|
|
6772
|
+
return true;
|
|
6773
|
+
}
|
|
6774
|
+
reject(raw, reason) {
|
|
6775
|
+
this.rejected.emit({ raw, reason });
|
|
6776
|
+
// The visual cue is a brief pulse a screen reader cannot see.
|
|
6777
|
+
this.announce(reason === 'duplicate' ? `${raw} is already added.` : `${raw} was not added: limit reached.`);
|
|
6778
|
+
}
|
|
6779
|
+
removeAt(index, focus = 'input') {
|
|
6780
|
+
const item = this.value()[index];
|
|
6781
|
+
if (!item || item.disabled)
|
|
6782
|
+
return;
|
|
6783
|
+
this.value.update((items) => items.filter((_, i) => i !== index));
|
|
6784
|
+
this.removed.emit(item);
|
|
6785
|
+
this.announce(`${this.labelOf(item)} removed. ${this.value().length} ${this.countNoun()}.`);
|
|
6786
|
+
const remaining = this.value().length;
|
|
6787
|
+
if (focus === 'left' && index > 0)
|
|
6788
|
+
this.focusChip(index - 1);
|
|
6789
|
+
else if (focus === 'right' && index < remaining)
|
|
6790
|
+
this.focusChip(index);
|
|
6791
|
+
else
|
|
6792
|
+
this.focusInput();
|
|
6793
|
+
}
|
|
6794
|
+
countNoun() {
|
|
6795
|
+
return this.value().length === 1 ? 'item' : 'items';
|
|
6796
|
+
}
|
|
6797
|
+
announce(message) {
|
|
6798
|
+
// Re-announce identical text by breaking the string equality.
|
|
6799
|
+
this.announcement.set(this.announcement() === message ? `${message} ` : message);
|
|
6800
|
+
}
|
|
6801
|
+
// --- Focus ---------------------------------------------------------------
|
|
6802
|
+
focusInput() {
|
|
6803
|
+
this.focusedChip.set(-1);
|
|
6804
|
+
queueMicrotask(() => this.inputRef().nativeElement.focus());
|
|
6805
|
+
}
|
|
6806
|
+
focusChip(index) {
|
|
6807
|
+
const clamped = Math.max(0, Math.min(index, this.value().length - 1));
|
|
6808
|
+
this.focusedChip.set(clamped);
|
|
6809
|
+
queueMicrotask(() => {
|
|
6810
|
+
const chip = this.chipRefs()[clamped]?.nativeElement;
|
|
6811
|
+
chip?.querySelector('button')?.focus();
|
|
6812
|
+
});
|
|
6813
|
+
}
|
|
6814
|
+
// --- Keyboard: focus in the text input -----------------------------------
|
|
6815
|
+
onInputKeydown(event) {
|
|
6816
|
+
if (this.list.navigate(event))
|
|
6817
|
+
return;
|
|
6818
|
+
const input = this.inputRef().nativeElement;
|
|
6819
|
+
const empty = input.value === '';
|
|
6820
|
+
if (this.separatorKeys().includes(event.key) && !empty) {
|
|
6821
|
+
event.preventDefault();
|
|
6822
|
+
this.commitDraft();
|
|
6823
|
+
return;
|
|
6824
|
+
}
|
|
6825
|
+
switch (event.key) {
|
|
6826
|
+
case 'Enter': {
|
|
6827
|
+
event.preventDefault();
|
|
6828
|
+
const active = this.list.activeIndex();
|
|
6829
|
+
if (this.list.open() && active >= 0)
|
|
6830
|
+
this.selectSuggestion(this.visibleSuggestions()[active]);
|
|
6831
|
+
else
|
|
6832
|
+
this.commitDraft();
|
|
6833
|
+
break;
|
|
6834
|
+
}
|
|
6835
|
+
case 'Tab':
|
|
6836
|
+
// Never trap: commit what is typed, then let focus move on.
|
|
6837
|
+
if (!empty)
|
|
6838
|
+
this.commitDraft();
|
|
6839
|
+
break;
|
|
6840
|
+
case 'Backspace':
|
|
6841
|
+
if (empty && this.value().length) {
|
|
6842
|
+
// Focus the last chip rather than deleting blind — a second
|
|
6843
|
+
// Backspace, now on the chip, removes it.
|
|
6844
|
+
event.preventDefault();
|
|
6845
|
+
this.focusChip(this.value().length - 1);
|
|
6846
|
+
}
|
|
6847
|
+
break;
|
|
6848
|
+
case 'ArrowLeft':
|
|
6849
|
+
if (input.selectionStart === 0 && this.value().length) {
|
|
6850
|
+
event.preventDefault();
|
|
6851
|
+
this.focusChip(this.value().length - 1);
|
|
6852
|
+
}
|
|
6853
|
+
break;
|
|
6854
|
+
case 'Escape':
|
|
6855
|
+
if (this.list.open())
|
|
6856
|
+
this.list.hide();
|
|
6857
|
+
else
|
|
6858
|
+
this.setDraft('');
|
|
6859
|
+
break;
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
onInput(value) {
|
|
6863
|
+
this.setDraft(value);
|
|
6864
|
+
this.list.show();
|
|
6865
|
+
this.list.setActive(-1);
|
|
6866
|
+
clearTimeout(this.queryTimer);
|
|
6867
|
+
this.queryTimer = setTimeout(() => this.query.emit(value), this.debounceTime());
|
|
6868
|
+
}
|
|
6869
|
+
onPaste(event) {
|
|
6870
|
+
const text = event.clipboardData?.getData('text') ?? '';
|
|
6871
|
+
if (!text)
|
|
6872
|
+
return;
|
|
6873
|
+
event.preventDefault();
|
|
6874
|
+
const tokens = this.parseRaw(text);
|
|
6875
|
+
// A trailing fragment with no separator after it is still being typed, so
|
|
6876
|
+
// it stays in the field rather than committing as a half-address. With a
|
|
6877
|
+
// custom `parse` the app owns tokenization and every token is complete.
|
|
6878
|
+
const separators = [...this.separatorKeys(), '\n', '\t'].map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
6879
|
+
const endsWithSeparator = new RegExp(`[${separators.join('')}]\\s*$`).test(text);
|
|
6880
|
+
const tail = this.parse() || endsWithSeparator ? '' : (tokens.pop() ?? '');
|
|
6881
|
+
tokens.forEach((token) => this.commit(token));
|
|
6882
|
+
this.setDraft(tail.trim());
|
|
6883
|
+
}
|
|
6884
|
+
onBlur() {
|
|
6885
|
+
this.touched.set(true);
|
|
6886
|
+
if (this.commitOnBlur())
|
|
6887
|
+
this.commitDraft();
|
|
6888
|
+
}
|
|
6889
|
+
onFocusOut(event) {
|
|
6890
|
+
this.list.closeOnFocusOut(event);
|
|
6891
|
+
}
|
|
6892
|
+
// --- Keyboard: focus on a chip -------------------------------------------
|
|
6893
|
+
onChipKeydown(event, index) {
|
|
6894
|
+
switch (event.key) {
|
|
6895
|
+
case 'ArrowLeft':
|
|
6896
|
+
event.preventDefault();
|
|
6897
|
+
if (index > 0)
|
|
6898
|
+
this.focusChip(index - 1);
|
|
6899
|
+
break;
|
|
6900
|
+
case 'ArrowRight':
|
|
6901
|
+
event.preventDefault();
|
|
6902
|
+
if (index < this.value().length - 1)
|
|
6903
|
+
this.focusChip(index + 1);
|
|
6904
|
+
else
|
|
6905
|
+
this.focusInput();
|
|
6906
|
+
break;
|
|
6907
|
+
case 'Home':
|
|
6908
|
+
event.preventDefault();
|
|
6909
|
+
this.focusChip(0);
|
|
6910
|
+
break;
|
|
6911
|
+
case 'End':
|
|
6912
|
+
event.preventDefault();
|
|
6913
|
+
this.focusChip(this.value().length - 1);
|
|
6914
|
+
break;
|
|
6915
|
+
case 'Backspace':
|
|
6916
|
+
// Two deletion keys with different focus outcomes: hold Backspace to
|
|
6917
|
+
// eat backwards, Delete to eat forwards, without the cursor jumping.
|
|
6918
|
+
event.preventDefault();
|
|
6919
|
+
this.removeAt(index, 'left');
|
|
6920
|
+
break;
|
|
6921
|
+
case 'Delete':
|
|
6922
|
+
event.preventDefault();
|
|
6923
|
+
this.removeAt(index, 'right');
|
|
6924
|
+
break;
|
|
6925
|
+
case 'Enter':
|
|
6926
|
+
case 'F2':
|
|
6927
|
+
event.preventDefault();
|
|
6928
|
+
this.editChip(index);
|
|
6929
|
+
break;
|
|
6930
|
+
case 'Escape':
|
|
6931
|
+
event.preventDefault();
|
|
6932
|
+
this.focusInput();
|
|
6933
|
+
break;
|
|
6934
|
+
default:
|
|
6935
|
+
// A printable key means the user wants to type, not navigate chips.
|
|
6936
|
+
if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
6937
|
+
this.setDraft(this.draft() + event.key);
|
|
6938
|
+
this.focusInput();
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
/** Lift a chip back into the input for correction. */
|
|
6943
|
+
editChip(index) {
|
|
6944
|
+
const item = this.value()[index];
|
|
6945
|
+
if (!item || item.disabled)
|
|
6946
|
+
return;
|
|
6947
|
+
this.value.update((items) => items.filter((_, i) => i !== index));
|
|
6948
|
+
this.removed.emit(item);
|
|
6949
|
+
const text = item.label && item.label !== item.value ? `${item.label} <${item.value}>` : item.value;
|
|
6950
|
+
this.setDraft(text);
|
|
6951
|
+
this.focusInput();
|
|
6952
|
+
}
|
|
6953
|
+
selectSuggestion(suggestion) {
|
|
6954
|
+
this.commit(suggestion.value);
|
|
6955
|
+
this.setDraft('');
|
|
6956
|
+
this.list.hide();
|
|
6957
|
+
this.focusInput();
|
|
6958
|
+
}
|
|
6959
|
+
commitDraft() {
|
|
6960
|
+
if (this.commit(this.draft()))
|
|
6961
|
+
this.setDraft('');
|
|
6962
|
+
else
|
|
6963
|
+
this.setDraft('');
|
|
6964
|
+
this.list.hide();
|
|
6965
|
+
}
|
|
6966
|
+
setDraft(text) {
|
|
6967
|
+
this.draft.set(text);
|
|
6968
|
+
const input = this.inputRef?.().nativeElement;
|
|
6969
|
+
if (input)
|
|
6970
|
+
input.value = text;
|
|
6971
|
+
}
|
|
6972
|
+
// --- Styling -------------------------------------------------------------
|
|
6973
|
+
className = computed(() => css({ display: 'block' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
6974
|
+
fieldClass = computed(() => {
|
|
6975
|
+
const options = this.componentOptions();
|
|
6976
|
+
return css({
|
|
6977
|
+
display: 'flex',
|
|
6978
|
+
flexWrap: 'wrap',
|
|
6979
|
+
alignItems: 'center',
|
|
6980
|
+
width: '100%',
|
|
6981
|
+
listStyle: 'none',
|
|
6982
|
+
margin: 0,
|
|
6983
|
+
padding: 0,
|
|
6984
|
+
...this.theme.gap(options.chipGap),
|
|
6985
|
+
});
|
|
6986
|
+
}, ...(ngDevMode ? [{ debugName: "fieldClass" }] : /* istanbul ignore next */ []));
|
|
6987
|
+
inputClass = computed(() => css({
|
|
6988
|
+
flex: 1,
|
|
6989
|
+
minWidth: this.componentOptions().minInputWidth ?? '12ch',
|
|
6990
|
+
border: 0,
|
|
6991
|
+
outline: 'none',
|
|
6992
|
+
background: 'transparent',
|
|
6993
|
+
color: 'inherit',
|
|
6994
|
+
font: 'inherit',
|
|
6995
|
+
padding: 0,
|
|
6996
|
+
}), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
|
|
6997
|
+
listClass = computed(() => {
|
|
6998
|
+
const options = this.componentOptions();
|
|
6999
|
+
return css({
|
|
7000
|
+
position: 'absolute',
|
|
7001
|
+
top: '100%',
|
|
7002
|
+
left: 0,
|
|
7003
|
+
right: 0,
|
|
7004
|
+
zIndex: 20,
|
|
7005
|
+
margin: '4px 0 0',
|
|
7006
|
+
padding: 4,
|
|
7007
|
+
listStyle: 'none',
|
|
7008
|
+
maxHeight: 280,
|
|
7009
|
+
overflowY: 'auto',
|
|
7010
|
+
...this.theme.backgroundColor(options.listColor ?? 'primary-surface'),
|
|
7011
|
+
...this.theme.boxShadow(options.listShadow ?? 'menu'),
|
|
7012
|
+
...this.theme.radius(options.listBorderRadius ?? 'xs'),
|
|
7013
|
+
'& [role="option"]': {
|
|
7014
|
+
padding: '8px 12px',
|
|
7015
|
+
cursor: 'pointer',
|
|
7016
|
+
...this.theme.typeface('label'),
|
|
7017
|
+
...this.theme.color('on-primary-surface'),
|
|
7018
|
+
...this.theme.radius('xxs'),
|
|
7019
|
+
'&.active, &:hover': {
|
|
7020
|
+
...this.theme.backgroundColor('primary-container'),
|
|
7021
|
+
...this.theme.color('on-primary-container'),
|
|
7022
|
+
},
|
|
7023
|
+
},
|
|
7024
|
+
});
|
|
7025
|
+
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
7026
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
7027
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagInputComponent, isStandalone: true, selector: "uni-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, tagVariant: { classPropertyName: "tagVariant", publicName: "tagVariant", isSignal: true, isRequired: false, transformFunction: null }, tagTone: { classPropertyName: "tagTone", publicName: "tagTone", isSignal: true, isRequired: false, transformFunction: null }, tagSize: { classPropertyName: "tagSize", publicName: "tagSize", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", added: "added", removed: "removed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "chipRefs", predicate: ["chip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div style=\"position: relative\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniTagComponent, selector: "uni-tag", inputs: ["size", "tone", "label", "value", "maxWidth", "avatarSrc", "avatarName", "iconName", "symbolName", "dot", "removable", "interactive", "selected", "invalid", "disabled", "removeLabel", "controlTabIndex"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7028
|
+
}
|
|
7029
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, decorators: [{
|
|
7030
|
+
type: Component,
|
|
7031
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tag-input', imports: [UniInputBoxComponent, UniTagComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div style=\"position: relative\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n" }]
|
|
7032
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], preset: [{ type: i0.Input, args: [{ isSignal: true, alias: "preset", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], parse: [{ type: i0.Input, args: [{ isSignal: true, alias: "parse", required: false }] }], tagVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagVariant", required: false }] }], tagTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagTone", required: false }] }], tagSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagSize", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], query: [{ type: i0.Output, args: ["query"] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], chipRefs: [{ type: i0.ViewChildren, args: ['chip', { isSignal: true }] }] } });
|
|
7033
|
+
|
|
7034
|
+
/**
|
|
7035
|
+
* UniTagInputComponent Barrel File
|
|
7036
|
+
*
|
|
7037
|
+
* This file exports all public-facing elements of the tag-input component.
|
|
7038
|
+
*/
|
|
7039
|
+
|
|
6307
7040
|
const SCHEMES = [
|
|
6308
7041
|
'monochromatic',
|
|
6309
7042
|
'analogous',
|
|
@@ -7359,9 +8092,211 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
7359
8092
|
args: [{ selector: 'uni-toggle', imports: [UniTextComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'toggle' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<label [class]=\"toggleLabel()\">\n <input\n type=\"checkbox\"\n role=\"switch\"\n [class]=\"toggleInput()\"\n [checked]=\"checked()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"toggle-switch\">\n <div class=\"toggle-slider\"></div>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n" }]
|
|
7360
8093
|
}], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
7361
8094
|
|
|
8095
|
+
class UniTooltipComponent extends BaseComponent {
|
|
8096
|
+
elRef = inject(ElementRef);
|
|
8097
|
+
renderer = inject(Renderer2);
|
|
8098
|
+
timer = useTimer();
|
|
8099
|
+
isMouseInside = signal(false, ...(ngDevMode ? [{ debugName: "isMouseInside" }] : /* istanbul ignore next */ []));
|
|
8100
|
+
/** Whether the bubble is currently shown (or fading out). */
|
|
8101
|
+
visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
8102
|
+
/**
|
|
8103
|
+
* Set when the wrapped control is activated: clicking a button is not a
|
|
8104
|
+
* request to toggle the bubble, and re-showing it right away makes a
|
|
8105
|
+
* state-flipping label ("Expand" → "Collapse") blink back mid-interaction.
|
|
8106
|
+
* Re-arms when the pointer leaves or focus moves away.
|
|
8107
|
+
*/
|
|
8108
|
+
suppressed = signal(false, ...(ngDevMode ? [{ debugName: "suppressed" }] : /* istanbul ignore next */ []));
|
|
8109
|
+
tooltipId = uniqueId('uni-tooltip');
|
|
8110
|
+
anchorName = newAnchorName();
|
|
8111
|
+
hoverDelay = input(500, ...(ngDevMode ? [{ debugName: "hoverDelay" }] : /* istanbul ignore next */ []));
|
|
8112
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
8113
|
+
placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
8114
|
+
inlineText = input(false, ...(ngDevMode ? [{ debugName: "inlineText" }] : /* istanbul ignore next */ []));
|
|
8115
|
+
/** @deprecated The tooltip renders in the native top layer; ignored. */
|
|
8116
|
+
appendToBody = input(false, ...(ngDevMode ? [{ debugName: "appendToBody" }] : /* istanbul ignore next */ []));
|
|
8117
|
+
tipRef = viewChild.required('tip');
|
|
8118
|
+
constructor() {
|
|
8119
|
+
super();
|
|
8120
|
+
effect(() => {
|
|
8121
|
+
const timerActive = this.timer.isActive();
|
|
8122
|
+
const mouseInside = this.isMouseInside();
|
|
8123
|
+
// If the timer finished and the mouse is still inside, show tooltip
|
|
8124
|
+
if (!timerActive && mouseInside && !this.suppressed()) {
|
|
8125
|
+
this.showTooltip();
|
|
8126
|
+
}
|
|
8127
|
+
// If the mouse left and the timer is not running, hide tooltip
|
|
8128
|
+
else if (!mouseInside) {
|
|
8129
|
+
this.hideTooltip();
|
|
8130
|
+
}
|
|
8131
|
+
});
|
|
8132
|
+
// A tooltip must be reachable by keyboard (WCAG 1.4.13): when the
|
|
8133
|
+
// projected content has no focusable element, the host itself joins
|
|
8134
|
+
// the tab sequence. The bubble is always in the DOM, so the describedby
|
|
8135
|
+
// relationship is wired once, on the element that receives focus.
|
|
8136
|
+
afterNextRender(() => {
|
|
8137
|
+
const host = this.elRef.nativeElement;
|
|
8138
|
+
if (!host.querySelector(FOCUSABLE_SELECTOR) && !host.matches(FOCUSABLE_SELECTOR)) {
|
|
8139
|
+
this.renderer.setAttribute(host, 'tabindex', '0');
|
|
8140
|
+
}
|
|
8141
|
+
this.renderer.setAttribute(resolveFocusTarget(host), 'aria-describedby', this.tooltipId);
|
|
8142
|
+
});
|
|
8143
|
+
}
|
|
8144
|
+
onFocusIn(event) {
|
|
8145
|
+
// Only keyboard-driven focus shows the tooltip immediately; mouse
|
|
8146
|
+
// interaction keeps the hover-delay behavior.
|
|
8147
|
+
if (event.target.matches(':focus-visible')) {
|
|
8148
|
+
this.showTooltip();
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
8151
|
+
onFocusOut() {
|
|
8152
|
+
this.suppressed.set(false);
|
|
8153
|
+
if (!this.isMouseInside()) {
|
|
8154
|
+
this.hideTooltip();
|
|
8155
|
+
}
|
|
8156
|
+
}
|
|
8157
|
+
onEscape(event) {
|
|
8158
|
+
if (this.visible()) {
|
|
8159
|
+
// Dismiss only the tooltip, not an enclosing dialog/popover
|
|
8160
|
+
event.stopPropagation();
|
|
8161
|
+
this.hideTooltip();
|
|
8162
|
+
}
|
|
8163
|
+
}
|
|
8164
|
+
/**
|
|
8165
|
+
* A click on an interactive element inside the host is an activation of
|
|
8166
|
+
* that control — hide the bubble and stay suppressed until re-arm. A click
|
|
8167
|
+
* anywhere else (inline-text tooltips, tap on a non-interactive host)
|
|
8168
|
+
* keeps the tap-to-toggle behavior.
|
|
8169
|
+
*/
|
|
8170
|
+
onClick(event) {
|
|
8171
|
+
const host = this.elRef.nativeElement;
|
|
8172
|
+
const control = event.target.closest(FOCUSABLE_SELECTOR);
|
|
8173
|
+
if (control && control !== host && host.contains(control)) {
|
|
8174
|
+
this.suppressed.set(true);
|
|
8175
|
+
this.hideTooltip();
|
|
8176
|
+
}
|
|
8177
|
+
else {
|
|
8178
|
+
this.toggleTooltip();
|
|
8179
|
+
}
|
|
8180
|
+
}
|
|
8181
|
+
toggleTooltip() {
|
|
8182
|
+
if (this.visible()) {
|
|
8183
|
+
this.hideTooltip();
|
|
8184
|
+
}
|
|
8185
|
+
else {
|
|
8186
|
+
this.showTooltip();
|
|
8187
|
+
}
|
|
8188
|
+
}
|
|
8189
|
+
mouseenter() {
|
|
8190
|
+
this.isMouseInside.set(true);
|
|
8191
|
+
this.timer.start(this.hoverDelay());
|
|
8192
|
+
}
|
|
8193
|
+
mouseleave() {
|
|
8194
|
+
this.isMouseInside.set(false);
|
|
8195
|
+
this.suppressed.set(false);
|
|
8196
|
+
this.timer.stop();
|
|
8197
|
+
}
|
|
8198
|
+
showTooltip() {
|
|
8199
|
+
if (this.visible())
|
|
8200
|
+
return;
|
|
8201
|
+
const tip = this.tipRef().nativeElement;
|
|
8202
|
+
tip.showPopover();
|
|
8203
|
+
this.renderer.setAttribute(tip, 'fade', 'in');
|
|
8204
|
+
this.visible.set(true);
|
|
8205
|
+
}
|
|
8206
|
+
hideTooltip() {
|
|
8207
|
+
if (!this.visible())
|
|
8208
|
+
return;
|
|
8209
|
+
this.renderer.setAttribute(this.tipRef().nativeElement, 'fade', 'out');
|
|
8210
|
+
}
|
|
8211
|
+
onAnimationEnd(event) {
|
|
8212
|
+
if (event.animationName.includes(this.tooltipFadeOut)) {
|
|
8213
|
+
this.tipRef().nativeElement.hidePopover();
|
|
8214
|
+
this.visible.set(false);
|
|
8215
|
+
}
|
|
8216
|
+
}
|
|
8217
|
+
className = computed(() => css({
|
|
8218
|
+
display: 'inline-flex',
|
|
8219
|
+
anchorName: this.anchorName,
|
|
8220
|
+
}, this.inlineText() && {
|
|
8221
|
+
cursor: 'help',
|
|
8222
|
+
textDecoration: 'underline',
|
|
8223
|
+
textDecorationStyle: 'dotted',
|
|
8224
|
+
}), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
8225
|
+
tooltipFadeIn = keyframes({ ...fadeIn });
|
|
8226
|
+
tooltipFadeOut = keyframes({ ...fadeOut });
|
|
8227
|
+
tooltipClassName = computed(() => css([
|
|
8228
|
+
{
|
|
8229
|
+
...this.theme.colorPair(this.componentOptions().color),
|
|
8230
|
+
...this.theme.radius(this.componentOptions().borderRadius),
|
|
8231
|
+
...this.theme.boxShadow(this.componentOptions().shadow),
|
|
8232
|
+
...this.theme.typeface(this.componentOptions().typeface),
|
|
8233
|
+
border: 'none',
|
|
8234
|
+
padding: 5,
|
|
8235
|
+
width: 'max-content',
|
|
8236
|
+
...anchorStyles(this.anchorName, this.placement(), { mainAxis: 6 }),
|
|
8237
|
+
'&[fade="in"]': {
|
|
8238
|
+
animation: `${this.tooltipFadeIn} ease-in 350ms`,
|
|
8239
|
+
},
|
|
8240
|
+
'&[fade="out"]': {
|
|
8241
|
+
animation: `${this.tooltipFadeOut} ease-in 350ms`,
|
|
8242
|
+
},
|
|
8243
|
+
},
|
|
8244
|
+
]), ...(ngDevMode ? [{ debugName: "tooltipClassName" }] : /* istanbul ignore next */ []));
|
|
8245
|
+
arrowClassName = computed(() => css({
|
|
8246
|
+
...this.theme.colorPair(this.componentOptions().color),
|
|
8247
|
+
...anchorArrowStyles(this.placement()),
|
|
8248
|
+
}), ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
|
|
8249
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8250
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: UniTooltipComponent, isStandalone: true, selector: "uni-tooltip", inputs: { hoverDelay: { classPropertyName: "hoverDelay", publicName: "hoverDelay", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, inlineText: { classPropertyName: "inlineText", publicName: "inlineText", isSignal: true, isRequired: false, transformFunction: null }, appendToBody: { classPropertyName: "appendToBody", publicName: "appendToBody", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)", "mouseenter": "mouseenter()", "mouseleave": "mouseleave()", "focusin": "onFocusIn($event)", "focusout": "onFocusOut()", "keydown.escape": "onEscape($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }], viewQueries: [{ propertyName: "tipRef", first: true, predicate: ["tip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `<ng-content></ng-content
|
|
8251
|
+
><span
|
|
8252
|
+
#tip
|
|
8253
|
+
popover="manual"
|
|
8254
|
+
role="tooltip"
|
|
8255
|
+
[id]="tooltipId"
|
|
8256
|
+
[class]="tooltipClassName()"
|
|
8257
|
+
(mouseenter)="isMouseInside.set(true)"
|
|
8258
|
+
(mouseleave)="isMouseInside.set(false)"
|
|
8259
|
+
(animationend)="onAnimationEnd($event)"
|
|
8260
|
+
>{{ label() }}<span [class]="arrowClassName()"></span
|
|
8261
|
+
></span>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8262
|
+
}
|
|
8263
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, decorators: [{
|
|
8264
|
+
type: Component,
|
|
8265
|
+
args: [{
|
|
8266
|
+
selector: 'uni-tooltip',
|
|
8267
|
+
imports: [],
|
|
8268
|
+
// The bubble lives declaratively in the template as a manual popover: the
|
|
8269
|
+
// top layer escapes any overflow context (which made appendToBody obsolete)
|
|
8270
|
+
// and native CSS anchor positioning keeps it attached to the host.
|
|
8271
|
+
template: `<ng-content></ng-content
|
|
8272
|
+
><span
|
|
8273
|
+
#tip
|
|
8274
|
+
popover="manual"
|
|
8275
|
+
role="tooltip"
|
|
8276
|
+
[id]="tooltipId"
|
|
8277
|
+
[class]="tooltipClassName()"
|
|
8278
|
+
(mouseenter)="isMouseInside.set(true)"
|
|
8279
|
+
(mouseleave)="isMouseInside.set(false)"
|
|
8280
|
+
(animationend)="onAnimationEnd($event)"
|
|
8281
|
+
>{{ label() }}<span [class]="arrowClassName()"></span
|
|
8282
|
+
></span>`,
|
|
8283
|
+
providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }],
|
|
8284
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8285
|
+
host: {
|
|
8286
|
+
'[class]': 'className()',
|
|
8287
|
+
'(click)': 'onClick($event)',
|
|
8288
|
+
'(mouseenter)': 'mouseenter()',
|
|
8289
|
+
'(mouseleave)': 'mouseleave()',
|
|
8290
|
+
'(focusin)': 'onFocusIn($event)',
|
|
8291
|
+
'(focusout)': 'onFocusOut()',
|
|
8292
|
+
'(keydown.escape)': 'onEscape($event)',
|
|
8293
|
+
},
|
|
8294
|
+
}]
|
|
8295
|
+
}], ctorParameters: () => [], propDecorators: { hoverDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverDelay", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], inlineText: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlineText", required: false }] }], appendToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendToBody", required: false }] }], tipRef: [{ type: i0.ViewChild, args: ['tip', { isSignal: true }] }] } });
|
|
8296
|
+
|
|
7362
8297
|
/**
|
|
7363
8298
|
* Generated bundle index. Do not edit.
|
|
7364
8299
|
*/
|
|
7365
8300
|
|
|
7366
|
-
export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniToggleComponent, UniTooltipComponent, UniWrapComponent, acceptableFile, anchorArrowStyles, anchorStyles, getFileExtension, motionSafe, newAnchorName, resolveFocusTarget, uniqueId, useTimer, visuallyHidden };
|
|
8301
|
+
export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniToggleComponent, UniTooltipComponent, UniWrapComponent, acceptableFile, anchorArrowStyles, anchorStyles, createListboxNavigation, getFileExtension, isDivider, motionSafe, newAnchorName, resolveFocusTarget, uniqueId, useTimer, visuallyHidden };
|
|
7367
8302
|
//# sourceMappingURL=uni-design-system-uni-angular.mjs.map
|