@uni-design-system/uni-angular 8.3.1 → 8.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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, viewChild, effect, untracked, afterNextRender, ViewChild, afterRenderEffect, booleanAttribute, viewChildren } from '@angular/core';
2
+ import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, viewChild, effect, untracked, afterRenderEffect, afterNextRender, ViewChild, booleanAttribute, viewChildren } from '@angular/core';
3
3
  import { injectGlobal, css, keyframes } from '@emotion/css';
4
- import { UniThemes, LightTheme, toTypefaces, parseTheme, formatThemeIssues, hydrateTheme, createThemeFromPalette, Z_INDEX, removeInputPlatformStyling, fadeIn, fadeOut, EXPAND_DEFAULT_SPEED, expandDuration, expandFadeIn, collapseFadeOut, HOVER_OR_KEYBOARD_FOCUS, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
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
5
  import { NgClass, NgTemplateOutlet, CommonModule } from '@angular/common';
6
6
 
7
7
  let nextUniqueId = 0;
@@ -49,6 +49,35 @@ const visuallyHidden = {
49
49
  function motionSafe(styles) {
50
50
  return { '@media (prefers-reduced-motion: no-preference)': styles };
51
51
  }
52
+ /**
53
+ * A polite live region's text, for the running commentary a form control owes
54
+ * a screen reader: commits, clears, refused entries, result counts — changes
55
+ * a sighted user sees but that are otherwise silent.
56
+ *
57
+ * Extracted because five controls (`uni-combobox`, `uni-tag-input`,
58
+ * `uni-time-input`, `uni-date-input`, `uni-calendar`) carried byte-identical
59
+ * copies, including the repeat trick below — the kind of subtlety that is
60
+ * quietly dropped when the sixth control hand-rolls its own.
61
+ *
62
+ * Announcing the same text twice must still be heard: assistive tech reads a
63
+ * live region when its content *changes*, so setting an identical string is a
64
+ * no-op and the second "No match." would be silent. A trailing space breaks
65
+ * the equality without changing a word of what is read. Successive repeats
66
+ * alternate between the padded and unpadded form, so nothing accumulates.
67
+ *
68
+ * Deliberately holds no DOM and no styling: the region belongs to the
69
+ * component's own template, where its placement and visually-hidden class are
70
+ * already the component's business.
71
+ */
72
+ function createAnnouncer() {
73
+ const message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
74
+ return {
75
+ message: message.asReadonly(),
76
+ announce(text) {
77
+ message.set(message() === text ? `${text} ` : text);
78
+ },
79
+ };
80
+ }
52
81
 
53
82
  /**
54
83
  * Canonical date/time value shapes shared by `uni-calendar`,
@@ -347,8 +376,9 @@ const joinDateTime = (date, time) => date && time ? `${date}T${time}` : undefine
347
376
  * Extracted because three controls need the identical contract
348
377
  * (`uni-search-input`, `uni-tag-input`, and the multi-select upgrade), and the
349
378
  * parts that silently drift between hand-rolled copies all live here: the
350
- * wrap-around arithmetic, Home/End, and keeping the active id in sync with the
351
- * option list.
379
+ * wrap-around arithmetic and keeping the active id in sync with the option
380
+ * list. Home/End are opt-in (`homeEndNavigates`) because a text field's caret
381
+ * has the better claim on them.
352
382
  *
353
383
  * `Enter` and `Escape` are deliberately *not* handled: what they mean depends
354
384
  * on the control (submit a search, commit a typed token, clear the field), and
@@ -362,6 +392,7 @@ class ListboxNavigation {
362
392
  _activeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeIndex" }] : /* istanbul ignore next */ []));
363
393
  wrap;
364
394
  isDisabled;
395
+ homeEndNavigates;
365
396
  /** Whether the popup is showing. Also false when there is nothing to show. */
