@uni-design-system/uni-angular 10.2.1 → 10.4.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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { signal, inject, DestroyRef, computed, linkedSignal, resource, Injectable, InjectionToken, input, ChangeDetectionStrategy, Component, ElementRef, Directive, model, afterRenderEffect, Renderer2, output, viewChild, ViewChild, viewChildren, effect, untracked, isDevMode, contentChildren, afterNextRender, forwardRef, booleanAttribute } from '@angular/core';
3
3
  import { injectGlobal, css, keyframes } from '@emotion/css';
4
4
  import { UniThemes, LightTheme, toTypefaces, parseTheme, formatThemeIssues, hydrateTheme, createThemeFromPalette, Z_INDEX, removeInputPlatformStyling, fadeIn, fadeOut, expandDuration, expandFadeIn, collapseFadeOut, HOVER_OR_KEYBOARD_FOCUS, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
5
- import { NgTemplateOutlet, NgClass, CommonModule } from '@angular/common';
5
+ import { NgTemplateOutlet, NgClass } from '@angular/common';
6
6
 
7
7
  let nextUniqueId = 0;
8
8
  /**
@@ -2042,12 +2042,16 @@ class ThemeService {
2042
2042
  // `?? {}` for themes registered as JSON that predate the motion scale —
2043
2043
  // the validator does not require it, so they must not crash on read.
2044
2044
  motions = computed(() => this.theme().motion ?? {}, ...(ngDevMode ? [{ debugName: "motions" }] : /* istanbul ignore next */ []));
2045
+ /** Same story as `motions`: a JSON theme may predate the backdrops scale. */
2046
+ backdrops = computed(() => this.theme().backdrops ?? {}, ...(ngDevMode ? [{ debugName: "backdrops" }] : /* istanbul ignore next */ []));
2045
2047
  constructor() {
2046
2048
  // Honor the user's reduced-motion preference across every component
2047
2049
  // (WCAG 2.3.3): collapse all animations/transitions to a single frame.
2048
2050
  injectGlobal `
2049
2051
  @media (prefers-reduced-motion: reduce) {
2050
- *, *::before, *::after {
2052
+ /* ::backdrop is not a descendant of anything, so the universal
2053
+ selector alone never reaches the scrim a <dialog> fades in. */
2054
+ *, *::before, *::after, *::backdrop {
2051
2055
  animation-duration: 0.01ms !important;
2052
2056
  animation-iteration-count: 1 !important;
2053
2057
  transition-duration: 0.01ms !important;
@@ -2356,6 +2360,35 @@ class ThemeService {
2356
2360
  return ((token ? motions[token] : undefined) ??
2357
2361
  motions['popup'] ?? { duration: 100, easing: 'linear', scale: 0.8 });
2358
2362
  }
2363
+ warnedBackdrops = new Set();
2364
+ /**
2365
+ * Resolves a named backdrop primitive — the wash a modal surface lays over
2366
+ * the page — so every such surface dims it the same way.
2367
+ *
2368
+ * A raw style object passes straight through: that is the shape the dialog's
2369
+ * and drawer's `backdrop` options took before the scale existed, and a
2370
+ * consumer theme that still states one keeps working.
2371
+ */
2372
+ backdrop(token) {
2373
+ if (!token)
2374
+ return undefined;
2375
+ if (typeof token !== 'string')
2376
+ return token;
2377
+ const backdrops = this.backdrops();
2378
+ const value = backdrops[token];
2379
+ if (value)
2380
+ return value;
2381
+ // The scale is open, so a mistyped name cannot be a compile error. Falling
2382
+ // through to `scrim` still dims the page; saying so once keeps the typo
2383
+ // from hiding behind a backdrop that looks almost right.
2384
+ if ((typeof ngDevMode === 'undefined' || ngDevMode) && !this.warnedBackdrops.has(token)) {
2385
+ this.warnedBackdrops.add(token);
2386
+ console.warn(`[uni] Unknown backdrop token "${token}": the active theme does not define it, ` +
2387
+ `so the shared \`scrim\` is used instead. Add it to the theme's \`backdrops\` ` +
2388
+ `map, or use one of: ${Object.keys(backdrops).join(', ')}.`);
2389
+ }
2390
+ return backdrops['scrim'] ?? { background: 'rgba(0, 0, 0, 0.4)' };
2391
+ }
2359
2392
  radius(size) {
2360
2393
  return !size ? undefined : { borderRadius: this.radii()[size] };
2361
2394
  }
@@ -3173,23 +3206,47 @@ class UniIconButtonComponent {
3173
3206
  !this.loading() && {
3174
3207
  padding: 0,
3175
3208
  },
3176
- this.variant() !== 'ghost' && {
3177
- '&:hover': {
3178
- ...this.theme.boxShadow('raised'),
3179
- },
3180
- },
3181
- this.variant() === 'ghost' && {
3182
- '&:hover': {
3183
- backgroundColor: 'rgba(0,0,0,0.1)',
3184
- },
3185
- },
3209
+ // The hover treatment is the theme's, declared per variant alongside the
3210
+ // colours it belongs with. It used to live here as a pair of branches on
3211
+ // `variant() === 'ghost'`, which was the last place a component decided a
3212
+ // variant's *appearance* from its *name*.
3213
+ //
3214
+ // That partition was binary, so under an open registry every intent a
3215
+ // consumer registered fell into the not-ghost half and got a raised
3216
+ // shadow whether or not it suited — a recessive intent included. And
3217
+ // because the branches were applied after the theme's own styles, no
3218
+ // theme could correct it: both themes in this repo declare a ghost hover
3219
+ // and had it silently overridden.
3186
3220
  !this.loading() && {
3187
3221
  '&:disabled': {
3188
3222
  ...this.config().variants?.disabled,
3189
3223
  },
3190
3224
  },
3225
+ // The keyboard-focus indicator (WCAG 2.4.7). The structural block above
3226
+ // clears the user-agent outline and, until now, put nothing back — so an
3227
+ // icon button had no focus indicator at all, in any variant. That is the
3228
+ // close affordance in every dialog and drawer header.
3229
+ //
3230
+ // Applied last on purpose: its *appearance* is the theme's, through
3231
+ // `focusColor` and the `focusRing` primitives `focusRingStyle` reads, but
3232
+ // whether an indicator exists is not a style choice a theme should be
3233
+ // able to switch off by accident.
3234
+ {
3235
+ '&:focus-visible': { ...this.theme.focusRingStyle(this.focusRingColor()) },
3236
+ },
3191
3237
  ]);
