@eagami/ui 5.29.0 → 5.30.1

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/README.md CHANGED
@@ -126,6 +126,17 @@ Every component adheres to WCAG 2.2 Level AA and follows the matching [WAI-ARIA
126
126
 
127
127
  The library is SSR-safe and renders on the server (Angular Universal / `@angular/ssr`) without reaching for `window` or `document`. Browser-only work (focus management, the native `<dialog>`, overlay positioning, resize/intersection observers) is deferred to the client via `afterNextRender` and `isPlatformBrowser` guards, so prerendering, streaming SSR, and hydration work with no extra configuration.
128
128
 
129
+ ## Testing
130
+
131
+ jsdom implements `<dialog>` without `showModal()` or `close()`, so a unit test that creates a component holding an open `<ea-dialog>` or `<ea-drawer>` throws before the spec body runs. The `@eagami/ui/testing` entry point ships a shim that fills in the missing methods; it patches only what the runtime lacks, so it is a no-op in real browsers. Call it once from your test setup file:
132
+
133
+ ```ts
134
+ // test-setup.ts
135
+ import { installNativeDialogShim } from '@eagami/ui/testing';
136
+
137
+ installNativeDialogShim();
138
+ ```
139
+
129
140
  ## Framework integration
130
141
 
131
142
  `@eagami/ui` is Angular-only, but its design tokens are framework-agnostic. Self-contained copy-and-paste guides and a machine-readable token export are published with the docs and kept in sync with the token source on every release:
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Fills in the native `<dialog>` methods jsdom leaves unimplemented, so a spec
3
+ * can create a component that opens an `ea-dialog` or `ea-drawer` without the
4
+ * test runner throwing on `showModal()` or `close()`. Call once from the test
5
+ * setup file. Only missing methods are patched, so a runtime that implements
6
+ * the element keeps its own behavior.
7
+ */
8
+ function installNativeDialogShim() {
9
+ if (typeof HTMLDialogElement === 'undefined') {
10
+ return;
11
+ }
12
+ const proto = HTMLDialogElement.prototype;
13
+ if (typeof proto.show !== 'function') {
14
+ proto.show = function () {
15
+ this.open = true;
16
+ };
17
+ }
18
+ if (typeof proto.showModal !== 'function') {
19
+ proto.showModal = function () {
20
+ this.open = true;
21
+ };
22
+ }
23
+ if (typeof proto.close !== 'function') {
24
+ proto.close = function (returnValue) {
25
+ if (!this.open) {
26
+ return;
27
+ }
28
+ if (returnValue !== undefined) {
29
+ this.returnValue = returnValue;
30
+ }
31
+ this.open = false;
32
+ this.dispatchEvent(new Event('close'));
33
+ };
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Generated bundle index. Do not edit.
39
+ */
40
+
41
+ export { installNativeDialogShim };
42
+ //# sourceMappingURL=eagami-ui-testing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eagami-ui-testing.mjs","sources":["../../../testing/src/native-dialog.ts","../../../testing/src/eagami-ui-testing.ts"],"sourcesContent":["/**\n * Fills in the native `<dialog>` methods jsdom leaves unimplemented, so a spec\n * can create a component that opens an `ea-dialog` or `ea-drawer` without the\n * test runner throwing on `showModal()` or `close()`. Call once from the test\n * setup file. Only missing methods are patched, so a runtime that implements\n * the element keeps its own behavior.\n */\nexport function installNativeDialogShim(): void {\n if (typeof HTMLDialogElement === 'undefined') {\n return;\n }\n const proto = HTMLDialogElement.prototype;\n if (typeof proto.show !== 'function') {\n proto.show = function (this: HTMLDialogElement): void {\n this.open = true;\n };\n }\n if (typeof proto.showModal !== 'function') {\n proto.showModal = function (this: HTMLDialogElement): void {\n this.open = true;\n };\n }\n if (typeof proto.close !== 'function') {\n proto.close = function (this: HTMLDialogElement, returnValue?: string): void {\n if (!this.open) {\n return;\n }\n if (returnValue !== undefined) {\n this.returnValue = returnValue;\n }\n this.open = false;\n this.dispatchEvent(new Event('close'));\n };\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"AAAA;;;;;;AAMG;SACa,uBAAuB,GAAA;AACrC,IAAA,IAAI,OAAO,iBAAiB,KAAK,WAAW,EAAE;QAC5C;IACF;AACA,IAAA,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS;AACzC,IAAA,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE;QACpC,KAAK,CAAC,IAAI,GAAG,YAAA;AACX,YAAA,IAAI,CAAC,IAAI,GAAG,IAAI;AAClB,QAAA,CAAC;IACH;AACA,IAAA,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,UAAU,EAAE;QACzC,KAAK,CAAC,SAAS,GAAG,YAAA;AAChB,YAAA,IAAI,CAAC,IAAI,GAAG,IAAI;AAClB,QAAA,CAAC;IACH;AACA,IAAA,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE;AACrC,QAAA,KAAK,CAAC,KAAK,GAAG,UAAmC,WAAoB,EAAA;AACnE,YAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBACd;YACF;AACA,YAAA,IAAI,WAAW,KAAK,SAAS,EAAE;AAC7B,gBAAA,IAAI,CAAC,WAAW,GAAG,WAAW;YAChC;AACA,YAAA,IAAI,CAAC,IAAI,GAAG,KAAK;YACjB,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AACxC,QAAA,CAAC;IACH;AACF;;AClCA;;AAEG;;;;"}
@@ -4767,6 +4767,12 @@ class PopoverComponent {
4767
4767
  this.latched = null;
4768
4768
  return;
4769
4769
  }
4770
+ // The closed state's `display: none` is still on the element: this
4771
+ // effect runs before the binding carrying `surfaceStyle()` reaches it.
4772
+ // `showPopover()` refuses an element with no layout box, and a refused
4773
+ // promotion leaves the surface portaled to `<body>`, outside the modal
4774
+ // and therefore inert, so it is cleared here rather than a frame later.
4775
+ surface.style.removeProperty('display');
4770
4776
  // Join the top layer before the first measurement below, so a popover
4771
4777
  // opened from inside a modal is not painted behind it. Promoting first
4772
4778
  // also gives the surface layout: a `popover` element is `display: none`
@@ -5954,19 +5960,18 @@ class TooltipDirective {
5954
5960
  return;
5955
5961
  }
5956
5962
  const style = getComputedStyle(this.tooltipEl);
5957
- const contentWidth = parseFloat(style.width);
5958
- if (!Number.isFinite(contentWidth)) {
5963
+ /* Whatever the border box holds beyond its content box: padding, borders,
5964
+ and a scrollbar if the height clamp put one there. Summed from the
5965
+ fractional longhands, since WebKit resolves computed `width` to the
5966
+ border box under `box-sizing: border-box`. Only the scrollbar goes
5967
+ through the whole-pixel `offsetWidth - clientWidth`. */
5968
+ const paddings = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
5969
+ const borders = parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
5970
+ const scrollbar = Math.max(0, this.tooltipEl.offsetWidth - this.tooltipEl.clientWidth - borders);
5971
+ const horizontalChrome = style.boxSizing === 'border-box' ? paddings + borders + scrollbar : scrollbar;
5972
+ if (!Number.isFinite(horizontalChrome)) {
5959
5973
  return;
5960
5974
  }
5961
- /* Whatever the border box holds beyond its content box: padding, borders,
5962
- and a scrollbar if the height clamp put one there. Read as the difference
5963
- between two fractional widths; `offsetWidth` and `clientWidth` round to
5964
- whole pixels, and under a page zoom the box sits on fractional ones, so
5965
- rounded arithmetic pins the bubble a hair narrower than its own text and
5966
- the last word drops to a second line inside a box sized for one. */
5967
- const horizontalChrome = style.boxSizing === 'border-box'
5968
- ? this.tooltipEl.getBoundingClientRect().width - contentWidth
5969
- : 0;
5970
5975
  this.renderer.setStyle(this.tooltipEl, 'width', `${Math.ceil(textWidth + horizontalChrome)}px`);
5971
5976
  }
5972
5977
  /* A scrollable bubble has to be reachable, so leaving the trigger only
@@ -10713,6 +10718,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
10713
10718
  * for browser-managed focus trapping, supports backdrop and Escape dismissal,
10714
10719
  * and exposes `header`, default, and `footer` content slots. The `open` state
10715
10720
  * is a two-way `model()` binding.
10721
+ *
10722
+ * The header is a row that lays its slot content out against the built-in
10723
+ * close button, and it applies the h4 type scale to that content, so a heading
10724
+ * element projected into it should carry `font: inherit`. Panel chrome is
10725
+ * tunable per dialog via `--ea-dialog-inset`, `--ea-dialog-radius`,
10726
+ * `--ea-dialog-max-height`, `--ea-dialog-header-padding`,
10727
+ * `--ea-dialog-body-padding`, and `--ea-dialog-status-padding`; an
10728
+ * edge-to-edge media surface zeroes the inset, radius, and paddings.
10729
+ *
10730
+ * Specs running under jsdom need `installNativeDialogShim()` from
10731
+ * `@eagami/ui/testing` before opening one.
10716
10732
  */
10717
10733
  class DialogComponent {
10718
10734
  dialogEl = viewChild('dialogEl', /* @ts-ignore */
@@ -10832,11 +10848,11 @@ class DialogComponent {
10832
10848
  this.handleClose();
10833
10849
  }
10834
10850
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10835
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: DialogComponent, isStandalone: true, selector: "ea-dialog", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdrop: { classPropertyName: "closeOnBackdrop", publicName: "closeOnBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, showClose: { classPropertyName: "showClose", publicName: "showClose", isSignal: true, isRequired: false, transformFunction: null }, closeDisabled: { classPropertyName: "closeDisabled", publicName: "closeDisabled", isSignal: true, isRequired: false, transformFunction: null }, manualClose: { classPropertyName: "manualClose", publicName: "manualClose", 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", closeRequested: "closeRequested" }, viewQueries: [{ propertyName: "dialogEl", first: true, predicate: ["dialogEl"], descendants: true, isSignal: true }, { propertyName: "headerEl", first: true, predicate: ["headerEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialogEl\n class=\"ea-dialog\"\n [id]=\"id()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"!ariaLabel() && hasHeader() ? id() + '-header' : null\"\n (click)=\"handleBackdropClick($event)\"\n (cancel)=\"handleCancel($event)\">\n <div\n class=\"ea-dialog__panel\"\n [ngClass]=\"panelClasses()\">\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"ea-dialog__close\"\n [attr.aria-label]=\"i18n.messages().dialog.close\"\n [disabled]=\"closeDisabled()\"\n (click)=\"requestClose()\">\n <ea-icon-x />\n </button>\n }\n\n <div\n #headerEl\n class=\"ea-dialog__header\"\n [id]=\"id() + '-header'\">\n <ng-content select=\"[slot=header]\" />\n </div>\n\n <div class=\"ea-dialog__body\">\n <ng-content />\n </div>\n\n <div class=\"ea-dialog__status\">\n <ng-content select=\"[slot=status]\" />\n </div>\n\n <div class=\"ea-dialog__footer\">\n <ng-content select=\"[slot=footer]\" />\n </div>\n </div>\n</dialog>\n", styles: [".ea-dialog{border:none;background:transparent;padding:0;margin:auto;max-width:none;max-height:none;overflow:visible}.ea-dialog::backdrop{background-color:var(--color-bg-overlay)}.ea-dialog__panel{position:relative;display:flex;flex-direction:column;width:var(--ea-dialog-size, 32rem);max-width:calc(100vw - 2rem);background-color:var(--ea-dialog-panel-background-color, var(--color-bg-elevated));border-radius:var(--radius-xl);box-shadow:var(--shadow-2xl);max-height:85vh;overflow:hidden}@media(forced-colors:active){.ea-dialog__panel{border:1px solid CanvasText}}.ea-dialog__panel--2xs{--ea-dialog-size: 16rem}.ea-dialog__panel--xs{--ea-dialog-size: 20rem}.ea-dialog__panel--sm{--ea-dialog-size: 24rem}.ea-dialog__panel--md{--ea-dialog-size: 32rem}.ea-dialog__panel--lg{--ea-dialog-size: 42rem}.ea-dialog__panel--xl{--ea-dialog-size: 52rem}.ea-dialog__panel--2xl{--ea-dialog-size: 64rem}.ea-dialog__panel--full{width:calc(100vw - 2rem);height:calc(100vh - 2rem);max-height:calc(100vh - 2rem)}.ea-dialog__panel:not(:has(>.ea-dialog__header:not(:empty)))>.ea-dialog__body{padding-top:var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty)))>.ea-dialog__status:not(:empty){padding-bottom:var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty),>.ea-dialog__status:not(:empty)))>.ea-dialog__body{padding-bottom:var(--space-5)}.ea-dialog__close{position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--ea-icon-button-size, 1.75em);height:var(--ea-icon-button-size, 1.75em);padding:0;border:none;border-radius:var(--radius-sm);background:none;color:var(--color-text-secondary);cursor:pointer;transition:var(--transition-colors)}.ea-dialog__close>*{font-size:1.25em}.ea-dialog__close:after{content:\"\";position:absolute;top:50%;left:50%;width:max(100%,var(--ea-icon-button-target, 24px));height:max(100%,var(--ea-icon-button-target, 24px));transform:translate(-50%,-50%)}.ea-dialog__close:hover{background-color:var(--color-state-hover);color:var(--color-text-primary)}.ea-dialog__close:focus-visible{outline:none;box-shadow:var(--shadow-focus-ring)}@media(forced-colors:active){.ea-dialog__close:focus-visible{outline:2px solid Highlight;outline-offset:2px}}.ea-dialog__close:disabled{cursor:not-allowed;opacity:.5}.ea-dialog__close{z-index:1;position:absolute;top:var(--space-3);inset-inline-end:var(--space-3)}.ea-dialog__header{padding:var(--space-5) var(--space-6) var(--space-3);font-size:var(--ea-dialog-header-font-size, var(--text-h4-size));font-weight:var(--ea-dialog-header-font-weight, var(--text-h4-weight));line-height:var(--text-h4-lh);color:var(--color-text-primary)}.ea-dialog__header:empty{display:none}.ea-dialog__body{flex:1;padding:var(--space-3) var(--space-6);overflow-y:auto;font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status{flex-shrink:0;padding:var(--space-2) var(--space-6);font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status:empty{display:none}.ea-dialog__footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-3);padding:var(--space-3) var(--space-6) var(--space-5)}.ea-dialog__footer>*{display:flex;align-items:center;gap:var(--space-3)}.ea-dialog__footer:empty{display:none}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: XIconComponent, selector: "ea-icon-x" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10851
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: DialogComponent, isStandalone: true, selector: "ea-dialog", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdrop: { classPropertyName: "closeOnBackdrop", publicName: "closeOnBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, showClose: { classPropertyName: "showClose", publicName: "showClose", isSignal: true, isRequired: false, transformFunction: null }, closeDisabled: { classPropertyName: "closeDisabled", publicName: "closeDisabled", isSignal: true, isRequired: false, transformFunction: null }, manualClose: { classPropertyName: "manualClose", publicName: "manualClose", 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", closeRequested: "closeRequested" }, viewQueries: [{ propertyName: "dialogEl", first: true, predicate: ["dialogEl"], descendants: true, isSignal: true }, { propertyName: "headerEl", first: true, predicate: ["headerEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialogEl\n class=\"ea-dialog\"\n [id]=\"id()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"!ariaLabel() && hasHeader() ? id() + '-header' : null\"\n (click)=\"handleBackdropClick($event)\"\n (cancel)=\"handleCancel($event)\">\n <div\n class=\"ea-dialog__panel\"\n [ngClass]=\"panelClasses()\">\n <div\n class=\"ea-dialog__header\"\n [class.ea-dialog__header--hidden]=\"!hasHeader() && !showClose()\">\n <div\n #headerEl\n class=\"ea-dialog__header-content\"\n [id]=\"id() + '-header'\">\n <ng-content select=\"[slot=header]\" />\n </div>\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"ea-dialog__close\"\n [attr.aria-label]=\"i18n.messages().dialog.close\"\n [disabled]=\"closeDisabled()\"\n (click)=\"requestClose()\">\n <ea-icon-x />\n </button>\n }\n </div>\n\n <div class=\"ea-dialog__body\">\n <ng-content />\n </div>\n\n <div class=\"ea-dialog__status\">\n <ng-content select=\"[slot=status]\" />\n </div>\n\n <div class=\"ea-dialog__footer\">\n <ng-content select=\"[slot=footer]\" />\n </div>\n </div>\n</dialog>\n", styles: [".ea-dialog{border:none;background:transparent;padding:0;margin:auto;max-width:none;max-height:none;overflow:visible}.ea-dialog::backdrop{background-color:var(--color-bg-overlay)}.ea-dialog__panel{--_ea-dialog-inset: var(--ea-dialog-inset, var(--space-4));position:relative;display:flex;flex-direction:column;width:var(--ea-dialog-size, 32rem);max-width:calc(100vw - 2 * var(--_ea-dialog-inset));max-height:var(--ea-dialog-max-height, 85vh);background-color:var(--ea-dialog-panel-background-color, var(--color-bg-elevated));border-radius:var(--ea-dialog-radius, var(--radius-xl));box-shadow:var(--shadow-2xl);overflow:hidden}@media(forced-colors:active){.ea-dialog__panel{border:1px solid CanvasText}}.ea-dialog__panel--2xs{--ea-dialog-size: 16rem}.ea-dialog__panel--xs{--ea-dialog-size: 20rem}.ea-dialog__panel--sm{--ea-dialog-size: 24rem}.ea-dialog__panel--md{--ea-dialog-size: 32rem}.ea-dialog__panel--lg{--ea-dialog-size: 42rem}.ea-dialog__panel--xl{--ea-dialog-size: 52rem}.ea-dialog__panel--2xl{--ea-dialog-size: 64rem}.ea-dialog__panel--full{width:calc(100vw - 2 * var(--_ea-dialog-inset));height:calc(100vh - 2 * var(--_ea-dialog-inset));max-height:var(--ea-dialog-max-height, calc(100vh - 2 * var(--_ea-dialog-inset)))}.ea-dialog__panel:has(>.ea-dialog__header--hidden)>.ea-dialog__body{--_ea-dialog-body-padding-top: var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty)))>.ea-dialog__status{--_ea-dialog-status-padding-bottom: var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty),>.ea-dialog__status:not(:empty)))>.ea-dialog__body{--_ea-dialog-body-padding-bottom: var(--space-5)}.ea-dialog__close{position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--ea-icon-button-size, 1.75em);height:var(--ea-icon-button-size, 1.75em);padding:0;border:none;border-radius:var(--radius-sm);background:none;color:var(--color-text-secondary);cursor:pointer;transition:var(--transition-colors)}.ea-dialog__close>*{font-size:1.25em}.ea-dialog__close:after{content:\"\";position:absolute;top:50%;left:50%;width:max(100%,var(--ea-icon-button-target, 24px));height:max(100%,var(--ea-icon-button-target, 24px));transform:translate(-50%,-50%)}.ea-dialog__close:hover{background-color:var(--color-state-hover);color:var(--color-text-primary)}.ea-dialog__close:focus-visible{outline:none;box-shadow:var(--shadow-focus-ring)}@media(forced-colors:active){.ea-dialog__close:focus-visible{outline:2px solid Highlight;outline-offset:2px}}.ea-dialog__close:disabled{cursor:not-allowed;opacity:.5}.ea-dialog__header{display:flex;align-items:flex-start;gap:var(--space-3);padding:var(--ea-dialog-header-padding, var(--space-5) var(--space-6) var(--space-3))}.ea-dialog__header--hidden{display:none}.ea-dialog__header-content{flex:1;min-width:0;font-size:var(--ea-dialog-header-font-size, var(--text-h4-size));font-weight:var(--ea-dialog-header-font-weight, var(--text-h4-weight));line-height:var(--text-h4-lh);color:var(--color-text-primary)}.ea-dialog__body{flex:1;padding:var(--ea-dialog-body-padding, var(--_ea-dialog-body-padding-top, var(--space-3)) var(--space-6) var(--_ea-dialog-body-padding-bottom, var(--space-3)));overflow-y:auto;font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status{flex-shrink:0;padding:var(--ea-dialog-status-padding, var(--space-2) var(--space-6) var(--_ea-dialog-status-padding-bottom, var(--space-2)));font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status:empty{display:none}.ea-dialog__footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-3);padding:var(--space-3) var(--space-6) var(--space-5)}.ea-dialog__footer>*{display:flex;align-items:center;gap:var(--space-3)}.ea-dialog__footer:empty{display:none}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: XIconComponent, selector: "ea-icon-x" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10836
10852
  }
10837
10853
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DialogComponent, decorators: [{
10838
10854
  type: Component,
10839
- args: [{ selector: 'ea-dialog', imports: [NgClass, XIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<dialog\n #dialogEl\n class=\"ea-dialog\"\n [id]=\"id()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"!ariaLabel() && hasHeader() ? id() + '-header' : null\"\n (click)=\"handleBackdropClick($event)\"\n (cancel)=\"handleCancel($event)\">\n <div\n class=\"ea-dialog__panel\"\n [ngClass]=\"panelClasses()\">\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"ea-dialog__close\"\n [attr.aria-label]=\"i18n.messages().dialog.close\"\n [disabled]=\"closeDisabled()\"\n (click)=\"requestClose()\">\n <ea-icon-x />\n </button>\n }\n\n <div\n #headerEl\n class=\"ea-dialog__header\"\n [id]=\"id() + '-header'\">\n <ng-content select=\"[slot=header]\" />\n </div>\n\n <div class=\"ea-dialog__body\">\n <ng-content />\n </div>\n\n <div class=\"ea-dialog__status\">\n <ng-content select=\"[slot=status]\" />\n </div>\n\n <div class=\"ea-dialog__footer\">\n <ng-content select=\"[slot=footer]\" />\n </div>\n </div>\n</dialog>\n", styles: [".ea-dialog{border:none;background:transparent;padding:0;margin:auto;max-width:none;max-height:none;overflow:visible}.ea-dialog::backdrop{background-color:var(--color-bg-overlay)}.ea-dialog__panel{position:relative;display:flex;flex-direction:column;width:var(--ea-dialog-size, 32rem);max-width:calc(100vw - 2rem);background-color:var(--ea-dialog-panel-background-color, var(--color-bg-elevated));border-radius:var(--radius-xl);box-shadow:var(--shadow-2xl);max-height:85vh;overflow:hidden}@media(forced-colors:active){.ea-dialog__panel{border:1px solid CanvasText}}.ea-dialog__panel--2xs{--ea-dialog-size: 16rem}.ea-dialog__panel--xs{--ea-dialog-size: 20rem}.ea-dialog__panel--sm{--ea-dialog-size: 24rem}.ea-dialog__panel--md{--ea-dialog-size: 32rem}.ea-dialog__panel--lg{--ea-dialog-size: 42rem}.ea-dialog__panel--xl{--ea-dialog-size: 52rem}.ea-dialog__panel--2xl{--ea-dialog-size: 64rem}.ea-dialog__panel--full{width:calc(100vw - 2rem);height:calc(100vh - 2rem);max-height:calc(100vh - 2rem)}.ea-dialog__panel:not(:has(>.ea-dialog__header:not(:empty)))>.ea-dialog__body{padding-top:var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty)))>.ea-dialog__status:not(:empty){padding-bottom:var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty),>.ea-dialog__status:not(:empty)))>.ea-dialog__body{padding-bottom:var(--space-5)}.ea-dialog__close{position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--ea-icon-button-size, 1.75em);height:var(--ea-icon-button-size, 1.75em);padding:0;border:none;border-radius:var(--radius-sm);background:none;color:var(--color-text-secondary);cursor:pointer;transition:var(--transition-colors)}.ea-dialog__close>*{font-size:1.25em}.ea-dialog__close:after{content:\"\";position:absolute;top:50%;left:50%;width:max(100%,var(--ea-icon-button-target, 24px));height:max(100%,var(--ea-icon-button-target, 24px));transform:translate(-50%,-50%)}.ea-dialog__close:hover{background-color:var(--color-state-hover);color:var(--color-text-primary)}.ea-dialog__close:focus-visible{outline:none;box-shadow:var(--shadow-focus-ring)}@media(forced-colors:active){.ea-dialog__close:focus-visible{outline:2px solid Highlight;outline-offset:2px}}.ea-dialog__close:disabled{cursor:not-allowed;opacity:.5}.ea-dialog__close{z-index:1;position:absolute;top:var(--space-3);inset-inline-end:var(--space-3)}.ea-dialog__header{padding:var(--space-5) var(--space-6) var(--space-3);font-size:var(--ea-dialog-header-font-size, var(--text-h4-size));font-weight:var(--ea-dialog-header-font-weight, var(--text-h4-weight));line-height:var(--text-h4-lh);color:var(--color-text-primary)}.ea-dialog__header:empty{display:none}.ea-dialog__body{flex:1;padding:var(--space-3) var(--space-6);overflow-y:auto;font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status{flex-shrink:0;padding:var(--space-2) var(--space-6);font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status:empty{display:none}.ea-dialog__footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-3);padding:var(--space-3) var(--space-6) var(--space-5)}.ea-dialog__footer>*{display:flex;align-items:center;gap:var(--space-3)}.ea-dialog__footer:empty{display:none}\n"] }]
10855
+ args: [{ selector: 'ea-dialog', imports: [NgClass, XIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<dialog\n #dialogEl\n class=\"ea-dialog\"\n [id]=\"id()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"!ariaLabel() && hasHeader() ? id() + '-header' : null\"\n (click)=\"handleBackdropClick($event)\"\n (cancel)=\"handleCancel($event)\">\n <div\n class=\"ea-dialog__panel\"\n [ngClass]=\"panelClasses()\">\n <div\n class=\"ea-dialog__header\"\n [class.ea-dialog__header--hidden]=\"!hasHeader() && !showClose()\">\n <div\n #headerEl\n class=\"ea-dialog__header-content\"\n [id]=\"id() + '-header'\">\n <ng-content select=\"[slot=header]\" />\n </div>\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"ea-dialog__close\"\n [attr.aria-label]=\"i18n.messages().dialog.close\"\n [disabled]=\"closeDisabled()\"\n (click)=\"requestClose()\">\n <ea-icon-x />\n </button>\n }\n </div>\n\n <div class=\"ea-dialog__body\">\n <ng-content />\n </div>\n\n <div class=\"ea-dialog__status\">\n <ng-content select=\"[slot=status]\" />\n </div>\n\n <div class=\"ea-dialog__footer\">\n <ng-content select=\"[slot=footer]\" />\n </div>\n </div>\n</dialog>\n", styles: [".ea-dialog{border:none;background:transparent;padding:0;margin:auto;max-width:none;max-height:none;overflow:visible}.ea-dialog::backdrop{background-color:var(--color-bg-overlay)}.ea-dialog__panel{--_ea-dialog-inset: var(--ea-dialog-inset, var(--space-4));position:relative;display:flex;flex-direction:column;width:var(--ea-dialog-size, 32rem);max-width:calc(100vw - 2 * var(--_ea-dialog-inset));max-height:var(--ea-dialog-max-height, 85vh);background-color:var(--ea-dialog-panel-background-color, var(--color-bg-elevated));border-radius:var(--ea-dialog-radius, var(--radius-xl));box-shadow:var(--shadow-2xl);overflow:hidden}@media(forced-colors:active){.ea-dialog__panel{border:1px solid CanvasText}}.ea-dialog__panel--2xs{--ea-dialog-size: 16rem}.ea-dialog__panel--xs{--ea-dialog-size: 20rem}.ea-dialog__panel--sm{--ea-dialog-size: 24rem}.ea-dialog__panel--md{--ea-dialog-size: 32rem}.ea-dialog__panel--lg{--ea-dialog-size: 42rem}.ea-dialog__panel--xl{--ea-dialog-size: 52rem}.ea-dialog__panel--2xl{--ea-dialog-size: 64rem}.ea-dialog__panel--full{width:calc(100vw - 2 * var(--_ea-dialog-inset));height:calc(100vh - 2 * var(--_ea-dialog-inset));max-height:var(--ea-dialog-max-height, calc(100vh - 2 * var(--_ea-dialog-inset)))}.ea-dialog__panel:has(>.ea-dialog__header--hidden)>.ea-dialog__body{--_ea-dialog-body-padding-top: var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty)))>.ea-dialog__status{--_ea-dialog-status-padding-bottom: var(--space-5)}.ea-dialog__panel:not(:has(>.ea-dialog__footer:not(:empty),>.ea-dialog__status:not(:empty)))>.ea-dialog__body{--_ea-dialog-body-padding-bottom: var(--space-5)}.ea-dialog__close{position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--ea-icon-button-size, 1.75em);height:var(--ea-icon-button-size, 1.75em);padding:0;border:none;border-radius:var(--radius-sm);background:none;color:var(--color-text-secondary);cursor:pointer;transition:var(--transition-colors)}.ea-dialog__close>*{font-size:1.25em}.ea-dialog__close:after{content:\"\";position:absolute;top:50%;left:50%;width:max(100%,var(--ea-icon-button-target, 24px));height:max(100%,var(--ea-icon-button-target, 24px));transform:translate(-50%,-50%)}.ea-dialog__close:hover{background-color:var(--color-state-hover);color:var(--color-text-primary)}.ea-dialog__close:focus-visible{outline:none;box-shadow:var(--shadow-focus-ring)}@media(forced-colors:active){.ea-dialog__close:focus-visible{outline:2px solid Highlight;outline-offset:2px}}.ea-dialog__close:disabled{cursor:not-allowed;opacity:.5}.ea-dialog__header{display:flex;align-items:flex-start;gap:var(--space-3);padding:var(--ea-dialog-header-padding, var(--space-5) var(--space-6) var(--space-3))}.ea-dialog__header--hidden{display:none}.ea-dialog__header-content{flex:1;min-width:0;font-size:var(--ea-dialog-header-font-size, var(--text-h4-size));font-weight:var(--ea-dialog-header-font-weight, var(--text-h4-weight));line-height:var(--text-h4-lh);color:var(--color-text-primary)}.ea-dialog__body{flex:1;padding:var(--ea-dialog-body-padding, var(--_ea-dialog-body-padding-top, var(--space-3)) var(--space-6) var(--_ea-dialog-body-padding-bottom, var(--space-3)));overflow-y:auto;font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status{flex-shrink:0;padding:var(--ea-dialog-status-padding, var(--space-2) var(--space-6) var(--_ea-dialog-status-padding-bottom, var(--space-2)));font-size:var(--font-size-sm);line-height:var(--line-height-normal);color:var(--color-text-secondary)}.ea-dialog__status:empty{display:none}.ea-dialog__footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-3);padding:var(--space-3) var(--space-6) var(--space-5)}.ea-dialog__footer>*{display:flex;align-items:center;gap:var(--space-3)}.ea-dialog__footer:empty{display:none}\n"] }]
10840
10856
  }], ctorParameters: () => [], propDecorators: { dialogEl: [{ type: i0.ViewChild, args: ['dialogEl', { isSignal: true }] }], headerEl: [{ type: i0.ViewChild, args: ['headerEl', { isSignal: true }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closeOnBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdrop", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], showClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClose", required: false }] }], closeDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeDisabled", required: false }] }], manualClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "manualClose", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
10841
10857
 
10842
10858
  // The padding side, per position, applied to the push target so its content
@@ -10858,6 +10874,9 @@ const PUSH_PROPERTIES = Object.values(PUSH_PROPERTY);
10858
10874
  * Escape dismissal, and exposes `header`, default, and `footer` content
10859
10875
  * slots. The `open` state is a two-way `model()` binding. In `push` mode it
10860
10876
  * opens non-modally and reflows the page content aside instead of overlaying it.
10877
+ *
10878
+ * Specs running under jsdom need `installNativeDialogShim()` from
10879
+ * `@eagami/ui/testing` before opening one.
10861
10880
  */
10862
10881
  class DrawerComponent {
10863
10882
  drawerEl = viewChild('drawerEl', /* @ts-ignore */
@@ -13447,8 +13466,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
13447
13466
  * accessible name of that button is configurable via `removeLabel` and
13448
13467
  * otherwise falls back to the active locale's translation.
13449
13468
  *
13450
- * The projected label is capped by `maxWidth` (or the `--ea-tag-max-width`
13451
- * custom property) and ellipsizes rather than widening the chip, revealing its
13469
+ * When capped by `maxWidth` (or the `--ea-tag-max-width` custom property),
13470
+ * the projected label ellipsizes rather than widening the chip, revealing its
13452
13471
  * full text in a tooltip while it is clipped.
13453
13472
  */
13454
13473
  class TagComponent {
@@ -13471,7 +13490,7 @@ class TagComponent {
13471
13490
  * has to give way to its container (`'100%'` in a responsive grid cell).
13472
13491
  * Also settable in CSS as `--ea-tag-max-width`, which this input overrides.
13473
13492
  */
13474
- maxWidth = input(200, /* @ts-ignore */
13493
+ maxWidth = input(undefined, /* @ts-ignore */
13475
13494
  ...(ngDevMode ? [{ debugName: "maxWidth" }] : /* istanbul ignore next */ []));
13476
13495
  /**
13477
13496
  * Fill colour as a hex value, for a tag carrying a colour of its own (a