366
397
  open = computed(() => this._open() && this.config.count() > 0, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
367
398
  /** Index of the highlighted option, or -1 when none is active. */
@@ -380,6 +411,7 @@ class ListboxNavigation {
380
411
  this.listboxId = uniqueId(config.idPrefix ?? 'uni-listbox');
381
412
  this.wrap = config.wrap ?? true;
382
413
  this.isDisabled = config.disabled ?? (() => false);
414
+ this.homeEndNavigates = config.homeEndNavigates ?? false;
383
415
  }
384
416
  /** Stable per-option id, for `role="option"` elements. */
385
417
  optionId(index) {
@@ -397,9 +429,9 @@ class ListboxNavigation {
397
429
  this._activeIndex.set(index);
398
430
  }
399
431
  /**
400
- * Handle ArrowDown / ArrowUp / Home / End, opening the popup if needed.
401
- * Returns true when the key was consumed, so a caller can fall through to
402
- * its own handling for everything else.
432
+ * Handle ArrowDown / ArrowUp and Home / End where `homeEndNavigates` is
433
+ * on. Opens the popup if needed. Returns true when the key was consumed, so
434
+ * a caller can fall through to its own handling for everything else.
403
435
  */
404
436
  navigate(event) {
405
437
  const count = this.config.count();
@@ -421,10 +453,11 @@ class ListboxNavigation {
421
453
  case 'ArrowUp':
422
454
  // Opening with ArrowUp lands on the last option, matching menus.
423
455
  return current < 0 ? this.seek(count - 1, -1, count) : this.step(current, -1, count);
456
+ // Left to the caret unless a consumer opts in — see `homeEndNavigates`.
424
457
  case 'Home':
425
- return this.seek(0, 1, count);
458
+ return this.homeEndNavigates ? this.seek(0, 1, count) : null;
426
459
  case 'End':
427
- return this.seek(count - 1, -1, count);
460
+ return this.homeEndNavigates ? this.seek(count - 1, -1, count) : null;
428
461
  default:
429
462
  return null;
430
463
  }
@@ -674,10 +707,31 @@ function transformOriginFor(panel, trigger) {
674
707
 
675
708
  /**
676
709
  * Shared plumbing for top-layer overlays built on the native `popover`
677
- * attribute (dropdown, popover, callout). Pure functions and data — no DOM
678
- * ownership so each component keeps its own template while the anchor
679
- * bookkeeping, discrete-transition block, and focus-restore rule stay single-
680
- * sourced.
710
+ * attribute (dropdown, popover, callout, tooltip, the listbox popups,
711
+ * snackbar). Pure functions and data no DOM ownership so each component
712
+ * keeps its own template while the anchor bookkeeping, discrete-transition
713
+ * block, and focus-restore rule stay single-sourced.
714
+ *
715
+ * These are deliberately à-la-carte rather than one factory: the overlays
716
+ * share *mechanics* but not *lifecycles*. A dropdown toggles from a trigger
717
+ * click and restores focus; a tooltip follows hover and never takes focus; a
718
+ * callout traps focus behind a scrim; a listbox popup is rendered by `@if`
719
+ * with focus staying in the field; a snackbar has no anchor at all and closes
720
+ * on a timer. Take what a component needs and leave the rest.
721
+ *
722
+ * **Choosing `popover="auto"` vs `"manual"`** — the one decision worth making
723
+ * deliberately. `auto` brings native light-dismiss: an outside pointerdown or
724
+ * Escape closes it. That is right for a panel the user opened and can dismiss
725
+ * by looking away — dropdown, popover, callout, tooltip.
726
+ *
727
+ * `manual` is right when the control already owns dismissal, and light-dismiss
728
+ * would fight it. Two cases in this library:
729
+ * - the listbox popups, whose own field is *outside* the popup, so `auto`
730
+ * would close the list on every click into the input;
731
+ * - the snackbar, which would be torn away from someone still reading it by
732
+ * a click anywhere on the page.
733
+ *
734
+ * `manual` means the component must close it — nothing else will.
681
735
  */
682
736
  /** Placement → `transform-origin`, so scale animations grow from the anchor. */
683
737
  const TRANSFORM_ORIGINS = {
@@ -721,13 +775,20 @@ function isToggleOpen(event) {
721
775
  * top layer: `transition-behavior: allow-discrete` over the `hidden` keys plus
722
776
  * `display`/`overlay`, the shown state under `:popover-open`, and
723
777
  * `@starting-style` so entry transitions run from the hidden state.
778
+ *
779
+ * `display` and `overlay` must ride along or the element cannot animate into
780
+ * and out of the top layer at all — it would simply appear and vanish.
781
+ *
782
+ * `timingFunction` is omitted from the output when not given, leaving the CSS
783
+ * initial value (`ease`), so callers that never asked for one are unaffected.
724
784
  */
725
- function discreteOverlayTransition(durationMs, hidden, shown) {
785
+ function discreteOverlayTransition(durationMs, hidden, shown, timingFunction) {
726
786
  return {
727
787
  transitionProperty: [...Object.keys(hidden), 'display', 'overlay']
728
788
  .map((key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`))
729
789
  .join(', '),
730
790
  transitionDuration: `${durationMs}ms`,
791
+ ...(timingFunction ? { transitionTimingFunction: timingFunction } : {}),
731
792
  transitionBehavior: 'allow-discrete',
732
793
  ...hidden,
733
794
  '&:popover-open': shown,
@@ -1276,6 +1337,9 @@ class ThemeService {
1276
1337
  borders = computed(() => this.theme().borders, ...(ngDevMode ? [{ debugName: "borders" }] : /* istanbul ignore next */ []));
1277
1338
  shadows = computed(() => this.theme().shadows, ...(ngDevMode ? [{ debugName: "shadows" }] : /* istanbul ignore next */ []));
1278
1339
  icons = computed(() => this.theme().icons, ...(ngDevMode ? [{ debugName: "icons" }] : /* istanbul ignore next */ []));
1340
+ // `?? {}` for themes registered as JSON that predate the motion scale —
1341
+ // the validator does not require it, so they must not crash on read.
1342
+ motions = computed(() => this.theme().motion ?? {}, ...(ngDevMode ? [{ debugName: "motions" }] : /* istanbul ignore next */ []));
1279
1343
  constructor() {
1280
1344
  // Honor the user's reduced-motion preference across every component
1281
1345
  // (WCAG 2.3.3): collapse all animations/transitions to a single frame.
@@ -1513,6 +1577,16 @@ class ThemeService {
1513
1577
  borderRadius,
1514
1578
  };
1515
1579
  }
1580
+ /**
1581
+ * Resolves a named motion primitive. Falls back to `popup` and then to a
1582
+ * hard default, so a theme that predates the motion scale — or names a
1583
+ * token that isn't there — still animates rather than snapping.
1584
+ */
1585
+ motion(token) {
1586
+ const motions = this.motions();
1587
+ return ((token ? motions[token] : undefined) ??
1588
+ motions['popup'] ?? { duration: 100, easing: 'linear', scale: 0.8 });
1589
+ }
1516
1590
  radius(size) {
1517
1591
  return !size ? undefined : { borderRadius: this.radii()[size] };
1518
1592
  }
@@ -2904,7 +2978,8 @@ class UniCalendarComponent extends BaseComponent {
2904
2978
  /** Hover/focus candidate painting the preview band while a range is pending. */
2905
2979
  previewDate = signal(null, ...(ngDevMode ? [{ debugName: "previewDate" }] : /* istanbul ignore next */ []));
2906
2980
  /** Live-region text; selections are otherwise silent for a screen reader. */
2907
- announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
2981
+ /** Commits, clears and refusals are otherwise silent to a screen reader. */
2982
+ announcer = createAnnouncer();
2908
2983
  resolvedLocale = computed(() => this.locale() ?? (document.documentElement.lang || navigator.language || 'en-US'), ...(ngDevMode ? [{ debugName: "resolvedLocale" }] : /* istanbul ignore next */ []));
2909
2984
  resolvedWeekStart = computed(() => this.weekStart() ?? localeWeekStart(this.resolvedLocale()), ...(ngDevMode ? [{ debugName: "resolvedWeekStart" }] : /* istanbul ignore next */ []));
2910
2985
  /**
@@ -3036,12 +3111,12 @@ class UniCalendarComponent extends BaseComponent {
3036
3111
  const full = (d) => formatDate(d, locale, { dateStyle: 'full' });
3037
3112
  if (this.mode() === 'single') {
3038
3113
  this.value.set(date);
3039
- this.announce(`${full(date)} selected.`);
3114
+ this.announcer.announce(`${full(date)} selected.`);
3040
3115
  }
3041
3116
  else if (!this.pendingStart()) {
3042
3117
  this.pendingStart.set(date);
3043
3118
  this.previewDate.set(date);
3044
- this.announce(`Start date ${full(date)}. Choose an end date.`);
3119
+ this.announcer.announce(`Start date ${full(date)}. Choose an end date.`);
3045
3120
  }
3046
3121
  else {
3047
3122
  let [start, end] = [this.pendingStart(), date];
@@ -3051,14 +3126,14 @@ class UniCalendarComponent extends BaseComponent {
3051
3126
  this.previewDate.set(null);
3052
3127
  this.value.set({ start, end });
3053
3128
  const days = inclusiveDayCount(start, end);
3054
- this.announce(`Range selected, ${full(start)} to ${full(end)}. ${days} ${days === 1 ? 'day' : 'days'}.`);
3129
+ this.announcer.announce(`Range selected, ${full(start)} to ${full(end)}. ${days} ${days === 1 ? 'day' : 'days'}.`);
3055
3130
  }
3056
3131
  this.selected.emit(date);
3057
3132
  }
3058
3133
  cancelPending() {
3059
3134
  this.pendingStart.set(null);
3060
3135
  this.previewDate.set(null);
3061
- this.announce('Range selection cancelled.');
3136
+ this.announcer.announce('Range selection cancelled.');
3062
3137
  }
3063
3138
  // --- Navigation ------------------------------------------------------------
3064
3139
  onNav(direction) {
@@ -3158,10 +3233,6 @@ class UniCalendarComponent extends BaseComponent {
3158
3233
  }
3159
3234
  return first;
3160
3235
  }
3161
- announce(message) {
3162
- // Re-announce identical text by breaking the string equality.
3163
- this.announcement.set(this.announcement() === message ? `${message} ` : message);
3164
- }
3165
3236
  // --- Styling ---------------------------------------------------------------
3166
3237
  daySize = computed(() => (this.componentTheme().sizes?.[this.size()] ?? {}), ...(ngDevMode ? [{ debugName: "daySize" }] : /* istanbul ignore next */ []));
3167
3238
  className = computed(() => css([(this.componentTheme().fixed ?? { display: 'inline-block' })]), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
@@ -3294,11 +3365,11 @@ class UniCalendarComponent extends BaseComponent {
3294
3365
  }
3295
3366
  showOutside = computed(() => this.componentOptions().showOutsideDays ?? false, ...(ngDevMode ? [{ debugName: "showOutside" }] : /* istanbul ignore next */ []));
3296
3367
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCalendarComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3297
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniCalendarComponent, isStandalone: true, selector: "uni-calendar, Calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, month: { classPropertyName: "month", publicName: "month", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", month: "monthChange", selected: "selected" }, host: { listeners: { "focusout": "onHostFocusOut($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'calendar' }], usesInheritance: true, ngImport: i0, template: "<div [class]=\"navClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navPrevSymbol ?? 'chevron_left'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(-1)\"\n >\n Previous month\n </button>\n <!-- aria-live so PageUp/Down narrates the new month without refocusing. -->\n <div [class]=\"headingClass()\" [id]=\"headingId\" aria-live=\"polite\">{{ heading() }}</div>\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navNextSymbol ?? 'chevron_right'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(1)\"\n >\n Next month\n </button>\n</div>\n\n<!-- One tab stop: the roving tabindex lives on the day buttons, and the grid\n itself only relays their bubbling keys (same delegation as tag-input). -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n role=\"grid\"\n [class]=\"gridClass()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : headingId\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div role=\"row\" [class]=\"rowClass()\">\n @for (weekday of weekdays(); track weekday.full) {\n <div role=\"columnheader\" [class]=\"weekdayClass()\">\n <abbr [attr.title]=\"weekday.full\">{{ weekday.label }}</abbr>\n </div>\n }\n </div>\n\n @for (week of gridWeeks(); track week[0].date) {\n <div role=\"row\" [class]=\"rowClass()\">\n @for (cell of week; track cell.date) {\n @if (cell.outside) {\n <!-- Outside days: hidden placeholders by default (geometry kept),\n muted and non-interactive under the showOutsideDays option. -->\n <div role=\"gridcell\" aria-hidden=\"true\" [class]=\"cell.cellClass\">\n @if (showOutside()) {\n <span [class]=\"outsideDayClass()\">{{ cell.day }}</span>\n }\n </div>\n } @else {\n <div\n role=\"gridcell\"\n [class]=\"cell.cellClass\"\n [attr.aria-selected]=\"cell.selected || cell.inBand ? true : null\"\n >\n <button\n type=\"button\"\n [class]=\"cell.dayClass\"\n [attr.data-date]=\"cell.date\"\n [tabindex]=\"cell.tabIndex\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n [disabled]=\"cell.disabled || disabled()\"\n (click)=\"select(cell.date)\"\n (mouseenter)=\"onDayHover(cell.date)\"\n >\n {{ cell.day }}\n @if (cell.markers.length) {\n <span [class]=\"dotsClass()\">\n @for (marker of cell.markers; track $index) {\n <span data-dot [class]=\"dotClassFor(marker.variant)\"></span>\n }\n </span>\n }\n </button>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<!-- Selections and range progress are otherwise silent for a screen reader. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3368
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniCalendarComponent, isStandalone: true, selector: "uni-calendar, Calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, month: { classPropertyName: "month", publicName: "month", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", month: "monthChange", selected: "selected" }, host: { listeners: { "focusout": "onHostFocusOut($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'calendar' }], usesInheritance: true, ngImport: i0, template: "<div [class]=\"navClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navPrevSymbol ?? 'chevron_left'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(-1)\"\n >\n Previous month\n </button>\n <!-- aria-live so PageUp/Down narrates the new month without refocusing. -->\n <div [class]=\"headingClass()\" [id]=\"headingId\" aria-live=\"polite\">{{ heading() }}</div>\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navNextSymbol ?? 'chevron_right'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(1)\"\n >\n Next month\n </button>\n</div>\n\n<!-- One tab stop: the roving tabindex lives on the day buttons, and the grid\n itself only relays their bubbling keys (same delegation as tag-input). -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n role=\"grid\"\n [class]=\"gridClass()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : headingId\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div role=\"row\" [class]=\"rowClass()\">\n @for (weekday of weekdays(); track weekday.full) {\n <div role=\"columnheader\" [class]=\"weekdayClass()\">\n <abbr [attr.title]=\"weekday.full\">{{ weekday.label }}</abbr>\n </div>\n }\n </div>\n\n @for (week of gridWeeks(); track week[0].date) {\n <div role=\"row\" [class]=\"rowClass()\">\n @for (cell of week; track cell.date) {\n @if (cell.outside) {\n <!-- Outside days: hidden placeholders by default (geometry kept),\n muted and non-interactive under the showOutsideDays option. -->\n <div role=\"gridcell\" aria-hidden=\"true\" [class]=\"cell.cellClass\">\n @if (showOutside()) {\n <span [class]=\"outsideDayClass()\">{{ cell.day }}</span>\n }\n </div>\n } @else {\n <div\n role=\"gridcell\"\n [class]=\"cell.cellClass\"\n [attr.aria-selected]=\"cell.selected || cell.inBand ? true : null\"\n >\n <button\n type=\"button\"\n [class]=\"cell.dayClass\"\n [attr.data-date]=\"cell.date\"\n [tabindex]=\"cell.tabIndex\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n [disabled]=\"cell.disabled || disabled()\"\n (click)=\"select(cell.date)\"\n (mouseenter)=\"onDayHover(cell.date)\"\n >\n {{ cell.day }}\n @if (cell.markers.length) {\n <span [class]=\"dotsClass()\">\n @for (marker of cell.markers; track $index) {\n <span data-dot [class]=\"dotClassFor(marker.variant)\"></span>\n }\n </span>\n }\n </button>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<!-- Selections and range progress are otherwise silent for a screen reader. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3298
3369
  }
3299
3370
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCalendarComponent, decorators: [{
3300
3371
  type: Component,
3301
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-calendar, Calendar', imports: [UniIconButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'calendar' }], host: { '[class]': 'className()', '(focusout)': 'onHostFocusOut($event)' }, template: "<div [class]=\"navClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navPrevSymbol ?? 'chevron_left'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(-1)\"\n >\n Previous month\n </button>\n <!-- aria-live so PageUp/Down narrates the new month without refocusing. -->\n <div [class]=\"headingClass()\" [id]=\"headingId\" aria-live=\"polite\">{{ heading() }}</div>\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navNextSymbol ?? 'chevron_right'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(1)\"\n >\n Next month\n </button>\n</div>\n\n<!-- One tab stop: the roving tabindex lives on the day buttons, and the grid\n itself only relays their bubbling keys (same delegation as tag-input). -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n role=\"grid\"\n [class]=\"gridClass()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : headingId\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div role=\"row\" [class]=\"rowClass()\">\n @for (weekday of weekdays(); track weekday.full) {\n <div role=\"columnheader\" [class]=\"weekdayClass()\">\n <abbr [attr.title]=\"weekday.full\">{{ weekday.label }}</abbr>\n </div>\n }\n </div>\n\n @for (week of gridWeeks(); track week[0].date) {\n <div role=\"row\" [class]=\"rowClass()\">\n @for (cell of week; track cell.date) {\n @if (cell.outside) {\n <!-- Outside days: hidden placeholders by default (geometry kept),\n muted and non-interactive under the showOutsideDays option. -->\n <div role=\"gridcell\" aria-hidden=\"true\" [class]=\"cell.cellClass\">\n @if (showOutside()) {\n <span [class]=\"outsideDayClass()\">{{ cell.day }}</span>\n }\n </div>\n } @else {\n <div\n role=\"gridcell\"\n [class]=\"cell.cellClass\"\n [attr.aria-selected]=\"cell.selected || cell.inBand ? true : null\"\n >\n <button\n type=\"button\"\n [class]=\"cell.dayClass\"\n [attr.data-date]=\"cell.date\"\n [tabindex]=\"cell.tabIndex\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n [disabled]=\"cell.disabled || disabled()\"\n (click)=\"select(cell.date)\"\n (mouseenter)=\"onDayHover(cell.date)\"\n >\n {{ cell.day }}\n @if (cell.markers.length) {\n <span [class]=\"dotsClass()\">\n @for (marker of cell.markers; track $index) {\n <span data-dot [class]=\"dotClassFor(marker.variant)\"></span>\n }\n </span>\n }\n </button>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<!-- Selections and range progress are otherwise silent for a screen reader. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n" }]
3372
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-calendar, Calendar', imports: [UniIconButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'calendar' }], host: { '[class]': 'className()', '(focusout)': 'onHostFocusOut($event)' }, template: "<div [class]=\"navClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navPrevSymbol ?? 'chevron_left'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(-1)\"\n >\n Previous month\n </button>\n <!-- aria-live so PageUp/Down narrates the new month without refocusing. -->\n <div [class]=\"headingClass()\" [id]=\"headingId\" aria-live=\"polite\">{{ heading() }}</div>\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navNextSymbol ?? 'chevron_right'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(1)\"\n >\n Next month\n </button>\n</div>\n\n<!-- One tab stop: the roving tabindex lives on the day buttons, and the grid\n itself only relays their bubbling keys (same delegation as tag-input). -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n role=\"grid\"\n [class]=\"gridClass()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : headingId\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div role=\"row\" [class]=\"rowClass()\">\n @for (weekday of weekdays(); track weekday.full) {\n <div role=\"columnheader\" [class]=\"weekdayClass()\">\n <abbr [attr.title]=\"weekday.full\">{{ weekday.label }}</abbr>\n </div>\n }\n </div>\n\n @for (week of gridWeeks(); track week[0].date) {\n <div role=\"row\" [class]=\"rowClass()\">\n @for (cell of week; track cell.date) {\n @if (cell.outside) {\n <!-- Outside days: hidden placeholders by default (geometry kept),\n muted and non-interactive under the showOutsideDays option. -->\n <div role=\"gridcell\" aria-hidden=\"true\" [class]=\"cell.cellClass\">\n @if (showOutside()) {\n <span [class]=\"outsideDayClass()\">{{ cell.day }}</span>\n }\n </div>\n } @else {\n <div\n role=\"gridcell\"\n [class]=\"cell.cellClass\"\n [attr.aria-selected]=\"cell.selected || cell.inBand ? true : null\"\n >\n <button\n type=\"button\"\n [class]=\"cell.dayClass\"\n [attr.data-date]=\"cell.date\"\n [tabindex]=\"cell.tabIndex\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n [disabled]=\"cell.disabled || disabled()\"\n (click)=\"select(cell.date)\"\n (mouseenter)=\"onDayHover(cell.date)\"\n >\n {{ cell.day }}\n @if (cell.markers.length) {\n <span [class]=\"dotsClass()\">\n @for (marker of cell.markers; track $index) {\n <span data-dot [class]=\"dotClassFor(marker.variant)\"></span>\n }\n </span>\n }\n </button>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<!-- Selections and range progress are otherwise silent for a screen reader. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n" }]
3302
3373
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], month: [{ type: i0.Input, args: [{ isSignal: true, alias: "month", required: false }] }, { type: i0.Output, args: ["monthChange"] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], disabledDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDates", required: false }] }], markers: [{ type: i0.Input, args: [{ isSignal: true, alias: "markers", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], weekStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStart", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
3303
3374
 
3304
3375
  /**
@@ -3466,7 +3537,7 @@ class UniCalloutComponent extends BaseComponent {
3466
3537
  clearAnchorName(target);
3467
3538
  this.activeTarget.set(null);
3468
3539
  this.scrimContent.set(false);
3469
- }, this.componentOptions().transitionMs);
3540
+ }, this.motion().duration);
3470
3541
  // The duet loop may have sent the user into the target on purpose — don't
3471
3542
  // yank them back out of it.
3472
3543
  if (!focusInTarget && this.prevFocus && document.contains(this.prevFocus)) {
@@ -3579,8 +3650,20 @@ class UniCalloutComponent extends BaseComponent {
3579
3650
  scrimColor: options.scrimColor,
3580
3651
  });
3581
3652
  }, ...(ngDevMode ? [{ debugName: "spotlight" }] : /* istanbul ignore next */ []));
3582
- scrimClassName = computed(() => {
3653
+ /**
3654
+ * Timing for the open/close fade and the teardown that follows it. The
3655
+ * deprecated `transitionMs` wins when a theme still sets it, so existing
3656
+ * themes keep their timing; otherwise the `motion` token decides.
3657
+ */
3658
+ motion = computed(() => {
3583
3659
  const options = this.componentOptions();
3660
+ const token = this.theme.motion(options.motion ?? 'panel');
3661
+ return options.transitionMs === undefined
3662
+ ? token
3663
+ : { ...token, duration: options.transitionMs };
3664
+ }, ...(ngDevMode ? [{ debugName: "motion" }] : /* istanbul ignore next */ []));
3665
+ scrimClassName = computed(() => {
3666
+ const motion = this.motion();
3584
3667
  return css({
3585
3668
  position: 'fixed',
3586
3669
  inset: 0,
@@ -3593,7 +3676,7 @@ class UniCalloutComponent extends BaseComponent {
3593
3676
  overflow: 'visible',
3594
3677
  pointerEvents: 'none',
3595
3678
  '& > *': { position: 'fixed' },
3596
- ...discreteOverlayTransition(options.transitionMs, { opacity: 0 }, { opacity: 1 }),
3679
+ ...discreteOverlayTransition(motion.duration, { opacity: 0 }, { opacity: 1 }, motion.easing),
3597
3680
  });
3598
3681
  }, ...(ngDevMode ? [{ debugName: "scrimClassName" }] : /* istanbul ignore next */ []));
3599
3682
  windowClassName = computed(() => {
@@ -3615,6 +3698,7 @@ class UniCalloutComponent extends BaseComponent {
3615
3698
  }), ...(ngDevMode ? [{ debugName: "fullCoverClassName" }] : /* istanbul ignore next */ []));
3616
3699
  panelClassName = computed(() => {
3617
3700
  const options = this.componentOptions();
3701
+ const motion = this.motion();
3618
3702
  const anchored = this.activeTarget() !== null;
3619
3703
  return css({
3620
3704
  ...this.theme.colorPair(options.color),
@@ -3633,7 +3717,7 @@ class UniCalloutComponent extends BaseComponent {
3633
3717
  mainAxis: options.offset + options.spotlightPadding,
3634
3718
  })
3635
3719
  : {}),
3636
- ...discreteOverlayTransition(options.transitionMs, { opacity: 0, translate: '0 6px' }, { opacity: 1, translate: '0 0' }),
3720
+ ...discreteOverlayTransition(motion.duration, { opacity: 0, translate: '0 6px' }, { opacity: 1, translate: '0 0' }, motion.easing),
3637
3721
  });
3638
3722
  }, ...(ngDevMode ? [{ debugName: "panelClassName" }] : /* istanbul ignore next */ []));
3639
3723
  headRowClassName = computed(() => {
@@ -3948,9 +4032,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3948
4032
  }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }, { type: i0.Output, args: ["indeterminateChange"] }] } });
3949
4033
 
3950
4034
  /**
3951
- * Style block for the popup behind every `ListboxNavigation` consumer: an
3952
- * absolutely-positioned `ul[role="listbox"]` under a `position: relative`
3953
- * field wrapper, with the shared option chrome and active/hover highlight.
4035
+ * Whether the browser can keep a top-layer popup attached to its field.
4036
+ *
4037
+ * The top layer and anchor positioning must be adopted together, and support
4038
+ * for them does not arrive together: Safari shipped `popover` in 17 but
4039
+ * `position-anchor` only in 26. Promoting the popup without anchor support
4040
+ * would strand it — a top-layer element has no positioned ancestor, so the
4041
+ * fallback's `position: absolute; top: 100%` would resolve against the
4042
+ * viewport and drop the list a full screen height down the page. So the
4043
+ * `popover` attribute is gated on this too, not just the anchored CSS.
4044
+ *
4045
+ * Undefined `CSS` (jsdom) reads as unsupported, which is also what keeps the
4046
+ * popup in the normal flow — and the component specs unchanged — under test.
4047
+ */
4048
+ const supportsAnchoredPopup = () => typeof CSS !== 'undefined' && CSS.supports?.('position-anchor: --a') === true;
4049
+ /** `popover` attribute value for a listbox popup, or null where unsupported.
4050
+ Always `manual`: these controls already own dismissal (focusout, Escape,
4051
+ commit), and `auto`'s light-dismiss fires on pointerdown outside the
4052
+ popup — which includes their own field, closing the list behind the
4053
+ component's back on every click into the input. */
4054
+ const listboxPopupAttr = () => supportsAnchoredPopup() ? 'manual' : null;
4055
+ /**
4056
+ * A document-unique `anchor-name`, plus the style fragment that puts it on the
4057
+ * field wrapper. Spread `style` into the wrapper's `css()` and pass `name` to
4058
+ * {@link listboxPopupStyles}.
4059
+ */
4060
+ function newListboxAnchor() {
4061
+ const name = newAnchorName();
4062
+ return { name, style: { anchorName: name } };
4063
+ }
4064
+ /**
4065
+ * Shows the popup in the top layer as soon as it renders, and scales its
4066
+ * entry animation out of the edge it actually opened from.
4067
+ *
4068
+ * The popups are `@if`-rendered, and a `popover` element is `display: none`
4069
+ * until `showPopover()` runs — so appearing in the DOM is not enough. Runs on
4070
+ * every render pass; `showPopover()` on an already-open popup throws, which is
4071
+ * the cheapest way to ask "is it open?".
4072
+ *
4073
+ * The origin is measured rather than assumed: `position-try-fallbacks` may
4074
+ * have flipped the popup above its field near the bottom of the viewport, and
4075
+ * the static `top center` would then grow it from the wrong edge. Measuring
4076
+ * forces layout, so it lands before the first frame of the transition.
4077
+ *
4078
+ * Call from an injection context (a field initializer or the constructor);
4079
+ * the component's host element is the anchor.
4080
+ */
4081
+ function promoteListboxPopup(ref) {
4082
+ const anchor = inject(ElementRef).nativeElement;
4083
+ afterRenderEffect(() => {
4084
+ const element = ref()?.nativeElement;
4085
+ // The attribute, not the `popover` IDL property: the property is what the
4086
+ // gate already decided, and jsdom does not reflect it.
4087
+ if (!element?.isConnected || !element.hasAttribute('popover'))
4088
+ return;
4089
+ try {
4090
+ element.showPopover();
4091
+ }
4092
+ catch {
4093
+ return; // Already open — its origin was set when it opened.
4094
+ }
4095
+ const origin = transformOriginFor(element.getBoundingClientRect(), anchor.getBoundingClientRect());
4096
+ if (origin)
4097
+ element.style.transformOrigin = origin;
4098
+ });
4099
+ }
4100
+ /**
4101
+ * The anchored half: the popup in the top layer, tracked to its field by the
4102
+ * browser with no scroll or resize listeners. This is what lets the list
4103
+ * escape an `overflow: hidden` ancestor, which the in-flow fallback below
4104
+ * cannot do.
4105
+ *
4106
+ * `anchor-size(width)` matches the field's width the way `left: 0; right: 0`
4107
+ * did in flow. Only the block flip is offered: the popup is as wide as its
4108
+ * own field, so flipping it inline would only slide it off that field.
4109
+ *
4110
+ * `border: none` drops the UA's `[popover]` border. Its `background-color:
4111
+ * Canvas` needs no reset — the base rules' `colorPair` beats it on origin
4112
+ * alone — and must not get one: `background` is a shorthand, so resetting it
4113
+ * here would erase that painted surface and leave the list transparent.
4114
+ *
4115
+ * `border-box` keeps the popup exactly as wide as its field: `anchor-size`
4116
+ * yields the anchor's border-box width, which the base rules' padding would
4117
+ * otherwise widen by 8px.
4118
+ */
4119
+ const anchoredPopupStyles = (anchor, motion) => ({
4120
+ ...anchorStyles(anchor, 'bottom-start', { mainAxis: 4 }),
4121
+ positionTryFallbacks: 'flip-block',
4122
+ width: 'anchor-size(width)',
4123
+ boxSizing: 'border-box',
4124
+ border: 'none',
4125
+ // Grows out of the field's bottom edge; corrected after measuring when the
4126
+ // browser flips the popup above instead (see promoteListboxPopup).
4127
+ transformOrigin: 'top center',
4128
+ ...motionSafe(discreteOverlayTransition(motion.duration, { opacity: 0, transform: `scale(${motion.scale ?? 1})` }, { opacity: 1, transform: 'scale(1)' }, motion.easing)),
4129
+ });
4130
+ /**
4131
+ * Style block for the popup behind every `ListboxNavigation` consumer: a
4132
+ * `ul[role="listbox"]` under its field wrapper, with the shared option chrome
4133
+ * and active/hover highlight.
3954
4134
  *
3955
4135
  * Extracted because four components (`uni-search-input`, `uni-tag-input`,
3956
4136
  * `uni-time-input`, `uni-combobox`) carried hand-rolled copies, and the parts
@@ -3958,11 +4138,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3958
4138
  * trio, and the `activeColor` pair that themes re-point when their container
3959
4139
  * tokens don't contrast (see the Wellsourced overrides).
3960
4140
  *
4141
+ * Pass `anchor` (from {@link newListboxAnchor}) to get the top-layer
4142
+ * positioning where the browser supports it. Without it — or on a browser that
4143
+ * lacks anchor positioning — the popup stays absolutely positioned under a
4144
+ * `position: relative` wrapper, which clips inside `overflow: hidden`
4145
+ * ancestors but at least stays on its field.
4146
+ *
3961
4147
  * Compose extras with the array form — `css([listboxPopupStyles(…), {…}])` —
3962
4148
  * so a component's own `& [role="option"]` block cascades after this one
3963
4149
  * instead of replacing it (an object spread would overwrite the key).
3964
4150
  */
3965
- const listboxPopupStyles = (theme, options, { maxHeight = 280 } = {}) => ({
4151
+ const listboxPopupStyles = (theme, options, { maxHeight = 280, anchor } = {}) => ({
3966
4152
  position: 'absolute',
3967
4153
  top: '100%',
3968
4154
  left: 0,
@@ -3985,6 +4171,10 @@ const listboxPopupStyles = (theme, options, { maxHeight = 280 } = {}) => ({
3985
4171
  ...theme.colorPair((options.activeColor ?? 'primary-container')),
3986
4172
  },
3987
4173
  },
4174
+ // Last, so the anchored rules win over the in-flow ones they replace.
4175
+ ...(anchor
4176
+ ? { '@supports (position-anchor: --a)': anchoredPopupStyles(anchor, theme.motion(options.motion)) }
4177
+ : {}),
3988
4178
  });
3989
4179
 
3990
4180
  class UniInputBoxComponent extends BaseComponent {
@@ -4107,12 +4297,18 @@ class UniComboboxComponent extends BaseComponent {
4107
4297
  listRef = viewChild('listbox', ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
4108
4298
  /** Cancelled on destroy — a late tick would emit on a destroyed OutputRef. */
4109
4299
  queryTimer;
4300
+ /** Ties the popup to the field so the browser tracks it in the top layer. */
4301
+ anchor = newListboxAnchor();
4302
+ /** `manual` where the top layer is usable, else null — see the popup helper. */
4303
+ popupAttr = listboxPopupAttr();
4110
4304
  constructor() {
4111
4305
  super();
4112
4306
  inject(DestroyRef).onDestroy(() => clearTimeout(this.queryTimer));
4307
+ promoteListboxPopup(this.listRef);
4113
4308
  }
4114
4309
  srOnly = css(visuallyHidden);
4115
- announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
4310
+ /** Commits, clears and refusals are otherwise silent to a screen reader. */
4311
+ announcer = createAnnouncer();
4116
4312
  /** null → the field shows the committed label; a string is an uncommitted draft. */
4117
4313
  draft = signal(null, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
4118
4314
  /**
@@ -4163,7 +4359,7 @@ class UniComboboxComponent extends BaseComponent {
4163
4359
  this.draft.set(null);
4164
4360
  this.closeList();
4165
4361
  this.setFieldText(this.displayValue());
4166
- this.announce(`${option.label} selected.`);
4362
+ this.announcer.announce(`${option.label} selected.`);
4167
4363
  this.selected.emit(option);
4168
4364
  return true;
4169
4365
  }
@@ -4172,7 +4368,7 @@ class UniComboboxComponent extends BaseComponent {
4172
4368
  this.draft.set(null);
4173
4369
  this.closeList();
4174
4370
  this.setFieldText('');
4175
- this.announce('Selection cleared.');
4371
+ this.announcer.announce('Selection cleared.');
4176
4372
  this.cleared.emit();
4177
4373
  this.inputRef().nativeElement.focus();
4178
4374
  }
@@ -4200,7 +4396,7 @@ class UniComboboxComponent extends BaseComponent {
4200
4396
  // clearable fields may null the model this way.
4201
4397
  if (this.clearable() && this.value() !== null && !enterOnly) {
4202
4398
  this.value.set(null);
4203
- this.announce('Selection cleared.');
4399
+ this.announcer.announce('Selection cleared.');
4204
4400
  this.cleared.emit();
4205
4401
  }
4206
4402
  this.draft.set(null);
@@ -4231,7 +4427,7 @@ class UniComboboxComponent extends BaseComponent {
4231
4427
  reject() {
4232
4428
  const query = this.revertDraft();
4233
4429
  if (query) {
4234
- this.announce(`No match for “${query}”.`);
4430
+ this.announcer.announce(`No match for “${query}”.`);
4235
4431
  this.rejected.emit({ query });
4236
4432
  }
4237
4433
  }
@@ -4267,12 +4463,9 @@ class UniComboboxComponent extends BaseComponent {
4267
4463
  this.scrollToActive();
4268
4464
  return;
4269
4465
  }
4270
- if (event.key === 'Home' || event.key === 'End') {
4271
- // Only while the list is open closed, they belong to the caret.
4272
- if (this.popupOpen() && this.list.navigate(event))
4273
- this.scrollToActive();
4274
- return;
4275
- }
4466
+ // Home/End are deliberately absent: they belong to the caret, even with
4467
+ // the list open (APG's editable-combobox pattern). ArrowUp on a closed
4468
+ // list already lands on the last option, so nothing is lost.
4276
4469
  switch (event.key) {
4277
4470
  case 'Enter':
4278
4471
  // Never submits a form while the list is open.
@@ -4280,7 +4473,7 @@ class UniComboboxComponent extends BaseComponent {
4280
4473
  event.preventDefault();
4281
4474
  if (!this.resolveDraft(true)) {
4282
4475
  const count = this.filteredIndices().length;
4283
- this.announce(count === 0 ? `${this.emptyText()}.` : `${count} results. Use the arrow keys.`);
4476
+ this.announcer.announce(count === 0 ? `${this.emptyText()}.` : `${count} results. Use the arrow keys.`);
4284
4477
  }
4285
4478
  return;
4286
4479
  case 'Escape':
@@ -4312,7 +4505,7 @@ class UniComboboxComponent extends BaseComponent {
4312
4505
  // Filtering is otherwise silent to a screen reader.
4313
4506
  if (this.filterLocally() && text !== '') {
4314
4507
  const count = this.filteredIndices().length;
4315
- this.announce(count === 0 ? `${this.emptyText()}.` : `${count} result${count === 1 ? '' : 's'}.`);
4508
+ this.announcer.announce(count === 0 ? `${this.emptyText()}.` : `${count} result${count === 1 ? '' : 's'}.`);
4316
4509
  }
4317
4510
  }, this.debounceTime());
4318
4511
  }
@@ -4380,12 +4573,8 @@ class UniComboboxComponent extends BaseComponent {
4380
4573
  if (element)
4381
4574
  element.value = text;
4382
4575
  }
4383
- announce(message) {
4384
- // Re-announce identical text by breaking the string equality.
4385
- this.announcement.set(this.announcement() === message ? `${message} ` : message);
4386
- }
4387
4576
  // --- Styling ----------------------------------------------------------------
4388
- className = computed(() => css({ display: 'block', position: 'relative', width: this.width() }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
4577
+ className = computed(() => css({ display: 'block', position: 'relative', width: this.width(), ...this.anchor.style }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
4389
4578
  rowClass = computed(() => css({ display: 'flex', alignItems: 'center', flex: 1, width: '100%', minWidth: 0 }), ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
4390
4579
  inputClass = computed(() => css({
4391
4580
  flex: 1,
@@ -4421,6 +4610,7 @@ class UniComboboxComponent extends BaseComponent {
4421
4610
  // A scroll height, never a cap: a closed-set control must not render a
4422
4611
  // reachable-by-keyboard-only subset (contrast searchInput.maxSuggestions).
4423
4612
  maxHeight: (options.maxVisibleOptions ?? 8) * 36 + 8,
4613
+ anchor: this.anchor.name,
4424
4614
  }),
4425
4615
  {
4426
4616
  '& [role="option"]': {
@@ -4459,11 +4649,11 @@ class UniComboboxComponent extends BaseComponent {
4459
4649
  ]);
4460
4650
  }, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
4461
4651
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4462
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniComboboxComponent, isStandalone: true, selector: "uni-combobox, Combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, filterLocally: { classPropertyName: "filterLocally", publicName: "filterLocally", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", selected: "selected", cleared: "cleared", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4652
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniComboboxComponent, isStandalone: true, selector: "uni-combobox, Combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, filterLocally: { classPropertyName: "filterLocally", publicName: "filterLocally", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", selected: "selected", cleared: "cleared", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4463
4653
  }
4464
4654
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, decorators: [{
4465
4655
  type: Component,
4466
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-combobox, Combobox', imports: [UniIconButtonComponent, UniIconComponent, UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n" }]
4656
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-combobox, Combobox', imports: [UniIconButtonComponent, UniIconComponent, UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n" }]
4467
4657
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], filterLocally: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLocally", required: false }] }], filterWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterWith", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], query: [{ type: i0.Output, args: ["query"] }], selected: [{ type: i0.Output, args: ["selected"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
4468
4658
 
4469
4659
  class UniDataSearchComponent extends BaseComponent {
@@ -4947,7 +5137,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4947
5137
 
4948
5138
  class UniDropdownComponent extends BaseComponent {
4949
5139
  renderer = inject(Renderer2);
4950
- delay = 100;
4951
5140
  // Reactively track visibility status using Signals
4952
5141
  showing = signal(false, ...(ngDevMode ? [{ debugName: "showing" }] : /* istanbul ignore next */ []));
4953
5142
  trigger = input.required(...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
@@ -4981,25 +5170,9 @@ class UniDropdownComponent extends BaseComponent {
4981
5170
  get _dropdown() {
4982
5171
  return this.dropdownRef.nativeElement;
4983
5172
  }
4984
- // Pre-measure default only: the requested placement's corner. The real
4985
- // origin is measured per toggle (syncTransformOrigin), because
4986
- // position-try fallbacks may have flipped the panel.
4987
- transformOriginMap = {
4988
- top: 'bottom center',
4989
- right: 'center left',
4990
- bottom: 'top center',
4991
- left: 'center right',
4992
- 'top-start': 'bottom left',
4993
- 'top-end': 'bottom right',
4994
- 'right-start': 'top left',
4995
- 'right-end': 'bottom left',
4996
- 'bottom-start': 'top left',
4997
- 'bottom-end': 'top right',
4998
- 'left-start': 'top right',
4999
- 'left-end': 'bottom right',
5000
- };
5001
5173
  dropdownClass = computed(() => {
5002
5174
  const currentPlacement = this.placement();
5175
+ const motion = this.theme.motion(this.componentOptions().motion);
5003
5176
  return css([
5004
5177
  {
5005
5178
  // Reset browser agent default popover styles
@@ -5011,27 +5184,14 @@ class UniDropdownComponent extends BaseComponent {
5011
5184
  // Native anchor positioning: the browser keeps the panel attached to
5012
5185
  // the trigger (no scroll/resize listeners needed)
5013
5186
  ...anchorStyles(this.anchorName, currentPlacement, this.offset()),
5014
- // 2. Animate discrete properties across top layer layout contexts
5015
- transitionProperty: 'transform, opacity, display, overlay',
5016
- transitionDuration: `${this.delay}ms`,
5017
- transitionTimingFunction: 'linear',
5018
- transitionBehavior: 'allow-discrete',
5019
- // Hidden State (Closed)
5020
- opacity: 0,
5021
- transform: 'scale(0.8)',
5022
- transformOrigin: this.transformOriginMap[currentPlacement],
5023
- // 3. Active state styling controlled via the native browser pseudo-class
5024
- ['&:popover-open']: {
5025
- opacity: 1,
5026
- transform: 'scale(1)',
5027
- },
5028
- // 4. Starting-style rules what properties animate *from* when transitioning in
5029
- ['@starting-style']: {
5030
- ['&:popover-open']: {
5031
- opacity: 0,
5032
- transform: 'scale(0.8)',
5033
- },
5034
- },
5187
+ // Grows out of the corner touching the trigger. A pre-measure default
5188
+ // only: the real origin is measured per toggle (syncTransformOrigin),
5189
+ // because position-try fallbacks may have flipped the panel.
5190
+ transformOrigin: TRANSFORM_ORIGINS[currentPlacement],
5191
+ // Scale-and-fade into and out of the top layer, including the
5192
+ // `@starting-style` the entry transition runs from. Timing comes from
5193
+ // the theme's motion scale, so retiming every overlay is one edit.
5194
+ ...discreteOverlayTransition(motion.duration, { opacity: 0, transform: `scale(${motion.scale ?? 1})` }, { opacity: 1, transform: 'scale(1)' }, motion.easing),
5035
5195
  },
5036
5196
  ]);
5037
5197
  }, ...(ngDevMode ? [{ debugName: "dropdownClass" }] : /* istanbul ignore next */ []));
@@ -5046,7 +5206,7 @@ class UniDropdownComponent extends BaseComponent {
5046
5206
  this.toggleDropdown();
5047
5207
  });
5048
5208
  // Anchor the popover panel to the trigger element
5049
- this.renderer.setStyle(this._trigger, 'anchor-name', this.anchorName);
5209
+ setAnchorName(this._trigger, this.anchorName);
5050
5210
  // Wire the ARIA popup contract onto the focusable trigger element
5051
5211
  const focusTarget = this._focusTarget;
5052
5212
  this.renderer.setAttribute(focusTarget, 'aria-expanded', 'false');
@@ -5056,7 +5216,7 @@ class UniDropdownComponent extends BaseComponent {
5056
5216
  }
5057
5217
  // Sync state if user invokes light-dismiss via outside click or Escape key
5058
5218
  this.renderer.listen(this._dropdown, 'toggle', (event) => {
5059
- const isOpened = event.newState === 'open';
5219
+ const isOpened = isToggleOpen(event);
5060
5220
  // Both edges: on open so the entry scale grows out of the trigger, and
5061
5221
  // on close-start so a panel the browser flipped while open (scroll near
5062
5222
  // a viewport edge) still collapses back toward the trigger.
@@ -5068,14 +5228,15 @@ class UniDropdownComponent extends BaseComponent {
5068
5228
  }
5069
5229
  else {
5070
5230
  this.dropdownHiding.emit(true);
5071
- this.restoreFocus();
5231
+ // Keyboard users are never stranded when the top layer closes (WCAG 2.4.3).
5232
+ restoreOverlayFocus(this._dropdown, this._focusTarget);
5072
5233
  }
5073
5234
  });
5074
5235
  }
5075
5236
  /**
5076
5237
  * Scale the open/close animation from the corner touching the trigger,
5077
5238
  * wherever the browser actually placed the panel. The static
5078
- * `transformOriginMap` covers only the *requested* placement; with
5239
+ * `TRANSFORM_ORIGINS` entry covers only the *requested* placement; with
5079
5240
  * `position-try-fallbacks` the panel may have flipped at a viewport edge,
5080
5241
  * and a `bottom-end` picker rendered above its field would otherwise still
5081
5242
  * animate from the top-right corner.
@@ -5085,17 +5246,6 @@ class UniDropdownComponent extends BaseComponent {
5085
5246
  if (origin)
5086
5247
  this.renderer.setStyle(this._dropdown, 'transform-origin', origin);
5087
5248
  }
5088
- /**
5089
- * Returns focus to the trigger when the popover closes while focus was
5090
- * inside it (or was dropped on <body> by the top layer closing), so
5091
- * keyboard users are never stranded (WCAG 2.4.3).
5092
- */
5093
- restoreFocus() {
5094
- const active = document.activeElement;
5095
- if (active === document.body || (active && this._dropdown.contains(active))) {
5096
- this._focusTarget.focus();
5097
- }
5098
- }
5099
5249
  toggleDropdown() {
5100
5250
  if (this.showing()) {
5101
5251
  this._dropdown.hidePopover();
@@ -5216,7 +5366,8 @@ class UniDateInputComponent extends BaseComponent {
5216
5366
  srOnly = css(visuallyHidden);
5217
5367
  /** A refused commit — styles the field and sets aria-invalid until edited. */
5218
5368
  draftInvalid = signal(false, ...(ngDevMode ? [{ debugName: "draftInvalid" }] : /* istanbul ignore next */ []));
5219
- announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
5369
+ /** Commits, clears and refusals are otherwise silent to a screen reader. */
5370
+ announcer = createAnnouncer();
5220
5371
  toggleElement = computed(() => this.toggleRef()?.nativeElement, ...(ngDevMode ? [{ debugName: "toggleElement" }] : /* istanbul ignore next */ []));
5221
5372
  popupOpen = computed(() => this.dropdown()?.showing() ?? false, ...(ngDevMode ? [{ debugName: "popupOpen" }] : /* istanbul ignore next */ []));
5222
5373
  resolvedLocale = computed(() => this.locale() ?? (document.documentElement.lang || navigator.language || 'en-US'), ...(ngDevMode ? [{ debugName: "resolvedLocale" }] : /* istanbul ignore next */ []));
@@ -5247,7 +5398,7 @@ class UniDateInputComponent extends BaseComponent {
5247
5398
  this.draftInvalid.set(false);
5248
5399
  this.setFieldText(this.displayText());
5249
5400
  if (!silent)
5250
- this.announce(date ? `${this.fullDate(date)}.` : 'Date cleared.');
5401
+ this.announcer.announce(date ? `${this.fullDate(date)}.` : 'Date cleared.');
5251
5402
  }
5252
5403
  refuse(raw, reason) {
5253
5404
  this.draftInvalid.set(true);
@@ -5256,7 +5407,7 @@ class UniDateInputComponent extends BaseComponent {
5256
5407
  'out-of-range': `${raw} is outside the allowed dates.`,
5257
5408
  disabled: `${raw} isn't available.`,
5258
5409
  }[reason];
5259
- this.announce(message);
5410
+ this.announcer.announce(message);
5260
5411
  this.rejected.emit({ raw, reason });
5261
5412
  }
5262
5413
  commit(raw) {
@@ -5404,9 +5555,6 @@ class UniDateInputComponent extends BaseComponent {
5404
5555
  if (element)
5405
5556
  element.value = text;
5406
5557
  }
5407
- announce(message) {
5408
- this.announcement.set(this.announcement() === message ? `${message} ` : message);
5409
- }
5410
5558
  // --- Styling -----------------------------------------------------------------------
5411
5559
  className = computed(() => css({ display: 'block', position: 'relative' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
5412
5560
  rowClass = computed(() => css({ display: 'flex', alignItems: 'center', flex: 1, width: '100%', minWidth: 0 }), ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
@@ -5440,7 +5588,7 @@ class UniDateInputComponent extends BaseComponent {
5440
5588
  ]);
5441
5589
  }, ...(ngDevMode ? [{ debugName: "embeddedClass" }] : /* istanbul ignore next */ []));
5442
5590
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDateInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5443
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDateInputComponent, isStandalone: true, selector: "uni-date-input, DateInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", opened: "opened", closed: "closed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dateInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "toggleRef", first: true, predicate: ["toggle"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "popupRef", first: true, predicate: ["popupDialog"], descendants: true, isSignal: true }, { propertyName: "dropdown", first: true, predicate: UniDropdownComponent, descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: UniCalendarComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so commits happen only when focus\n leaves the whole field, not while moving into the popup. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <!-- The dropdown wires aria-haspopup/aria-expanded/aria-controls onto\n this button; its name tracks the value (\"Change date, \u2026\"). -->\n <button\n #toggle\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'calendar_month'\"\n [disable]=\"disabled()\"\n >\n {{ toggleLabel() }}\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (toggleElement()) {\n <uni-dropdown\n [trigger]=\"toggleElement()!\"\n ariaHasPopup=\"dialog\"\n placement=\"bottom-end\"\n [color]=\"componentOptions().popupColor ?? 'primary-surface'\"\n [shadow]=\"componentOptions().popupShadow ?? 'menu'\"\n [borderRadius]=\"componentOptions().popupBorderRadius ?? 'xs'\"\n paddingVertical=\"xs\"\n paddingHorizontal=\"xs\"\n (dropdownShowing)=\"onPopupShowing()\"\n (dropdownHiding)=\"onPopupHiding()\"\n >\n <!-- A focus-holding dialog per the APG date-picker pattern: Tab cycles\n inside, Escape closes and returns focus to the field. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div #popupDialog role=\"dialog\" aria-label=\"Choose date\" (keydown)=\"onPopupKeydown($event)\">\n <uni-calendar\n mode=\"single\"\n [value]=\"value()\"\n [minDate]=\"minDate()\"\n [maxDate]=\"maxDate()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n (selected)=\"onCalendarPick($event)\"\n />\n </div>\n </uni-dropdown>\n }\n\n <!-- Commits, steps and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniCalendarComponent, selector: "uni-calendar, Calendar", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "mode", "month", "minDate", "maxDate", "disabledDates", "markers", "locale", "weekStart", "ariaLabel", "size"], outputs: ["valueChange", "touchedChange", "monthChange", "selected"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "color"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5591
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDateInputComponent, isStandalone: true, selector: "uni-date-input, DateInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", opened: "opened", closed: "closed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'dateInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "toggleRef", first: true, predicate: ["toggle"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "popupRef", first: true, predicate: ["popupDialog"], descendants: true, isSignal: true }, { propertyName: "dropdown", first: true, predicate: UniDropdownComponent, descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: UniCalendarComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so commits happen only when focus\n leaves the whole field, not while moving into the popup. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <!-- The dropdown wires aria-haspopup/aria-expanded/aria-controls onto\n this button; its name tracks the value (\"Change date, \u2026\"). -->\n <button\n #toggle\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'calendar_month'\"\n [disable]=\"disabled()\"\n >\n {{ toggleLabel() }}\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (toggleElement()) {\n <uni-dropdown\n [trigger]=\"toggleElement()!\"\n ariaHasPopup=\"dialog\"\n placement=\"bottom-end\"\n [color]=\"componentOptions().popupColor ?? 'primary-surface'\"\n [shadow]=\"componentOptions().popupShadow ?? 'menu'\"\n [borderRadius]=\"componentOptions().popupBorderRadius ?? 'xs'\"\n paddingVertical=\"xs\"\n paddingHorizontal=\"xs\"\n (dropdownShowing)=\"onPopupShowing()\"\n (dropdownHiding)=\"onPopupHiding()\"\n >\n <!-- A focus-holding dialog per the APG date-picker pattern: Tab cycles\n inside, Escape closes and returns focus to the field. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div #popupDialog role=\"dialog\" aria-label=\"Choose date\" (keydown)=\"onPopupKeydown($event)\">\n <uni-calendar\n mode=\"single\"\n [value]=\"value()\"\n [minDate]=\"minDate()\"\n [maxDate]=\"maxDate()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n (selected)=\"onCalendarPick($event)\"\n />\n </div>\n </uni-dropdown>\n }\n\n <!-- Commits, steps and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniCalendarComponent, selector: "uni-calendar, Calendar", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "mode", "month", "minDate", "maxDate", "disabledDates", "markers", "locale", "weekStart", "ariaLabel", "size"], outputs: ["valueChange", "touchedChange", "monthChange", "selected"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "color"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5444
5592
  }
5445
5593
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDateInputComponent, decorators: [{
5446
5594
  type: Component,
@@ -5450,7 +5598,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5450
5598
  UniDropdownComponent,
5451
5599
  UniIconButtonComponent,
5452
5600
  UniInputBoxComponent,
5453
- ], providers: [{ provide: COMPONENT_NAME, useValue: 'dateInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so commits happen only when focus\n leaves the whole field, not while moving into the popup. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <!-- The dropdown wires aria-haspopup/aria-expanded/aria-controls onto\n this button; its name tracks the value (\"Change date, \u2026\"). -->\n <button\n #toggle\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'calendar_month'\"\n [disable]=\"disabled()\"\n >\n {{ toggleLabel() }}\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (toggleElement()) {\n <uni-dropdown\n [trigger]=\"toggleElement()!\"\n ariaHasPopup=\"dialog\"\n placement=\"bottom-end\"\n [color]=\"componentOptions().popupColor ?? 'primary-surface'\"\n [shadow]=\"componentOptions().popupShadow ?? 'menu'\"\n [borderRadius]=\"componentOptions().popupBorderRadius ?? 'xs'\"\n paddingVertical=\"xs\"\n paddingHorizontal=\"xs\"\n (dropdownShowing)=\"onPopupShowing()\"\n (dropdownHiding)=\"onPopupHiding()\"\n >\n <!-- A focus-holding dialog per the APG date-picker pattern: Tab cycles\n inside, Escape closes and returns focus to the field. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div #popupDialog role=\"dialog\" aria-label=\"Choose date\" (keydown)=\"onPopupKeydown($event)\">\n <uni-calendar\n mode=\"single\"\n [value]=\"value()\"\n [minDate]=\"minDate()\"\n [maxDate]=\"maxDate()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n (selected)=\"onCalendarPick($event)\"\n />\n </div>\n </uni-dropdown>\n }\n\n <!-- Commits, steps and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n" }]
5601
+ ], providers: [{ provide: COMPONENT_NAME, useValue: 'dateInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so commits happen only when focus\n leaves the whole field, not while moving into the popup. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <!-- The dropdown wires aria-haspopup/aria-expanded/aria-controls onto\n this button; its name tracks the value (\"Change date, \u2026\"). -->\n <button\n #toggle\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'calendar_month'\"\n [disable]=\"disabled()\"\n >\n {{ toggleLabel() }}\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (toggleElement()) {\n <uni-dropdown\n [trigger]=\"toggleElement()!\"\n ariaHasPopup=\"dialog\"\n placement=\"bottom-end\"\n [color]=\"componentOptions().popupColor ?? 'primary-surface'\"\n [shadow]=\"componentOptions().popupShadow ?? 'menu'\"\n [borderRadius]=\"componentOptions().popupBorderRadius ?? 'xs'\"\n paddingVertical=\"xs\"\n paddingHorizontal=\"xs\"\n (dropdownShowing)=\"onPopupShowing()\"\n (dropdownHiding)=\"onPopupHiding()\"\n >\n <!-- A focus-holding dialog per the APG date-picker pattern: Tab cycles\n inside, Escape closes and returns focus to the field. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div #popupDialog role=\"dialog\" aria-label=\"Choose date\" (keydown)=\"onPopupKeydown($event)\">\n <uni-calendar\n mode=\"single\"\n [value]=\"value()\"\n [minDate]=\"minDate()\"\n [maxDate]=\"maxDate()\"\n [disabledDates]=\"disabledDates()\"\n [markers]=\"markers()\"\n [weekStart]=\"weekStart()\"\n [locale]=\"locale()\"\n (selected)=\"onCalendarPick($event)\"\n />\n </div>\n </uni-dropdown>\n }\n\n <!-- Commits, steps and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n" }]
5454
5602
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], parse: [{ type: i0.Input, args: [{ isSignal: true, alias: "parse", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], disabledDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDates", required: false }] }], markers: [{ type: i0.Input, args: [{ isSignal: true, alias: "markers", required: false }] }], weekStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStart", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], toggleRef: [{ type: i0.ViewChild, args: ['toggle', { ...{ read: ElementRef }, isSignal: true }] }], popupRef: [{ type: i0.ViewChild, args: ['popupDialog', { isSignal: true }] }], dropdown: [{ type: i0.ViewChild, args: [i0.forwardRef(() => UniDropdownComponent), { isSignal: true }] }], calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => UniCalendarComponent), { isSignal: true }] }] } });
5455
5603
 
5456
5604
  const toMinutes = (time) => {
@@ -5501,10 +5649,19 @@ class UniTimeInputComponent extends BaseComponent {
5501
5649
  host = inject(ElementRef);
5502
5650
  inputRef = viewChild('field', ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
5503
5651
  listRef = viewChild('listbox', ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
5652
+ /** Ties the popup to the field so the browser tracks it in the top layer. */
5653
+ anchor = newListboxAnchor();
5654
+ /** `manual` where the top layer is usable, else null — see the popup helper. */
5655
+ popupAttr = listboxPopupAttr();
5656
+ constructor() {
5657
+ super();
5658
+ promoteListboxPopup(this.listRef);
5659
+ }
5504
5660
  srOnly = css(visuallyHidden);
5505
5661
  /** A refused commit — styles the field and sets aria-invalid until edited. */
5506
5662
  draftInvalid = signal(false, ...(ngDevMode ? [{ debugName: "draftInvalid" }] : /* istanbul ignore next */ []));
5507
- announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
5663
+ /** Commits, clears and refusals are otherwise silent to a screen reader. */
5664
+ announcer = createAnnouncer();
5508
5665
  resolvedLocale = computed(() => this.locale() ?? (document.documentElement.lang || navigator.language || 'en-US'), ...(ngDevMode ? [{ debugName: "resolvedLocale" }] : /* istanbul ignore next */ []));
5509
5666
  resolvedHour12 = computed(() => this.hour12() ?? localeDefaultHour12(this.resolvedLocale()), ...(ngDevMode ? [{ debugName: "resolvedHour12" }] : /* istanbul ignore next */ []));
5510
5667
  /** The listed times: pinned `slots` verbatim, else the generated step grid. */
@@ -5530,7 +5687,7 @@ class UniTimeInputComponent extends BaseComponent {
5530
5687
  this.draftInvalid.set(false);
5531
5688
  this.setFieldText(this.displayText());
5532
5689
  if (!silent)
5533
- this.announce(time ? `${this.formatValue(time)}.` : 'Time cleared.');
5690
+ this.announcer.announce(time ? `${this.formatValue(time)}.` : 'Time cleared.');
5534
5691
  }
5535
5692
  refuse(raw, reason, shown = raw) {
5536
5693
  this.draftInvalid.set(true);
@@ -5539,7 +5696,7 @@ class UniTimeInputComponent extends BaseComponent {
5539
5696
  'out-of-range': `${shown} is outside the allowed times.`,
5540
5697
  unavailable: `${shown} isn't available.`,
5541
5698
  }[reason];
5542
- this.announce(message);
5699
+ this.announcer.announce(message);
5543
5700
  this.rejected.emit({ raw, reason });
5544
5701
  }
5545
5702
  commit(raw) {
@@ -5721,11 +5878,8 @@ class UniTimeInputComponent extends BaseComponent {
5721
5878
  if (element)
5722
5879
  element.value = text;
5723
5880
  }
5724
- announce(message) {
5725
- this.announcement.set(this.announcement() === message ? `${message} ` : message);
5726
- }
5727
5881
  // --- Styling -----------------------------------------------------------------------
5728
- className = computed(() => css({ display: 'block', position: 'relative' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
5882
+ className = computed(() => css({ display: 'block', position: 'relative', ...this.anchor.style }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
5729
5883
  rowClass = computed(() => css({ display: 'flex', alignItems: 'center', flex: 1, width: '100%', minWidth: 0 }), ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
5730
5884
  inputClass = computed(() => {
5731
5885
  const colors = this.theme.colorPalette();
@@ -5754,15 +5908,16 @@ class UniTimeInputComponent extends BaseComponent {
5754
5908
  const options = this.componentOptions();
5755
5909
  return css(listboxPopupStyles(this.theme, options, {
5756
5910
  maxHeight: (options.maxVisibleOptions ?? 7) * 36,
5911
+ anchor: this.anchor.name,
5757
5912
  }));
5758
5913
  }, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
5759
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTimeInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5760
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTimeInputComponent, isStandalone: true, selector: "uni-time-input, TimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5914
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTimeInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5915
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTimeInputComponent, isStandalone: true, selector: "uni-time-input, TimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5761
5916
  }
5762
5917
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTimeInputComponent, decorators: [{
5763
5918
  type: Component,
5764
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-time-input, TimeInput', imports: [NgTemplateOutlet, UniIconButtonComponent, UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n" }]
5765
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], minTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTime", required: false }] }], maxTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTime", required: false }] }], slots: [{ type: i0.Input, args: [{ isSignal: true, alias: "slots", required: false }] }], hour12: [{ type: i0.Input, args: [{ isSignal: true, alias: "hour12", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
5919
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-time-input, TimeInput', imports: [NgTemplateOutlet, UniIconButtonComponent, UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n" }]
5920
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], minTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTime", required: false }] }], maxTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTime", required: false }] }], slots: [{ type: i0.Input, args: [{ isSignal: true, alias: "slots", required: false }] }], hour12: [{ type: i0.Input, args: [{ isSignal: true, alias: "hour12", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
5766
5921
 
5767
5922
  /**
5768
5923
  * One field for a date and a time: a thin composer seating a uni-date-input
@@ -6357,10 +6512,23 @@ class UniExpandComponent extends BaseComponent {
6357
6512
  const override = this.transitionSpeed();
6358
6513
  if (override !== undefined)
6359
6514
  return override;
6360
- const speed = this.componentOptions().transitionSpeed ?? EXPAND_DEFAULT_SPEED;
6361
6515
  const height = this.contentHeight();
6516
+ const speed = this.baseSpeed();
6362
6517
  return height === undefined ? speed : expandDuration(height, speed);
6363
6518
  }, ...(ngDevMode ? [{ debugName: "duration" }] : /* istanbul ignore next */ []));
6519
+ /**
6520
+ * Base speed in seconds, before the size-aware scaling above. The
6521
+ * deprecated `transitionSpeed` option wins when a theme still sets it;
6522
+ * otherwise the `motion` token's duration (ms) converts to seconds.
6523
+ */
6524
+ baseSpeed = computed(() => {
6525
+ const options = this.componentOptions();
6526
+ if (options.transitionSpeed !== undefined)
6527
+ return options.transitionSpeed;
6528
+ return this.theme.motion(options.motion ?? 'reveal').duration / 1000;
6529
+ }, ...(ngDevMode ? [{ debugName: "baseSpeed" }] : /* istanbul ignore next */ []));
6530
+ /** Curve for the reveal, from the same token as the speed. */
6531
+ easing = computed(() => this.theme.motion(this.componentOptions().motion ?? 'reveal').easing, ...(ngDevMode ? [{ debugName: "easing" }] : /* istanbul ignore next */ []));
6364
6532
  cssDuration = computed(() => `${this.duration()}s`, ...(ngDevMode ? [{ debugName: "cssDuration" }] : /* istanbul ignore next */ []));
6365
6533
  /**
6366
6534
  * A custom element is `display: inline` by default, which would lay the
@@ -6393,19 +6561,19 @@ class UniExpandComponent extends BaseComponent {
6393
6561
  * `duration`, so the classes stay static while timing tracks the theme and
6394
6562
  * the content's size through signals alone.
6395
6563
  */
6396
- expandAnimation = css(motionSafe({
6564
+ expandAnimation = computed(() => css(motionSafe({
6397
6565
  overflow: 'hidden',
6398
- animation: `${this.expand} ease-in-out ${EXPAND_DEFAULT_SPEED}s`,
6399
- }));
6400
- collapseAnimation = css(motionSafe({
6566
+ animation: `${this.expand} ${this.easing()} ${this.baseSpeed()}s`,
6567
+ })), ...(ngDevMode ? [{ debugName: "expandAnimation" }] : /* istanbul ignore next */ []));
6568
+ collapseAnimation = computed(() => css(motionSafe({
6401
6569
  overflow: 'hidden',
6402
- animation: `${this.collapse} ease-in-out ${EXPAND_DEFAULT_SPEED}s`,
6403
- }));
6570
+ animation: `${this.collapse} ${this.easing()} ${this.baseSpeed()}s`,
6571
+ })), ...(ngDevMode ? [{ debugName: "collapseAnimation" }] : /* istanbul ignore next */ []));
6404
6572
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6405
6573
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniExpandComponent, isStandalone: true, selector: "uni-expand", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, transitionSpeed: { classPropertyName: "transitionSpeed", publicName: "transitionSpeed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "attr.id": "regionId", "class": "hostClassName" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'expand' }], viewQueries: [{ propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `@if (!collapsed()) {
6406
6574
  <div
6407
- [animate.enter]="ready() ? expandAnimation : ''"
6408
- [animate.leave]="collapseAnimation"
6575
+ [animate.enter]="ready() ? expandAnimation() : ''"
6576
+ [animate.leave]="collapseAnimation()"
6409
6577
  [class]="expandClassName"
6410
6578
  [style.animation-duration]="cssDuration()"
6411
6579
  >
@@ -6424,8 +6592,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
6424
6592
  providers: [{ provide: COMPONENT_NAME, useValue: 'expand' }],
6425
6593
  template: `@if (!collapsed()) {
6426
6594
  <div
6427
- [animate.enter]="ready() ? expandAnimation : ''"
6428
- [animate.leave]="collapseAnimation"
6595
+ [animate.enter]="ready() ? expandAnimation() : ''"
6596
+ [animate.leave]="collapseAnimation()"
6429
6597
  [class]="expandClassName"
6430
6598
  [style.animation-duration]="cssDuration()"
6431
6599
  >
@@ -6471,9 +6639,20 @@ class UniExpandToggleComponent {
6471
6639
  * region is size-scaled or overridden per instance.
6472
6640
  */
6473
6641
  transitionSpeed = input(...(ngDevMode ? [undefined, { debugName: "transitionSpeed" }] : /* istanbul ignore next */ []));
6474
- /** Fallback clock when no `transitionSpeed` is bound: the `expand` theme options' `transitionSpeed`. */
6475
- expandOptions = inject(ThemeService).getComponentOptions('expand');
6476
- speed = computed(() => this.transitionSpeed() ?? this.expandOptions().transitionSpeed ?? EXPAND_DEFAULT_SPEED, ...(ngDevMode ? [{ debugName: "speed" }] : /* istanbul ignore next */ []));
6642
+ /** Fallback clock when no `transitionSpeed` is bound: the `expand` entry's
6643
+ motion token — the same one the region itself reads. */
6644
+ themeService = inject(ThemeService);
6645
+ expandOptions = this.themeService.getComponentOptions('expand');
6646
+ speed = computed(() => {
6647
+ const override = this.transitionSpeed();
6648
+ if (override !== undefined)
6649
+ return override;
6650
+ const options = this.expandOptions();
6651
+ // Same order uni-expand uses, so the chevron and the region never drift.
6652
+ if (options.transitionSpeed !== undefined)
6653
+ return options.transitionSpeed;
6654
+ return this.themeService.motion(options.motion ?? 'reveal').duration / 1000;
6655
+ }, ...(ngDevMode ? [{ debugName: "speed" }] : /* istanbul ignore next */ []));
6477
6656
  /**
6478
6657
  * The glyph rotates, never the host.
6479
6658
  *
@@ -6954,7 +7133,10 @@ class UniMenuItemComponent {
6954
7133
  const variantStyle = variant
6955
7134
  ? this.theme.component('menuItem')().variants?.[variant]
6956
7135
  : undefined;
6957
- const transitionSpeed = options.transitionSpeed ?? 0;
7136
+ // Neither set means no transition at all — the escape hatch predates the
7137
+ // motion scale and still works. The deprecated option wins over the token.
7138
+ const motion = options.motion ? this.theme.motion(options.motion) : undefined;
7139
+ const transitionSpeed = options.transitionSpeed ?? (motion ? motion.duration / 1000 : 0);
6958
7140
  return css([
6959
7141
  {
6960
7142
  display: 'flex',
@@ -6983,7 +7165,7 @@ class UniMenuItemComponent {
6983
7165
  pointerEvents: 'none',
6984
7166
  },
6985
7167
  },
6986
- transitionSpeed > 0 && { transition: `all ${transitionSpeed}s ease` },
7168
+ transitionSpeed > 0 && { transition: `all ${transitionSpeed}s ${motion?.easing ?? 'ease'}` },
6987
7169
  // Variant tones override the base look. A variant that restyles the
6988
7170
  // highlight must key it with HOVER_OR_KEYBOARD_FOCUS — Emotion merges by
6989
7171
  // exact selector text, so a variant spelling it `&:hover, &:focus`
@@ -7263,6 +7445,9 @@ class UniMultiSelectComponent {
7263
7445
  })), ...(ngDevMode ? [{ debugName: "optionsWithSelections" }] : /* istanbul ignore next */ []));
7264
7446
  className = css({ display: 'contents' });
7265
7447
  handleCheck(checked, value) {
7448
+ // The rendered checkbox is disabled, so this only guards a direct call.
7449
+ if (this.options().find((option) => option.value === value)?.disabled)
7450
+ return;
7266
7451
  const selections = this.selections();
7267
7452
  if (!selections && !checked)
7268
7453
  return;
@@ -7277,8 +7462,11 @@ class UniMultiSelectComponent {
7277
7462
  return;
7278
7463
  }
7279
7464
  }
7465
+ /** Selects every *enabled* option — a disabled option is not committable. */
7280
7466
  selectAll() {
7281
- const allValues = this.options().map((option) => option.value);
7467
+ const allValues = this.options()
7468
+ .filter((option) => !option.disabled)
7469
+ .map((option) => option.value);
7282
7470
  this.updates.emit(allValues);
7283
7471
  }
7284
7472
  deselectAll() {
@@ -7290,11 +7478,11 @@ class UniMultiSelectComponent {
7290
7478
  width: '100%',
7291
7479
  });
7292
7480
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7293
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectComponent, isStandalone: true, selector: "uni-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, flexDirection: { classPropertyName: "flexDirection", publicName: "flexDirection", isSignal: true, isRequired: false, transformFunction: null }, checkboxGap: { classPropertyName: "checkboxGap", publicName: "checkboxGap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { updates: "updates" }, host: { properties: { "class": "className" } }, ngImport: i0, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7481
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectComponent, isStandalone: true, selector: "uni-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, flexDirection: { classPropertyName: "flexDirection", publicName: "flexDirection", isSignal: true, isRequired: false, transformFunction: null }, checkboxGap: { classPropertyName: "checkboxGap", publicName: "checkboxGap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { updates: "updates" }, host: { properties: { "class": "className" } }, ngImport: i0, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n [disabled]=\"!!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7294
7482
  }
7295
7483
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectComponent, decorators: [{
7296
7484
  type: Component,
7297
- args: [{ selector: 'uni-multi-select', imports: [UniCheckboxComponent, UniBoxComponent], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n" }]
7485
+ args: [{ selector: 'uni-multi-select', imports: [UniCheckboxComponent, UniBoxComponent], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n [disabled]=\"!!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n" }]
7298
7486
  }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], selections: [{ type: i0.Input, args: [{ isSignal: true, alias: "selections", required: false }] }], updates: [{ type: i0.Output, args: ["updates"] }], flexDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexDirection", required: false }] }], checkboxGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkboxGap", required: false }] }] } });
7299
7487
 
7300
7488
  class UniMultiSelectDropdownComponent extends BaseComponent {
@@ -7337,10 +7525,18 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
7337
7525
  * arithmetic — wrapping, Home/End, and never pointing past a list the
7338
7526
  * filter has narrowed — the same contract `uni-search-input` and
7339
7527
  * `uni-tag-input` use, so the keys behave identically across all three.
7528
+ *
7529
+ * `disabled` indexes into `filteredOptions`, matching `count`: arrows step
7530
+ * over disabled rows and Home/End land on the nearest enabled one, so the
7531
+ * focus target is always a checkbox that can actually take focus.
7340
7532
  */
7341
7533
  list = createListboxNavigation({
7342
7534
  count: () => this.filteredOptions().length,
7343
7535
  idPrefix: 'uni-multi-select',
7536
+ disabled: (index) => !!this.filteredOptions()[index]?.disabled,
7537
+ // Focus rides the option checkboxes here, not a text field, so Home/End
7538
+ // have no caret to defer to — unlike the combobox-style consumers.
7539
+ homeEndNavigates: true,
7344
7540
  });
7345
7541
  /** Announced with the selection so the count is not left to guesswork. */
7346
7542
  selectionSummary = computed(() => {
@@ -7407,11 +7603,18 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
7407
7603
  this.list.show();
7408
7604
  this.list.setActive(index);
7409
7605
  }
7606
+ /**
7607
+ * Selects every *enabled* option. A disabled option is not committable, so
7608
+ * "select all" must not commit one on the user's behalf — the same rule
7609
+ * `toggleOption` and the keyboard path follow.
7610
+ */
7410
7611
  selectAll() {
7411
7612
  if (this.disabled())
7412
7613
  return;
7413
7614
  this.touched.set(true);
7414
- const allValues = this.options().map((option) => option.value);
7615
+ const allValues = this.options()
7616
+ .filter((option) => !option.disabled)
7617
+ .map((option) => option.value);
7415
7618
  this.value.set(allValues);
7416
7619
  }
7417
7620
  deselectAll() {
@@ -7424,7 +7627,8 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
7424
7627
  return computed(() => this.value().includes(option.value));
7425
7628
  }
7426
7629
  toggleOption(option, checked) {
7427
- if (this.disabled())
7630
+ // The nav hook keeps the keyboard off disabled rows; this stops a pointer.
7631
+ if (this.disabled() || option.disabled)
7428
7632
  return;
7429
7633
  this.touched.set(true);
7430
7634
  const { value } = option;
@@ -7440,7 +7644,7 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
7440
7644
  });
7441
7645
  }
7442
7646
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7443
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], viewQueries: [{ propertyName: "optionRefs", predicate: ["optionRow"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled()\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "color"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniStackComponent, selector: "[uni-stack-layout], [stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniRowComponent, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7647
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], viewQueries: [{ propertyName: "optionRefs", predicate: ["optionRow"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled() || !!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "color"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "component", type: UniStackComponent, selector: "[uni-stack-layout], [stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniRowComponent, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7444
7648
  }
7445
7649
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, decorators: [{
7446
7650
  type: Component,
@@ -7455,7 +7659,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
7455
7659
  UniSymbolComponent,
7456
7660
  UniRowComponent,
7457
7661
  UniInputBoxComponent,
7458
- ], providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled()\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n" }]
7662
+ ], providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled() || !!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n" }]
7459
7663
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], optionRefs: [{ type: i0.ViewChildren, args: ['optionRow', { isSignal: true }] }] } });
7460
7664
 
7461
7665
  class UniNotificationBadgeComponent extends BaseComponent {
@@ -7568,12 +7772,23 @@ class UniAlertComponent extends BaseComponent {
7568
7772
  alertRef;
7569
7773
  alertState = signal('closed', ...(ngDevMode ? [{ debugName: "alertState" }] : /* istanbul ignore next */ []));
7570
7774
  effectiveVariant = computed(() => this.variant() || this.componentOptions().defaultVariant, ...(ngDevMode ? [{ debugName: "effectiveVariant" }] : /* istanbul ignore next */ []));
7775
+ /**
7776
+ * Timing for the enter/leave transition. The deprecated `transitionSpeed`
7777
+ * (seconds) wins when a theme still sets it.
7778
+ */
7779
+ motion = computed(() => {
7780
+ const options = this.componentOptions();
7781
+ const token = this.theme.motion(options.motion ?? 'notification');
7782
+ return options.transitionSpeed === undefined
7783
+ ? token
7784
+ : { ...token, duration: options.transitionSpeed * 1000 };
7785
+ }, ...(ngDevMode ? [{ debugName: "motion" }] : /* istanbul ignore next */ []));
7571
7786
  alertClass = computed(() => css({
7572
7787
  ...this.theme.getContainerColors(this.effectiveVariant(), this.useVariant()),
7573
7788
  ...this.theme.radius(this.componentOptions().borderRadius),
7574
7789
  ...this.theme.border(this.effectiveVariant()),
7575
7790
  ...this.theme.boxShadow(this.componentOptions().elevation),
7576
- transition: `all ${this.componentOptions().transitionSpeed}s ease-in-out`,
7791
+ transition: `all ${this.motion().duration / 1000}s ${this.motion().easing}`,
7577
7792
  transitionBehavior: 'allow-discrete',
7578
7793
  opacity: 1,
7579
7794
  top: this.componentOptions().topPosition,
@@ -7679,19 +7894,48 @@ class UniSnackbarComponent extends BaseComponent {
7679
7894
  super();
7680
7895
  effect(() => (this.show() ? this.open() : this.close()));
7681
7896
  }
7897
+ /**
7898
+ * Timing for the enter/leave transition. The deprecated `transitionDelay`
7899
+ * wins when a theme still sets it — it is a CSS time string, so `0.35s` and
7900
+ * `350ms` both parse back to milliseconds.
7901
+ */
7902
+ motion = computed(() => {
7903
+ const options = this.componentOptions();
7904
+ const token = this.theme.motion(options.motion ?? 'notification');
7905
+ const legacy = options.transitionDelay;
7906
+ if (!legacy)
7907
+ return token;
7908
+ const ms = legacy.trim().endsWith('ms') ? parseFloat(legacy) : parseFloat(legacy) * 1000;
7909
+ return Number.isFinite(ms) ? { ...token, duration: ms } : token;
7910
+ }, ...(ngDevMode ? [{ debugName: "motion" }] : /* istanbul ignore next */ []));
7682
7911
  snackbarClass = computed(() => css({
7912
+ // The UA's `[popover]` border, before the theme's own so a themed border
7913
+ // still wins. Its `background-color: Canvas` needs no reset — the
7914
+ // container colors below beat it on origin, and resetting `background`
7915
+ // here would erase them, since it is a shorthand.
7916
+ border: 'none',
7683
7917
  ...this.theme.getContainerColors(this.variant() || 'primary', this.useVariant()),
7684
7918
  ...this.theme.radius('sm'),
7685
7919
  ...this.theme.border(this.variant() || 'primary'),
7686
7920
  ...this.theme.boxShadow('dialog'),
7687
7921
  padding: 0,
7688
- transition: `all ${this.componentOptions().transitionDelay} ease-in-out`,
7922
+ transition: `all ${this.motion().duration}ms ${this.motion().easing}`,
7689
7923
  transitionBehavior: 'allow-discrete',
7690
7924
  opacity: 1,
7691
- bottom: this.componentOptions().bottomPosition,
7692
- zIndex: Z_INDEX.dialog,
7925
+ // `[popover]` arrives centred by `inset: 0; margin: auto`. Undo that,
7926
+ // then rebuild the bottom-centred placement `<dialog>` used to get from
7927
+ // its own UA rules: shrink to the content, pin to the bottom, and let
7928
+ // auto inline margins centre it between the viewport edges.
7693
7929
  position: 'fixed',
7694
- '&[open]': {
7930
+ inset: 'auto',
7931
+ left: 0,
7932
+ right: 0,
7933
+ bottom: this.componentOptions().bottomPosition,
7934
+ width: 'fit-content',
7935
+ maxWidth: '100%',
7936
+ marginInline: 'auto',
7937
+ marginBlock: 0,
7938
+ '&:popover-open': {
7695
7939
  '@starting-style': {
7696
7940
  bottom: 0,
7697
7941
  opacity: 0,
@@ -7712,7 +7956,9 @@ class UniSnackbarComponent extends BaseComponent {
7712
7956
  ngAfterViewInit() {
7713
7957
  this._snackbar?.addEventListener('animationend', (e) => {
7714
7958
  if (e.animationName == this.fadeOut) {
7715
- this._snackbar?.close();
7959
+ // Leaves the top layer only once the fade has finished — hiding first
7960
+ // would remove the bar mid-animation.
7961
+ this.hide();
7716
7962
  this.showing.emit(false);
7717
7963
  }
7718
7964
  });
@@ -7727,7 +7973,12 @@ class UniSnackbarComponent extends BaseComponent {
7727
7973
  }
7728
7974
  open() {
7729
7975
  this._snackbar?.removeAttribute('closing');
7730
- this._snackbar?.show();
7976
+ try {
7977
+ this._snackbar?.showPopover();
7978
+ }
7979
+ catch {
7980
+ // Already showing — reopening is a no-op, the timer below still restarts.
7981
+ }
7731
7982
  this.show.set(true);
7732
7983
  this.showing.emit(true);
7733
7984
  if (this._timeout)
@@ -7737,6 +7988,15 @@ class UniSnackbarComponent extends BaseComponent {
7737
7988
  this._snackbar?.setAttribute('closing', 'true');
7738
7989
  this.show.set(false);
7739
7990
  }
7991
+ /** Drops out of the top layer. Called once the closing fade has run. */
7992
+ hide() {
7993
+ try {
7994
+ this._snackbar?.hidePopover();
7995
+ }
7996
+ catch {
7997
+ // Already hidden.
7998
+ }
7999
+ }
7740
8000
  pauseTimer() {
7741
8001
  this.timer.pause();
7742
8002
  }
@@ -7744,7 +8004,7 @@ class UniSnackbarComponent extends BaseComponent {
7744
8004
  this.timer.resume();
7745
8005
  }
7746
8006
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSnackbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7747
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSnackbarComponent, isStandalone: true, selector: "uni-snackbar", inputs: { show: { classPropertyName: "show", publicName: "show", 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 }, timeout: { classPropertyName: "timeout", publicName: "timeout", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, useVariant: { classPropertyName: "useVariant", publicName: "useVariant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { show: "showChange", action: "action", showing: "showing" }, providers: [{ provide: COMPONENT_NAME, useValue: 'snackbar' }], viewQueries: [{ propertyName: "snackbarRef", first: true, predicate: ["snackbar"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@let icon = iconName();\n@let symbol = symbolName();\n<!-- role=\"status\" announces the message politely without stealing focus -->\n<dialog\n #snackbar\n [class]=\"snackbarClass()\"\n role=\"status\"\n (mouseenter)=\"pauseTimer()\"\n (mouseleave)=\"resumeTimer()\"\n (focusin)=\"pauseTimer()\"\n (focusout)=\"resumeTimer()\"\n>\n <div row-layout alignItems=\"center\">\n @if (icon || symbol) {\n <div box-layout [height]=\"26\" [width]=\"26\" paddingLeft=\"sm\">\n @if (icon) {\n <uni-icon [name]=\"icon\"></uni-icon>\n } @else if (symbol) {\n <uni-symbol [name]=\"symbol\" [opticalSize]=\"26\"></uni-symbol>\n }\n </div>\n }\n <div box-layout padding=\"sm\">\n <span uni-text>\n <ng-content></ng-content>\n </span>\n </div>\n <div box-layout paddingRight=\"sm\">\n @if (!actionLabel()) {\n <button icon-button (click)=\"close()\" iconName=\"close\" size=\"md\">Close</button>\n } @else {\n <button text-button variant=\"ghost\" (click)=\"this.action.emit(); close()\">\n {{ actionLabel() }}\n </button>\n }\n </div>\n </div>\n</dialog>\n", dependencies: [{ kind: "component", type: UniRowComponent, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8007
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSnackbarComponent, isStandalone: true, selector: "uni-snackbar", inputs: { show: { classPropertyName: "show", publicName: "show", 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 }, timeout: { classPropertyName: "timeout", publicName: "timeout", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, useVariant: { classPropertyName: "useVariant", publicName: "useVariant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { show: "showChange", action: "action", showing: "showing" }, providers: [{ provide: COMPONENT_NAME, useValue: 'snackbar' }], viewQueries: [{ propertyName: "snackbarRef", first: true, predicate: ["snackbar"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@let icon = iconName();\n@let symbol = symbolName();\n<!-- role=\"status\" announces the message politely without stealing focus.\n popover=\"manual\" puts the bar in the top layer so no stacking context or\n `overflow: hidden` ancestor can cover or clip it; \"manual\" because a\n snackbar is dismissed by its own button or its timer, never by a click\n elsewhere on the page. Not a <dialog>: it is never modal, and dialog's\n `open` attribute would be a second, competing notion of \"shown\". -->\n<div\n #snackbar\n popover=\"manual\"\n [class]=\"snackbarClass()\"\n role=\"status\"\n (mouseenter)=\"pauseTimer()\"\n (mouseleave)=\"resumeTimer()\"\n (focusin)=\"pauseTimer()\"\n (focusout)=\"resumeTimer()\"\n>\n <div row-layout alignItems=\"center\">\n @if (icon || symbol) {\n <div box-layout [height]=\"26\" [width]=\"26\" paddingLeft=\"sm\">\n @if (icon) {\n <uni-icon [name]=\"icon\"></uni-icon>\n } @else if (symbol) {\n <uni-symbol [name]=\"symbol\" [opticalSize]=\"26\"></uni-symbol>\n }\n </div>\n }\n <div box-layout padding=\"sm\">\n <span uni-text>\n <ng-content></ng-content>\n </span>\n </div>\n <div box-layout paddingRight=\"sm\">\n @if (!actionLabel()) {\n <button icon-button (click)=\"close()\" iconName=\"close\" size=\"md\">Close</button>\n } @else {\n <button text-button variant=\"ghost\" (click)=\"this.action.emit(); close()\">\n {{ actionLabel() }}\n </button>\n }\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "component", type: UniRowComponent, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7748
8008
  }
7749
8009
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSnackbarComponent, decorators: [{
7750
8010
  type: Component,
@@ -7756,7 +8016,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
7756
8016
  UniSymbolComponent,
7757
8017
  UniIconComponent,
7758
8018
  UniButtonComponent,
7759
- ], providers: [{ provide: COMPONENT_NAME, useValue: 'snackbar' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let icon = iconName();\n@let symbol = symbolName();\n<!-- role=\"status\" announces the message politely without stealing focus -->\n<dialog\n #snackbar\n [class]=\"snackbarClass()\"\n role=\"status\"\n (mouseenter)=\"pauseTimer()\"\n (mouseleave)=\"resumeTimer()\"\n (focusin)=\"pauseTimer()\"\n (focusout)=\"resumeTimer()\"\n>\n <div row-layout alignItems=\"center\">\n @if (icon || symbol) {\n <div box-layout [height]=\"26\" [width]=\"26\" paddingLeft=\"sm\">\n @if (icon) {\n <uni-icon [name]=\"icon\"></uni-icon>\n } @else if (symbol) {\n <uni-symbol [name]=\"symbol\" [opticalSize]=\"26\"></uni-symbol>\n }\n </div>\n }\n <div box-layout padding=\"sm\">\n <span uni-text>\n <ng-content></ng-content>\n </span>\n </div>\n <div box-layout paddingRight=\"sm\">\n @if (!actionLabel()) {\n <button icon-button (click)=\"close()\" iconName=\"close\" size=\"md\">Close</button>\n } @else {\n <button text-button variant=\"ghost\" (click)=\"this.action.emit(); close()\">\n {{ actionLabel() }}\n </button>\n }\n </div>\n </div>\n</dialog>\n" }]
8019
+ ], providers: [{ provide: COMPONENT_NAME, useValue: 'snackbar' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let icon = iconName();\n@let symbol = symbolName();\n<!-- role=\"status\" announces the message politely without stealing focus.\n popover=\"manual\" puts the bar in the top layer so no stacking context or\n `overflow: hidden` ancestor can cover or clip it; \"manual\" because a\n snackbar is dismissed by its own button or its timer, never by a click\n elsewhere on the page. Not a <dialog>: it is never modal, and dialog's\n `open` attribute would be a second, competing notion of \"shown\". -->\n<div\n #snackbar\n popover=\"manual\"\n [class]=\"snackbarClass()\"\n role=\"status\"\n (mouseenter)=\"pauseTimer()\"\n (mouseleave)=\"resumeTimer()\"\n (focusin)=\"pauseTimer()\"\n (focusout)=\"resumeTimer()\"\n>\n <div row-layout alignItems=\"center\">\n @if (icon || symbol) {\n <div box-layout [height]=\"26\" [width]=\"26\" paddingLeft=\"sm\">\n @if (icon) {\n <uni-icon [name]=\"icon\"></uni-icon>\n } @else if (symbol) {\n <uni-symbol [name]=\"symbol\" [opticalSize]=\"26\"></uni-symbol>\n }\n </div>\n }\n <div box-layout padding=\"sm\">\n <span uni-text>\n <ng-content></ng-content>\n </span>\n </div>\n <div box-layout paddingRight=\"sm\">\n @if (!actionLabel()) {\n <button icon-button (click)=\"close()\" iconName=\"close\" size=\"md\">Close</button>\n } @else {\n <button text-button variant=\"ghost\" (click)=\"this.action.emit(); close()\">\n {{ actionLabel() }}\n </button>\n }\n </div>\n </div>\n</div>\n" }]
7760
8020
  }], ctorParameters: () => [], propDecorators: { show: [{ type: i0.Input, args: [{ isSignal: true, alias: "show", required: false }] }, { type: i0.Output, args: ["showChange"] }], iconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconName", required: false }] }], symbolName: [{ type: i0.Input, args: [{ isSignal: true, alias: "symbolName", required: false }] }], timeout: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeout", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], useVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "useVariant", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], showing: [{ type: i0.Output, args: ["showing"] }], snackbarRef: [{ type: i0.ViewChild, args: ['snackbar', { isSignal: true }] }] } });
7761
8021
 
7762
8022
  class NotificationsComponent {
@@ -8134,6 +8394,7 @@ class UniPopoverComponent extends BaseComponent {
8134
8394
  }, ...(ngDevMode ? [{ debugName: "resolvedMaxWidth" }] : /* istanbul ignore next */ []));
8135
8395
  popoverClassName = computed(() => {
8136
8396
  const options = this.componentOptions();
8397
+ const motion = this.theme.motion(options.motion);
8137
8398
  return css({
8138
8399
  ...this.theme.colorPair(options.color),
8139
8400
  ...this.theme.radius(options.borderRadius),
@@ -8145,7 +8406,7 @@ class UniPopoverComponent extends BaseComponent {
8145
8406
  maxWidth: this.resolvedMaxWidth(),
8146
8407
  overflow: 'visible',
8147
8408
  ...anchorStyles(this.anchorName, this.placement(), { mainAxis: options.offset }),
8148
- ...discreteOverlayTransition(250, { opacity: 0 }, { opacity: 1 }),
8409
+ ...discreteOverlayTransition(motion.duration, { opacity: 0 }, { opacity: 1 }, motion.easing),
8149
8410
  });
8150
8411
  }, ...(ngDevMode ? [{ debugName: "popoverClassName" }] : /* istanbul ignore next */ []));
8151
8412
  /** Empty regions collapse, so bare content renders v1's single-region look. */
@@ -8344,9 +8605,12 @@ class UniRadioComponent extends BaseComponent {
8344
8605
  // transitions are scoped — never `all` — so the focus ring's outline and
8345
8606
  // shadow apply instantly instead of interpolating from a stale outline
8346
8607
  // color, which flashed a dark ring before the themed ring color landed.
8347
- const speed = this.componentOptions().transitionSpeed ?? 0.3;
8348
- const ringTransition = `border-color ${speed}s ease, background-color ${speed}s ease`;
8349
- const dotTransition = `transform ${speed}s ease`;
8608
+ const options = this.componentOptions();
8609
+ // The deprecated option wins so a theme that set it keeps its timing.
8610
+ const motion = this.theme.motion(options.motion ?? 'control');
8611
+ const speed = options.transitionSpeed ?? motion.duration / 1000;
8612
+ const ringTransition = `border-color ${speed}s ${motion.easing}, background-color ${speed}s ${motion.easing}`;
8613
+ const dotTransition = `transform ${speed}s ${motion.easing}`;
8350
8614
  return css({
8351
8615
  userSelect: 'none',
8352
8616
  cursor: this.disabled() ? 'not-allowed' : 'pointer',
@@ -8455,6 +8719,15 @@ class UniSearchInputComponent extends BaseComponent {
8455
8719
  search = output();
8456
8720
  suggestionSelected = output();
8457
8721
  field = viewChild.required(UniDebounceInputComponent);
8722
+ listRef = viewChild('listbox', ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
8723
+ /** Ties the popup to the field so the browser tracks it in the top layer. */
8724
+ anchor = newListboxAnchor();
8725
+ /** `manual` where the top layer is usable, else null — see the popup helper. */
8726
+ popupAttr = listboxPopupAttr();
8727
+ constructor() {
8728
+ super();
8729
+ promoteListboxPopup(this.listRef);
8730
+ }
8458
8731
  visibleSuggestions = computed(() => this.suggestions().slice(0, this.componentOptions().maxSuggestions ?? 8), ...(ngDevMode ? [{ debugName: "visibleSuggestions" }] : /* istanbul ignore next */ []));
8459
8732
  /** Shared combobox bookkeeping: open state, active option, ARIA ids. */
8460
8733
  list = createListboxNavigation({
@@ -8513,20 +8786,21 @@ class UniSearchInputComponent extends BaseComponent {
8513
8786
  display: 'block',
8514
8787
  position: 'relative',
8515
8788
  width: this.width(),
8789
+ ...this.anchor.style,
8516
8790
  '& .uni-search-lead': {
8517
8791
  fontSize: 20,
8518
8792
  ...this.theme.color('on-background-variant'),
8519
8793
  ...this.theme.paddingLeft('sm'),
8520
8794
  },
8521
8795
  }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
8522
- listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions())), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
8523
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSearchInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8524
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSearchInputComponent, isStandalone: true, selector: "uni-search-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change", search: "search", suggestionSelected: "suggestionSelected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], viewQueries: [{ propertyName: "field", first: true, predicate: UniDebounceInputComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul [id]=\"listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniDebounceInputComponent, selector: "uni-debounce-input", inputs: ["inputName", "inputId", "debounceTime", "label", "placeholder", "disabled", "role", "ariaExpanded", "ariaControls", "ariaActivedescendant"], outputs: ["change"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8796
+ listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions(), { anchor: this.anchor.name })), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
8797
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSearchInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8798
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSearchInputComponent, isStandalone: true, selector: "uni-search-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change", search: "search", suggestionSelected: "suggestionSelected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], viewQueries: [{ propertyName: "field", first: true, predicate: UniDebounceInputComponent, descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniDebounceInputComponent, selector: "uni-debounce-input", inputs: ["inputName", "inputId", "debounceTime", "label", "placeholder", "disabled", "role", "ariaExpanded", "ariaControls", "ariaActivedescendant"], outputs: ["change"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8525
8799
  }
8526
8800
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSearchInputComponent, decorators: [{
8527
8801
  type: Component,
8528
- args: [{ selector: 'uni-search-input', imports: [UniDebounceInputComponent, UniIconButtonComponent, UniSymbolComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], host: { '[class]': 'className()' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul [id]=\"listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n" }]
8529
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], change: [{ type: i0.Output, args: ["change"] }], search: [{ type: i0.Output, args: ["search"] }], suggestionSelected: [{ type: i0.Output, args: ["suggestionSelected"] }], field: [{ type: i0.ViewChild, args: [i0.forwardRef(() => UniDebounceInputComponent), { isSignal: true }] }] } });
8802
+ args: [{ selector: 'uni-search-input', imports: [UniDebounceInputComponent, UniIconButtonComponent, UniSymbolComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], host: { '[class]': 'className()' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n" }]
8803
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], change: [{ type: i0.Output, args: ["change"] }], search: [{ type: i0.Output, args: ["search"] }], suggestionSelected: [{ type: i0.Output, args: ["suggestionSelected"] }], field: [{ type: i0.ViewChild, args: [i0.forwardRef(() => UniDebounceInputComponent), { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
8530
8804
 
8531
8805
  /**
8532
8806
  * UniSearchInputComponent Barrel File
@@ -8603,11 +8877,11 @@ class UniSelectComponent {
8603
8877
  pointerEvents: 'none' /* Crucial for clicking through */,
8604
8878
  });
8605
8879
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8606
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option [value]=\"i\" [selected]=\"currentSelectedIndex() === i.toString()\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8880
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option\n [value]=\"i\"\n [selected]=\"currentSelectedIndex() === i.toString()\"\n [disabled]=\"opt.disabled ?? false\"\n >\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8607
8881
  }
8608
8882
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, decorators: [{
8609
8883
  type: Component,
8610
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-select', imports: [UniInputBoxComponent, UniSymbolComponent], template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option [value]=\"i\" [selected]=\"currentSelectedIndex() === i.toString()\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n" }]
8884
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-select', imports: [UniInputBoxComponent, UniSymbolComponent], template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option\n [value]=\"i\"\n [selected]=\"currentSelectedIndex() === i.toString()\"\n [disabled]=\"opt.disabled ?? false\"\n >\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n" }]
8611
8885
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
8612
8886
 
8613
8887
  /**
@@ -8615,6 +8889,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
8615
8889
  * lines (the last line shortened, as real text would be), `rect` and `circle`
8616
8890
  * render fixed shapes. The shimmer only animates when the user allows motion;
8617
8891
  * it degrades to static blocks under `prefers-reduced-motion`.
8892
+ *
8893
+ * Color and radius are theme options with per-instance overrides, because one
8894
+ * app routinely needs several: skeletons on a card and on the page background
8895
+ * want different tints, and a pill placeholder wants a different corner than
8896
+ * the text bars beside it.
8618
8897
  */
8619
8898
  class UniSkeletonComponent extends BaseComponent {
8620
8899
  shape = input('text', ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
@@ -8624,6 +8903,19 @@ class UniSkeletonComponent extends BaseComponent {
8624
8903
  height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
8625
8904
  /** Number of text lines (text shape only). */
8626
8905
  lines = input(1, ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
8906
+ /** Base color token; overrides the theme option for this skeleton. */
8907
+ color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
8908
+ /** Shimmer highlight token; overrides the theme option for this skeleton. */
8909
+ highlightColor = input(undefined, ...(ngDevMode ? [{ debugName: "highlightColor" }] : /* istanbul ignore next */ []));
8910
+ /** Radius token; overrides the theme option. Circles are always round. */
8911
+ borderRadius = input(undefined, ...(ngDevMode ? [{ debugName: "borderRadius" }] : /* istanbul ignore next */ []));
8912
+ /**
8913
+ * Announces the skeleton to assistive tech as a polite status. Leave unset
8914
+ * when a container already carries `aria-busy` — the skeleton then stays
8915
+ * `aria-hidden`, as a decorative placeholder should.
8916
+ */
8917
+ label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
8918
+ srOnly = css(visuallyHidden);
8627
8919
  cssSize = (value) => typeof value === 'number' ? `${value}px` : value;
8628
8920
  resolvedHeight = computed(() => {
8629
8921
  const height = this.height();
@@ -8641,36 +8933,61 @@ class UniSkeletonComponent extends BaseComponent {
8641
8933
  // Multi-line text blocks end on a short line, like real paragraphs do.
8642
8934
  return Array.from({ length: lines }, (_, i) => lines > 1 && i === lines - 1 ? (width ?? '60%') : (width ?? '100%'));
8643
8935
  }, ...(ngDevMode ? [{ debugName: "lineWidths" }] : /* istanbul ignore next */ []));
8644
- sweep = keyframes({
8645
- from: { backgroundPosition: '200% 0' },
8646
- to: { backgroundPosition: '-200% 0' },
8647
- });
8936
+ /**
8937
+ * The band is `bandWidth`% of the block, so it clears the block after
8938
+ * travelling `100 / bandWidth` of its own width — the offsets below are
8939
+ * percentages of the band, not of the block.
8940
+ */
8941
+ sweep = (bandWidth, direction) => {
8942
+ const travel = `${Number(((100 / bandWidth) * 100).toFixed(2))}%`;
8943
+ const [from, to] = direction === 'rtl' ? [travel, '-100%'] : ['-100%', travel];
8944
+ return keyframes({
8945
+ from: { transform: `translateX(${from})` },
8946
+ to: { transform: `translateX(${to})` },
8947
+ });
8948
+ };
8648
8949
  className = computed(() => {
8649
8950
  const options = this.componentOptions();
8650
- const base = this.theme.colors()[options.color ?? 'surface-variant'];
8651
- const highlight = this.theme.colors()[options.highlightColor ?? 'surface'];
8951
+ const base = this.theme.colors()[this.color() ?? options.color ?? 'surface-variant'];
8952
+ const highlight = this.theme.colors()[this.highlightColor() ?? options.highlightColor ?? 'surface'];
8652
8953
  const animated = (options.animation ?? 'shimmer') === 'shimmer';
8954
+ const bandWidth = Math.max(1, options.highlightWidth ?? 40);
8653
8955
  return css({
8654
8956
  display: 'flex',
8655
8957
  flexDirection: 'column',
8656
8958
  ...this.theme.gap(options.gap),
8657
8959
  '& .uni-skeleton-block': {
8960
+ position: 'relative',
8961
+ overflow: 'hidden',
8658
8962
  height: this.resolvedHeight(),
8659
8963
  backgroundColor: base,
8660
8964
  ...(this.shape() === 'circle'
8661
8965
  ? { borderRadius: '50%', flex: 'none' }
8662
- : this.theme.radius(options.borderRadius)),
8966
+ : this.theme.radius(this.borderRadius() ?? options.borderRadius)),
8663
8967
  ...(animated &&
8664
8968
  motionSafe({
8665
- backgroundImage: `linear-gradient(90deg, ${base} 40%, ${highlight} 50%, ${base} 60%)`,
8666
- backgroundSize: '200% 100%',
8667
- animation: `${this.sweep} ${options.duration ?? 1.4}s ease-in-out infinite`,
8969
+ // A translated band composites; animating background-position
8970
+ // repaints the block every frame. Both gradient ends are the base
8971
+ // color, so the band dissolves into the block with no alpha and
8972
+ // no fringing where it meets the edges.
8973
+ '&::after': {
8974
+ content: '""',
8975
+ position: 'absolute',
8976
+ insetBlock: 0,
8977
+ left: 0,
8978
+ width: `${bandWidth}%`,
8979
+ backgroundImage: `linear-gradient(90deg, ${base} 0%, ${highlight} 50%, ${base} 100%)`,
8980
+ animation: `${this.sweep(bandWidth, options.direction ?? 'ltr')} ${options.duration ?? 1.4}s ease-in-out infinite`,
8981
+ },
8668
8982
  })),
8669
8983
  },
8670
8984
  });
8671
8985
  }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
8672
8986
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSkeletonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8673
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSkeletonComponent, isStandalone: true, selector: "uni-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'skeleton' }], usesInheritance: true, ngImport: i0, template: `
8987
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSkeletonComponent, isStandalone: true, selector: "uni-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, highlightColor: { classPropertyName: "highlightColor", publicName: "highlightColor", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "attr.aria-hidden": "label() ? null : true", "attr.role": "label() ? 'status' : null" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'skeleton' }], usesInheritance: true, ngImport: i0, template: `
8988
+ @if (label(); as text) {
8989
+ <span [class]="srOnly">{{ text }}</span>
8990
+ }
8674
8991
  @for (line of lineWidths(); track $index) {
8675
8992
  <div class="uni-skeleton-block" [style.width]="line"></div>
8676
8993
  }
@@ -8682,14 +8999,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
8682
8999
  changeDetection: ChangeDetectionStrategy.OnPush,
8683
9000
  selector: 'uni-skeleton',
8684
9001
  providers: [{ provide: COMPONENT_NAME, useValue: 'skeleton' }],
8685
- host: { '[class]': 'className()', 'aria-hidden': 'true' },
9002
+ host: {
9003
+ '[class]': 'className()',
9004
+ '[attr.aria-hidden]': 'label() ? null : true',
9005
+ '[attr.role]': "label() ? 'status' : null",
9006
+ },
8686
9007
  template: `
9008
+ @if (label(); as text) {
9009
+ <span [class]="srOnly">{{ text }}</span>
9010
+ }
8687
9011
  @for (line of lineWidths(); track $index) {
8688
9012
  <div class="uni-skeleton-block" [style.width]="line"></div>
8689
9013
  }
8690
9014
  `,
8691
9015
  }]
8692
- }], propDecorators: { shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }] } });
9016
+ }], propDecorators: { shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], highlightColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightColor", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
8693
9017
 
8694
9018
  /**
8695
9019
  * Range slider on a native `<input type="range">` — keyboard interaction and
@@ -9492,12 +9816,21 @@ class UniTagInputComponent extends BaseComponent {
9492
9816
  rejected = output();
9493
9817
  inputRef = viewChild.required('field');
9494
9818
  chipRefs = viewChildren('chip', ...(ngDevMode ? [{ debugName: "chipRefs" }] : /* istanbul ignore next */ []));
9819
+ listRef = viewChild('listbox', ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
9820
+ /** Ties the popup to the field so the browser tracks it in the top layer. */
9821
+ anchor = newListboxAnchor();
9822
+ /** `manual` where the top layer is usable, else null — see the popup helper. */
9823
+ popupAttr = listboxPopupAttr();
9824
+ constructor() {
9825
+ super();
9826
+ promoteListboxPopup(this.listRef);
9827
+ }
9495
9828
  /** Uncommitted text in the field. */
9496
9829
  draft = signal('', ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
9497
9830
  /** Index of the focused chip, or -1 when focus is in the text input. */
9498
9831
  focusedChip = signal(-1, ...(ngDevMode ? [{ debugName: "focusedChip" }] : /* istanbul ignore next */ []));
9499
- /** Announcement for the status region; add/remove are otherwise silent. */
9500
- announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
9832
+ /** Adds, removes and refusals are otherwise silent to a screen reader. */
9833
+ announcer = createAnnouncer();
9501
9834
  hintId = uniqueId('uni-tag-input-hint');
9502
9835
  srOnly = css(visuallyHidden);
9503
9836
  queryTimer;
@@ -9563,13 +9896,13 @@ class UniTagInputComponent extends BaseComponent {
9563
9896
  };
9564
9897
  this.value.update((items) => [...items, item]);
9565
9898
  this.added.emit(item);
9566
- this.announce(`${this.labelOf(item)} added. ${this.value().length} ${this.countNoun()}.`);
9899
+ this.announcer.announce(`${this.labelOf(item)} added. ${this.value().length} ${this.countNoun()}.`);
9567
9900
  return true;
9568
9901
  }
9569
9902
  reject(raw, reason) {
9570
9903
  this.rejected.emit({ raw, reason });
9571
9904
  // The visual cue is a brief pulse a screen reader cannot see.
9572
- this.announce(reason === 'duplicate' ? `${raw} is already added.` : `${raw} was not added: limit reached.`);
9905
+ this.announcer.announce(reason === 'duplicate' ? `${raw} is already added.` : `${raw} was not added: limit reached.`);
9573
9906
  }
9574
9907
  removeAt(index, focus = 'input') {
9575
9908
  const item = this.value()[index];
@@ -9577,7 +9910,7 @@ class UniTagInputComponent extends BaseComponent {
9577
9910
  return;
9578
9911
  this.value.update((items) => items.filter((_, i) => i !== index));
9579
9912
  this.removed.emit(item);
9580
- this.announce(`${this.labelOf(item)} removed. ${this.value().length} ${this.countNoun()}.`);
9913
+ this.announcer.announce(`${this.labelOf(item)} removed. ${this.value().length} ${this.countNoun()}.`);
9581
9914
  const remaining = this.value().length;
9582
9915
  if (focus === 'left' && index > 0)
9583
9916
  this.focusChip(index - 1);
@@ -9589,10 +9922,6 @@ class UniTagInputComponent extends BaseComponent {
9589
9922
  countNoun() {
9590
9923
  return this.value().length === 1 ? 'item' : 'items';
9591
9924
  }
9592
- announce(message) {
9593
- // Re-announce identical text by breaking the string equality.
9594
- this.announcement.set(this.announcement() === message ? `${message} ` : message);
9595
- }
9596
9925
  // --- Focus ---------------------------------------------------------------
9597
9926
  focusInput() {
9598
9927
  this.focusedChip.set(-1);
@@ -9765,7 +10094,11 @@ class UniTagInputComponent extends BaseComponent {
9765
10094
  input.value = text;
9766
10095
  }
9767
10096
  // --- Styling -------------------------------------------------------------
9768
- className = computed(() => css({ display: 'block' }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
10097
+ /** The host is the popup's anchor the box the list mirrors the width of,
10098
+ and the one its entry animation is measured against. The wrapper below
10099
+ has the same geometry but is only the fallback's positioning context. */
10100
+ className = computed(() => css({ display: 'block', ...this.anchor.style }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
10101
+ wrapperClass = computed(() => css({ position: 'relative' }), ...(ngDevMode ? [{ debugName: "wrapperClass" }] : /* istanbul ignore next */ []));
9769
10102
  fieldClass = computed(() => {
9770
10103
  const options = this.componentOptions();
9771
10104
  return css({
@@ -9789,14 +10122,14 @@ class UniTagInputComponent extends BaseComponent {
9789
10122
  font: 'inherit',
9790
10123
  padding: 0,
9791
10124
  }), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
9792
- listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions())), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
9793
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
9794
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagInputComponent, isStandalone: true, selector: "uni-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, tagVariant: { classPropertyName: "tagVariant", publicName: "tagVariant", isSignal: true, isRequired: false, transformFunction: null }, tagTone: { classPropertyName: "tagTone", publicName: "tagTone", isSignal: true, isRequired: false, transformFunction: null }, tagSize: { classPropertyName: "tagSize", publicName: "tagSize", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", added: "added", removed: "removed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "chipRefs", predicate: ["chip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div style=\"position: relative\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniTagComponent, selector: "uni-tag", inputs: ["size", "tone", "label", "value", "maxWidth", "avatarSrc", "avatarName", "iconName", "symbolName", "dot", "removable", "interactive", "selected", "invalid", "disabled", "removeLabel", "controlTabIndex"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10125
+ listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions(), { anchor: this.anchor.name })), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
10126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagInputComponent, isStandalone: true, selector: "uni-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, tagVariant: { classPropertyName: "tagVariant", publicName: "tagVariant", isSignal: true, isRequired: false, transformFunction: null }, tagTone: { classPropertyName: "tagTone", publicName: "tagTone", isSignal: true, isRequired: false, transformFunction: null }, tagSize: { classPropertyName: "tagSize", publicName: "tagSize", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", added: "added", removed: "removed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "chipRefs", predicate: ["chip"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div [class]=\"wrapperClass()\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniTagComponent, selector: "uni-tag", inputs: ["size", "tone", "label", "value", "maxWidth", "avatarSrc", "avatarName", "iconName", "symbolName", "dot", "removable", "interactive", "selected", "invalid", "disabled", "removeLabel", "controlTabIndex"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9795
10128
  }
9796
10129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, decorators: [{
9797
10130
  type: Component,
9798
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tag-input', imports: [UniInputBoxComponent, UniTagComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div style=\"position: relative\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n" }]
9799
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], preset: [{ type: i0.Input, args: [{ isSignal: true, alias: "preset", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], parse: [{ type: i0.Input, args: [{ isSignal: true, alias: "parse", required: false }] }], tagVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagVariant", required: false }] }], tagTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagTone", required: false }] }], tagSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagSize", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], query: [{ type: i0.Output, args: ["query"] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], chipRefs: [{ type: i0.ViewChildren, args: ['chip', { isSignal: true }] }] } });
10131
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tag-input', imports: [UniInputBoxComponent, UniTagComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div [class]=\"wrapperClass()\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul\n #listbox\n [id]=\"list.listboxId\"\n role=\"listbox\"\n [attr.popover]=\"popupAttr\"\n [attr.aria-label]=\"label()\"\n [class]=\"listClass()\"\n >\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcer.message() }}</span>\n</div>\n" }]
10132
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], preset: [{ type: i0.Input, args: [{ isSignal: true, alias: "preset", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], parse: [{ type: i0.Input, args: [{ isSignal: true, alias: "parse", required: false }] }], tagVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagVariant", required: false }] }], tagTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagTone", required: false }] }], tagSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagSize", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], query: [{ type: i0.Output, args: ["query"] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], chipRefs: [{ type: i0.ViewChildren, args: ['chip', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
9800
10133
 
9801
10134
  /**
9802
10135
  * UniTagInputComponent Barrel File
@@ -11090,7 +11423,9 @@ class UniTourComponent extends BaseComponent {
11090
11423
  skipped = output();
11091
11424
  calloutOpen = signal(false, ...(ngDevMode ? [{ debugName: "calloutOpen" }] : /* istanbul ignore next */ []));
11092
11425
  satisfied = signal(true, ...(ngDevMode ? [{ debugName: "satisfied" }] : /* istanbul ignore next */ []));
11093
- announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
11426
+ /** Repeats must still be heard: the same gate message can come round
11427
+ again on a later step. */
11428
+ announcer = createAnnouncer();
11094
11429
  resolvedTarget = signal(undefined, ...(ngDevMode ? [{ debugName: "resolvedTarget" }] : /* istanbul ignore next */ []));
11095
11430
  presentedIndex = signal(null, ...(ngDevMode ? [{ debugName: "presentedIndex" }] : /* istanbul ignore next */ []));
11096
11431
  gateCleanup = null;
@@ -11194,7 +11529,7 @@ class UniTourComponent extends BaseComponent {
11194
11529
  this.advance();
11195
11530
  }
11196
11531
  else {
11197
- this.announcement.set('Next available');
11532
+ this.announcer.announce('Next available');
11198
11533
  }
11199
11534
  });
11200
11535
  this.gateCleanup = () => {
@@ -11262,11 +11597,11 @@ class UniTourComponent extends BaseComponent {
11262
11597
  }, ...(ngDevMode ? [{ debugName: "dotsClassName" }] : /* istanbul ignore next */ []));
11263
11598
  fractionClassName = css({ margin: '0 auto' });
11264
11599
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTourComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11265
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTourComponent, isStandalone: true, selector: "uni-tour", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { active: "activeChange", started: "started", stepChanged: "stepChanged", finished: "finished", skipped: "skipped" }, providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], usesInheritance: true, ngImport: i0, template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcement() }}</span>\n", dependencies: [{ kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniCalloutComponent, selector: "uni-callout", inputs: ["open", "key", "target", "placement", "backdrop", "targetInteractive", "dismissible", "dismissOnBackdrop", "header", "arrow", "ariaLabel", "closeLabel"], outputs: ["openChange", "opened", "closed", "dismissed", "panelKeydown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11600
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTourComponent, isStandalone: true, selector: "uni-tour", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { active: "activeChange", started: "started", stepChanged: "stepChanged", finished: "finished", skipped: "skipped" }, providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], usesInheritance: true, ngImport: i0, template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcer.message() }}</span>\n", dependencies: [{ kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniCalloutComponent, selector: "uni-callout", inputs: ["open", "key", "target", "placement", "backdrop", "targetInteractive", "dismissible", "dismissOnBackdrop", "header", "arrow", "ariaLabel", "closeLabel"], outputs: ["openChange", "opened", "closed", "dismissed", "panelKeydown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11266
11601
  }
11267
11602
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTourComponent, decorators: [{
11268
11603
  type: Component,
11269
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tour', imports: [UniButtonComponent, UniCalloutComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcement() }}</span>\n" }]
11604
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tour', imports: [UniButtonComponent, UniCalloutComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcer.message() }}</span>\n" }]
11270
11605
  }], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }, { type: i0.Output, args: ["activeChange"] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], backLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLabel", required: false }] }], skipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], started: [{ type: i0.Output, args: ["started"] }], stepChanged: [{ type: i0.Output, args: ["stepChanged"] }], finished: [{ type: i0.Output, args: ["finished"] }], skipped: [{ type: i0.Output, args: ["skipped"] }] } });
