@uni-design-system/uni-angular 7.1.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, effect, viewChild, afterNextRender, ViewChild, booleanAttribute, viewChildren } from '@angular/core';
2
+ import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, effect, viewChild, afterNextRender, ViewChild, afterRenderEffect, booleanAttribute, viewChildren } from '@angular/core';
3
3
  import { injectGlobal, css, keyframes } from '@emotion/css';
4
- import { UniThemes, LightTheme, toTypefaces, createThemeFromPalette, Z_INDEX, fadeIn, fadeOut, expandFadeIn, collapseFadeOut, removeInputPlatformStyling, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
4
+ import { UniThemes, LightTheme, toTypefaces, createThemeFromPalette, Z_INDEX, fadeIn, fadeOut, EXPAND_DEFAULT_SPEED, expandDuration, expandFadeIn, collapseFadeOut, removeInputPlatformStyling, 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;
@@ -1388,7 +1388,13 @@ class UniIconComponent {
1388
1388
  const size = this.size();
1389
1389
  if (size === undefined || size === null || size === '')
1390
1390
  return null;
1391
- return typeof size === 'number' ? `${size}px` : size;
1391
+ if (typeof size === 'number')
1392
+ return `${size}px`;
1393
+ // A static attribute (`size="24"`) arrives as a string, so a bare number
1394
+ // has to mean px here too — otherwise it emits the invalid `width: 24`,
1395
+ // which the browser drops, silently falling back to filling the container.
1396
+ const trimmed = size.trim();
1397
+ return /^-?\d*\.?\d+$/.test(trimmed) ? `${trimmed}px` : trimmed;
1392
1398
  }, ...(ngDevMode ? [{ debugName: "sizeValue" }] : /* istanbul ignore next */ []));
1393
1399
  className = computed(() => css([{ ...this.themeService.color(this.color()) }]), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1394
1400
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -2285,6 +2291,20 @@ class UniIconButtonComponent {
2285
2291
  loading = input(...(ngDevMode ? [undefined, { debugName: "loading" }] : /* istanbul ignore next */ []));
2286
2292
  opticalSize = input(24, ...(ngDevMode ? [{ debugName: "opticalSize" }] : /* istanbul ignore next */ []));
2287
2293
  srOnlyClass = css(visuallyHidden);
2294
+ /**
2295
+ * Sizes `iconName` from the size token's `fontSize`, the same value that sizes
2296
+ * a `symbolName` ligature — so the two paths render the same glyph size and
2297
+ * `symbolName` → `iconName` is a like-for-like swap. Without it a masked icon
2298
+ * fills the whole button box, since the base size tokens carry no padding.
2299
+ * Themes that do use padding (Carbon) set a matching `fontSize`, so they land
2300
+ * on the same glyph either way.
2301
+ */
2302
+ glyphSize = computed(() => {
2303
+ // Size tokens are Emotion style objects, so `fontSize` is typed wider than
2304
+ // a CSS length; anything exotic falls back to filling the button as before.
2305
+ const fontSize = this.config().sizes?.[this.size()]?.fontSize;
2306
+ return typeof fontSize === 'number' || typeof fontSize === 'string' ? fontSize : undefined;
2307
+ }, ...(ngDevMode ? [{ debugName: "glyphSize" }] : /* istanbul ignore next */ []));
2288
2308
  className = computed(() => {
2289
2309
  const { sizes, variants } = this.config();
2290
2310
  const sizeConfig = sizes && sizes[this.size()];
@@ -2300,7 +2320,15 @@ class UniIconButtonComponent {
2300
2320
  // Token-driven radius (`max` = circle) with the legacy 999 fallback
2301
2321
  // for hand-authored themes that predate iconButton options.
2302
2322
  ...(this.theme.radius(this.config().options?.borderRadius) ?? { borderRadius: 999 }),
2303
- display: 'block',
2323
+ // Block-level, but centring: the size tokens make the box bigger than
2324
+ // the glyph (an `sm` button is 22px around an 18px icon), so a plain
2325
+ // `display: block` parks the glyph in the top-left corner. Flex is
2326
+ // still block-level, so nothing about the button's own layout changes.
2327
+ // The accessible-name span is absolutely positioned and so stays out
2328
+ // of the flex flow.
2329
+ display: 'flex',
2330
+ alignItems: 'center',
2331
+ justifyContent: 'center',
2304
2332
  '&:disabled': {
2305
2333
  cursor: 'not-allowed !important',
2306
2334
  },
@@ -2343,7 +2371,7 @@ class UniIconButtonComponent {
2343
2371
  } @else if (symbolName()) {
2344
2372
  <uni-symbol [name]="symbolName()!" [opticalSize]="opticalSize()" />
2345
2373
  } @else if (iconName()) {
2346
- <uni-icon [name]="iconName()!" />
2374
+ <uni-icon [name]="iconName()!" [size]="glyphSize()" />
2347
2375
  }
2348
2376
  <!-- Projected text is the button's accessible name (visually hidden) -->
2349
2377
  <span [class]="srOnlyClass"><ng-content /></span>
@@ -2360,7 +2388,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
2360
2388
  } @else if (symbolName()) {
2361
2389
  <uni-symbol [name]="symbolName()!" [opticalSize]="opticalSize()" />
2362
2390
  } @else if (iconName()) {
2363
- <uni-icon [name]="iconName()!" />
2391
+ <uni-icon [name]="iconName()!" [size]="glyphSize()" />
2364
2392
  }
2365
2393
  <!-- Projected text is the button's accessible name (visually hidden) -->
2366
2394
  <span [class]="srOnlyClass"><ng-content /></span>
@@ -3280,6 +3308,13 @@ class UniDropdownComponent extends BaseComponent {
3280
3308
  popoverId = uniqueId('uni-dropdown');
3281
3309
  paddingVertical = input(...(ngDevMode ? [undefined, { debugName: "paddingVertical" }] : /* istanbul ignore next */ []));
3282
3310
  paddingHorizontal = input(...(ngDevMode ? [undefined, { debugName: "paddingHorizontal" }] : /* istanbul ignore next */ []));
3311
+ // Per-instance panel-chrome overrides; undefined falls back to the theme's
3312
+ // `dropdown` options, so hosts like uni-menu can restyle their panel
3313
+ // without forking the shared dropdown entry.
3314
+ border = input(...(ngDevMode ? [undefined, { debugName: "border" }] : /* istanbul ignore next */ []));
3315
+ borderRadius = input(...(ngDevMode ? [undefined, { debugName: "borderRadius" }] : /* istanbul ignore next */ []));
3316
+ shadow = input(...(ngDevMode ? [undefined, { debugName: "shadow" }] : /* istanbul ignore next */ []));
3317
+ color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : /* istanbul ignore next */ []));
3283
3318
  dropdownShowing = output();
3284
3319
  dropdownHiding = output();
3285
3320
  dropdownRef;
@@ -3404,17 +3439,17 @@ class UniDropdownComponent extends BaseComponent {
3404
3439
  }
3405
3440
  }
3406
3441
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3407
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDropdownComponent, isStandalone: true, selector: "uni-dropdown", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, ariaHasPopup: { classPropertyName: "ariaHasPopup", publicName: "ariaHasPopup", isSignal: true, isRequired: false, transformFunction: null }, paddingVertical: { classPropertyName: "paddingVertical", publicName: "paddingVertical", isSignal: true, isRequired: false, transformFunction: null }, paddingHorizontal: { classPropertyName: "paddingHorizontal", publicName: "paddingHorizontal", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dropdownShowing: "dropdownShowing", dropdownHiding: "dropdownHiding" }, providers: [{ provide: COMPONENT_NAME, useValue: 'dropdown' }], viewQueries: [{ propertyName: "dropdownRef", first: true, predicate: ["dropdown"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
3442
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniDropdownComponent, isStandalone: true, selector: "uni-dropdown", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, ariaHasPopup: { classPropertyName: "ariaHasPopup", publicName: "ariaHasPopup", isSignal: true, isRequired: false, transformFunction: null }, paddingVertical: { classPropertyName: "paddingVertical", publicName: "paddingVertical", isSignal: true, isRequired: false, transformFunction: null }, paddingHorizontal: { classPropertyName: "paddingHorizontal", publicName: "paddingHorizontal", isSignal: true, isRequired: false, transformFunction: null }, border: { classPropertyName: "border", publicName: "border", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dropdownShowing: "dropdownShowing", dropdownHiding: "dropdownHiding" }, providers: [{ provide: COMPONENT_NAME, useValue: 'dropdown' }], viewQueries: [{ propertyName: "dropdownRef", first: true, predicate: ["dropdown"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
3408
3443
  <!-- 1. The native 'popover' attribute brings it to the top layer with native light-dismiss -->
3409
3444
  <div #dropdown popover="auto" [id]="popoverId" [class]="dropdownClass()">
3410
3445
  <div
3411
3446
  box-layout
3412
- [border]="componentOptions().border"
3413
- [borderRadius]="componentOptions().borderRadius"
3447
+ [border]="border() ?? componentOptions().border"
3448
+ [borderRadius]="borderRadius() ?? componentOptions().borderRadius"
3414
3449
  [paddingVertical]="paddingVertical()"
3415
3450
  [paddingHorizontal]="paddingHorizontal()"
3416
- [color]="componentOptions().color"
3417
- [shadow]="componentOptions().shadow"
3451
+ [color]="color() ?? componentOptions().color"
3452
+ [shadow]="shadow() ?? componentOptions().shadow"
3418
3453
  >
3419
3454
  <ng-content></ng-content>
3420
3455
  </div>
@@ -3432,12 +3467,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3432
3467
  <div #dropdown popover="auto" [id]="popoverId" [class]="dropdownClass()">
3433
3468
  <div
3434
3469
  box-layout
3435
- [border]="componentOptions().border"
3436
- [borderRadius]="componentOptions().borderRadius"
3470
+ [border]="border() ?? componentOptions().border"
3471
+ [borderRadius]="borderRadius() ?? componentOptions().borderRadius"
3437
3472
  [paddingVertical]="paddingVertical()"
3438
3473
  [paddingHorizontal]="paddingHorizontal()"
3439
- [color]="componentOptions().color"
3440
- [shadow]="componentOptions().shadow"
3474
+ [color]="color() ?? componentOptions().color"
3475
+ [shadow]="shadow() ?? componentOptions().shadow"
3441
3476
  >
3442
3477
  <ng-content></ng-content>
3443
3478
  </div>
@@ -3445,13 +3480,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3445
3480
  `,
3446
3481
  providers: [{ provide: COMPONENT_NAME, useValue: 'dropdown' }],
3447
3482
  }]
3448
- }], propDecorators: { trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], ariaHasPopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaHasPopup", required: false }] }], paddingVertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingVertical", required: false }] }], paddingHorizontal: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingHorizontal", required: false }] }], dropdownShowing: [{ type: i0.Output, args: ["dropdownShowing"] }], dropdownHiding: [{ type: i0.Output, args: ["dropdownHiding"] }], dropdownRef: [{
3483
+ }], propDecorators: { trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], ariaHasPopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaHasPopup", required: false }] }], paddingVertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingVertical", required: false }] }], paddingHorizontal: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingHorizontal", required: false }] }], border: [{ type: i0.Input, args: [{ isSignal: true, alias: "border", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }], shadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "shadow", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], dropdownShowing: [{ type: i0.Output, args: ["dropdownShowing"] }], dropdownHiding: [{ type: i0.Output, args: ["dropdownHiding"] }], dropdownRef: [{
3449
3484
  type: ViewChild,
3450
3485
  args: ['dropdown', { static: true }]
3451
3486
  }] } });
3452
3487
 
3453
- class UniExpandComponent {
3488
+ class UniExpandComponent extends BaseComponent {
3454
3489
  collapsed = model(true, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
3490
+ /**
3491
+ * Exact per-instance duration in seconds. Bypasses size-aware scaling —
3492
+ * an explicit number means the consumer wants that number. Omitted, the
3493
+ * duration derives from the `expand` theme options' `transitionSpeed`
3494
+ * scaled by content height (see `duration`).
3495
+ */
3496
+ transitionSpeed = input(...(ngDevMode ? [undefined, { debugName: "transitionSpeed" }] : /* istanbul ignore next */ []));
3455
3497
  /** Referenced by the controlling toggle's aria-controls. */
3456
3498
  regionId = uniqueId('uni-expand');
3457
3499
  /**
@@ -3460,12 +3502,58 @@ class UniExpandComponent {
3460
3502
  * animations only after the first render, i.e. on real state changes.
3461
3503
  */
3462
3504
  ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
3505
+ contentRef = viewChild('content', ...(ngDevMode ? [{ debugName: "contentRef" }] : /* istanbul ignore next */ []));
3506
+ /**
3507
+ * Last observed content height. Retained after collapse (the observer
3508
+ * disconnects but the signal keeps its value) so the leave animation and
3509
+ * the next reveal are timed to the real size. Until the first measurement
3510
+ * lands — one frame into the very first reveal — the duration falls back
3511
+ * to the unscaled token; CSS remaps animation progress when
3512
+ * `animation-duration` updates, so the retime is imperceptible that early.
3513
+ */
3514
+ contentHeight = signal(undefined, ...(ngDevMode ? [{ debugName: "contentHeight" }] : /* istanbul ignore next */ []));
3463
3515
  constructor() {
3516
+ super();
3464
3517
  afterNextRender(() => this.ready.set(true));
3518
+ afterRenderEffect((onCleanup) => {
3519
+ const content = this.contentRef()?.nativeElement;
3520
+ if (!content)
3521
+ return;
3522
+ const observer = new ResizeObserver(() => this.contentHeight.set(content.scrollHeight));
3523
+ observer.observe(content);
3524
+ onCleanup(() => observer.disconnect());
3525
+ });
3465
3526
  }
3466
3527
  toggle() {
3467
3528
  this.collapsed.update((collapsed) => !collapsed);
3468
3529
  }
3530
+ /**
3531
+ * The resolved duration in seconds: the `transitionSpeed` input verbatim,
3532
+ * or the `expand` theme options' `transitionSpeed` scaled by content height
3533
+ * (`expandDuration` — √-of-height, clamped) so short regions stay snappy
3534
+ * and tall ones aren't rushed. The public sync hook: Expand Area binds this
3535
+ * to its toggle so the chevron rotates on the region's clock, and consumers
3536
+ * can bind adjacent styling the same way
3537
+ * (`[style.transition-duration]="expand.duration() + 's'"`).
3538
+ */
3539
+ duration = computed(() => {
3540
+ const override = this.transitionSpeed();
3541
+ if (override !== undefined)
3542
+ return override;
3543
+ const speed = this.componentOptions().transitionSpeed ?? EXPAND_DEFAULT_SPEED;
3544
+ const height = this.contentHeight();
3545
+ return height === undefined ? speed : expandDuration(height, speed);
3546
+ }, ...(ngDevMode ? [{ debugName: "duration" }] : /* istanbul ignore next */ []));
3547
+ cssDuration = computed(() => `${this.duration()}s`, ...(ngDevMode ? [{ debugName: "cssDuration" }] : /* istanbul ignore next */ []));
3548
+ /**
3549
+ * A custom element is `display: inline` by default, which would lay the
3550
+ * animated grid out as a block-in-inline box and distort the revealed
3551
+ * content's spacing. Every consumer would otherwise have to write
3552
+ * `uni-expand { display: block }` themselves.
3553
+ */
3554
+ hostClassName = css({
3555
+ display: 'block',
3556
+ });
3469
3557
  expandClassName = css({
3470
3558
  display: 'grid',
3471
3559
  });
@@ -3474,22 +3562,37 @@ class UniExpandComponent {
3474
3562
  });
3475
3563
  expand = keyframes({ ...expandFadeIn });
3476
3564
  collapse = keyframes({ ...collapseFadeOut });
3477
- expandAnimation = css({
3565
+ /**
3566
+ * Reveal is motion-safe (WCAG 2.3.3): under `prefers-reduced-motion` these
3567
+ * classes are empty, so the region appears and disappears instantly.
3568
+ * `overflow: hidden` rides inside the guard deliberately — it exists to clip
3569
+ * the growing box mid-animation, and leaving it applied at rest would crop
3570
+ * decorations that legitimately paint outside the region (focus rings,
3571
+ * offset outlines). Angular removes a leaving node on the next frame when it
3572
+ * detects no animation, so nothing hangs when the guard strips them.
3573
+ *
3574
+ * The classes carry the default duration; the animated div's
3575
+ * `[style.animation-duration]` binding overrides it with the resolved
3576
+ * `duration`, so the classes stay static while timing tracks the theme and
3577
+ * the content's size through signals alone.
3578
+ */
3579
+ expandAnimation = css(motionSafe({
3478
3580
  overflow: 'hidden',
3479
- animation: `${this.expand} ease-in 350ms`,
3480
- });
3481
- collapseAnimation = css({
3581
+ animation: `${this.expand} ease-in-out ${EXPAND_DEFAULT_SPEED}s`,
3582
+ }));
3583
+ collapseAnimation = css(motionSafe({
3482
3584
  overflow: 'hidden',
3483
- animation: `${this.collapse} ease-in 350ms`,
3484
- });
3585
+ animation: `${this.collapse} ease-in-out ${EXPAND_DEFAULT_SPEED}s`,
3586
+ }));
3485
3587
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3486
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniExpandComponent, isStandalone: true, selector: "uni-expand", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "attr.id": "regionId" } }, ngImport: i0, template: `@if (!collapsed()) {
3588
+ 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()) {
3487
3589
  <div
3488
3590
  [animate.enter]="ready() ? expandAnimation : ''"
3489
3591
  [animate.leave]="collapseAnimation"
3490
3592
  [class]="expandClassName"
3593
+ [style.animation-duration]="cssDuration()"
3491
3594
  >
3492
- <div [class]="contentClassName">
3595
+ <div #content [class]="contentClassName">
3493
3596
  <ng-content></ng-content>
3494
3597
  </div>
3495
3598
  </div>
@@ -3501,22 +3604,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3501
3604
  changeDetection: ChangeDetectionStrategy.OnPush,
3502
3605
  selector: 'uni-expand',
3503
3606
  imports: [],
3607
+ providers: [{ provide: COMPONENT_NAME, useValue: 'expand' }],
3504
3608
  template: `@if (!collapsed()) {
3505
3609
  <div
3506
3610
  [animate.enter]="ready() ? expandAnimation : ''"
3507
3611
  [animate.leave]="collapseAnimation"
3508
3612
  [class]="expandClassName"
3613
+ [style.animation-duration]="cssDuration()"
3509
3614
  >
3510
- <div [class]="contentClassName">
3615
+ <div #content [class]="contentClassName">
3511
3616
  <ng-content></ng-content>
3512
3617
  </div>
3513
3618
  </div>
3514
3619
  }`,
3515
3620
  host: {
3516
3621
  '[attr.id]': 'regionId',
3622
+ '[class]': 'hostClassName',
3517
3623
  },
3518
3624
  }]
3519
- }], ctorParameters: () => [], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }] } });
3625
+ }], ctorParameters: () => [], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], transitionSpeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "transitionSpeed", required: false }] }], contentRef: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
3520
3626
 
