@eagami/ui 5.17.1 → 5.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/eagami-ui.mjs +162 -51
- package/fesm2022/eagami-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_mixins.scss +13 -0
- package/src/styles/_tooltip.scss +10 -1
- package/types/eagami-ui.d.ts +16 -7
package/fesm2022/eagami-ui.mjs
CHANGED
|
@@ -1,44 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer,
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer, inject, CSP_NONCE, signal, effect, computed, Injectable, input, ChangeDetectionStrategy, Component, HostBinding, Directive, model, output, Injector, afterNextRender, DestroyRef, viewChild, ElementRef, HostListener, forwardRef, Renderer2, ViewContainerRef, TemplateRef, untracked, ViewEncapsulation, contentChild, viewChildren, PLATFORM_ID } from '@angular/core';
|
|
3
3
|
import { NgClass, NgComponentOutlet, NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
|
|
4
4
|
import { NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
* language-switcher order: English first (the default fallback), then by each
|
|
9
|
-
* language's own name (Latin scripts first, then Greek, Cyrillic, Hebrew,
|
|
10
|
-
* Arabic, Devanagari, and Chinese by Unicode block order). Keyed by
|
|
11
|
-
* `EagamiLocale` so a locale added to the union without an entry fails to
|
|
12
|
-
* compile, keeping this single source exhaustive.
|
|
13
|
-
*/
|
|
14
|
-
const LOCALE_DISPLAY = {
|
|
15
|
-
en: { label: 'English', flag: '🇬🇧', dir: 'ltr' },
|
|
16
|
-
de: { label: 'Deutsch', flag: '🇩🇪', dir: 'ltr' },
|
|
17
|
-
'es-ES': { label: 'Español', flag: '🇪🇸', dir: 'ltr' },
|
|
18
|
-
'fr-FR': { label: 'Français', flag: '🇫🇷', dir: 'ltr' },
|
|
19
|
-
is: { label: 'Íslenska', flag: '🇮🇸', dir: 'ltr' },
|
|
20
|
-
nl: { label: 'Nederlands', flag: '🇳🇱', dir: 'ltr' },
|
|
21
|
-
pl: { label: 'Polski', flag: '🇵🇱', dir: 'ltr' },
|
|
22
|
-
'pt-BR': { label: 'Português (Brasil)', flag: '🇧🇷', dir: 'ltr' },
|
|
23
|
-
el: { label: 'Ελληνικά', flag: '🇬🇷', dir: 'ltr' },
|
|
24
|
-
ru: { label: 'Русский', flag: '🇷🇺', dir: 'ltr' },
|
|
25
|
-
uk: { label: 'Українська', flag: '🇺🇦', dir: 'ltr' },
|
|
26
|
-
he: { label: 'עברית', flag: '🇮🇱', dir: 'rtl' },
|
|
27
|
-
ar: { label: 'العربية', flag: '🇸🇦', dir: 'rtl' },
|
|
28
|
-
hi: { label: 'हिन्दी', flag: '🇮🇳', dir: 'ltr' },
|
|
29
|
-
'zh-CN': { label: '中文', flag: '🇨🇳', dir: 'ltr' },
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Display metadata for every built-in locale. The single source the Storybook
|
|
33
|
-
* locale toolbar and any consumer-built language switcher derive from, so the
|
|
34
|
-
* displayed list never drifts from the locales the library ships.
|
|
35
|
-
*/
|
|
36
|
-
const EAGAMI_LOCALE_META = Object.keys(LOCALE_DISPLAY).map(locale => ({ locale, ...LOCALE_DISPLAY[locale] }));
|
|
37
|
-
/**
|
|
38
|
-
* Supported locales for language switchers, in display order. Derived from
|
|
39
|
-
* `EAGAMI_LOCALE_META` so the two never drift.
|
|
40
|
-
*/
|
|
41
|
-
const EAGAMI_LOCALES = EAGAMI_LOCALE_META.map(m => m.locale);
|
|
6
|
+
/** DI token carrying the consumer-supplied i18n configuration. */
|
|
7
|
+
const EAGAMI_I18N_CONFIG = new InjectionToken('EAGAMI_I18N_CONFIG');
|
|
42
8
|
|
|
43
9
|
const STYLE_TAG_ID = 'eagami-palette';
|
|
44
10
|
/**
|
|
@@ -51,8 +17,12 @@ const STYLE_TAG_ID = 'eagami-palette';
|
|
|
51
17
|
* Idempotent: subsequent calls replace the previous stylesheet rather than
|
|
52
18
|
* append, so a theme switcher can re-apply without leaking style tags.
|
|
53
19
|
* No-ops outside a DOM context (SSR).
|
|
20
|
+
*
|
|
21
|
+
* Pass `nonce` under a `style-src` policy that requires one, or the browser
|
|
22
|
+
* drops the stylesheet and the app silently renders the library's own colours.
|
|
23
|
+
* `provideEagamiUi()` forwards Angular's `CSP_NONCE` automatically.
|
|
54
24
|
*/
|
|
55
|
-
function applyPalette(palette) {
|
|
25
|
+
function applyPalette(palette, nonce) {
|
|
56
26
|
if (typeof document === 'undefined') {
|
|
57
27
|
return;
|
|
58
28
|
}
|
|
@@ -61,6 +31,10 @@ function applyPalette(palette) {
|
|
|
61
31
|
if (!tag) {
|
|
62
32
|
tag = document.createElement('style');
|
|
63
33
|
tag.id = STYLE_TAG_ID;
|
|
34
|
+
// Before insertion: the policy is checked as the tag enters the document
|
|
35
|
+
if (nonce) {
|
|
36
|
+
tag.setAttribute('nonce', nonce);
|
|
37
|
+
}
|
|
64
38
|
document.head.appendChild(tag);
|
|
65
39
|
}
|
|
66
40
|
tag.textContent = css;
|
|
@@ -347,8 +321,6 @@ function formatViolations(violations) {
|
|
|
347
321
|
].join('\n');
|
|
348
322
|
}
|
|
349
323
|
|
|
350
|
-
/** DI token carrying the consumer-supplied i18n configuration. */
|
|
351
|
-
const EAGAMI_I18N_CONFIG = new InjectionToken('EAGAMI_I18N_CONFIG');
|
|
352
324
|
/**
|
|
353
325
|
* Configures Eagami UI for the application.
|
|
354
326
|
*
|
|
@@ -378,6 +350,10 @@ const EAGAMI_I18N_CONFIG = new InjectionToken('EAGAMI_I18N_CONFIG');
|
|
|
378
350
|
* },
|
|
379
351
|
* });
|
|
380
352
|
* ```
|
|
353
|
+
*
|
|
354
|
+
* A `palette` installs a `<style>` tag at bootstrap, carrying Angular's
|
|
355
|
+
* `CSP_NONCE` when the app provides one, so a strict `style-src` policy admits
|
|
356
|
+
* it instead of dropping the theme.
|
|
381
357
|
*/
|
|
382
358
|
function provideEagamiUi(config = {}) {
|
|
383
359
|
const i18nConfig = {
|
|
@@ -397,11 +373,48 @@ function provideEagamiUi(config = {}) {
|
|
|
397
373
|
if (violations.length > 0) {
|
|
398
374
|
throw new Error(formatViolations(violations));
|
|
399
375
|
}
|
|
400
|
-
applyPalette(palette);
|
|
376
|
+
applyPalette(palette, inject(CSP_NONCE, { optional: true }));
|
|
401
377
|
}),
|
|
402
378
|
]);
|
|
403
379
|
}
|
|
404
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Display name, flag, and reading direction for every built-in locale, in
|
|
383
|
+
* language-switcher order: English first (the default fallback), then by each
|
|
384
|
+
* language's own name (Latin scripts first, then Greek, Cyrillic, Hebrew,
|
|
385
|
+
* Arabic, Devanagari, and Chinese by Unicode block order). Keyed by
|
|
386
|
+
* `EagamiLocale` so a locale added to the union without an entry fails to
|
|
387
|
+
* compile, keeping this single source exhaustive.
|
|
388
|
+
*/
|
|
389
|
+
const LOCALE_DISPLAY = {
|
|
390
|
+
en: { label: 'English', flag: '🇬🇧', dir: 'ltr' },
|
|
391
|
+
de: { label: 'Deutsch', flag: '🇩🇪', dir: 'ltr' },
|
|
392
|
+
'es-ES': { label: 'Español', flag: '🇪🇸', dir: 'ltr' },
|
|
393
|
+
'fr-FR': { label: 'Français', flag: '🇫🇷', dir: 'ltr' },
|
|
394
|
+
is: { label: 'Íslenska', flag: '🇮🇸', dir: 'ltr' },
|
|
395
|
+
nl: { label: 'Nederlands', flag: '🇳🇱', dir: 'ltr' },
|
|
396
|
+
pl: { label: 'Polski', flag: '🇵🇱', dir: 'ltr' },
|
|
397
|
+
'pt-BR': { label: 'Português (Brasil)', flag: '🇧🇷', dir: 'ltr' },
|
|
398
|
+
el: { label: 'Ελληνικά', flag: '🇬🇷', dir: 'ltr' },
|
|
399
|
+
ru: { label: 'Русский', flag: '🇷🇺', dir: 'ltr' },
|
|
400
|
+
uk: { label: 'Українська', flag: '🇺🇦', dir: 'ltr' },
|
|
401
|
+
he: { label: 'עברית', flag: '🇮🇱', dir: 'rtl' },
|
|
402
|
+
ar: { label: 'العربية', flag: '🇸🇦', dir: 'rtl' },
|
|
403
|
+
hi: { label: 'हिन्दी', flag: '🇮🇳', dir: 'ltr' },
|
|
404
|
+
'zh-CN': { label: '中文', flag: '🇨🇳', dir: 'ltr' },
|
|
405
|
+
};
|
|
406
|
+
/**
|
|
407
|
+
* Display metadata for every built-in locale. The single source the Storybook
|
|
408
|
+
* locale toolbar and any consumer-built language switcher derive from, so the
|
|
409
|
+
* displayed list never drifts from the locales the library ships.
|
|
410
|
+
*/
|
|
411
|
+
const EAGAMI_LOCALE_META = Object.keys(LOCALE_DISPLAY).map(locale => ({ locale, ...LOCALE_DISPLAY[locale] }));
|
|
412
|
+
/**
|
|
413
|
+
* Supported locales for language switchers, in display order. Derived from
|
|
414
|
+
* `EAGAMI_LOCALE_META` so the two never drift.
|
|
415
|
+
*/
|
|
416
|
+
const EAGAMI_LOCALES = EAGAMI_LOCALE_META.map(m => m.locale);
|
|
417
|
+
|
|
405
418
|
/**
|
|
406
419
|
* Module-level signal that lets external tooling (notably the Storybook
|
|
407
420
|
* preview decorator) imperatively override the active locale on a running
|
|
@@ -4328,6 +4341,73 @@ function isRtl(element) {
|
|
|
4328
4341
|
return getComputedStyle(element).direction === 'rtl';
|
|
4329
4342
|
}
|
|
4330
4343
|
|
|
4344
|
+
/*
|
|
4345
|
+
* A modal `<dialog>` (`showModal()`) renders in the browser's top layer, which
|
|
4346
|
+
* paints above every z-index in the normal layer. An overlay portaled to
|
|
4347
|
+
* `<body>` therefore disappears behind the modal it was opened from, no matter
|
|
4348
|
+
* how high `--z-index-popover` climbs. The popover API is the only way to join
|
|
4349
|
+
* that layer; `manual` opts out of the UA's light-dismiss and Escape handling
|
|
4350
|
+
* so the owning component keeps full control of dismissal. Top-layer elements
|
|
4351
|
+
* stack in promotion order, so an overlay shown while a modal is open always
|
|
4352
|
+
* lands above it.
|
|
4353
|
+
*
|
|
4354
|
+
* Promotion is deliberately conditional: outside a top-layer container the
|
|
4355
|
+
* existing z-index scale already orders overlays against each other (toasts
|
|
4356
|
+
* above popovers, tooltips above both), and promoting unconditionally would
|
|
4357
|
+
* flatten that scale into "whatever opened last wins".
|
|
4358
|
+
*/
|
|
4359
|
+
const TOP_LAYER_CONTAINER = 'dialog:modal, :popover-open';
|
|
4360
|
+
// `selector()` takes one complex selector, so probing a comma-separated list
|
|
4361
|
+
// parses as invalid and answers false everywhere; test each selector alone
|
|
4362
|
+
let selectorSupport = null;
|
|
4363
|
+
function popoverSupported() {
|
|
4364
|
+
if (typeof HTMLElement === 'undefined' ||
|
|
4365
|
+
typeof HTMLElement.prototype.showPopover !== 'function' ||
|
|
4366
|
+
typeof CSS === 'undefined') {
|
|
4367
|
+
return false;
|
|
4368
|
+
}
|
|
4369
|
+
selectorSupport ??=
|
|
4370
|
+
CSS.supports?.('selector(dialog:modal)') === true &&
|
|
4371
|
+
CSS.supports?.('selector(:popover-open)') === true;
|
|
4372
|
+
return selectorSupport;
|
|
4373
|
+
}
|
|
4374
|
+
function isPromoted(el) {
|
|
4375
|
+
return el.hasAttribute('popover') && el.matches(':popover-open');
|
|
4376
|
+
}
|
|
4377
|
+
/**
|
|
4378
|
+
* Raises `surface` into the top layer when `anchor` sits inside something
|
|
4379
|
+
* that is already there. Callers must do this before measuring the surface: a
|
|
4380
|
+
* `popover` element is `display: none` until shown, so any rect read while it
|
|
4381
|
+
* is still hidden comes back zeroed.
|
|
4382
|
+
*/
|
|
4383
|
+
function enterTopLayer(surface, anchor) {
|
|
4384
|
+
if (!popoverSupported() || !surface.isConnected || isPromoted(surface)) {
|
|
4385
|
+
return;
|
|
4386
|
+
}
|
|
4387
|
+
if (!anchor.closest(TOP_LAYER_CONTAINER)) {
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
surface.setAttribute('popover', 'manual');
|
|
4391
|
+
try {
|
|
4392
|
+
surface.showPopover();
|
|
4393
|
+
}
|
|
4394
|
+
catch {
|
|
4395
|
+
// Refused promotion would leave the surface stuck at the UA's
|
|
4396
|
+
// `display: none`, so drop back to plain z-index stacking
|
|
4397
|
+
surface.removeAttribute('popover');
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
/** Returns `surface` to the normal layer, undoing {@link enterTopLayer}. */
|
|
4401
|
+
function leaveTopLayer(surface) {
|
|
4402
|
+
if (!surface.hasAttribute('popover')) {
|
|
4403
|
+
return;
|
|
4404
|
+
}
|
|
4405
|
+
if (surface.isConnected && isPromoted(surface)) {
|
|
4406
|
+
surface.hidePopover();
|
|
4407
|
+
}
|
|
4408
|
+
surface.removeAttribute('popover');
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4331
4411
|
/** True for cardinal placements that centre the popover on the perpendicular axis. */
|
|
4332
4412
|
function isCardinal(placement) {
|
|
4333
4413
|
return (placement === 'top' ||
|
|
@@ -4620,10 +4700,18 @@ class PopoverComponent {
|
|
|
4620
4700
|
const anchor = this.resolveAnchor();
|
|
4621
4701
|
const isOpen = this.open();
|
|
4622
4702
|
if (!surface || !anchor || !isOpen) {
|
|
4703
|
+
if (surface) {
|
|
4704
|
+
leaveTopLayer(surface);
|
|
4705
|
+
}
|
|
4623
4706
|
this.position.set(null);
|
|
4624
4707
|
this.stable.set(false);
|
|
4625
4708
|
return;
|
|
4626
4709
|
}
|
|
4710
|
+
// Join the top layer before the first measurement below, so a popover
|
|
4711
|
+
// opened from inside a modal is not painted behind it. Promoting first
|
|
4712
|
+
// also gives the surface layout: a `popover` element is `display: none`
|
|
4713
|
+
// until shown, and would measure as a zero-sized box.
|
|
4714
|
+
enterTopLayer(surface, anchor);
|
|
4627
4715
|
// Re-read inputs so signal subscriptions stay current after a re-open
|
|
4628
4716
|
this.placement();
|
|
4629
4717
|
this.offset();
|
|
@@ -4820,14 +4908,17 @@ class PopoverComponent {
|
|
|
4820
4908
|
first.focus();
|
|
4821
4909
|
}
|
|
4822
4910
|
}
|
|
4823
|
-
onEscape() {
|
|
4911
|
+
onEscape(event) {
|
|
4824
4912
|
if (!this.open() || !this.closeOnEscape()) {
|
|
4825
4913
|
return;
|
|
4826
4914
|
}
|
|
4915
|
+
// Consume the key, or a native modal hosting this popover reads the same
|
|
4916
|
+
// Escape as its own close request and both shut at once
|
|
4917
|
+
event.preventDefault();
|
|
4827
4918
|
this.closeRequested.emit();
|
|
4828
4919
|
}
|
|
4829
4920
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4830
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.8", type: PopoverComponent, isStandalone: true, selector: "ea-popover", inputs: { anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, trapFocus: { classPropertyName: "trapFocus", publicName: "trapFocus", isSignal: true, isRequired: false, transformFunction: null }, surfaceId: { classPropertyName: "surfaceId", publicName: "surfaceId", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "flip", isSignal: true, isRequired: false, transformFunction: null }, clamp: { classPropertyName: "clamp", publicName: "clamp", isSignal: true, isRequired: false, transformFunction: null }, matchAnchorWidth: { classPropertyName: "matchAnchorWidth", publicName: "matchAnchorWidth", isSignal: true, isRequired: false, transformFunction: null }, closeOnOutsideClick: { classPropertyName: "closeOnOutsideClick", publicName: "closeOnOutsideClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, scrollBehavior: { classPropertyName: "scrollBehavior", publicName: "scrollBehavior", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" }, properties: { "attr.role": "null", "attr.aria-label": "null", "attr.aria-labelledby": "null" } }, viewQueries: [{ propertyName: "surfaceEl", first: true, predicate: ["surfaceEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n The surface is rendered unconditionally so the `<ng-content/>` slot always\n exists. If we gated it on `@if (open())`, Angular would re-project the\n consumer's content at the popover host's position whenever the surface was\n absent, leaking menu items / picker controls into the document flow (made\n worse by `display: contents` on the host). Hiding via `display: none` keeps\n the projected DOM owned by the surface and out of the flow when closed.\n-->\n<div\n #surfaceEl\n [class]=\"surfaceClass()\"\n [id]=\"surfaceId()\"\n [attr.role]=\"open() ? role() : null\"\n [attr.aria-label]=\"open() ? ariaLabel() : null\"\n [attr.aria-labelledby]=\"open() ? ariaLabelledby() : null\"\n [attr.aria-hidden]=\"open() ? null : true\"\n [style]=\"surfaceStyle()\"\n (keydown)=\"onSurfaceKeydown($event)\">\n <ng-content />\n</div>\n", styles: [":host{display:contents}.ea-popover__surface{z-index:var(--z-index-popover);position:fixed;visibility:hidden;font-family:var(--font-family-sans)}.ea-popover__surface--positioned{visibility:visible}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4921
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.8", type: PopoverComponent, isStandalone: true, selector: "ea-popover", inputs: { anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, trapFocus: { classPropertyName: "trapFocus", publicName: "trapFocus", isSignal: true, isRequired: false, transformFunction: null }, surfaceId: { classPropertyName: "surfaceId", publicName: "surfaceId", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "flip", isSignal: true, isRequired: false, transformFunction: null }, clamp: { classPropertyName: "clamp", publicName: "clamp", isSignal: true, isRequired: false, transformFunction: null }, matchAnchorWidth: { classPropertyName: "matchAnchorWidth", publicName: "matchAnchorWidth", isSignal: true, isRequired: false, transformFunction: null }, closeOnOutsideClick: { classPropertyName: "closeOnOutsideClick", publicName: "closeOnOutsideClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, scrollBehavior: { classPropertyName: "scrollBehavior", publicName: "scrollBehavior", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape($event)" }, properties: { "attr.role": "null", "attr.aria-label": "null", "attr.aria-labelledby": "null" } }, viewQueries: [{ propertyName: "surfaceEl", first: true, predicate: ["surfaceEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n The surface is rendered unconditionally so the `<ng-content/>` slot always\n exists. If we gated it on `@if (open())`, Angular would re-project the\n consumer's content at the popover host's position whenever the surface was\n absent, leaking menu items / picker controls into the document flow (made\n worse by `display: contents` on the host). Hiding via `display: none` keeps\n the projected DOM owned by the surface and out of the flow when closed.\n-->\n<div\n #surfaceEl\n [class]=\"surfaceClass()\"\n [id]=\"surfaceId()\"\n [attr.role]=\"open() ? role() : null\"\n [attr.aria-label]=\"open() ? ariaLabel() : null\"\n [attr.aria-labelledby]=\"open() ? ariaLabelledby() : null\"\n [attr.aria-hidden]=\"open() ? null : true\"\n [style]=\"surfaceStyle()\"\n (keydown)=\"onSurfaceKeydown($event)\">\n <ng-content />\n</div>\n", styles: [":host{display:contents}.ea-popover__surface{z-index:var(--z-index-popover);position:fixed;visibility:hidden;font-family:var(--font-family-sans)}.ea-popover__surface--positioned{visibility:visible}.ea-popover__surface[popover]{inset:auto;overflow:visible;width:auto;height:auto;padding:0;margin:0;border:0;background:none;color:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4831
4922
|
}
|
|
4832
4923
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PopoverComponent, decorators: [{
|
|
4833
4924
|
type: Component,
|
|
@@ -4835,13 +4926,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
4835
4926
|
'[attr.role]': 'null',
|
|
4836
4927
|
'[attr.aria-label]': 'null',
|
|
4837
4928
|
'[attr.aria-labelledby]': 'null',
|
|
4838
|
-
}, template: "<!--\n The surface is rendered unconditionally so the `<ng-content/>` slot always\n exists. If we gated it on `@if (open())`, Angular would re-project the\n consumer's content at the popover host's position whenever the surface was\n absent, leaking menu items / picker controls into the document flow (made\n worse by `display: contents` on the host). Hiding via `display: none` keeps\n the projected DOM owned by the surface and out of the flow when closed.\n-->\n<div\n #surfaceEl\n [class]=\"surfaceClass()\"\n [id]=\"surfaceId()\"\n [attr.role]=\"open() ? role() : null\"\n [attr.aria-label]=\"open() ? ariaLabel() : null\"\n [attr.aria-labelledby]=\"open() ? ariaLabelledby() : null\"\n [attr.aria-hidden]=\"open() ? null : true\"\n [style]=\"surfaceStyle()\"\n (keydown)=\"onSurfaceKeydown($event)\">\n <ng-content />\n</div>\n", styles: [":host{display:contents}.ea-popover__surface{z-index:var(--z-index-popover);position:fixed;visibility:hidden;font-family:var(--font-family-sans)}.ea-popover__surface--positioned{visibility:visible}\n"] }]
|
|
4929
|
+
}, template: "<!--\n The surface is rendered unconditionally so the `<ng-content/>` slot always\n exists. If we gated it on `@if (open())`, Angular would re-project the\n consumer's content at the popover host's position whenever the surface was\n absent, leaking menu items / picker controls into the document flow (made\n worse by `display: contents` on the host). Hiding via `display: none` keeps\n the projected DOM owned by the surface and out of the flow when closed.\n-->\n<div\n #surfaceEl\n [class]=\"surfaceClass()\"\n [id]=\"surfaceId()\"\n [attr.role]=\"open() ? role() : null\"\n [attr.aria-label]=\"open() ? ariaLabel() : null\"\n [attr.aria-labelledby]=\"open() ? ariaLabelledby() : null\"\n [attr.aria-hidden]=\"open() ? null : true\"\n [style]=\"surfaceStyle()\"\n (keydown)=\"onSurfaceKeydown($event)\">\n <ng-content />\n</div>\n", styles: [":host{display:contents}.ea-popover__surface{z-index:var(--z-index-popover);position:fixed;visibility:hidden;font-family:var(--font-family-sans)}.ea-popover__surface--positioned{visibility:visible}.ea-popover__surface[popover]{inset:auto;overflow:visible;width:auto;height:auto;padding:0;margin:0;border:0;background:none;color:inherit}\n"] }]
|
|
4839
4930
|
}], ctorParameters: () => [], propDecorators: { surfaceEl: [{ type: i0.ViewChild, args: ['surfaceEl', { isSignal: true }] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], trapFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "trapFocus", required: false }] }], surfaceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceId", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], flip: [{ type: i0.Input, args: [{ isSignal: true, alias: "flip", required: false }] }], clamp: [{ type: i0.Input, args: [{ isSignal: true, alias: "clamp", required: false }] }], matchAnchorWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchAnchorWidth", required: false }] }], closeOnOutsideClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnOutsideClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], scrollBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollBehavior", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }], onDocumentClick: [{
|
|
4840
4931
|
type: HostListener,
|
|
4841
4932
|
args: ['document:click', ['$event']]
|
|
4842
4933
|
}], onEscape: [{
|
|
4843
4934
|
type: HostListener,
|
|
4844
|
-
args: ['document:keydown.escape']
|
|
4935
|
+
args: ['document:keydown.escape', ['$event']]
|
|
4845
4936
|
}] } });
|
|
4846
4937
|
|
|
4847
4938
|
/**
|
|
@@ -5662,6 +5753,10 @@ class TooltipDirective {
|
|
|
5662
5753
|
this.renderer.setStyle(this.tooltipEl, 'white-space', 'normal');
|
|
5663
5754
|
}
|
|
5664
5755
|
this.renderer.appendChild(document.body, this.tooltipEl);
|
|
5756
|
+
// Before any measuring below: a trigger inside a modal needs its bubble in
|
|
5757
|
+
// the top layer to be visible at all, and a promoted bubble only has layout
|
|
5758
|
+
// once shown.
|
|
5759
|
+
enterTopLayer(this.tooltipEl, this.el.nativeElement);
|
|
5665
5760
|
document.addEventListener('keydown', this.keydownHandler);
|
|
5666
5761
|
this.appendDescribedBy();
|
|
5667
5762
|
this.shrinkToContent();
|
|
@@ -5700,6 +5795,7 @@ class TooltipDirective {
|
|
|
5700
5795
|
}
|
|
5701
5796
|
if (this.tooltipEl) {
|
|
5702
5797
|
document.removeEventListener('keydown', this.keydownHandler);
|
|
5798
|
+
leaveTopLayer(this.tooltipEl);
|
|
5703
5799
|
this.tooltipEl.remove();
|
|
5704
5800
|
this.tooltipEl = null;
|
|
5705
5801
|
this.templateView?.destroy();
|
|
@@ -5805,6 +5901,12 @@ class TooltipDirective {
|
|
|
5805
5901
|
if (underBubble && !this.el.nativeElement.contains(underBubble)) {
|
|
5806
5902
|
let cursor = underBubble;
|
|
5807
5903
|
while (cursor && cursor !== document.body) {
|
|
5904
|
+
/* A fixed / sticky container that also holds the trigger (a modal
|
|
5905
|
+
dialog, a popover surface) is the surface the bubble sits on, not
|
|
5906
|
+
chrome covering it, so stop before mistaking it for an overlay. */
|
|
5907
|
+
if (cursor.contains(this.el.nativeElement)) {
|
|
5908
|
+
break;
|
|
5909
|
+
}
|
|
5808
5910
|
const pos = getComputedStyle(cursor).position;
|
|
5809
5911
|
if (pos === 'fixed' || pos === 'sticky') {
|
|
5810
5912
|
this.hide();
|
|
@@ -5814,8 +5916,8 @@ class TooltipDirective {
|
|
|
5814
5916
|
}
|
|
5815
5917
|
}
|
|
5816
5918
|
}
|
|
5817
|
-
this.renderer.setStyle(this.tooltipEl, 'top', `${top
|
|
5818
|
-
this.renderer.setStyle(this.tooltipEl, 'left', `${left
|
|
5919
|
+
this.renderer.setStyle(this.tooltipEl, 'top', `${top}px`);
|
|
5920
|
+
this.renderer.setStyle(this.tooltipEl, 'left', `${left}px`);
|
|
5819
5921
|
}
|
|
5820
5922
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5821
5923
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: TooltipDirective, isStandalone: true, selector: "[eaTooltip]", inputs: { eaTooltip: { classPropertyName: "eaTooltip", publicName: "eaTooltip", isSignal: true, isRequired: true, transformFunction: null }, tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
@@ -9922,8 +10024,14 @@ class DrawerComponent {
|
|
|
9922
10024
|
this.open.set(false);
|
|
9923
10025
|
this.closed.emit();
|
|
9924
10026
|
}
|
|
9925
|
-
// Non-modal push drawers do not emit `cancel`, so Escape is handled here
|
|
10027
|
+
// Non-modal push drawers do not emit `cancel`, so Escape is handled here.
|
|
10028
|
+
// An overlay inside the drawer (menu, dropdown, picker) consumes the key it
|
|
10029
|
+
// acts on, which a modal drawer honours through `cancel`; respect it here too
|
|
10030
|
+
// so one press never dismisses both the overlay and the drawer.
|
|
9926
10031
|
handleKeydown(event) {
|
|
10032
|
+
if (event.defaultPrevented) {
|
|
10033
|
+
return;
|
|
10034
|
+
}
|
|
9927
10035
|
if (this.mode() === 'push' && this.closeOnEscape() && event.key === 'Escape') {
|
|
9928
10036
|
this.handleClose();
|
|
9929
10037
|
}
|
|
@@ -12501,14 +12609,17 @@ class MenuComponent {
|
|
|
12501
12609
|
this.focusItem(items[next]);
|
|
12502
12610
|
}
|
|
12503
12611
|
}
|
|
12504
|
-
onEscape() {
|
|
12612
|
+
onEscape(event) {
|
|
12505
12613
|
if (!this.open()) {
|
|
12506
12614
|
return;
|
|
12507
12615
|
}
|
|
12616
|
+
// Consume the key, or a native modal hosting this menu reads the same
|
|
12617
|
+
// Escape as its own close request and both shut at once
|
|
12618
|
+
event.preventDefault();
|
|
12508
12619
|
this.close(true);
|
|
12509
12620
|
}
|
|
12510
12621
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
12511
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.8", type: MenuComponent, isStandalone: true, selector: "ea-menu", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", opened: "opened", closed: "closed" }, host: { listeners: { "document:keydown": "onKeydown($event)", "document:keydown.escape": "onEscape()" } }, viewQueries: [{ propertyName: "listEl", first: true, predicate: ["listEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<ea-popover\n [anchor]=\"triggerEl()\"\n [open]=\"open()\"\n [placement]=\"placement()\"\n role=\"menu\"\n [surfaceId]=\"id()\"\n [aria-label]=\"resolvedAriaLabel()\"\n [closeOnEscape]=\"false\"\n scrollBehavior=\"reposition\"\n (closeRequested)=\"onPopoverCloseRequested()\">\n <div\n #listEl\n class=\"ea-menu__list ea-menu__list--{{ size() }}\"\n [style.max-height]=\"maxHeight()\">\n <ng-content />\n </div>\n</ea-popover>\n", styles: [":host{display:contents}.ea-menu__list{min-width:10em;padding:.25em 0;overflow-y:auto;border:var(--border-width-thin) solid var(--color-border-default);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);background-color:var(--ea-menu-list-background-color, var(--color-bg-elevated))}@media(forced-colors:active){.ea-menu__list{border:1px solid CanvasText}}.ea-menu__list--xs{font-size:var(--font-size-xs)}.ea-menu__list--sm{font-size:var(--font-size-sm)}.ea-menu__list--md{font-size:var(--font-size-md)}.ea-menu__list--lg{font-size:var(--font-size-lg)}.ea-menu__list--xl{font-size:var(--font-size-xl)}\n"], dependencies: [{ kind: "component", type: PopoverComponent, selector: "ea-popover", inputs: ["anchor", "open", "placement", "role", "aria-label", "aria-labelledby", "trapFocus", "surfaceId", "offset", "flip", "clamp", "matchAnchorWidth", "closeOnOutsideClick", "closeOnEscape", "scrollBehavior"], outputs: ["closeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12622
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.8", type: MenuComponent, isStandalone: true, selector: "ea-menu", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", opened: "opened", closed: "closed" }, host: { listeners: { "document:keydown": "onKeydown($event)", "document:keydown.escape": "onEscape($event)" } }, viewQueries: [{ propertyName: "listEl", first: true, predicate: ["listEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<ea-popover\n [anchor]=\"triggerEl()\"\n [open]=\"open()\"\n [placement]=\"placement()\"\n role=\"menu\"\n [surfaceId]=\"id()\"\n [aria-label]=\"resolvedAriaLabel()\"\n [closeOnEscape]=\"false\"\n scrollBehavior=\"reposition\"\n (closeRequested)=\"onPopoverCloseRequested()\">\n <div\n #listEl\n class=\"ea-menu__list ea-menu__list--{{ size() }}\"\n [style.max-height]=\"maxHeight()\">\n <ng-content />\n </div>\n</ea-popover>\n", styles: [":host{display:contents}.ea-menu__list{min-width:10em;padding:.25em 0;overflow-y:auto;border:var(--border-width-thin) solid var(--color-border-default);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);background-color:var(--ea-menu-list-background-color, var(--color-bg-elevated))}@media(forced-colors:active){.ea-menu__list{border:1px solid CanvasText}}.ea-menu__list--xs{font-size:var(--font-size-xs)}.ea-menu__list--sm{font-size:var(--font-size-sm)}.ea-menu__list--md{font-size:var(--font-size-md)}.ea-menu__list--lg{font-size:var(--font-size-lg)}.ea-menu__list--xl{font-size:var(--font-size-xl)}\n"], dependencies: [{ kind: "component", type: PopoverComponent, selector: "ea-popover", inputs: ["anchor", "open", "placement", "role", "aria-label", "aria-labelledby", "trapFocus", "surfaceId", "offset", "flip", "clamp", "matchAnchorWidth", "closeOnOutsideClick", "closeOnEscape", "scrollBehavior"], outputs: ["closeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12512
12623
|
}
|
|
12513
12624
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MenuComponent, decorators: [{
|
|
12514
12625
|
type: Component,
|
|
@@ -12518,7 +12629,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
12518
12629
|
args: ['document:keydown', ['$event']]
|
|
12519
12630
|
}], onEscape: [{
|
|
12520
12631
|
type: HostListener,
|
|
12521
|
-
args: ['document:keydown.escape']
|
|
12632
|
+
args: ['document:keydown.escape', ['$event']]
|
|
12522
12633
|
}] } });
|
|
12523
12634
|
|
|
12524
12635
|
/**
|