11271
11606
 
11272
11607
  /**
@@ -11279,5 +11614,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
11279
11614
  * Generated bundle index. Do not edit.
11280
11615
  */
11281
11616
 
11282
- export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, TRANSFORM_ORIGINS, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCalendarComponent, UniCalloutComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniComboboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDateInputComponent, UniDateTimeInputComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniTimeInputComponent, UniToggleComponent, UniTooltipComponent, UniTourComponent, UniWrapComponent, acceptableFile, addDays, addMonths, anchorArrowStyles, anchorStyles, buildMonthGrid, clearAnchorName, createListboxNavigation, dayOfWeek, daysInMonth, discreteOverlayTransition, focusableElements, formatDate, formatMonthHeading, formatTime, getFileExtension, inclusiveDayCount, isDivider, isToggleOpen, isValidDate, isoDate, joinDateTime, listboxPopupStyles, localeDatePlaceholder, localeDefaultHour12, localeFieldOrder, localeMonthNames, localeWeekStart, monthOf, motionSafe, newAnchorName, parseDateText, parseTimeText, resolveElement, resolveFocusTarget, restoreOverlayFocus, setAnchorName, splitDateTime, spotlightStyles, timeSlots, todayIso, transformOriginFor, uniqueId, useTimer, visuallyHidden, weekdayNames };
11617
+ export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, TRANSFORM_ORIGINS, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCalendarComponent, UniCalloutComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniComboboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDateInputComponent, UniDateTimeInputComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniTimeInputComponent, UniToggleComponent, UniTooltipComponent, UniTourComponent, UniWrapComponent, acceptableFile, addDays, addMonths, anchorArrowStyles, anchorStyles, buildMonthGrid, clearAnchorName, createAnnouncer, createListboxNavigation, dayOfWeek, daysInMonth, discreteOverlayTransition, focusableElements, formatDate, formatMonthHeading, formatTime, getFileExtension, inclusiveDayCount, isDivider, isToggleOpen, isValidDate, isoDate, joinDateTime, listboxPopupAttr, listboxPopupStyles, localeDatePlaceholder, localeDefaultHour12, localeFieldOrder, localeMonthNames, localeWeekStart, monthOf, motionSafe, newAnchorName, newListboxAnchor, parseDateText, parseTimeText, promoteListboxPopup, resolveElement, resolveFocusTarget, restoreOverlayFocus, setAnchorName, splitDateTime, spotlightStyles, supportsAnchoredPopup, timeSlots, todayIso, transformOriginFor, uniqueId, useTimer, visuallyHidden, weekdayNames };
11283
11618
  //# sourceMappingURL=uni-design-system-uni-angular.mjs.map