3521
3627
  /**
3522
3628
  * UniExpandComponent Barrel File
@@ -3524,242 +3630,155 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3524
3630
  * This file exports all public-facing elements of the expand component.
3525
3631
  */
3526
3632
 
3527
- class UniTooltipComponent extends BaseComponent {
3528
- elRef = inject(ElementRef);
3529
- renderer = inject(Renderer2);
3530
- timer = useTimer();
3531
- isMouseInside = signal(false, ...(ngDevMode ? [{ debugName: "isMouseInside" }] : /* istanbul ignore next */ []));
3532
- /** Whether the bubble is currently shown (or fading out). */
3533
- visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
3534
- tooltipId = uniqueId('uni-tooltip');
3535
- anchorName = newAnchorName();
3536
- hoverDelay = input(500, ...(ngDevMode ? [{ debugName: "hoverDelay" }] : /* istanbul ignore next */ []));
3537
- label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
3538
- placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
3539
- inlineText = input(false, ...(ngDevMode ? [{ debugName: "inlineText" }] : /* istanbul ignore next */ []));
3540
- /** @deprecated The tooltip renders in the native top layer; ignored. */
3541
- appendToBody = input(false, ...(ngDevMode ? [{ debugName: "appendToBody" }] : /* istanbul ignore next */ []));
3542
- tipRef = viewChild.required('tip');
3543
- constructor() {
3544
- super();
3545
- effect(() => {
3546
- const timerActive = this.timer.isActive();
3547
- const mouseInside = this.isMouseInside();
3548
- // If the timer finished and the mouse is still inside, show tooltip
3549
- if (!timerActive && mouseInside) {
3550
- this.showTooltip();
3551
- }
3552
- // If the mouse left and the timer is not running, hide tooltip
3553
- else if (!mouseInside) {
3554
- this.hideTooltip();
3555
- }
3556
- });
3557
- // A tooltip must be reachable by keyboard (WCAG 1.4.13): when the
3558
- // projected content has no focusable element, the host itself joins
3559
- // the tab sequence. The bubble is always in the DOM, so the describedby
3560
- // relationship is wired once, on the element that receives focus.
3561
- afterNextRender(() => {
3562
- const host = this.elRef.nativeElement;
3563
- if (!host.querySelector(FOCUSABLE_SELECTOR) && !host.matches(FOCUSABLE_SELECTOR)) {
3564
- this.renderer.setAttribute(host, 'tabindex', '0');
3565
- }
3566
- this.renderer.setAttribute(resolveFocusTarget(host), 'aria-describedby', this.tooltipId);
3567
- });
3568
- }
3569
- onFocusIn(event) {
3570
- // Only keyboard-driven focus shows the tooltip immediately; mouse
3571
- // interaction keeps the hover-delay behavior.
3572
- if (event.target.matches(':focus-visible')) {
3573
- this.showTooltip();
3574
- }
3575
- }
3576
- onFocusOut() {
3577
- if (!this.isMouseInside()) {
3578
- this.hideTooltip();
3579
- }
3580
- }
3581
- onEscape(event) {
3582
- if (this.visible()) {
3583
- // Dismiss only the tooltip, not an enclosing dialog/popover
3584
- event.stopPropagation();
3585
- this.hideTooltip();
3586
- }
3587
- }
3588
- toggleTooltip() {
3589
- if (this.visible()) {
3590
- this.hideTooltip();
3591
- }
3592
- else {
3593
- this.showTooltip();
3594
- }
3595
- }
3596
- mouseenter() {
3597
- this.isMouseInside.set(true);
3598
- this.timer.start(this.hoverDelay());
3599
- }
3600
- mouseleave() {
3601
- this.isMouseInside.set(false);
3602
- this.timer.stop();
3603
- }
3604
- showTooltip() {
3605
- if (this.visible())
3606
- return;
3607
- const tip = this.tipRef().nativeElement;
3608
- tip.showPopover();
3609
- this.renderer.setAttribute(tip, 'fade', 'in');
3610
- this.visible.set(true);
3611
- }
3612
- hideTooltip() {
3613
- if (!this.visible())
3614
- return;
3615
- this.renderer.setAttribute(this.tipRef().nativeElement, 'fade', 'out');
3616
- }
3617
- onAnimationEnd(event) {
3618
- if (event.animationName.includes(this.tooltipFadeOut)) {
3619
- this.tipRef().nativeElement.hidePopover();
3620
- this.visible.set(false);
3621
- }
3622
- }
3623
- className = computed(() => css({
3624
- display: 'inline-flex',
3625
- anchorName: this.anchorName,
3626
- }, this.inlineText() && {
3627
- cursor: 'help',
3628
- textDecoration: 'underline',
3629
- textDecorationStyle: 'dotted',
3630
- }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
3631
- tooltipFadeIn = keyframes({ ...fadeIn });
3632
- tooltipFadeOut = keyframes({ ...fadeOut });
3633
- tooltipClassName = computed(() => css([
3634
- {
3635
- ...this.theme.colorPair(this.componentOptions().color),
3636
- ...this.theme.radius(this.componentOptions().borderRadius),
3637
- ...this.theme.boxShadow(this.componentOptions().shadow),
3638
- ...this.theme.typeface(this.componentOptions().typeface),
3639
- border: 'none',
3640
- padding: 5,
3641
- width: 'max-content',
3642
- ...anchorStyles(this.anchorName, this.placement(), { mainAxis: 6 }),
3643
- '&[fade="in"]': {
3644
- animation: `${this.tooltipFadeIn} ease-in 350ms`,
3645
- },
3646
- '&[fade="out"]': {
3647
- animation: `${this.tooltipFadeOut} ease-in 350ms`,
3648
- },
3649
- },
3650
- ]), ...(ngDevMode ? [{ debugName: "tooltipClassName" }] : /* istanbul ignore next */ []));
3651
- arrowClassName = computed(() => css({
3652
- ...this.theme.colorPair(this.componentOptions().color),
3653
- ...anchorArrowStyles(this.placement()),
3654
- }), ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
3655
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3656
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: UniTooltipComponent, isStandalone: true, selector: "uni-tooltip", inputs: { hoverDelay: { classPropertyName: "hoverDelay", publicName: "hoverDelay", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, inlineText: { classPropertyName: "inlineText", publicName: "inlineText", isSignal: true, isRequired: false, transformFunction: null }, appendToBody: { classPropertyName: "appendToBody", publicName: "appendToBody", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "toggleTooltip()", "mouseenter": "mouseenter()", "mouseleave": "mouseleave()", "focusin": "onFocusIn($event)", "focusout": "onFocusOut()", "keydown.escape": "onEscape($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }], viewQueries: [{ propertyName: "tipRef", first: true, predicate: ["tip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `<ng-content></ng-content
3657
- ><span
3658
- #tip
3659
- popover="manual"
3660
- role="tooltip"
3661
- [id]="tooltipId"
3662
- [class]="tooltipClassName()"
3663
- (mouseenter)="isMouseInside.set(true)"
3664
- (mouseleave)="isMouseInside.set(false)"
3665
- (animationend)="onAnimationEnd($event)"
3666
- >{{ label() }}<span [class]="arrowClassName()"></span
3667
- ></span>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3668
- }
3669
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, decorators: [{
3670
- type: Component,
3671
- args: [{
3672
- selector: 'uni-tooltip',
3673
- imports: [],
3674
- // The bubble lives declaratively in the template as a manual popover: the
3675
- // top layer escapes any overflow context (which made appendToBody obsolete)
3676
- // and native CSS anchor positioning keeps it attached to the host.
3677
- template: `<ng-content></ng-content
3678
- ><span
3679
- #tip
3680
- popover="manual"
3681
- role="tooltip"
3682
- [id]="tooltipId"
3683
- [class]="tooltipClassName()"
3684
- (mouseenter)="isMouseInside.set(true)"
3685
- (mouseleave)="isMouseInside.set(false)"
3686
- (animationend)="onAnimationEnd($event)"
3687
- >{{ label() }}<span [class]="arrowClassName()"></span
3688
- ></span>`,
3689
- providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }],
3690
- changeDetection: ChangeDetectionStrategy.OnPush,
3691
- host: {
3692
- '[class]': 'className()',
3693
- '(click)': 'toggleTooltip()',
3694
- '(mouseenter)': 'mouseenter()',
3695
- '(mouseleave)': 'mouseleave()',
3696
- '(focusin)': 'onFocusIn($event)',
3697
- '(focusout)': 'onFocusOut()',
3698
- '(keydown.escape)': 'onEscape($event)',
3699
- },
3700
- }]
3701
- }], ctorParameters: () => [], propDecorators: { hoverDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverDelay", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], inlineText: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlineText", required: false }] }], appendToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendToBody", required: false }] }], tipRef: [{ type: i0.ViewChild, args: ['tip', { isSignal: true }] }] } });
3702
-
3633
+ /**
3634
+ * Trigger for a {@link UniExpandComponent} region.
3635
+ *
3636
+ * Two shapes, chosen by whether `label` is set:
3637
+ * - **Icon only** (default) the bare chevron, sized for a Card Header.
3638
+ * - **Labelled** chevron plus a label and optional muted sublabel, as a
3639
+ * single full-width button. Most real disclosures need a name for the
3640
+ * section, and building that row by hand is the thing this saves; it also
3641
+ * makes the label the button's accessible name rather than adjacent text.
3642
+ */
3703
3643
  class UniExpandToggleComponent {
3704
3644
  collapsed = model(true, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
3705
3645
  /** Id of the Expand region this toggle controls (see UniExpandComponent.regionId). */
3706
3646
  ariaControls = input(...(ngDevMode ? [undefined, { debugName: "ariaControls" }] : /* istanbul ignore next */ []));
3647
+ /** Names the region. Setting it switches the toggle to its labelled shape. */
3648
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
3649
+ /** Muted qualifier beside the label ("for POs & custom orders"). Needs `label`. */
3650
+ sublabel = input(...(ngDevMode ? [undefined, { debugName: "sublabel" }] : /* istanbul ignore next */ []));
3651
+ /**
3652
+ * Rotation duration in seconds. Expand Area binds the region's resolved
3653
+ * `duration` here so chevron and reveal share one clock even when the
3654
+ * region is size-scaled or overridden per instance.
3655
+ */
3656
+ transitionSpeed = input(...(ngDevMode ? [undefined, { debugName: "transitionSpeed" }] : /* istanbul ignore next */ []));
3657
+ /** Fallback clock when no `transitionSpeed` is bound: the `expand` theme options' `transitionSpeed`. */
3658
+ expandOptions = inject(ThemeService).getComponentOptions('expand');
3659
+ speed = computed(() => this.transitionSpeed() ?? this.expandOptions().transitionSpeed ?? EXPAND_DEFAULT_SPEED, ...(ngDevMode ? [{ debugName: "speed" }] : /* istanbul ignore next */ []));
3660
+ /**
3661
+ * The glyph rotates, never the host.
3662
+ *
3663
+ * The host is taller than the glyph (an inline-level box reserves baseline
3664
+ * descender space), so spinning it about its own centre walks the glyph
3665
+ * off-centre. `uni-icon` is a centred square sized to the glyph, which makes
3666
+ * it the only box here that rotates symmetrically. It also keeps a label
3667
+ * from turning upside down with the chevron.
3668
+ */
3669
+ glyphStyles = computed(() => ({
3670
+ '& uni-icon': {
3671
+ flexShrink: 0,
3672
+ ...motionSafe({
3673
+ transition: `transform ${this.speed()}s ease-in-out`,
3674
+ }),
3675
+ transform: this.collapsed() ? 'rotate(-180deg)' : 'rotate(0)',
3676
+ },
3677
+ }), ...(ngDevMode ? [{ debugName: "glyphStyles" }] : /* istanbul ignore next */ []));
3707
3678
  className = computed(() => {
3679
+ if (this.label()) {
3680
+ return css({ display: 'block', ...this.glyphStyles() });
3681
+ }
3708
3682
  return css({
3709
3683
  display: 'inline-flex',
3710
3684
  cursor: 'pointer',
3711
- transition: 'transform 350ms ease-in-out',
3712
- transform: 'rotate(0)',
3713
- '&[toggled]': {
3714
- transform: 'rotate(-180deg)',
3715
- },
3685
+ ...this.glyphStyles(),
3716
3686
  });
3717
3687
  }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
3688
+ triggerClassName = css({
3689
+ display: 'flex',
3690
+ alignItems: 'center',
3691
+ gap: 8,
3692
+ width: '100%',
3693
+ padding: 0,
3694
+ border: 0,
3695
+ background: 'none',
3696
+ font: 'inherit',
3697
+ color: 'inherit',
3698
+ textAlign: 'left',
3699
+ cursor: 'pointer',
3700
+ });
3701
+ labelClassName = css({
3702
+ display: 'flex',
3703
+ alignItems: 'baseline',
3704
+ gap: 6,
3705
+ minWidth: 0,
3706
+ });
3718
3707
  toggle() {
3719
3708
  this.collapsed.update((collapsed) => !collapsed);
3720
3709
  }
3721
3710
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3722
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniExpandToggleComponent, isStandalone: true, selector: "uni-expand-toggle", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, ariaControls: { classPropertyName: "ariaControls", publicName: "ariaControls", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "class": "className()", "attr.toggled": "collapsed() || null" } }, ngImport: i0, template: `<uni-tooltip
3723
- [label]="collapsed() ? 'Expand' : 'Collapse'"
3724
- placement="right"
3725
- >
3726
- <button
3727
- icon-button
3728
- iconName="chevronUp"
3729
- (click)="toggle()"
3730
- [attr.aria-expanded]="!collapsed()"
3731
- [attr.aria-controls]="ariaControls() || null"
3732
- >
3733
- {{ collapsed() ? 'Expand' : 'Collapse' }}
3734
- </button>
3735
- </uni-tooltip>`, isInline: true, dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniTooltipComponent, selector: "uni-tooltip", inputs: ["hoverDelay", "label", "placement", "inlineText", "appendToBody"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3711
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniExpandToggleComponent, isStandalone: true, selector: "uni-expand-toggle", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, ariaControls: { classPropertyName: "ariaControls", publicName: "ariaControls", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, sublabel: { classPropertyName: "sublabel", publicName: "sublabel", isSignal: true, isRequired: false, transformFunction: null }, transitionSpeed: { classPropertyName: "transitionSpeed", publicName: "transitionSpeed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "class": "className()", "attr.toggled": "collapsed() || null" } }, ngImport: i0, template: `
3712
+ @if (label()) {
3713
+ <button
3714
+ type="button"
3715
+ [class]="triggerClassName"
3716
+ (click)="toggle()"
3717
+ [attr.aria-expanded]="!collapsed()"
3718
+ [attr.aria-controls]="ariaControls() || null"
3719
+ >
3720
+ <uni-icon name="chevronUp" size="1em" />
3721
+ <span [class]="labelClassName">
3722
+ <span uni-text="label">{{ label() }}</span>
3723
+ @if (sublabel()) {
3724
+ <span uni-text="caption" color="on-background-variant">{{ sublabel() }}</span>
3725
+ }
3726
+ </span>
3727
+ </button>
3728
+ } @else {
3729
+ <button
3730
+ icon-button
3731
+ iconName="chevronUp"
3732
+ (click)="toggle()"
3733
+ [attr.aria-expanded]="!collapsed()"
3734
+ [attr.aria-controls]="ariaControls() || null"
3735
+ >
3736
+ {{ collapsed() ? 'Expand' : 'Collapse' }}
3737
+ </button>
3738
+ }
3739
+ `, isInline: true, dependencies: [{ kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3736
3740
  }
3737
3741
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandToggleComponent, decorators: [{
3738
3742
  type: Component,
3739
3743
  args: [{
3740
3744
  selector: 'uni-expand-toggle',
3741
- imports: [UniIconButtonComponent, UniTooltipComponent],
3742
- template: `<uni-tooltip
3743
- [label]="collapsed() ? 'Expand' : 'Collapse'"
3744
- placement="right"
3745
- >
3746
- <button
3747
- icon-button
3748
- iconName="chevronUp"
3749
- (click)="toggle()"
3750
- [attr.aria-expanded]="!collapsed()"
3751
- [attr.aria-controls]="ariaControls() || null"
3752
- >
3753
- {{ collapsed() ? 'Expand' : 'Collapse' }}
3754
- </button>
3755
- </uni-tooltip>`,
3745
+ imports: [UniIconComponent, UniIconButtonComponent, UniTextComponent],
3746
+ template: `
3747
+ @if (label()) {
3748
+ <button
3749
+ type="button"
3750
+ [class]="triggerClassName"
3751
+ (click)="toggle()"
3752
+ [attr.aria-expanded]="!collapsed()"
3753
+ [attr.aria-controls]="ariaControls() || null"
3754
+ >
3755
+ <uni-icon name="chevronUp" size="1em" />
3756
+ <span [class]="labelClassName">
3757
+ <span uni-text="label">{{ label() }}</span>
3758
+ @if (sublabel()) {
3759
+ <span uni-text="caption" color="on-background-variant">{{ sublabel() }}</span>
3760
+ }
3761
+ </span>
3762
+ </button>
3763
+ } @else {
3764
+ <button
3765
+ icon-button
3766
+ iconName="chevronUp"
3767
+ (click)="toggle()"
3768
+ [attr.aria-expanded]="!collapsed()"
3769
+ [attr.aria-controls]="ariaControls() || null"
3770
+ >
3771
+ {{ collapsed() ? 'Expand' : 'Collapse' }}
3772
+ </button>
3773
+ }
3774
+ `,
3756
3775
  host: {
3757
3776
  '[class]': 'className()',
3758
3777
  '[attr.toggled]': 'collapsed() || null',
3759
3778
  },
3760
3779
  changeDetection: ChangeDetectionStrategy.OnPush,
3761
3780
  }]
3762
- }], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], ariaControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaControls", required: false }] }] } });
3781
+ }], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], ariaControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaControls", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], sublabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sublabel", required: false }] }], transitionSpeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "transitionSpeed", required: false }] }] } });
3763
3782
 
3764
3783
  class UniExpandAreaComponent {
3765
3784
  title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
@@ -3795,6 +3814,7 @@ class UniExpandAreaComponent {
3795
3814
  style="display: inline-flex; margin: -2px"
3796
3815
  [collapsed]="initCollapsed()"
3797
3816
  [ariaControls]="expand.regionId"
3817
+ [transitionSpeed]="expand.duration()"
3798
3818
  />
3799
3819
  </div>
3800
3820
 
@@ -3804,7 +3824,7 @@ class UniExpandAreaComponent {
3804
3824
  </div>
3805
3825
  </uni-expand>
3806
3826
  </div>
3807
- `, isInline: true, dependencies: [{ kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniExpandToggleComponent, selector: "uni-expand-toggle", inputs: ["collapsed", "ariaControls"], outputs: ["collapsedChange"] }, { kind: "component", type: UniExpandComponent, selector: "uni-expand", inputs: ["collapsed"], outputs: ["collapsedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3827
+ `, isInline: true, dependencies: [{ kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniExpandToggleComponent, selector: "uni-expand-toggle", inputs: ["collapsed", "ariaControls", "label", "sublabel", "transitionSpeed"], outputs: ["collapsedChange"] }, { kind: "component", type: UniExpandComponent, selector: "uni-expand", inputs: ["collapsed", "transitionSpeed"], outputs: ["collapsedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3808
3828
  }
3809
3829
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniExpandAreaComponent, decorators: [{
3810
3830
  type: Component,
@@ -3836,6 +3856,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3836
3856
  style="display: inline-flex; margin: -2px"
3837
3857
  [collapsed]="initCollapsed()"
3838
3858
  [ariaControls]="expand.regionId"
3859
+ [transitionSpeed]="expand.duration()"
3839
3860
  />
3840
3861
  </div>
3841
3862
 
@@ -4142,32 +4163,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4142
4163
  * This file exports all public-facing elements of the json-view component.
4143
4164
  */
4144
4165
 
4145
- class UniMenuItemComponent extends UniBoxComponent {
4166
+ const isDivider = (item) => 'divider' in item;
4167
+
4168
+ class UniMenuItemComponent {
4169
+ theme = inject(ThemeService);
4146
4170
  _elementRef = inject(ElementRef);
4147
- display = input('flex', ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
4148
- flexDirection = input('row', ...(ngDevMode ? [{ debugName: "flexDirection" }] : /* istanbul ignore next */ []));
4149
- alignItems = input('center', ...(ngDevMode ? [{ debugName: "alignItems" }] : /* istanbul ignore next */ []));
4150
- paddingHorizontal = input('md', ...(ngDevMode ? [{ debugName: "paddingHorizontal" }] : /* istanbul ignore next */ []));
4151
- gap = input('md', ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
4152
4171
  label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
4153
4172
  template = input(...(ngDevMode ? [undefined, { debugName: "template" }] : /* istanbul ignore next */ []));
4154
4173
  context = input(...(ngDevMode ? [undefined, { debugName: "context" }] : /* istanbul ignore next */ []));
4155
4174
  symbolName = input(...(ngDevMode ? [undefined, { debugName: "symbolName" }] : /* istanbul ignore next */ []));
4156
4175
  active = input(...(ngDevMode ? [undefined, { debugName: "active" }] : /* istanbul ignore next */ []));
4157
- hoverColor = input('primary-container', ...(ngDevMode ? [{ debugName: "hoverColor" }] : /* istanbul ignore next */ []));
4176
+ /** Tone routed through the theme's `menuItem` variants (e.g. 'warn'). */
4177
+ variant = input(...(ngDevMode ? [undefined, { debugName: "variant" }] : /* istanbul ignore next */ []));
4178
+ /** Per-instance override of the theme's `menuItem.hoverColor`. */
4179
+ hoverColor = input(...(ngDevMode ? [undefined, { debugName: "hoverColor" }] : /* istanbul ignore next */ []));
4158
4180
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4159
- menuItemClassName = computed(() => css([
4160
- {
4161
- cursor: 'pointer',
4162
- transition: 'all 0.35s ease',
4163
- height: 38,
4164
- // Roving focus highlights items the same way hover does
4165
- '&:hover, &:focus': {
4166
- ...this.theme.colorPair(this.hoverColor()),
4167
- outline: 'none',
4181
+ options = computed(() => this.theme.getComponentOptions('menuItem')(), ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
4182
+ activeSymbol = computed(() => this.options().activeSymbol, ...(ngDevMode ? [{ debugName: "activeSymbol" }] : /* istanbul ignore next */ []));
4183
+ /** Typography comes from the host's themed typeface; the span only lays out. */
4184
+ LabelClassName = css({ display: 'block', whiteSpace: 'nowrap' });
4185
+ menuItemClassName = computed(() => {
4186
+ const options = this.options();
4187
+ const variant = this.variant();
4188
+ const variantStyle = variant
4189
+ ? this.theme.component('menuItem')().variants?.[variant]
4190
+ : undefined;
4191
+ const transitionSpeed = options.transitionSpeed ?? 0;
4192
+ return css([
4193
+ {
4194
+ display: 'flex',
4195
+ flexDirection: 'row',
4196
+ alignItems: 'center',
4197
+ boxSizing: 'border-box',
4198
+ cursor: 'pointer',
4199
+ height: options.height,
4200
+ ...this.theme.horizontalPadding(options.paddingHorizontal),
4201
+ ...this.theme.gap(options.gap),
4202
+ ...this.theme.radius(options.borderRadius),
4203
+ ...this.theme.typeface(options.typeface),
4204
+ ...this.theme.color(options.textColor),
4205
+ // Roving focus highlights items the same way hover does
4206
+ '&:hover, &:focus': {
4207
+ ...this.theme.colorPair(this.hoverColor() ?? options.hoverColor),
4208
+ outline: 'none',
4209
+ },
4210
+ '&[aria-disabled="true"]': {
4211
+ color: this.theme.colors()['on-disabled-surface'],
4212
+ pointerEvents: 'none',
4213
+ },
4168
4214
  },
4169
- },
4170
- ]), ...(ngDevMode ? [{ debugName: "menuItemClassName" }] : /* istanbul ignore next */ []));
4215
+ transitionSpeed > 0 && { transition: `all ${transitionSpeed}s ease` },
4216
+ // Variant tones override the base look; a same-key '&:hover, &:focus'
4217
+ // in the variant replaces the default hover pair outright.
4218
+ { ...variantStyle },
4219
+ ]);
4220
+ }, ...(ngDevMode ? [{ debugName: "menuItemClassName" }] : /* istanbul ignore next */ []));
4171
4221
  /** Host element, used by Menu for roving-focus bookkeeping. */
4172
4222
  get host() {
4173
4223
  return this._elementRef.nativeElement;
@@ -4175,21 +4225,22 @@ class UniMenuItemComponent extends UniBoxComponent {
4175
4225
  focus() {
4176
4226
  this._elementRef.nativeElement.focus();
4177
4227
  }
4178
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
4179
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMenuItemComponent, isStandalone: true, selector: "[uni-menu-item], [menu-item]", inputs: { display: { classPropertyName: "display", publicName: "display", isSignal: true, isRequired: false, transformFunction: null }, flexDirection: { classPropertyName: "flexDirection", publicName: "flexDirection", isSignal: true, isRequired: false, transformFunction: null }, alignItems: { classPropertyName: "alignItems", publicName: "alignItems", isSignal: true, isRequired: false, transformFunction: null }, paddingHorizontal: { classPropertyName: "paddingHorizontal", publicName: "paddingHorizontal", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, symbolName: { classPropertyName: "symbolName", publicName: "symbolName", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, hoverColor: { classPropertyName: "hoverColor", publicName: "hoverColor", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem", "tabindex": "-1" }, properties: { "attr.aria-disabled": "disabled() ? 'true' : null", "attr.aria-current": "active() ? 'true' : null", "class": "menuItemClassName()" } }, usesInheritance: true, ngImport: i0, template: "@let tpl = template();\n@let symbol = symbolName();\n@if (symbol) {\n <uni-symbol [name]=\"symbol\"></uni-symbol>\n}\n<div box-layout [grow]=\"1\">\n @if (tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"context()\"></ng-container>\n } @else {\n <span uni-text=\"label\" display=\"block\" [nowrap]=\"true\">\n {{ label() }}\n </span>\n }\n</div>\n@if (active()) {\n <uni-symbol name=\"check\"></uni-symbol>\n}\n", styles: [":host.disabled{color:#ddd;pointer-events:none}\n"], dependencies: [{ 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: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4228
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4229
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMenuItemComponent, isStandalone: true, selector: "[uni-menu-item], [menu-item]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, symbolName: { classPropertyName: "symbolName", publicName: "symbolName", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, hoverColor: { classPropertyName: "hoverColor", publicName: "hoverColor", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem", "tabindex": "-1" }, properties: { "attr.aria-disabled": "disabled() ? 'true' : null", "attr.aria-current": "active() ? 'true' : null", "class": "menuItemClassName()" } }, ngImport: i0, template: "@let tpl = template();\n@let symbol = symbolName();\n@if (symbol) {\n <uni-symbol [name]=\"symbol\"></uni-symbol>\n}\n<div box-layout [grow]=\"1\">\n @if (tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"context()\"></ng-container>\n } @else {\n <span [class]=\"LabelClassName\">{{ label() }}</span>\n }\n</div>\n@let activeSym = activeSymbol();\n@if (active() && activeSym) {\n <uni-symbol [name]=\"activeSym\"></uni-symbol>\n}\n", dependencies: [{ kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4180
4230
  }
4181
4231
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuItemComponent, decorators: [{
4182
4232
  type: Component,
4183
- args: [{ selector: '[uni-menu-item], [menu-item]', imports: [UniTextComponent, UniSymbolComponent, UniBoxComponent, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
4233
+ args: [{ selector: '[uni-menu-item], [menu-item]', imports: [UniSymbolComponent, UniBoxComponent, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
4184
4234
  role: 'menuitem',
4185
4235
  tabindex: '-1',
4186
4236
  '[attr.aria-disabled]': "disabled() ? 'true' : null",
4187
4237
  '[attr.aria-current]': "active() ? 'true' : null",
4188
4238
  '[class]': 'menuItemClassName()',
4189
- }, template: "@let tpl = template();\n@let symbol = symbolName();\n@if (symbol) {\n <uni-symbol [name]=\"symbol\"></uni-symbol>\n}\n<div box-layout [grow]=\"1\">\n @if (tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"context()\"></ng-container>\n } @else {\n <span uni-text=\"label\" display=\"block\" [nowrap]=\"true\">\n {{ label() }}\n </span>\n }\n</div>\n@if (active()) {\n <uni-symbol name=\"check\"></uni-symbol>\n}\n", styles: [":host.disabled{color:#ddd;pointer-events:none}\n"] }]
4190
- }], propDecorators: { display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], flexDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexDirection", required: false }] }], alignItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignItems", required: false }] }], paddingHorizontal: [{ type: i0.Input, args: [{ isSignal: true, alias: "paddingHorizontal", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], symbolName: [{ type: i0.Input, args: [{ isSignal: true, alias: "symbolName", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], hoverColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverColor", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
4239
+ }, template: "@let tpl = template();\n@let symbol = symbolName();\n@if (symbol) {\n <uni-symbol [name]=\"symbol\"></uni-symbol>\n}\n<div box-layout [grow]=\"1\">\n @if (tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"context()\"></ng-container>\n } @else {\n <span [class]=\"LabelClassName\">{{ label() }}</span>\n }\n</div>\n@let activeSym = activeSymbol();\n@if (active() && activeSym) {\n <uni-symbol [name]=\"activeSym\"></uni-symbol>\n}\n" }]
4240
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], symbolName: [{ type: i0.Input, args: [{ isSignal: true, alias: "symbolName", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], hoverColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverColor", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
4191
4241
 
4192
4242
  class UniMenuComponent {
4243
+ theme = inject(ThemeService);
4193
4244
  // Modern Signal Inputs for perfect Zoneless tracking
4194
4245
  menuItems = input.required(...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
4195
4246
  activeItem = input(...(ngDevMode ? [undefined, { debugName: "activeItem" }] : /* istanbul ignore next */ []));
@@ -4197,6 +4248,21 @@ class UniMenuComponent {
4197
4248
  // Modern Signal Output
4198
4249
  menuItemClicked = output();
4199
4250
  TriggerClassName = css({ display: 'inline-block' });
4251
+ /** Type guard for the template: dividers render as separators, not items. */
4252
+ isDivider = isDivider;
4253
+ menuOptions = computed(() => this.theme.getComponentOptions('menu')(), ...(ngDevMode ? [{ debugName: "menuOptions" }] : /* istanbul ignore next */ []));
4254
+ menuClassName = computed(() => css({ minWidth: this.menuOptions().minWidth }), ...(ngDevMode ? [{ debugName: "menuClassName" }] : /* istanbul ignore next */ []));
4255
+ dividerClassName = computed(() => {
4256
+ const { dividerBorder, dividerSpacing } = this.menuOptions();
4257
+ // Read spacing directly: getSpacing('none') yields the string 'none',
4258
+ // which is invalid as a margin and must resolve to 0 instead.
4259
+ const spacing = this.theme.spacing()[dividerSpacing ?? 'none'];
4260
+ return css({
4261
+ ...this.theme.borderTop(dividerBorder),
4262
+ marginBlock: spacing,
4263
+ marginInline: spacing,
4264
+ });
4265
+ }, ...(ngDevMode ? [{ debugName: "dividerClassName" }] : /* istanbul ignore next */ []));
4200
4266
  itemComponents = viewChildren(UniMenuItemComponent, ...(ngDevMode ? [{ debugName: "itemComponents" }] : /* istanbul ignore next */ []));
4201
4267
  dropdownComponent = viewChild(UniDropdownComponent, ...(ngDevMode ? [{ debugName: "dropdownComponent" }] : /* istanbul ignore next */ []));
4202
4268
  /** Which item receives focus when the menu opens (ArrowUp opens onto the last item). */
@@ -4208,7 +4274,7 @@ class UniMenuComponent {
4208
4274
  });
4209
4275
  }, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
4210
4276
  handleMenuItemClick(item, dropdown) {
4211
- if (item.action) {
4277
+ if (!isDivider(item) && item.action) {
4212
4278
  item.action();
4213
4279
  }
4214
4280
  this.menuItemClicked.emit(item);
@@ -4305,7 +4371,12 @@ class UniMenuComponent {
4305
4371
  [trigger]="trigger"
4306
4372
  [placement]="placement()"
4307
4373
  ariaHasPopup="menu"
4308
- paddingVertical="xs"
4374
+ [color]="menuOptions().color"
4375
+ [border]="menuOptions().border"
4376
+ [borderRadius]="menuOptions().borderRadius"
4377
+ [shadow]="menuOptions().shadow"
4378
+ [paddingVertical]="menuOptions().paddingVertical"
4379
+ [paddingHorizontal]="menuOptions().paddingHorizontal"
4309
4380
  #dropdown
4310
4381
  (dropdownShowing)="onOpened()"
4311
4382
  >
@@ -4313,23 +4384,29 @@ class UniMenuComponent {
4313
4384
  the focused item (menu-item hosts carry tabindex="-1"), and
4314
4385
  Enter/Space activation is dispatched from onMenuKeydown. -->
4315
4386
  <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
4316
- <div role="menu" (keydown)="onMenuKeydown($event, dropdown)">
4387
+ <div role="menu" [class]="menuClassName()" (keydown)="onMenuKeydown($event, dropdown)">
4317
4388
  @for (item of menuItems(); track item) {
4318
- <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
4319
- <div
4320
- menu-item
4321
- [label]="item.label"
4322
- [symbolName]="item.symbolName"
4323
- [active]="activeItem() === item"
4324
- [template]="item.template"
4325
- [context]="item.context"
4326
- (click)="handleMenuItemClick(item, dropdown)"
4327
- ></div>
4389
+ @if (isDivider(item)) {
4390
+ <div role="separator" [class]="dividerClassName()"></div>
4391
+ } @else {
4392
+ <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->
4393
+ <div
4394
+ menu-item
4395
+ [label]="item.label"
4396
+ [symbolName]="item.symbolName"
4397
+ [active]="activeItem() === item"
4398
+ [template]="item.template"
4399
+ [context]="item.context"
4400
+ [variant]="item.variant"
4401
+ [disabled]="item.disabled ?? false"
4402
+ (click)="handleMenuItemClick(item, dropdown)"
4403
+ ></div>
4404
+ }
4328
4405
  }
4329
4406
  </div>
4330
4407
  </uni-dropdown>
4331
4408
  }
4332
- `, isInline: true, dependencies: [{ kind: "component", type: UniMenuItemComponent, selector: "[uni-menu-item], [menu-item]", inputs: ["display", "flexDirection", "alignItems", "paddingHorizontal", "gap", "label", "template", "context", "symbolName", "active", "hoverColor", "disabled"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal"], outputs: ["dropdownShowing", "dropdownHiding"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4409
+ `, isInline: true, dependencies: [{ kind: "component", type: UniMenuItemComponent, selector: "[uni-menu-item], [menu-item]", inputs: ["label", "template", "context", "symbolName", "active", "variant", "hoverColor", "disabled"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "color"], outputs: ["dropdownShowing", "dropdownHiding"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4333
4410
  }
4334
4411
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMenuComponent, decorators: [{
4335
4412
  type: Component,
@@ -4350,7 +4427,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4350
4427
  [trigger]="trigger"
4351
4428
  [placement]="placement()"
4352
4429
  ariaHasPopup="menu"
4353
- paddingVertical="xs"
4430
+ [color]="menuOptions().color"
4431
+ [border]="menuOptions().border"
4432
+ [borderRadius]="menuOptions().borderRadius"
4433
+ [shadow]="menuOptions().shadow"
4434
+ [paddingVertical]="menuOptions().paddingVertical"
4435
+ [paddingHorizontal]="menuOptions().paddingHorizontal"
4354
4436
  #dropdown
4355
4437
  (dropdownShowing)="onOpened()"
4356
4438
  >
@@ -4358,18 +4440,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4358
4440
  the focused item (menu-item hosts carry tabindex="-1"), and
4359
4441
  Enter/Space activation is dispatched from onMenuKeydown. -->
4360
4442
  <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
4361
- <div role="menu" (keydown)="onMenuKeydown($event, dropdown)">
4443
+ <div role="menu" [class]="menuClassName()" (keydown)="onMenuKeydown($event, dropdown)">
4362
4444
  @for (item of menuItems(); track item) {
4363
- <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
4364
- <div
4365
- menu-item
4366
- [label]="item.label"
4367
- [symbolName]="item.symbolName"
4368
- [active]="activeItem() === item"
4369
- [template]="item.template"
4370
- [context]="item.context"
4371
- (click)="handleMenuItemClick(item, dropdown)"
4372
- ></div>
4445
+ @if (isDivider(item)) {
4446
+ <div role="separator" [class]="dividerClassName()"></div>
4447
+ } @else {
4448
+ <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->
4449
+ <div
4450
+ menu-item
4451
+ [label]="item.label"
4452
+ [symbolName]="item.symbolName"
4453
+ [active]="activeItem() === item"
4454
+ [template]="item.template"
4455
+ [context]="item.context"
4456
+ [variant]="item.variant"
4457
+ [disabled]="item.disabled ?? false"
4458
+ (click)="handleMenuItemClick(item, dropdown)"
4459
+ ></div>
4460
+ }
4373
4461
  }
4374
4462
  </div>
4375
4463
  </uni-dropdown>
@@ -4525,7 +4613,7 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
4525
4613
  });
4526
4614
  }
4527
4615
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
4528
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div stack-layout gap=\"xs\" padding=\"xs\">\n @for (option of filteredOptions(); track option.value) {\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 }\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</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"], 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 });
4616
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div stack-layout gap=\"xs\" padding=\"xs\">\n @for (option of filteredOptions(); track option.value) {\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 }\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</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 });
4529
4617
  }
4530
4618
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, decorators: [{
4531
4619
  type: Component,
@@ -7227,9 +7315,211 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
7227
7315
  args: [{ selector: 'uni-toggle', imports: [UniTextComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'toggle' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<label [class]=\"toggleLabel()\">\n <input\n type=\"checkbox\"\n role=\"switch\"\n [class]=\"toggleInput()\"\n [checked]=\"checked()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"toggle-switch\">\n <div class=\"toggle-slider\"></div>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n" }]
7228
7316
  }], 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 }] }] } });
7229
7317
 
7318
+ class UniTooltipComponent extends BaseComponent {
7319
+ elRef = inject(ElementRef);
7320
+ renderer = inject(Renderer2);
7321
+ timer = useTimer();
7322
+ isMouseInside = signal(false, ...(ngDevMode ? [{ debugName: "isMouseInside" }] : /* istanbul ignore next */ []));
7323
+ /** Whether the bubble is currently shown (or fading out). */
7324
+ visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
7325
+ /**
7326
+ * Set when the wrapped control is activated: clicking a button is not a
7327
+ * request to toggle the bubble, and re-showing it right away makes a
7328
+ * state-flipping label ("Expand" → "Collapse") blink back mid-interaction.
7329
+ * Re-arms when the pointer leaves or focus moves away.
7330
+ */
7331
+ suppressed = signal(false, ...(ngDevMode ? [{ debugName: "suppressed" }] : /* istanbul ignore next */ []));
7332
+ tooltipId = uniqueId('uni-tooltip');
7333
+ anchorName = newAnchorName();
7334
+ hoverDelay = input(500, ...(ngDevMode ? [{ debugName: "hoverDelay" }] : /* istanbul ignore next */ []));
7335
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
7336
+ placement = input('top', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
7337
+ inlineText = input(false, ...(ngDevMode ? [{ debugName: "inlineText" }] : /* istanbul ignore next */ []));
7338
+ /** @deprecated The tooltip renders in the native top layer; ignored. */
7339
+ appendToBody = input(false, ...(ngDevMode ? [{ debugName: "appendToBody" }] : /* istanbul ignore next */ []));
7340
+ tipRef = viewChild.required('tip');
7341
+ constructor() {
7342
+ super();
7343
+ effect(() => {
7344
+ const timerActive = this.timer.isActive();
7345
+ const mouseInside = this.isMouseInside();
7346
+ // If the timer finished and the mouse is still inside, show tooltip
7347
+ if (!timerActive && mouseInside && !this.suppressed()) {
7348
+ this.showTooltip();
7349
+ }
7350
+ // If the mouse left and the timer is not running, hide tooltip
7351
+ else if (!mouseInside) {
7352
+ this.hideTooltip();
7353
+ }
7354
+ });
7355
+ // A tooltip must be reachable by keyboard (WCAG 1.4.13): when the
7356
+ // projected content has no focusable element, the host itself joins
7357
+ // the tab sequence. The bubble is always in the DOM, so the describedby
7358
+ // relationship is wired once, on the element that receives focus.
7359
+ afterNextRender(() => {
7360
+ const host = this.elRef.nativeElement;
7361
+ if (!host.querySelector(FOCUSABLE_SELECTOR) && !host.matches(FOCUSABLE_SELECTOR)) {
7362
+ this.renderer.setAttribute(host, 'tabindex', '0');
7363
+ }
7364
+ this.renderer.setAttribute(resolveFocusTarget(host), 'aria-describedby', this.tooltipId);
7365
+ });
7366
+ }
7367
+ onFocusIn(event) {
7368
+ // Only keyboard-driven focus shows the tooltip immediately; mouse
7369
+ // interaction keeps the hover-delay behavior.
7370
+ if (event.target.matches(':focus-visible')) {
7371
+ this.showTooltip();
7372
+ }
7373
+ }
7374
+ onFocusOut() {
7375
+ this.suppressed.set(false);
7376
+ if (!this.isMouseInside()) {
7377
+ this.hideTooltip();
7378
+ }
7379
+ }
7380
+ onEscape(event) {
7381
+ if (this.visible()) {
7382
+ // Dismiss only the tooltip, not an enclosing dialog/popover
7383
+ event.stopPropagation();
7384
+ this.hideTooltip();
7385
+ }
7386
+ }
7387
+ /**
7388
+ * A click on an interactive element inside the host is an activation of
7389
+ * that control — hide the bubble and stay suppressed until re-arm. A click
7390
+ * anywhere else (inline-text tooltips, tap on a non-interactive host)
7391
+ * keeps the tap-to-toggle behavior.
7392
+ */
7393
+ onClick(event) {
7394
+ const host = this.elRef.nativeElement;
7395
+ const control = event.target.closest(FOCUSABLE_SELECTOR);
7396
+ if (control && control !== host && host.contains(control)) {
7397
+ this.suppressed.set(true);
7398
+ this.hideTooltip();
7399
+ }
7400
+ else {
7401
+ this.toggleTooltip();
7402
+ }
7403
+ }
7404
+ toggleTooltip() {
7405
+ if (this.visible()) {
7406
+ this.hideTooltip();
7407
+ }
7408
+ else {
7409
+ this.showTooltip();
7410
+ }
7411
+ }
7412
+ mouseenter() {
7413
+ this.isMouseInside.set(true);
7414
+ this.timer.start(this.hoverDelay());
7415
+ }
7416
+ mouseleave() {
7417
+ this.isMouseInside.set(false);
7418
+ this.suppressed.set(false);
7419
+ this.timer.stop();
7420
+ }
7421
+ showTooltip() {
7422
+ if (this.visible())
7423
+ return;
7424
+ const tip = this.tipRef().nativeElement;
7425
+ tip.showPopover();
7426
+ this.renderer.setAttribute(tip, 'fade', 'in');
7427
+ this.visible.set(true);
7428
+ }
7429
+ hideTooltip() {
7430
+ if (!this.visible())
7431
+ return;
7432
+ this.renderer.setAttribute(this.tipRef().nativeElement, 'fade', 'out');
7433
+ }
7434
+ onAnimationEnd(event) {
7435
+ if (event.animationName.includes(this.tooltipFadeOut)) {
7436
+ this.tipRef().nativeElement.hidePopover();
7437
+ this.visible.set(false);
7438
+ }
7439
+ }
7440
+ className = computed(() => css({
7441
+ display: 'inline-flex',
7442
+ anchorName: this.anchorName,
7443
+ }, this.inlineText() && {
7444
+ cursor: 'help',
7445
+ textDecoration: 'underline',
7446
+ textDecorationStyle: 'dotted',
7447
+ }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
7448
+ tooltipFadeIn = keyframes({ ...fadeIn });
7449
+ tooltipFadeOut = keyframes({ ...fadeOut });
7450
+ tooltipClassName = computed(() => css([
7451
+ {
7452
+ ...this.theme.colorPair(this.componentOptions().color),
7453
+ ...this.theme.radius(this.componentOptions().borderRadius),
7454
+ ...this.theme.boxShadow(this.componentOptions().shadow),
7455
+ ...this.theme.typeface(this.componentOptions().typeface),
7456
+ border: 'none',
7457
+ padding: 5,
7458
+ width: 'max-content',
7459
+ ...anchorStyles(this.anchorName, this.placement(), { mainAxis: 6 }),
7460
+ '&[fade="in"]': {
7461
+ animation: `${this.tooltipFadeIn} ease-in 350ms`,
7462
+ },
7463
+ '&[fade="out"]': {
7464
+ animation: `${this.tooltipFadeOut} ease-in 350ms`,
7465
+ },
7466
+ },
7467
+ ]), ...(ngDevMode ? [{ debugName: "tooltipClassName" }] : /* istanbul ignore next */ []));
7468
+ arrowClassName = computed(() => css({
7469
+ ...this.theme.colorPair(this.componentOptions().color),
7470
+ ...anchorArrowStyles(this.placement()),
7471
+ }), ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
7472
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7473
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.12", type: UniTooltipComponent, isStandalone: true, selector: "uni-tooltip", inputs: { hoverDelay: { classPropertyName: "hoverDelay", publicName: "hoverDelay", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, inlineText: { classPropertyName: "inlineText", publicName: "inlineText", isSignal: true, isRequired: false, transformFunction: null }, appendToBody: { classPropertyName: "appendToBody", publicName: "appendToBody", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)", "mouseenter": "mouseenter()", "mouseleave": "mouseleave()", "focusin": "onFocusIn($event)", "focusout": "onFocusOut()", "keydown.escape": "onEscape($event)" }, properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }], viewQueries: [{ propertyName: "tipRef", first: true, predicate: ["tip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `<ng-content></ng-content
7474
+ ><span
7475
+ #tip
7476
+ popover="manual"
7477
+ role="tooltip"
7478
+ [id]="tooltipId"
7479
+ [class]="tooltipClassName()"
7480
+ (mouseenter)="isMouseInside.set(true)"
7481
+ (mouseleave)="isMouseInside.set(false)"
7482
+ (animationend)="onAnimationEnd($event)"
7483
+ >{{ label() }}<span [class]="arrowClassName()"></span
7484
+ ></span>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
7485
+ }
7486
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTooltipComponent, decorators: [{
7487
+ type: Component,
7488
+ args: [{
7489
+ selector: 'uni-tooltip',
7490
+ imports: [],
7491
+ // The bubble lives declaratively in the template as a manual popover: the
7492
+ // top layer escapes any overflow context (which made appendToBody obsolete)
7493
+ // and native CSS anchor positioning keeps it attached to the host.
7494
+ template: `<ng-content></ng-content
7495
+ ><span
7496
+ #tip
7497
+ popover="manual"
7498
+ role="tooltip"
7499
+ [id]="tooltipId"
7500
+ [class]="tooltipClassName()"
7501
+ (mouseenter)="isMouseInside.set(true)"
7502
+ (mouseleave)="isMouseInside.set(false)"
7503
+ (animationend)="onAnimationEnd($event)"
7504
+ >{{ label() }}<span [class]="arrowClassName()"></span
7505
+ ></span>`,
7506
+ providers: [{ provide: COMPONENT_NAME, useValue: 'tooltip' }],
7507
+ changeDetection: ChangeDetectionStrategy.OnPush,
7508
+ host: {
7509
+ '[class]': 'className()',
7510
+ '(click)': 'onClick($event)',
7511
+ '(mouseenter)': 'mouseenter()',
7512
+ '(mouseleave)': 'mouseleave()',
7513
+ '(focusin)': 'onFocusIn($event)',
7514
+ '(focusout)': 'onFocusOut()',
7515
+ '(keydown.escape)': 'onEscape($event)',
7516
+ },
7517
+ }]
7518
+ }], ctorParameters: () => [], propDecorators: { hoverDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverDelay", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], inlineText: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlineText", required: false }] }], appendToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendToBody", required: false }] }], tipRef: [{ type: i0.ViewChild, args: ['tip', { isSignal: true }] }] } });
7519
+
7230
7520
  /**
7231
7521
  * Generated bundle index. Do not edit.
7232
7522
  */
7233
7523
 
7234
- export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniToggleComponent, UniTooltipComponent, UniWrapComponent, acceptableFile, anchorArrowStyles, anchorStyles, getFileExtension, motionSafe, newAnchorName, resolveFocusTarget, uniqueId, useTimer, visuallyHidden };
7524
+ export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniToggleComponent, UniTooltipComponent, UniWrapComponent, acceptableFile, anchorArrowStyles, anchorStyles, getFileExtension, isDivider, motionSafe, newAnchorName, resolveFocusTarget, uniqueId, useTimer, visuallyHidden };
7235
7525
  //# sourceMappingURL=uni-design-system-uni-angular.mjs.map