3192
3238
  }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
3239
+ /**
3240
+ * The focus ring's colour, from the variant's theme entry — never from the
3241
+ * variant *name*, which is what left `uni-button`'s ring transparent on
3242
+ * `ghost` and absent on every unthemed intent. Falls back to the reserved
3243
+ * `primary` accent so a ring always renders.
3244
+ */
3245
+ focusRingColor = computed(() => {
3246
+ const colors = this.theme.colors();
3247
+ const token = this.config().variantOptions?.[this.variant()]?.focusColor;
3248
+ return (token && colors[token]) || colors['primary'];
3249
+ }, ...(ngDevMode ? [{ debugName: "focusRingColor" }] : /* istanbul ignore next */ []));
3193
3250
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniIconButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3194
3251
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniIconButtonComponent, isStandalone: true, selector: "button[uni-icon-button], button[icon-button]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", 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 }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disable: { classPropertyName: "disable", publicName: "disable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, opticalSize: { classPropertyName: "opticalSize", publicName: "opticalSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.disabled": "disable() || loading() || null", "attr.aria-busy": "loading() ? 'true' : null", "attr.aria-label": "ariaLabel() || null", "class": "className()" } }, hostDirectives: [{ directive: RippleDirective }], ngImport: i0, template: `
3195
3252
  @if (loading()) {
@@ -5495,18 +5552,28 @@ class UniButtonComponent extends BaseComponent {
5495
5552
  // radii/families in older themes) keep winning.
5496
5553
  this.theme.radius(this.componentOptions().borderRadius),
5497
5554
  { ...this.theme.typeface(this.componentOptions().typeface) },
5498
- this.style() && {
5499
- ...this.style(), // TODO: Set priority on theme-defined styles
5500
- },
5555
+ // Presentational defaults, deliberately *before* `style()` so the theme
5556
+ // outranks them. They used to sit after it, which made them unthemeable:
5557
+ // a variant could not give a button a border without `!important`, and
5558
+ // the shorthand then forced `!important` onto every state that adjusted
5559
+ // it. The base theme was caught by this too — its `secondary` variant is
5560
+ // commented "Hollow" and declares `1px solid`, which this block erased.
5501
5561
  {
5502
- display: 'flex',
5503
- alignItems: 'center',
5504
- position: 'relative',
5505
5562
  overflow: 'hidden',
5506
5563
  outline: 0,
5507
5564
  border: 0,
5508
5565
  cursor: 'pointer',
5509
5566
  transition: 'all 0.28s ease',
5567
+ },
5568
+ this.style() && {
5569
+ ...this.style(),
5570
+ },
5571
+ // Structure the component genuinely owns: `position` anchors the ripple,
5572
+ // and the rest is its internal slot layout.
5573
+ {
5574
+ display: 'flex',
5575
+ alignItems: 'center',
5576
+ position: 'relative',
5510
5577
  '&:disabled': {
5511
5578
  cursor: 'not-allowed !important',
5512
5579
  },
@@ -5526,14 +5593,14 @@ class UniButtonComponent extends BaseComponent {
5526
5593
  fontSize: this.symbolSize(),
5527
5594
  },
5528
5595
  },
5529
- // Hover/pressed styling now lives in the theme's button variants
5530
- // (solid vs. hollow, per-variant states). Only the keyboard-focus
5531
- // indicator (WCAG 2.4.7) stays component-owned.
5596
+ // Hover/pressed styling lives in the theme's button variants (solid vs.
5597
+ // hollow, per-variant states). The keyboard-focus indicator (WCAG 2.4.7)
5598
+ // stays component-owned, but its *colour* is the theme's — see
5599
+ // `focusRingColor`. Routed through the shared `focusRingStyle` so a theme
5600
+ // defining `focusRing` border/shadow primitives restyles it here too,
5601
+ // exactly as it already does for checkbox, radio and toggle.
5532
5602
  {
5533
- '&:focus-visible': {
5534
- outline: `2px solid ${this.theme.colors()[this.variant()]}`,
5535
- outlineOffset: '2px',
5536
- },
5603
+ '&:focus-visible': { ...this.theme.focusRingStyle(this.focusRingColor()) },
5537
5604
  },
5538
5605
  this.fullWidth() && {
5539
5606
  width: '100%',
@@ -5552,6 +5619,27 @@ class UniButtonComponent extends BaseComponent {
5552
5619
  },
5553
5620
  },
5554
5621
  ]), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
5622
+ /**
5623
+ * The focus ring's colour, from the variant's theme entry.
5624
+ *
5625
+ * This used to be `colors[variant()]` — the variant *name* resolved as a
5626
+ * colour token. That held together only while every variant happened to also
5627
+ * be a colour, and it failed silently in both directions: `ghost` resolves to
5628
+ * `transparent`, and `light`/`onLight`/`dark`/`onDark` (and any name a
5629
+ * consumer registers) resolve to nothing, emitting `2px solid undefined`
5630
+ * which the parser drops. `outline-offset` survived either way, so the
5631
+ * element still shifted on focus and the missing ring went unnoticed.
5632
+ *
5633
+ * Falls back to the reserved `primary` accent rather than to `currentColor`:
5634
+ * the ring is drawn outside the element, so on a filled button
5635
+ * `currentColor` is the label colour and would sit near-invisibly against
5636
+ * the page rather than the button.
5637
+ */
5638
+ focusRingColor = computed(() => {
5639
+ const colors = this.theme.colors();
5640
+ const token = this.variantRoles()?.focusColor;
5641
+ return (token && colors[token]) || colors['primary'];
5642
+ }, ...(ngDevMode ? [{ debugName: "focusRingColor" }] : /* istanbul ignore next */ []));
5555
5643
  symbolSize = computed(() => {
5556
5644
  const fontSize = parseFloat(String(this.style()['fontSize'] ?? ''));
5557
5645
  // Fall back to the default icon size when the theme defines no fontSize
@@ -10461,6 +10549,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
10461
10549
  * This file exports all public-facing elements of the data-table component.
10462
10550
  */
10463
10551
 
10552
+ /**
10553
+ * Modal dialog on the native `<dialog>` element, laid out as three rows:
10554
+ * an optional `[dialog-header]`, the projected body, an optional
10555
+ * `[dialog-buttons]`.
10556
+ *
10557
+ * **The surface is never the scroll container.** It is a flex column that is
10558
+ * `overflow: clip` on both axes; the header and buttons pin while only the
10559
+ * body row scrolls. Unlike the drawer, which is deliberately viewport-tall,
10560
+ * the dialog stays content-sized: it has no `height`, and grows until it hits
10561
+ * `calc(100dvh - 2 x inset)` — the point at which the body starts to scroll.
10562
+ *
10563
+ * The theme's `padding` insets all three rows (which is what makes the default
10564
+ * header read as a pill floating inside the surface); `bodyPadding` pads the
10565
+ * scrolling row alone.
10566
+ */
10464
10567
  class UniDialogComponent extends BaseComponent {
10465
10568
  elem = inject(ElementRef);
10466
10569
  /** Two-way bindable open state: [(show)]. */
@@ -10481,6 +10584,9 @@ class UniDialogComponent extends BaseComponent {
10481
10584
  hasTitle = signal(false, ...(ngDevMode ? [{ debugName: "hasTitle" }] : /* istanbul ignore next */ []));
10482
10585
  /** Emits true once opened and false once the closing animation finishes. */
10483
10586
  showing = output();
10587
+ headerTemplate = viewChild.required('header');
10588
+ bodyTemplate = viewChild.required('body');
10589
+ footerTemplate = viewChild.required('footer');
10484
10590
  constructor() {
10485
10591
  super();
10486
10592
  effect(() => (this.show() ? this.open() : this.close()));
@@ -10488,24 +10594,73 @@ class UniDialogComponent extends BaseComponent {
10488
10594
  get _dialog() {
10489
10595
  return this.elem.nativeElement;
10490
10596
  }
10491
- className = computed(() => css([
10492
- {
10493
- ...this.theme.radius(this.componentOptions().borderRadius),
10494
- ...this.theme.colorPair(this.componentOptions().color),
10495
- ...this.theme.border(this.componentOptions().border),
10496
- ...this.theme.boxShadow(this.componentOptions().elevation),
10497
- ...this.theme.padding(this.componentOptions().padding || 'none'),
10498
- '&::backdrop': {
10499
- ...this.componentOptions().backdrop,
10500
- },
10501
- '&[open], &::backdrop': {
10502
- animation: `${this.dialogFadeIn} ease-in 350ms`,
10503
- },
10504
- '&[closing], &[closing]::backdrop': {
10505
- animation: `${this.dialogFadeOut} ease-in 350ms`,
10597
+ /**
10598
+ * One timing for the whole move, panel and scrim together — the same `panel`
10599
+ * token the drawer's slide reads, so the two surfaces no longer disagree
10600
+ * about how long arriving takes.
10601
+ */
10602
+ motion = computed(() => this.theme.motion(this.componentOptions().motion ?? 'panel'), ...(ngDevMode ? [{ debugName: "motion" }] : /* istanbul ignore next */ []));
10603
+ /** The gap the surface keeps from the viewport edge before its body scrolls. */
10604
+ inset = computed(() => this.theme.getSpacing(this.componentOptions().inset ?? 'lg'), ...(ngDevMode ? [{ debugName: "inset" }] : /* istanbul ignore next */ []));
10605
+ className = computed(() => {
10606
+ const inset = this.inset();
10607
+ const motion = this.motion();
10608
+ return css([
10609
+ {
10610
+ boxSizing: 'border-box',
10611
+ display: 'flex',
10612
+ flexDirection: 'column',
10613
+ // Both axes, explicitly, and never the shorthand. Setting one axis
10614
+ // alone computes the other to `auto` — which is precisely how a
10615
+ // surface becomes an accidental scroll container.
10616
+ overflowX: 'clip',
10617
+ overflowY: 'clip',
10618
+ // No `height`: the dialog is content-sized right up to this cap, and
10619
+ // only then does the body row start to scroll.
10620
+ maxHeight: `calc(100dvh - ${inset} * 2)`,
10621
+ maxWidth: `calc(100vw - ${inset} * 2)`,
10622
+ ...this.theme.radius(this.componentOptions().borderRadius),
10623
+ ...this.theme.colorPair(this.componentOptions().color),
10624
+ ...this.theme.border(this.componentOptions().border),
10625
+ ...this.theme.boxShadow(this.componentOptions().elevation),
10626
+ ...this.theme.padding(this.componentOptions().padding || 'none'),
10627
+ // The UA stylesheet hides a closed dialog with `display: none`, which
10628
+ // the `display: flex` above would otherwise beat on specificity —
10629
+ // leaving the surface in normal flow behind the page whenever it is
10630
+ // shut. The closing animation still runs: `open` is only removed once
10631
+ // it ends.
10632
+ '&:not([open])': { display: 'none' },
10633
+ // The shared `backdrops` primitive, so the dialog and the drawer dim
10634
+ // the page identically. A raw style object is still honoured.
10635
+ '&::backdrop': {
10636
+ ...this.theme.backdrop(this.componentOptions().backdrop),
10637
+ },
10638
+ '&[open], &::backdrop': {
10639
+ animation: `${this.dialogFadeIn} ${motion.easing} ${motion.duration}ms`,
10640
+ },
10641
+ '&[closing], &[closing]::backdrop': {
10642
+ animation: `${this.dialogFadeOut} ${motion.easing} ${motion.duration}ms`,
10643
+ },
10506
10644
  },
10507
- },
10508
- ]), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
10645
+ ]);
10646
+ }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
10647
+ /** The only scrolling row, and the only one `bodyPadding` touches. */
10648
+ bodyClass = computed(() => css({
10649
+ // Not the drawer's `1 1 auto`: the dialog must not stretch to fill a cap
10650
+ // it has not reached, so the body never grows past its content.
10651
+ flex: '0 1 auto',
10652
+ // What lets this row shrink below its content and scroll, rather than
10653
+ // pushing the buttons off the surface.
10654
+ minHeight: 0,
10655
+ // Defence in depth: a positioned body is the containing block for any
10656
+ // stray absolute descendant, so nothing can re-home into an ancestor and
10657
+ // inflate its scrollHeight.
10658
+ position: 'relative',
10659
+ overflowX: 'hidden',
10660
+ overflowY: 'auto',
10661
+ overscrollBehavior: 'contain',
10662
+ ...this.theme.padding(this.componentOptions().bodyPadding),
10663
+ }), ...(ngDevMode ? [{ debugName: "bodyClass" }] : /* istanbul ignore next */ []));
10509
10664
  backdropClick(event) {
10510
10665
  if (event.target.nodeName === 'DIALOG') {
10511
10666
  this.close();
@@ -10528,10 +10683,18 @@ class UniDialogComponent extends BaseComponent {
10528
10683
  this.showing.emit(false);
10529
10684
  }
10530
10685
  }
10686
+ /**
10687
+ * Doubled selector (`.css-x.css-x`) so these win over the icon button's own
10688
+ * class, which sets `position: relative` and is emitted after this one.
10689
+ * Without it the close never leaves the flow — it used to sit inline at the
10690
+ * top of the surface, and under the flex column it would take a whole row.
10691
+ */
10531
10692
  closeButton = css({
10532
- position: 'absolute',
10533
- right: 12,
10534
- top: 12,
10693
+ '&&': {
10694
+ position: 'absolute',
10695
+ right: 12,
10696
+ top: 12,
10697
+ },
10535
10698
  });
10536
10699
  dialogFadeIn = keyframes({ ...fadeIn });
10537
10700
  dialogFadeOut = keyframes({ ...fadeOut });
@@ -10559,7 +10722,7 @@ class UniDialogComponent extends BaseComponent {
10559
10722
  this.show.set(false);
10560
10723
  }
10561
10724
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10562
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDialogComponent, isStandalone: true, selector: "dialog[uni-dialog]", inputs: { show: { classPropertyName: "show", publicName: "show", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, initialFocus: { classPropertyName: "initialFocus", publicName: "initialFocus", isSignal: true, isRequired: false, transformFunction: null }, defaultCloseButton: { classPropertyName: "defaultCloseButton", publicName: "defaultCloseButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { show: "showChange", showing: "showing" }, host: { listeners: { "click": "backdropClick($event)", "cancel": "escapeCancel($event)", "animationend": "closingAnimation($event)" }, properties: { "attr.aria-labelledby": "hasTitle() ? titleId : null", "attr.aria-label": "ariaLabel() ?? null", "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dialog' }], usesInheritance: true, ngImport: i0, template: `
10725
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDialogComponent, isStandalone: true, selector: "dialog[uni-dialog]", inputs: { show: { classPropertyName: "show", publicName: "show", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, initialFocus: { classPropertyName: "initialFocus", publicName: "initialFocus", isSignal: true, isRequired: false, transformFunction: null }, defaultCloseButton: { classPropertyName: "defaultCloseButton", publicName: "defaultCloseButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { show: "showChange", showing: "showing" }, host: { listeners: { "click": "backdropClick($event)", "cancel": "escapeCancel($event)", "animationend": "closingAnimation($event)" }, properties: { "attr.aria-labelledby": "hasTitle() ? titleId : null", "attr.aria-label": "ariaLabel() ?? null", "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dialog' }], viewQueries: [{ propertyName: "headerTemplate", first: true, predicate: ["header"], descendants: true, isSignal: true }, { propertyName: "bodyTemplate", first: true, predicate: ["body"], descendants: true, isSignal: true }, { propertyName: "footerTemplate", first: true, predicate: ["footer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
10563
10726
  @if (defaultCloseButton()) {
10564
10727
  <button
10565
10728
  icon-button
@@ -10572,14 +10735,24 @@ class UniDialogComponent extends BaseComponent {
10572
10735
  Close
10573
10736
  </button>
10574
10737
  }
10575
- <ng-content></ng-content>
10576
- `, isInline: true, dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10738
+
10739
+ <!-- One <ng-content> per slot, each parked in a template. Angular claims
10740
+ projected nodes in declaration order, so the catch-all must be declared
10741
+ last — while being rendered between the two pinned rows below. -->
10742
+ <ng-template #header><ng-content select="[uni-dialog-header], [dialog-header]" /></ng-template>
10743
+ <ng-template #footer><ng-content select="[uni-dialog-buttons], [dialog-buttons]" /></ng-template>
10744
+ <ng-template #body><ng-content /></ng-template>
10745
+
10746
+ <ng-container [ngTemplateOutlet]="headerTemplate()" />
10747
+ <div [class]="bodyClass()"><ng-container [ngTemplateOutlet]="bodyTemplate()" /></div>
10748
+ <ng-container [ngTemplateOutlet]="footerTemplate()" />
10749
+ `, isInline: true, dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10577
10750
  }
10578
10751
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDialogComponent, decorators: [{
10579
10752
  type: Component,
10580
10753
  args: [{
10581
10754
  selector: 'dialog[uni-dialog]',
10582
- imports: [UniIconButtonComponent, CommonModule],
10755
+ imports: [UniIconButtonComponent, NgTemplateOutlet],
10583
10756
  template: `
10584
10757
  @if (defaultCloseButton()) {
10585
10758
  <button
@@ -10593,7 +10766,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
10593
10766
  Close
10594
10767
  </button>
10595
10768
  }
10596
- <ng-content></ng-content>
10769
+
10770
+ <!-- One <ng-content> per slot, each parked in a template. Angular claims
10771
+ projected nodes in declaration order, so the catch-all must be declared
10772
+ last — while being rendered between the two pinned rows below. -->
10773
+ <ng-template #header><ng-content select="[uni-dialog-header], [dialog-header]" /></ng-template>
10774
+ <ng-template #footer><ng-content select="[uni-dialog-buttons], [dialog-buttons]" /></ng-template>
10775
+ <ng-template #body><ng-content /></ng-template>
10776
+
10777
+ <ng-container [ngTemplateOutlet]="headerTemplate()" />
10778
+ <div [class]="bodyClass()"><ng-container [ngTemplateOutlet]="bodyTemplate()" /></div>
10779
+ <ng-container [ngTemplateOutlet]="footerTemplate()" />
10597
10780
  `,
10598
10781
  providers: [{ provide: COMPONENT_NAME, useValue: 'dialog' }],
10599
10782
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -10606,7 +10789,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
10606
10789
  '(animationend)': 'closingAnimation($event)',
10607
10790
  },
10608
10791
  }]
10609
- }], ctorParameters: () => [], propDecorators: { show: [{ type: i0.Input, args: [{ isSignal: true, alias: "show", required: false }] }, { type: i0.Output, args: ["showChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], initialFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFocus", required: false }] }], defaultCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCloseButton", required: false }] }], showing: [{ type: i0.Output, args: ["showing"] }] } });
10792
+ }], ctorParameters: () => [], propDecorators: { show: [{ type: i0.Input, args: [{ isSignal: true, alias: "show", required: false }] }, { type: i0.Output, args: ["showChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], initialFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFocus", required: false }] }], defaultCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCloseButton", required: false }] }], showing: [{ type: i0.Output, args: ["showing"] }], headerTemplate: [{ type: i0.ViewChild, args: ['header', { isSignal: true }] }], bodyTemplate: [{ type: i0.ViewChild, args: ['body', { isSignal: true }] }], footerTemplate: [{ type: i0.ViewChild, args: ['footer', { isSignal: true }] }] } });
10610
10793
 
10611
10794
  class UniDialogButtonsComponent extends BaseComponent {
10612
10795
  dialog = inject(UniDialogComponent, {
@@ -10631,6 +10814,11 @@ class UniDialogButtonsComponent extends BaseComponent {
10631
10814
  justifyContentValue = computed(() => this.justifyContent() ?? this.componentOptions().justifyContent ?? 'center', ...(ngDevMode ? [{ debugName: "justifyContentValue" }] : /* istanbul ignore next */ []));
10632
10815
  gapValue = computed(() => this.componentOptions().gap ?? 'md', ...(ngDevMode ? [{ debugName: "gapValue" }] : /* istanbul ignore next */ []));
10633
10816
  buttonSize = computed(() => this.componentOptions().buttonSize ?? 'lg', ...(ngDevMode ? [{ debugName: "buttonSize" }] : /* istanbul ignore next */ []));
10817
+ /** A pinned row, sized by its content rather than by the body beside it. */
10818
+ hostClass = computed(() => css({
10819
+ flex: 'none',
10820
+ ...this.theme.borderTop(this.componentOptions().divider),
10821
+ }), ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
10634
10822
  className = computed(() => css([
10635
10823
  this.componentTheme().fixed,
10636
10824
  this.componentOptions().stretch && {
@@ -10643,11 +10831,11 @@ class UniDialogButtonsComponent extends BaseComponent {
10643
10831
  this.dialog?.close();
10644
10832
  }
10645
10833
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDialogButtonsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
10646
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDialogButtonsComponent, isStandalone: true, selector: "[uni-dialog-buttons], [dialog-buttons]", inputs: { confirmButtonText: { classPropertyName: "confirmButtonText", publicName: "confirmButtonText", isSignal: true, isRequired: false, transformFunction: null }, confirmButtonVariant: { classPropertyName: "confirmButtonVariant", publicName: "confirmButtonVariant", isSignal: true, isRequired: false, transformFunction: null }, cancelButtonText: { classPropertyName: "cancelButtonText", publicName: "cancelButtonText", isSignal: true, isRequired: false, transformFunction: null }, cancelButtonVariant: { classPropertyName: "cancelButtonVariant", publicName: "cancelButtonVariant", isSignal: true, isRequired: false, transformFunction: null }, disableConfirm: { classPropertyName: "disableConfirm", publicName: "disableConfirm", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, paddingBottom: { classPropertyName: "paddingBottom", publicName: "paddingBottom", isSignal: true, isRequired: false, transformFunction: null }, justifyContent: { classPropertyName: "justifyContent", publicName: "justifyContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed" }, providers: [{ provide: COMPONENT_NAME, useValue: 'dialogButtons' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n [gap]=\"gapValue()\"\n [padding]=\"paddingValue()\"\n [paddingBottom]=\"paddingBottomValue()\"\n [justifyContent]=\"justifyContentValue()\"\n [flexDirection]=\"componentOptions().reverseOrder ? 'row-reverse' : 'row'\"\n [class]=\"className()\"\n>\n <button\n text-button\n [variant]=\"confirmVariant()\"\n [size]=\"buttonSize()\"\n (click)=\"confirmed.emit(); closeDialog()\"\n [disable]=\"disableConfirm()\"\n >\n {{ confirmButtonText() || 'Confirm' }}\n </button>\n <button text-button [variant]=\"cancelVariant()\" [size]=\"buttonSize()\" (click)=\"closeDialog()\">\n {{ cancelButtonText() || 'Cancel' }}\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10834
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDialogButtonsComponent, isStandalone: true, selector: "[uni-dialog-buttons], [dialog-buttons]", inputs: { confirmButtonText: { classPropertyName: "confirmButtonText", publicName: "confirmButtonText", isSignal: true, isRequired: false, transformFunction: null }, confirmButtonVariant: { classPropertyName: "confirmButtonVariant", publicName: "confirmButtonVariant", isSignal: true, isRequired: false, transformFunction: null }, cancelButtonText: { classPropertyName: "cancelButtonText", publicName: "cancelButtonText", isSignal: true, isRequired: false, transformFunction: null }, cancelButtonVariant: { classPropertyName: "cancelButtonVariant", publicName: "cancelButtonVariant", isSignal: true, isRequired: false, transformFunction: null }, disableConfirm: { classPropertyName: "disableConfirm", publicName: "disableConfirm", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, paddingBottom: { classPropertyName: "paddingBottom", publicName: "paddingBottom", isSignal: true, isRequired: false, transformFunction: null }, justifyContent: { classPropertyName: "justifyContent", publicName: "justifyContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed" }, host: { properties: { "class": "hostClass()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dialogButtons' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n [gap]=\"gapValue()\"\n [padding]=\"paddingValue()\"\n [paddingBottom]=\"paddingBottomValue()\"\n [justifyContent]=\"justifyContentValue()\"\n [flexDirection]=\"componentOptions().reverseOrder ? 'row-reverse' : 'row'\"\n [class]=\"className()\"\n>\n <button\n text-button\n [variant]=\"confirmVariant()\"\n [size]=\"buttonSize()\"\n (click)=\"confirmed.emit(); closeDialog()\"\n [disable]=\"disableConfirm()\"\n >\n {{ confirmButtonText() || 'Confirm' }}\n </button>\n <button text-button [variant]=\"cancelVariant()\" [size]=\"buttonSize()\" (click)=\"closeDialog()\">\n {{ cancelButtonText() || 'Cancel' }}\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10647
10835
  }
10648
10836
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDialogButtonsComponent, decorators: [{
10649
10837
  type: Component,
10650
- args: [{ selector: '[uni-dialog-buttons], [dialog-buttons]', imports: [UniRowDirective, UniButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'dialogButtons' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n row-layout\n [gap]=\"gapValue()\"\n [padding]=\"paddingValue()\"\n [paddingBottom]=\"paddingBottomValue()\"\n [justifyContent]=\"justifyContentValue()\"\n [flexDirection]=\"componentOptions().reverseOrder ? 'row-reverse' : 'row'\"\n [class]=\"className()\"\n>\n <button\n text-button\n [variant]=\"confirmVariant()\"\n [size]=\"buttonSize()\"\n (click)=\"confirmed.emit(); closeDialog()\"\n [disable]=\"disableConfirm()\"\n >\n {{ confirmButtonText() || 'Confirm' }}\n </button>\n <button text-button [variant]=\"cancelVariant()\" [size]=\"buttonSize()\" (click)=\"closeDialog()\">\n {{ cancelButtonText() || 'Cancel' }}\n </button>\n</div>\n" }]
10838
+ args: [{ selector: '[uni-dialog-buttons], [dialog-buttons]', imports: [UniRowDirective, UniButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'dialogButtons' }], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class]': 'hostClass()' }, template: "<div\n row-layout\n [gap]=\"gapValue()\"\n [padding]=\"paddingValue()\"\n [paddingBottom]=\"paddingBottomValue()\"\n [justifyContent]=\"justifyContentValue()\"\n [flexDirection]=\"componentOptions().reverseOrder ? 'row-reverse' : 'row'\"\n [class]=\"className()\"\n>\n <button\n text-button\n [variant]=\"confirmVariant()\"\n [size]=\"buttonSize()\"\n (click)=\"confirmed.emit(); closeDialog()\"\n [disable]=\"disableConfirm()\"\n >\n {{ confirmButtonText() || 'Confirm' }}\n </button>\n <button text-button [variant]=\"cancelVariant()\" [size]=\"buttonSize()\" (click)=\"closeDialog()\">\n {{ cancelButtonText() || 'Cancel' }}\n </button>\n</div>\n" }]
10651
10839
  }], propDecorators: { confirmButtonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmButtonText", required: false }] }], confirmButtonVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmButtonVariant", required: false }] }], cancelButtonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelButtonText", required: false }] }], cancelButtonVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelButtonVariant", required: false }] }], disableConfirm: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableConfirm", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], paddingBottom: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingBottom", required: false }] }], justifyContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "justifyContent", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }] } });
10652
10840
 
10653
10841
  class UniDialogHeaderComponent extends BaseComponent {
@@ -10665,12 +10853,20 @@ class UniDialogHeaderComponent extends BaseComponent {
10665
10853
  closeDialog() {
10666
10854
  this.dialog?.close();
10667
10855
  }
10856
+ /**
10857
+ * A pinned row: it is a flex child of the dialog and must not be sized by
10858
+ * the scrolling body beside it.
10859
+ */
10860
+ className = computed(() => css({
10861
+ flex: 'none',
10862
+ ...this.theme.borderBottom(this.componentOptions().divider),
10863
+ }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
10668
10864
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDialogHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10669
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDialogHeaderComponent, isStandalone: true, selector: "[uni-dialog-header]", providers: [{ provide: COMPONENT_NAME, useValue: 'dialogHeader' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n [containerColor]=\"componentOptions().color\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [height]=\"componentOptions().height\"\n alignItems=\"center\"\n [paddingHorizontal]=\"componentOptions().paddingHorizontal ?? 'sm'\"\n>\n <!-- Balance spacer: only centered titles need to offset the close button. -->\n @if ((componentOptions().textAlign || 'center') === 'center') {\n <div box-layout [width]=\"26\"></div>\n }\n <div box-layout [grow]=\"1\" [attr.id]=\"titleId\">\n <span uni-text\n [typeface]=\"componentOptions().textRole\"\n [color]=\"componentOptions().textColor\"\n display=\"block\"\n [align]=\"componentOptions().textAlign || 'center'\"\n ><ng-content></ng-content\n ></span>\n </div>\n <button\n icon-button\n [iconName]=\"componentOptions().closeButtonIcon\"\n [symbolName]=\"componentOptions().closeButtonSymbol\"\n variant=\"ghost\"\n (click)=\"closeDialog()\"\n [size]=\"componentOptions().closeButtonSize || 'md'\"\n >\n Close\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10865
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDialogHeaderComponent, isStandalone: true, selector: "[uni-dialog-header], [dialog-header]", host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dialogHeader' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n [containerColor]=\"componentOptions().color\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [height]=\"componentOptions().height\"\n alignItems=\"center\"\n [paddingHorizontal]=\"componentOptions().paddingHorizontal ?? 'sm'\"\n>\n <!-- Balance spacer: only centered titles need to offset the close button. -->\n @if ((componentOptions().textAlign || 'center') === 'center') {\n <div box-layout [width]=\"26\"></div>\n }\n <div box-layout [grow]=\"1\" [attr.id]=\"titleId\">\n <span uni-text\n [typeface]=\"componentOptions().textRole\"\n [color]=\"componentOptions().textColor\"\n display=\"block\"\n [align]=\"componentOptions().textAlign || 'center'\"\n ><ng-content></ng-content\n ></span>\n </div>\n <button\n icon-button\n [iconName]=\"componentOptions().closeButtonIcon\"\n [symbolName]=\"componentOptions().closeButtonSymbol\"\n variant=\"ghost\"\n (click)=\"closeDialog()\"\n [size]=\"componentOptions().closeButtonSize || 'md'\"\n >\n Close\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10670
10866
  }
10671
10867
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDialogHeaderComponent, decorators: [{
10672
10868
  type: Component,
10673
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: '[uni-dialog-header]', imports: [UniBoxDirective, UniIconButtonComponent, UniTextDirective, UniRowDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'dialogHeader' }], template: "<div\n row-layout\n [containerColor]=\"componentOptions().color\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [height]=\"componentOptions().height\"\n alignItems=\"center\"\n [paddingHorizontal]=\"componentOptions().paddingHorizontal ?? 'sm'\"\n>\n <!-- Balance spacer: only centered titles need to offset the close button. -->\n @if ((componentOptions().textAlign || 'center') === 'center') {\n <div box-layout [width]=\"26\"></div>\n }\n <div box-layout [grow]=\"1\" [attr.id]=\"titleId\">\n <span uni-text\n [typeface]=\"componentOptions().textRole\"\n [color]=\"componentOptions().textColor\"\n display=\"block\"\n [align]=\"componentOptions().textAlign || 'center'\"\n ><ng-content></ng-content\n ></span>\n </div>\n <button\n icon-button\n [iconName]=\"componentOptions().closeButtonIcon\"\n [symbolName]=\"componentOptions().closeButtonSymbol\"\n variant=\"ghost\"\n (click)=\"closeDialog()\"\n [size]=\"componentOptions().closeButtonSize || 'md'\"\n >\n Close\n </button>\n</div>\n" }]
10869
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: '[uni-dialog-header], [dialog-header]', imports: [UniBoxDirective, UniIconButtonComponent, UniTextDirective, UniRowDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'dialogHeader' }], host: { '[class]': 'className()' }, template: "<div\n row-layout\n [containerColor]=\"componentOptions().color\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [height]=\"componentOptions().height\"\n alignItems=\"center\"\n [paddingHorizontal]=\"componentOptions().paddingHorizontal ?? 'sm'\"\n>\n <!-- Balance spacer: only centered titles need to offset the close button. -->\n @if ((componentOptions().textAlign || 'center') === 'center') {\n <div box-layout [width]=\"26\"></div>\n }\n <div box-layout [grow]=\"1\" [attr.id]=\"titleId\">\n <span uni-text\n [typeface]=\"componentOptions().textRole\"\n [color]=\"componentOptions().textColor\"\n display=\"block\"\n [align]=\"componentOptions().textAlign || 'center'\"\n ><ng-content></ng-content\n ></span>\n </div>\n <button\n icon-button\n [iconName]=\"componentOptions().closeButtonIcon\"\n [symbolName]=\"componentOptions().closeButtonSymbol\"\n variant=\"ghost\"\n (click)=\"closeDialog()\"\n [size]=\"componentOptions().closeButtonSize || 'md'\"\n >\n Close\n </button>\n</div>\n" }]
10674
10870
  }], ctorParameters: () => [] });
10675
10871
 
10676
10872
  const DRAWER_PANEL = new InjectionToken('uni-drawer-panel');
@@ -10901,6 +11097,15 @@ class UniDrawerComponent extends BaseComponent {
10901
11097
  edge = computed(() => (this.position() === 'start' ? '-100%' : '100%'), ...(ngDevMode ? [{ debugName: "edge" }] : /* istanbul ignore next */ []));
10902
11098
  slideIn = computed(() => keyframes({ from: { transform: `translateX(${this.edge()})` }, to: { transform: 'translateX(0)' } }), ...(ngDevMode ? [{ debugName: "slideIn" }] : /* istanbul ignore next */ []));
10903
11099
  slideOut = computed(() => keyframes({ from: { transform: 'translateX(0)' }, to: { transform: `translateX(${this.edge()})` } }), ...(ngDevMode ? [{ debugName: "slideOut" }] : /* istanbul ignore next */ []));
11100
+ /** The scrim's own fade, so the dimming arrives and leaves with the panel. */
11101
+ scrimIn = keyframes({ ...fadeIn });
11102
+ scrimOut = keyframes({ ...fadeOut });
11103
+ /**
11104
+ * One timing for everything the panel does. The scrim used to snap in and
11105
+ * out around a panel that took 250ms to slide, because `::backdrop` had a
11106
+ * background and no animation at all.
11107
+ */
11108
+ motion = computed(() => this.theme.motion(this.componentOptions().motion ?? 'panel'), ...(ngDevMode ? [{ debugName: "motion" }] : /* istanbul ignore next */ []));
10904
11109
  /** Input wins over the theme option; the literal is the last-resort default. */
10905
11110
  panelWidth = computed(() => this.width() ?? this.componentOptions().width ?? 280, ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
10906
11111
  showScrim = computed(() => this.scrim() ?? this.componentOptions().scrim ?? true, ...(ngDevMode ? [{ debugName: "showScrim" }] : /* istanbul ignore next */ []));
@@ -10943,13 +11148,14 @@ class UniDrawerComponent extends BaseComponent {
10943
11148
  };
10944
11149
  sideClass = computed(() => {
10945
11150
  const options = this.componentOptions();
11151
+ const motion = this.motion();
10946
11152
  const width = this.panelWidth();
10947
11153
  const start = this.position() === 'start';
10948
11154
  return css({
10949
11155
  ...this.shell,
10950
11156
  height: '100%',
10951
11157
  flex: 'none',
10952
- transition: 'width 0.25s ease, visibility 0.25s',
11158
+ transition: `width ${motion.duration}ms ${motion.easing}, visibility ${motion.duration}ms`,
10953
11159
  ...this.surface(),
10954
11160
  ...(start
10955
11161
  ? this.theme.borderRight(options.divider)
@@ -10961,6 +11167,8 @@ class UniDrawerComponent extends BaseComponent {
10961
11167
  }, ...(ngDevMode ? [{ debugName: "sideClass" }] : /* istanbul ignore next */ []));
10962
11168
  overClass = computed(() => {
10963
11169
  const options = this.componentOptions();
11170
+ const motion = this.motion();
11171
+ const scrim = this.showScrim();
10964
11172
  const start = this.position() === 'start';
10965
11173
  return css({
10966
11174
  ...this.shell,
@@ -10980,9 +11188,25 @@ class UniDrawerComponent extends BaseComponent {
10980
11188
  '&:not([open])': { display: 'none' },
10981
11189
  // `scrim: false` keeps the modality — focus trap, inert page — but stops
10982
11190
  // the drawer dimming what it covers.
10983
- '&::backdrop': this.showScrim() ? { ...options.backdrop } : { background: 'transparent' },
10984
- '&[open]': { animation: `${this.slideIn()} 250ms ease-out` },
10985
- '&[closing]': { animation: `${this.slideOut()} 250ms ease-in` },
11191
+ '&::backdrop': scrim
11192
+ ? { ...this.theme.backdrop(options.backdrop) }
11193
+ : { background: 'transparent' },
11194
+ '&[open]': { animation: `${this.slideIn()} ${motion.duration}ms ${motion.easing}` },
11195
+ '&[closing]': { animation: `${this.slideOut()} ${motion.duration}ms ${motion.easing}` },
11196
+ // Same duration as the slide, so the dimming tracks the panel rather
11197
+ // than snapping. Declared after the `[open]` rules: the two match on
11198
+ // specificity while closing, so source order is what makes the fade-out
11199
+ // win. Nothing to fade when there is no scrim.
11200
+ ...(scrim
11201
+ ? {
11202
+ '&[open]::backdrop': {
11203
+ animation: `${this.scrimIn} ${motion.duration}ms ${motion.easing}`,
11204
+ },
11205
+ '&[closing]::backdrop': {
11206
+ animation: `${this.scrimOut} ${motion.duration}ms ${motion.easing}`,
11207
+ },
11208
+ }
11209
+ : {}),
10986
11210
  });
10987
11211
  }, ...(ngDevMode ? [{ debugName: "overClass" }] : /* istanbul ignore next */ []));
10988
11212
  /** The only scrolling row, and the only padded one. */
@@ -12147,7 +12371,7 @@ class ConfirmationDialogComponent {
12147
12371
  confirmation = input(...(ngDevMode ? [undefined, { debugName: "confirmation" }] : /* istanbul ignore next */ []));
12148
12372
  showing = output();
12149
12373
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConfirmationDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12150
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: ConfirmationDialogComponent, isStandalone: true, selector: "uni-confirmation-dialog", inputs: { show: { classPropertyName: "show", publicName: "show", isSignal: true, isRequired: false, transformFunction: null }, confirmation: { classPropertyName: "confirmation", publicName: "confirmation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { showing: "showing" }, ngImport: i0, template: "<dialog uni-dialog [show]=\"show()\" (showing)=\"showing.emit($event)\">\n <div uni-dialog-header>\n {{ confirmation()?.title }}\n </div>\n <div box-layout padding=\"md\">\n <span uni-text>\n {{ confirmation()?.message }}\n </span>\n </div>\n <div\n dialog-buttons\n [confirmButtonText]=\"confirmation()?.actionLabel\"\n (confirmed)=\"confirmation()?.action()\"\n [cancelButtonText]=\"confirmation()?.dismissLabel\"\n ></div>\n</dialog>\n", dependencies: [{ kind: "component", type: UniDialogComponent, selector: "dialog[uni-dialog]", inputs: ["show", "ariaLabel", "initialFocus", "defaultCloseButton"], outputs: ["showChange", "showing"] }, { kind: "component", type: UniDialogHeaderComponent, selector: "[uni-dialog-header]" }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniDialogButtonsComponent, selector: "[uni-dialog-buttons], [dialog-buttons]", inputs: ["confirmButtonText", "confirmButtonVariant", "cancelButtonText", "cancelButtonVariant", "disableConfirm", "padding", "paddingBottom", "justifyContent"], outputs: ["confirmed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12374
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: ConfirmationDialogComponent, isStandalone: true, selector: "uni-confirmation-dialog", inputs: { show: { classPropertyName: "show", publicName: "show", isSignal: true, isRequired: false, transformFunction: null }, confirmation: { classPropertyName: "confirmation", publicName: "confirmation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { showing: "showing" }, ngImport: i0, template: "<dialog uni-dialog [show]=\"show()\" (showing)=\"showing.emit($event)\">\n <div dialog-header>\n {{ confirmation()?.title }}\n </div>\n <div box-layout padding=\"md\">\n <span uni-text>\n {{ confirmation()?.message }}\n </span>\n </div>\n <div\n dialog-buttons\n [confirmButtonText]=\"confirmation()?.actionLabel\"\n (confirmed)=\"confirmation()?.action()\"\n [cancelButtonText]=\"confirmation()?.dismissLabel\"\n ></div>\n</dialog>\n", dependencies: [{ kind: "component", type: UniDialogComponent, selector: "dialog[uni-dialog]", inputs: ["show", "ariaLabel", "initialFocus", "defaultCloseButton"], outputs: ["showChange", "showing"] }, { kind: "component", type: UniDialogHeaderComponent, selector: "[uni-dialog-header], [dialog-header]" }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniDialogButtonsComponent, selector: "[uni-dialog-buttons], [dialog-buttons]", inputs: ["confirmButtonText", "confirmButtonVariant", "cancelButtonText", "cancelButtonVariant", "disableConfirm", "padding", "paddingBottom", "justifyContent"], outputs: ["confirmed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12151
12375
  }
12152
12376
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConfirmationDialogComponent, decorators: [{
12153
12377
  type: Component,
@@ -12157,7 +12381,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
12157
12381
  UniBoxDirective,
12158
12382
  UniTextDirective,
12159
12383
  UniDialogButtonsComponent,
12160
- ], template: "<dialog uni-dialog [show]=\"show()\" (showing)=\"showing.emit($event)\">\n <div uni-dialog-header>\n {{ confirmation()?.title }}\n </div>\n <div box-layout padding=\"md\">\n <span uni-text>\n {{ confirmation()?.message }}\n </span>\n </div>\n <div\n dialog-buttons\n [confirmButtonText]=\"confirmation()?.actionLabel\"\n (confirmed)=\"confirmation()?.action()\"\n [cancelButtonText]=\"confirmation()?.dismissLabel\"\n ></div>\n</dialog>\n" }]
12384
+ ], template: "<dialog uni-dialog [show]=\"show()\" (showing)=\"showing.emit($event)\">\n <div dialog-header>\n {{ confirmation()?.title }}\n </div>\n <div box-layout padding=\"md\">\n <span uni-text>\n {{ confirmation()?.message }}\n </span>\n </div>\n <div\n dialog-buttons\n [confirmButtonText]=\"confirmation()?.actionLabel\"\n (confirmed)=\"confirmation()?.action()\"\n [cancelButtonText]=\"confirmation()?.dismissLabel\"\n ></div>\n</dialog>\n" }]
12161
12385
  }], propDecorators: { show: [{ type: i0.Input, args: [{ isSignal: true, alias: "show", required: false }] }], confirmation: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmation", required: false }] }], showing: [{ type: i0.Output, args: ["showing"] }] } });
12162
12386
 
12163
12387
  class UniSnackbarComponent extends BaseComponent {