@uni-design-system/uni-angular 8.1.0 → 8.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,
|
|
2
|
+
import { signal, computed, linkedSignal, resource, Injectable, inject, DestroyRef, InjectionToken, input, ChangeDetectionStrategy, Component, contentChildren, output, Renderer2, ElementRef, Directive, model, viewChild, effect, untracked, afterNextRender, ViewChild, afterRenderEffect, booleanAttribute, viewChildren } from '@angular/core';
|
|
3
3
|
import { injectGlobal, css, keyframes } from '@emotion/css';
|
|
4
|
-
import { UniThemes, LightTheme, toTypefaces, parseTheme, formatThemeIssues, hydrateTheme, createThemeFromPalette, Z_INDEX,
|
|
4
|
+
import { UniThemes, LightTheme, toTypefaces, parseTheme, formatThemeIssues, hydrateTheme, createThemeFromPalette, Z_INDEX, removeInputPlatformStyling, fadeIn, fadeOut, EXPAND_DEFAULT_SPEED, expandDuration, expandFadeIn, collapseFadeOut, HOVER_OR_KEYBOARD_FOCUS, ShapeRadii, generatePalette, emitThemeFile, emitDtcgTokens } from '@uni-design-system/uni-core';
|
|
5
5
|
import { NgClass, NgTemplateOutlet, CommonModule } from '@angular/common';
|
|
6
6
|
|
|
7
7
|
let nextUniqueId = 0;
|
|
@@ -361,6 +361,7 @@ class ListboxNavigation {
|
|
|
361
361
|
_open = signal(false, ...(ngDevMode ? [{ debugName: "_open" }] : /* istanbul ignore next */ []));
|
|
362
362
|
_activeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeIndex" }] : /* istanbul ignore next */ []));
|
|
363
363
|
wrap;
|
|
364
|
+
isDisabled;
|
|
364
365
|
/** Whether the popup is showing. Also false when there is nothing to show. */
|
|
365
366
|
open = computed(() => this._open() && this.config.count() > 0, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
366
367
|
/** Index of the highlighted option, or -1 when none is active. */
|
|
@@ -378,6 +379,7 @@ class ListboxNavigation {
|
|
|
378
379
|
this.config = config;
|
|
379
380
|
this.listboxId = uniqueId(config.idPrefix ?? 'uni-listbox');
|
|
380
381
|
this.wrap = config.wrap ?? true;
|
|
382
|
+
this.isDisabled = config.disabled ?? (() => false);
|
|
381
383
|
}
|
|
382
384
|
/** Stable per-option id, for `role="option"` elements. */
|
|
383
385
|
optionId(index) {
|
|
@@ -418,20 +420,35 @@ class ListboxNavigation {
|
|
|
418
420
|
return this.step(current, 1, count);
|
|
419
421
|
case 'ArrowUp':
|
|
420
422
|
// Opening with ArrowUp lands on the last option, matching menus.
|
|
421
|
-
return current < 0 ? count - 1 : this.step(current, -1, count);
|
|
423
|
+
return current < 0 ? this.seek(count - 1, -1, count) : this.step(current, -1, count);
|
|
422
424
|
case 'Home':
|
|
423
|
-
return 0;
|
|
425
|
+
return this.seek(0, 1, count);
|
|
424
426
|
case 'End':
|
|
425
|
-
return count - 1;
|
|
427
|
+
return this.seek(count - 1, -1, count);
|
|
426
428
|
default:
|
|
427
429
|
return null;
|
|
428
430
|
}
|
|
429
431
|
}
|
|
432
|
+
/** Nearest enabled index at or after `start`, walking by `delta` with wrap;
|
|
433
|
+
null when every option is disabled. */
|
|
434
|
+
seek(start, delta, count) {
|
|
435
|
+
for (let i = start, n = 0; n < count; n++, i += delta) {
|
|
436
|
+
const index = (i + count) % count;
|
|
437
|
+
if (!this.isDisabled(index))
|
|
438
|
+
return index;
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
430
442
|
step(current, delta, count) {
|
|
431
|
-
const next = current + delta;
|
|
432
443
|
if (this.wrap)
|
|
433
|
-
return (
|
|
434
|
-
|
|
444
|
+
return this.seek((current + delta + count) % count, delta, count);
|
|
445
|
+
// Clamped: walk toward the boundary only; no enabled candidate → hold position.
|
|
446
|
+
const from = Math.min(Math.max(current + delta, 0), count - 1);
|
|
447
|
+
for (let i = from; i >= 0 && i < count; i += delta) {
|
|
448
|
+
if (!this.isDisabled(i))
|
|
449
|
+
return i;
|
|
450
|
+
}
|
|
451
|
+
return current >= 0 && !this.isDisabled(current) ? current : null;
|
|
435
452
|
}
|
|
436
453
|
/** Close when focus leaves the control entirely (not on internal moves). */
|
|
437
454
|
closeOnFocusOut(event) {
|
|
@@ -499,29 +516,59 @@ function anchorStyles(anchor, placement, offset = {}) {
|
|
|
499
516
|
}
|
|
500
517
|
return styles;
|
|
501
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* The half of the arrow square kept by `clip-path`, in the square's local
|
|
521
|
+
* (pre-rotation) coordinates: the outer triangle, cut exactly on the
|
|
522
|
+
* panel-edge diagonal. Without the clip, the two bordered edges run the full
|
|
523
|
+
* square and their strokes visibly cut into the panel surface. The diagonal
|
|
524
|
+
* cut ends the strokes precisely at the panel edge, and the panel's own
|
|
525
|
+
* border line — which sits just *outside* that edge — falls inside the kept
|
|
526
|
+
* half, so the arrow background covers it and the base reads as an opening.
|
|
527
|
+
* Keyed by the side of the anchor the panel sits on.
|
|
528
|
+
*
|
|
529
|
+
* A positive `overlap` extends the kept half past the diagonal INTO the
|
|
530
|
+
* panel — only for borderless arrows that want anti-aliasing insurance along
|
|
531
|
+
* the base; on a bordered arrow it turns the stroke ends into visible stubs
|
|
532
|
+
* inside the panel body.
|
|
533
|
+
*/
|
|
534
|
+
const ARROW_CLIP = {
|
|
535
|
+
// Arrow at the panel's top edge, borders top + left → keep the top-left half.
|
|
536
|
+
bottom: (d) => `polygon(0 0, ${past(d)} 0, 0 ${past(d)})`,
|
|
537
|
+
// Arrow at the panel's bottom edge, borders right + bottom.
|
|
538
|
+
top: (d) => `polygon(${before(d)} 100%, 100% ${before(d)}, 100% 100%)`,
|
|
539
|
+
// Arrow at the panel's right edge, borders right + top.
|
|
540
|
+
left: (d) => `polygon(${before(d)} 0, 100% 0, 100% ${past(d)})`,
|
|
541
|
+
// Arrow at the panel's left edge, borders left + bottom.
|
|
542
|
+
right: (d) => `polygon(0 ${before(d)}, ${past(d)} 100%, 0 100%)`,
|
|
543
|
+
};
|
|
544
|
+
const past = (d) => (d ? `calc(100% + ${d}px)` : '100%');
|
|
545
|
+
const before = (d) => (d ? `calc(0% - ${d}px)` : '0');
|
|
502
546
|
/**
|
|
503
547
|
* Absolute positioning for a rotated-square arrow sitting on the panel edge
|
|
504
548
|
* that faces the anchor. Static per placement: if the browser flips the panel
|
|
505
549
|
* via position-try, the arrow stays on the configured side (cosmetic-only
|
|
506
550
|
* degradation, matching the flip-support caveat above).
|
|
551
|
+
*
|
|
552
|
+
* `overlap` (default 0 — cut exactly on the panel edge) extends the kept half
|
|
553
|
+
* into the panel; see {@link ARROW_CLIP} for when that is ever wanted.
|
|
507
554
|
*/
|
|
508
|
-
function anchorArrowStyles(placement, size = 8) {
|
|
555
|
+
function anchorArrowStyles(placement, size = 8, overlap = 0) {
|
|
509
556
|
const [side, align] = placement.split('-');
|
|
510
|
-
const overlap = -(size / 2);
|
|
511
557
|
const styles = {
|
|
512
558
|
position: 'absolute',
|
|
513
559
|
width: size,
|
|
514
560
|
height: size,
|
|
515
561
|
transform: 'rotate(45deg)',
|
|
562
|
+
clipPath: ARROW_CLIP[side](overlap),
|
|
516
563
|
};
|
|
517
|
-
// Edge facing the anchor
|
|
564
|
+
// Edge facing the anchor: half the square pokes out past the panel edge
|
|
518
565
|
const facing = {
|
|
519
566
|
top: 'bottom',
|
|
520
567
|
bottom: 'top',
|
|
521
568
|
left: 'right',
|
|
522
569
|
right: 'left',
|
|
523
570
|
};
|
|
524
|
-
styles[facing[side]] =
|
|
571
|
+
styles[facing[side]] = -(size / 2);
|
|
525
572
|
// Cross-axis position along that edge
|
|
526
573
|
const horizontal = side === 'top' || side === 'bottom';
|
|
527
574
|
const crossProp = horizontal ? 'left' : 'top';
|
|
@@ -537,6 +584,147 @@ function anchorArrowStyles(placement, size = 8) {
|
|
|
537
584
|
}
|
|
538
585
|
return styles;
|
|
539
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Scrim pieces that cut a spotlight hole around the element carrying
|
|
589
|
+
* `anchor-name: <anchor>`. Every inset is `calc(anchor(<side>) ± px)`, so the
|
|
590
|
+
* browser tracks the target through scroll/resize/layout with zero listeners.
|
|
591
|
+
*
|
|
592
|
+
* The window paints everything: its `border` is the focus ring and its huge
|
|
593
|
+
* spread shadow is the scrim (an outer shadow of a rounded rect covers the
|
|
594
|
+
* whole viewport except the rect, rounded corners included). It is
|
|
595
|
+
* click-through; the four strips do the click-blocking, and the optional
|
|
596
|
+
* cover blocks the hole itself. Callers append these to a fixed, transparent,
|
|
597
|
+
* `pointer-events: none` full-viewport container (each piece is
|
|
598
|
+
* `position: fixed`).
|
|
599
|
+
*/
|
|
600
|
+
function spotlightStyles(anchor, options = {}) {
|
|
601
|
+
const pad = options.pad ?? 6;
|
|
602
|
+
const ringWidth = options.ringWidth ?? 2;
|
|
603
|
+
const scrimColor = options.scrimColor ?? 'rgba(0, 0, 0, 0.45)';
|
|
604
|
+
const holeInset = (extra) => Object.fromEntries(['top', 'left', 'right', 'bottom'].map((side) => [
|
|
605
|
+
side,
|
|
606
|
+
`calc(anchor(${side}) - ${pad + extra}px)`,
|
|
607
|
+
]));
|
|
608
|
+
const piece = { position: 'fixed', positionAnchor: anchor };
|
|
609
|
+
const blocker = { ...piece, pointerEvents: 'auto', background: 'transparent' };
|
|
610
|
+
return {
|
|
611
|
+
window: {
|
|
612
|
+
...piece,
|
|
613
|
+
...holeInset(ringWidth), // the ring sits outside the padded hole
|
|
614
|
+
pointerEvents: 'none',
|
|
615
|
+
borderWidth: ringWidth,
|
|
616
|
+
borderStyle: 'solid',
|
|
617
|
+
boxShadow: `0 0 0 200vmax ${scrimColor}`,
|
|
618
|
+
},
|
|
619
|
+
strips: {
|
|
620
|
+
top: { ...blocker, top: 0, left: 0, right: 0, bottom: `calc(anchor(top) + ${pad}px)` },
|
|
621
|
+
bottom: { ...blocker, bottom: 0, left: 0, right: 0, top: `calc(anchor(bottom) + ${pad}px)` },
|
|
622
|
+
left: {
|
|
623
|
+
...blocker,
|
|
624
|
+
left: 0,
|
|
625
|
+
right: `calc(anchor(left) + ${pad}px)`,
|
|
626
|
+
top: `calc(anchor(top) - ${pad}px)`,
|
|
627
|
+
bottom: `calc(anchor(bottom) - ${pad}px)`,
|
|
628
|
+
},
|
|
629
|
+
right: {
|
|
630
|
+
...blocker,
|
|
631
|
+
right: 0,
|
|
632
|
+
left: `calc(anchor(right) + ${pad}px)`,
|
|
633
|
+
top: `calc(anchor(top) - ${pad}px)`,
|
|
634
|
+
bottom: `calc(anchor(bottom) - ${pad}px)`,
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
cover: { ...blocker, ...holeInset(0) },
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Shared plumbing for top-layer overlays built on the native `popover`
|
|
643
|
+
* attribute (dropdown, popover, callout). Pure functions and data — no DOM
|
|
644
|
+
* ownership — so each component keeps its own template while the anchor
|
|
645
|
+
* bookkeeping, discrete-transition block, and focus-restore rule stay single-
|
|
646
|
+
* sourced.
|
|
647
|
+
*/
|
|
648
|
+
/** Placement → `transform-origin`, so scale animations grow from the anchor. */
|
|
649
|
+
const TRANSFORM_ORIGINS = {
|
|
650
|
+
top: 'bottom center',
|
|
651
|
+
right: 'center left',
|
|
652
|
+
bottom: 'top center',
|
|
653
|
+
left: 'center right',
|
|
654
|
+
'top-start': 'bottom left',
|
|
655
|
+
'top-end': 'bottom right',
|
|
656
|
+
'right-start': 'top left',
|
|
657
|
+
'right-end': 'bottom left',
|
|
658
|
+
'bottom-start': 'top left',
|
|
659
|
+
'bottom-end': 'top right',
|
|
660
|
+
'left-start': 'top right',
|
|
661
|
+
'left-end': 'bottom right',
|
|
662
|
+
};
|
|
663
|
+
/** Write a CSS `anchor-name` onto an element (detached anchors and targets). */
|
|
664
|
+
function setAnchorName(el, name) {
|
|
665
|
+
el.style.setProperty('anchor-name', name);
|
|
666
|
+
}
|
|
667
|
+
function clearAnchorName(el) {
|
|
668
|
+
el.style.removeProperty('anchor-name');
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Resolve an element-or-id reference. Id strings are looked up at call time —
|
|
672
|
+
* never cache the result across opens, the element may have been re-rendered.
|
|
673
|
+
* `''` and null-ish mean "unset" (falsy guard: an empty string is the only
|
|
674
|
+
* typeable empty for a string-typed input).
|
|
675
|
+
*/
|
|
676
|
+
function resolveElement(ref) {
|
|
677
|
+
if (!ref)
|
|
678
|
+
return null;
|
|
679
|
+
return typeof ref === 'string' ? document.getElementById(ref) : ref;
|
|
680
|
+
}
|
|
681
|
+
/** True when a native `toggle` event reports the overlay opening. */
|
|
682
|
+
function isToggleOpen(event) {
|
|
683
|
+
return event.newState === 'open';
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* The discrete-transition block that animates an element into and out of the
|
|
687
|
+
* top layer: `transition-behavior: allow-discrete` over the `hidden` keys plus
|
|
688
|
+
* `display`/`overlay`, the shown state under `:popover-open`, and
|
|
689
|
+
* `@starting-style` so entry transitions run from the hidden state.
|
|
690
|
+
*/
|
|
691
|
+
function discreteOverlayTransition(durationMs, hidden, shown) {
|
|
692
|
+
return {
|
|
693
|
+
transitionProperty: [...Object.keys(hidden), 'display', 'overlay']
|
|
694
|
+
.map((key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`))
|
|
695
|
+
.join(', '),
|
|
696
|
+
transitionDuration: `${durationMs}ms`,
|
|
697
|
+
transitionBehavior: 'allow-discrete',
|
|
698
|
+
...hidden,
|
|
699
|
+
'&:popover-open': shown,
|
|
700
|
+
'@starting-style': {
|
|
701
|
+
'&:popover-open': hidden,
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Returns focus to `target` when an overlay closes while focus was inside its
|
|
707
|
+
* panel (or was dropped on `<body>` by the top layer closing), so keyboard
|
|
708
|
+
* users are never stranded (WCAG 2.4.3). Focus resting anywhere else is left
|
|
709
|
+
* alone.
|
|
710
|
+
*/
|
|
711
|
+
function restoreOverlayFocus(panel, target) {
|
|
712
|
+
const active = document.activeElement;
|
|
713
|
+
if (active === document.body || (active && panel.contains(active))) {
|
|
714
|
+
target?.focus();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* The focusable elements inside `root`, in DOM order. Filters disabled
|
|
719
|
+
* controls, and hidden ones where the environment can tell: jsdom reports
|
|
720
|
+
* `offsetParent` as null for every element, so the visibility filter applies
|
|
721
|
+
* only when the document actually lays out (some element has an offsetParent).
|
|
722
|
+
*/
|
|
723
|
+
function focusableElements(root) {
|
|
724
|
+
const all = Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => !el.disabled);
|
|
725
|
+
const laidOut = all.some((el) => el.offsetParent !== null);
|
|
726
|
+
return laidOut ? all.filter((el) => el.offsetParent !== null) : all;
|
|
727
|
+
}
|
|
540
728
|
|
|
541
729
|
class UniBaseDatasource {
|
|
542
730
|
selections = signal([], ...(ngDevMode ? [{ debugName: "selections" }] : /* istanbul ignore next */ []));
|
|
@@ -2685,8 +2873,13 @@ class UniCalendarComponent extends BaseComponent {
|
|
|
2685
2873
|
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
2686
2874
|
resolvedLocale = computed(() => this.locale() ?? (document.documentElement.lang || navigator.language || 'en-US'), ...(ngDevMode ? [{ debugName: "resolvedLocale" }] : /* istanbul ignore next */ []));
|
|
2687
2875
|
resolvedWeekStart = computed(() => this.weekStart() ?? localeWeekStart(this.resolvedLocale()), ...(ngDevMode ? [{ debugName: "resolvedWeekStart" }] : /* istanbul ignore next */ []));
|
|
2688
|
-
/**
|
|
2689
|
-
|
|
2876
|
+
/**
|
|
2877
|
+
* The month on screen: the `month` model, else the value's month, else
|
|
2878
|
+
* today's. Falsy guards on purpose: `''` — the only typeable empty for a
|
|
2879
|
+
* string-typed model — counts as unset, or it would reach the month math
|
|
2880
|
+
* and blow up the grid and heading.
|
|
2881
|
+
*/
|
|
2882
|
+
viewMonth = computed(() => this.month() || monthOf(this.anchorDate() || todayIso()), ...(ngDevMode ? [{ debugName: "viewMonth" }] : /* istanbul ignore next */ []));
|
|
2690
2883
|
anchorDate = computed(() => {
|
|
2691
2884
|
const value = this.value();
|
|
2692
2885
|
return this.mode() === 'range'
|
|
@@ -3074,6 +3267,389 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3074
3267
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-calendar, Calendar', imports: [UniIconButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'calendar' }], host: { '[class]': 'className()', '(focusout)': 'onHostFocusOut($event)' }, template: "<div [class]=\"navClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navPrevSymbol ?? 'chevron_left'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(-1)\"\n >\n Previous month\n </button>\n <!-- aria-live so PageUp/Down narrates the new month without refocusing. -->\n <div [class]=\"headingClass()\" [id]=\"headingId\" aria-live=\"polite\">{{ heading() }}</div>\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navNextSymbol ?? 'chevron_right'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(1)\"\n >\n Next month\n </button>\n</div>\n\n<!-- One tab stop: the roving tabindex lives on the day buttons, and the grid\n itself only relays their bubbling keys (same delegation as tag-input). -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n role=\"grid\"\n [class]=\"gridClass()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : headingId\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div role=\"row\" [class]=\"rowClass()\">\n @for (weekday of weekdays(); track weekday.full) {\n <div role=\"columnheader\" [class]=\"weekdayClass()\">\n <abbr [attr.title]=\"weekday.full\">{{ weekday.label }}</abbr>\n </div>\n }\n </div>\n\n @for (week of gridWeeks(); track week[0].date) {\n <div role=\"row\" [class]=\"rowClass()\">\n @for (cell of week; track cell.date) {\n @if (cell.outside) {\n <!-- Outside days: hidden placeholders by default (geometry kept),\n muted and non-interactive under the showOutsideDays option. -->\n <div role=\"gridcell\" aria-hidden=\"true\" [class]=\"cell.cellClass\">\n @if (showOutside()) {\n <span [class]=\"outsideDayClass()\">{{ cell.day }}</span>\n }\n </div>\n } @else {\n <div\n role=\"gridcell\"\n [class]=\"cell.cellClass\"\n [attr.aria-selected]=\"cell.selected || cell.inBand ? true : null\"\n >\n <button\n type=\"button\"\n [class]=\"cell.dayClass\"\n [attr.data-date]=\"cell.date\"\n [tabindex]=\"cell.tabIndex\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n [disabled]=\"cell.disabled || disabled()\"\n (click)=\"select(cell.date)\"\n (mouseenter)=\"onDayHover(cell.date)\"\n >\n {{ cell.day }}\n @if (cell.markers.length) {\n <span [class]=\"dotsClass()\">\n @for (marker of cell.markers; track $index) {\n <span data-dot [class]=\"dotClassFor(marker.variant)\"></span>\n }\n </span>\n }\n </button>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<!-- Selections and range progress are otherwise silent for a screen reader. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n" }]
|
|
3075
3268
|
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], month: [{ type: i0.Input, args: [{ isSignal: true, alias: "month", required: false }] }, { type: i0.Output, args: ["monthChange"] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], disabledDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDates", required: false }] }], markers: [{ type: i0.Input, args: [{ isSignal: true, alias: "markers", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], weekStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStart", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
|
|
3076
3269
|
|
|
3270
|
+
/**
|
|
3271
|
+
* Anchored coach-mark panel that dims the page and cuts a spotlight hole
|
|
3272
|
+
* around its target. Both the scrim and the panel are `popover="manual"`
|
|
3273
|
+
* elements shown in order, so they stack deterministically in the top layer
|
|
3274
|
+
* above every app z-index, and every scrim piece is CSS-anchor-positioned to
|
|
3275
|
+
* the target — the hole tracks scroll/resize/layout with zero listeners.
|
|
3276
|
+
*
|
|
3277
|
+
* The panel is a non-modal `role="dialog"` (deliberately no `aria-modal`):
|
|
3278
|
+
* focus moves into it on open, Tab runs a "duet loop" over the panel's
|
|
3279
|
+
* focusables plus the spotlit target while it stays interactive, and on close
|
|
3280
|
+
* focus returns to the pre-open element — unless the user moved into the
|
|
3281
|
+
* target, where it stays.
|
|
3282
|
+
*
|
|
3283
|
+
* Storage-free by rule: `key` and the `dismissed` output are the "don't show
|
|
3284
|
+
* again" hooks; persistence is the app's business (e.g. cdk local-storage).
|
|
3285
|
+
*/
|
|
3286
|
+
class UniCalloutComponent extends BaseComponent {
|
|
3287
|
+
destroyRef = inject(DestroyRef);
|
|
3288
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
3289
|
+
/** Identifies this callout in `dismissed` payloads. Empty string = unset. */
|
|
3290
|
+
key = input(...(ngDevMode ? [undefined, { debugName: "key" }] : /* istanbul ignore next */ []));
|
|
3291
|
+
/** Element (or id, resolved at open time) to spotlight. '' = unset. */
|
|
3292
|
+
target = input(...(ngDevMode ? [undefined, { debugName: "target" }] : /* istanbul ignore next */ []));
|
|
3293
|
+
placement = input('bottom', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
3294
|
+
/** Defaults to `spotlight` when a target resolves, else `dim`. */
|
|
3295
|
+
backdrop = input(...(ngDevMode ? [undefined, { debugName: "backdrop" }] : /* istanbul ignore next */ []));
|
|
3296
|
+
/** When false, a transparent cover blocks the spotlit target. */
|
|
3297
|
+
targetInteractive = input(true, ...(ngDevMode ? [{ debugName: "targetInteractive" }] : /* istanbul ignore next */ []));
|
|
3298
|
+
/** Gates Escape and the close button. */
|
|
3299
|
+
dismissible = input(true, ...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []));
|
|
3300
|
+
/** When true, scrim clicks close (reason `backdrop`); else the panel pulses. */
|
|
3301
|
+
dismissOnBackdrop = input(false, ...(ngDevMode ? [{ debugName: "dismissOnBackdrop" }] : /* istanbul ignore next */ []));
|
|
3302
|
+
header = input(...(ngDevMode ? [undefined, { debugName: "header" }] : /* istanbul ignore next */ []));
|
|
3303
|
+
arrow = input(true, ...(ngDevMode ? [{ debugName: "arrow" }] : /* istanbul ignore next */ []));
|
|
3304
|
+
/** Wins over the header/body labelling; the tour sets "{title}, step n of N". */
|
|
3305
|
+
ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
3306
|
+
/** Accessible name of the dismiss button (the tour passes its skip label). */
|
|
3307
|
+
closeLabel = input('Close', ...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
|
|
3308
|
+
opened = output();
|
|
3309
|
+
closed = output();
|
|
3310
|
+
dismissed = output();
|
|
3311
|
+
/** Keys pressed while focus is inside the panel (the tour's arrow-key hook). */
|
|
3312
|
+
panelKeydown = output();
|
|
3313
|
+
panelId = uniqueId('uni-callout');
|
|
3314
|
+
headerId = uniqueId('uni-callout-header');
|
|
3315
|
+
bodyId = uniqueId('uni-callout-body');
|
|
3316
|
+
anchorName = newAnchorName();
|
|
3317
|
+
showing = signal(false, ...(ngDevMode ? [{ debugName: "showing" }] : /* istanbul ignore next */ []));
|
|
3318
|
+
activeTarget = signal(null, ...(ngDevMode ? [{ debugName: "activeTarget" }] : /* istanbul ignore next */ []));
|
|
3319
|
+
scrimShown = signal(false, ...(ngDevMode ? [{ debugName: "scrimShown" }] : /* istanbul ignore next */ []));
|
|
3320
|
+
/**
|
|
3321
|
+
* Keeps the spotlight pieces rendered and the panel anchored through the
|
|
3322
|
+
* close fade — tearing the anchor down at close time made the panel jump
|
|
3323
|
+
* and the scrim vanish mid-transition.
|
|
3324
|
+
*/
|
|
3325
|
+
scrimContent = signal(false, ...(ngDevMode ? [{ debugName: "scrimContent" }] : /* istanbul ignore next */ []));
|
|
3326
|
+
pendingTeardown = null;
|
|
3327
|
+
prevFocus = null;
|
|
3328
|
+
detachKeydown = null;
|
|
3329
|
+
stripSides = ['top', 'bottom', 'left', 'right'];
|
|
3330
|
+
scrimRef = viewChild.required('scrim');
|
|
3331
|
+
panelRef = viewChild.required('panel');
|
|
3332
|
+
actionsRef = viewChild.required('actions');
|
|
3333
|
+
constructor() {
|
|
3334
|
+
super();
|
|
3335
|
+
effect(() => {
|
|
3336
|
+
const want = this.open();
|
|
3337
|
+
if (want === untracked(this.showing))
|
|
3338
|
+
return;
|
|
3339
|
+
if (want)
|
|
3340
|
+
this.show();
|
|
3341
|
+
else
|
|
3342
|
+
this.close('programmatic');
|
|
3343
|
+
});
|
|
3344
|
+
// Re-seat an open callout when its target or backdrop changes (a tour
|
|
3345
|
+
// stepping) instead of closing and reopening — the panel stays up, the
|
|
3346
|
+
// original pre-open focus is preserved for the eventual close, and the
|
|
3347
|
+
// duet loop re-seats onto the new step's content.
|
|
3348
|
+
effect(() => {
|
|
3349
|
+
const ref = this.target();
|
|
3350
|
+
this.backdrop();
|
|
3351
|
+
if (!untracked(this.showing))
|
|
3352
|
+
return;
|
|
3353
|
+
untracked(() => this.retarget(resolveElement(ref)));
|
|
3354
|
+
});
|
|
3355
|
+
this.destroyRef.onDestroy(() => {
|
|
3356
|
+
this.detachKeydown?.();
|
|
3357
|
+
if (this.pendingTeardown)
|
|
3358
|
+
clearTimeout(this.pendingTeardown);
|
|
3359
|
+
const target = untracked(this.activeTarget);
|
|
3360
|
+
if (target)
|
|
3361
|
+
clearAnchorName(target);
|
|
3362
|
+
for (const ref of [this.panelRef, this.scrimRef]) {
|
|
3363
|
+
try {
|
|
3364
|
+
ref().nativeElement.hidePopover();
|
|
3365
|
+
}
|
|
3366
|
+
catch {
|
|
3367
|
+
// popover was already closed or detached
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
effectiveBackdrop = computed(() => this.backdrop() ?? (this.activeTarget() ? 'spotlight' : 'dim'), ...(ngDevMode ? [{ debugName: "effectiveBackdrop" }] : /* istanbul ignore next */ []));
|
|
3373
|
+
show() {
|
|
3374
|
+
if (this.showing())
|
|
3375
|
+
return;
|
|
3376
|
+
if (this.pendingTeardown) {
|
|
3377
|
+
clearTimeout(this.pendingTeardown);
|
|
3378
|
+
this.pendingTeardown = null;
|
|
3379
|
+
}
|
|
3380
|
+
const target = resolveElement(this.target());
|
|
3381
|
+
const stale = this.activeTarget();
|
|
3382
|
+
if (stale && stale !== target)
|
|
3383
|
+
clearAnchorName(stale); // a close was mid-fade
|
|
3384
|
+
this.activeTarget.set(target);
|
|
3385
|
+
this.scrimContent.set(true);
|
|
3386
|
+
this.prevFocus = document.activeElement;
|
|
3387
|
+
// Anchor insets need the target on screen before the scrim paints; the
|
|
3388
|
+
// hole then tracks any further movement natively.
|
|
3389
|
+
target?.scrollIntoView?.({ block: 'center', behavior: 'instant' });
|
|
3390
|
+
if (target)
|
|
3391
|
+
setAnchorName(target, this.anchorName);
|
|
3392
|
+
// Scrim first, panel second: top-layer order is stacking order.
|
|
3393
|
+
if ((this.backdrop() ?? (target ? 'spotlight' : 'dim')) !== 'none') {
|
|
3394
|
+
this.scrimRef().nativeElement.showPopover();
|
|
3395
|
+
this.scrimShown.set(true);
|
|
3396
|
+
}
|
|
3397
|
+
this.panelRef().nativeElement.showPopover();
|
|
3398
|
+
const onKeydown = (event) => this.onKeydown(event);
|
|
3399
|
+
document.addEventListener('keydown', onKeydown, true);
|
|
3400
|
+
this.detachKeydown = () => {
|
|
3401
|
+
document.removeEventListener('keydown', onKeydown, true);
|
|
3402
|
+
this.detachKeydown = null;
|
|
3403
|
+
};
|
|
3404
|
+
this.showing.set(true);
|
|
3405
|
+
this.open.set(true);
|
|
3406
|
+
this.focusInitial();
|
|
3407
|
+
this.opened.emit();
|
|
3408
|
+
}
|
|
3409
|
+
close(reason) {
|
|
3410
|
+
if (!this.showing())
|
|
3411
|
+
return;
|
|
3412
|
+
this.detachKeydown?.();
|
|
3413
|
+
const target = this.activeTarget();
|
|
3414
|
+
const active = document.activeElement;
|
|
3415
|
+
const focusInTarget = !!target && (target === active || target.contains(active));
|
|
3416
|
+
for (const ref of [this.panelRef, this.scrimRef]) {
|
|
3417
|
+
try {
|
|
3418
|
+
ref().nativeElement.hidePopover();
|
|
3419
|
+
}
|
|
3420
|
+
catch {
|
|
3421
|
+
// scrim may not have been shown (backdrop="none")
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
this.showing.set(false);
|
|
3425
|
+
this.scrimShown.set(false);
|
|
3426
|
+
this.open.set(false);
|
|
3427
|
+
// The close fade is still running: the panel must stay anchored and the
|
|
3428
|
+
// spotlight pieces rendered until it finishes, or everything jumps.
|
|
3429
|
+
this.pendingTeardown = setTimeout(() => {
|
|
3430
|
+
this.pendingTeardown = null;
|
|
3431
|
+
if (target)
|
|
3432
|
+
clearAnchorName(target);
|
|
3433
|
+
this.activeTarget.set(null);
|
|
3434
|
+
this.scrimContent.set(false);
|
|
3435
|
+
}, this.componentOptions().transitionMs);
|
|
3436
|
+
// The duet loop may have sent the user into the target on purpose — don't
|
|
3437
|
+
// yank them back out of it.
|
|
3438
|
+
if (!focusInTarget && this.prevFocus && document.contains(this.prevFocus)) {
|
|
3439
|
+
this.prevFocus.focus();
|
|
3440
|
+
}
|
|
3441
|
+
this.dismissed.emit({ key: this.key() || undefined, reason });
|
|
3442
|
+
this.closed.emit();
|
|
3443
|
+
}
|
|
3444
|
+
/** Move an open callout to a new target without a close/open round trip. */
|
|
3445
|
+
retarget(target) {
|
|
3446
|
+
const prev = this.activeTarget();
|
|
3447
|
+
if (prev && prev !== target)
|
|
3448
|
+
clearAnchorName(prev);
|
|
3449
|
+
target?.scrollIntoView?.({ block: 'center', behavior: 'instant' });
|
|
3450
|
+
if (target)
|
|
3451
|
+
setAnchorName(target, this.anchorName);
|
|
3452
|
+
this.activeTarget.set(target);
|
|
3453
|
+
const wantScrim = (this.backdrop() ?? (target ? 'spotlight' : 'dim')) !== 'none';
|
|
3454
|
+
if (wantScrim !== this.scrimShown()) {
|
|
3455
|
+
const scrim = this.scrimRef().nativeElement;
|
|
3456
|
+
const panel = this.panelRef().nativeElement;
|
|
3457
|
+
try {
|
|
3458
|
+
if (wantScrim) {
|
|
3459
|
+
// Top-layer order is stacking order: re-show the panel above the
|
|
3460
|
+
// late-arriving scrim.
|
|
3461
|
+
scrim.showPopover();
|
|
3462
|
+
panel.hidePopover();
|
|
3463
|
+
panel.showPopover();
|
|
3464
|
+
}
|
|
3465
|
+
else {
|
|
3466
|
+
scrim.hidePopover();
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
catch {
|
|
3470
|
+
// a piece was already in the requested state
|
|
3471
|
+
}
|
|
3472
|
+
this.scrimShown.set(wantScrim);
|
|
3473
|
+
}
|
|
3474
|
+
this.focusInitial();
|
|
3475
|
+
}
|
|
3476
|
+
/** `[autofocus]` → first action → first panel focusable → the panel itself. */
|
|
3477
|
+
focusInitial() {
|
|
3478
|
+
const panel = this.panelRef().nativeElement;
|
|
3479
|
+
const first = panel.querySelector('[autofocus]') ??
|
|
3480
|
+
focusableElements(this.actionsRef().nativeElement)[0] ??
|
|
3481
|
+
focusableElements(panel)[0] ??
|
|
3482
|
+
panel;
|
|
3483
|
+
first.focus({ preventScroll: true });
|
|
3484
|
+
}
|
|
3485
|
+
/**
|
|
3486
|
+
* Capture phase so Escape works from inside the spotlit target and the tab
|
|
3487
|
+
* loop preempts the page. Non-Tab keys surface through `panelKeydown`.
|
|
3488
|
+
*/
|
|
3489
|
+
onKeydown(event) {
|
|
3490
|
+
if (event.key === 'Escape') {
|
|
3491
|
+
if (this.dismissible()) {
|
|
3492
|
+
event.preventDefault();
|
|
3493
|
+
this.close('escape');
|
|
3494
|
+
}
|
|
3495
|
+
return;
|
|
3496
|
+
}
|
|
3497
|
+
if (event.key !== 'Tab') {
|
|
3498
|
+
if (this.panelRef().nativeElement.contains(document.activeElement)) {
|
|
3499
|
+
this.panelKeydown.emit(event);
|
|
3500
|
+
}
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
if (this.effectiveBackdrop() === 'none')
|
|
3504
|
+
return; // nothing is blocked — tab naturally
|
|
3505
|
+
// Duet loop: panel focusables plus the interactive target.
|
|
3506
|
+
const loop = focusableElements(this.panelRef().nativeElement);
|
|
3507
|
+
const target = this.activeTarget();
|
|
3508
|
+
if (target && this.targetInteractive())
|
|
3509
|
+
loop.push(target);
|
|
3510
|
+
if (!loop.length)
|
|
3511
|
+
return;
|
|
3512
|
+
const index = loop.indexOf(document.activeElement);
|
|
3513
|
+
event.preventDefault();
|
|
3514
|
+
const next = event.shiftKey
|
|
3515
|
+
? loop[(index <= 0 ? loop.length : index) - 1]
|
|
3516
|
+
: loop[(index + 1) % loop.length];
|
|
3517
|
+
next.focus();
|
|
3518
|
+
}
|
|
3519
|
+
/** A stray click shouldn't kill onboarding: nudge the panel instead. */
|
|
3520
|
+
onBackdropClick() {
|
|
3521
|
+
if (this.dismissOnBackdrop()) {
|
|
3522
|
+
this.close('backdrop');
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
try {
|
|
3526
|
+
if (matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
3527
|
+
return;
|
|
3528
|
+
this.panelRef().nativeElement.animate?.([
|
|
3529
|
+
{ translate: '0 0' },
|
|
3530
|
+
{ translate: '0 -3px', offset: 0.3 },
|
|
3531
|
+
{ translate: '0 2px', offset: 0.6 },
|
|
3532
|
+
{ translate: '0 0' },
|
|
3533
|
+
], { duration: 400, easing: 'ease' });
|
|
3534
|
+
}
|
|
3535
|
+
catch {
|
|
3536
|
+
// animation is decoration; environments without WAAPI just skip it
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
// --- styling --------------------------------------------------------------
|
|
3540
|
+
spotlight = computed(() => {
|
|
3541
|
+
const options = this.componentOptions();
|
|
3542
|
+
return spotlightStyles(this.anchorName, {
|
|
3543
|
+
pad: options.spotlightPadding,
|
|
3544
|
+
ringWidth: options.ringWidth,
|
|
3545
|
+
scrimColor: options.scrimColor,
|
|
3546
|
+
});
|
|
3547
|
+
}, ...(ngDevMode ? [{ debugName: "spotlight" }] : /* istanbul ignore next */ []));
|
|
3548
|
+
scrimClassName = computed(() => {
|
|
3549
|
+
const options = this.componentOptions();
|
|
3550
|
+
return css({
|
|
3551
|
+
position: 'fixed',
|
|
3552
|
+
inset: 0,
|
|
3553
|
+
width: '100%',
|
|
3554
|
+
height: '100%',
|
|
3555
|
+
margin: 0,
|
|
3556
|
+
border: 0,
|
|
3557
|
+
padding: 0,
|
|
3558
|
+
background: 'transparent',
|
|
3559
|
+
overflow: 'visible',
|
|
3560
|
+
pointerEvents: 'none',
|
|
3561
|
+
'& > *': { position: 'fixed' },
|
|
3562
|
+
...discreteOverlayTransition(options.transitionMs, { opacity: 0 }, { opacity: 1 }),
|
|
3563
|
+
});
|
|
3564
|
+
}, ...(ngDevMode ? [{ debugName: "scrimClassName" }] : /* istanbul ignore next */ []));
|
|
3565
|
+
windowClassName = computed(() => {
|
|
3566
|
+
const ring = this.theme.colorPalette()[this.variant()] ?? this.theme.colorPalette()['primary'];
|
|
3567
|
+
return css({
|
|
3568
|
+
...this.spotlight().window,
|
|
3569
|
+
...this.theme.radius(this.componentOptions().spotlightRadius),
|
|
3570
|
+
borderColor: ring,
|
|
3571
|
+
});
|
|
3572
|
+
}, ...(ngDevMode ? [{ debugName: "windowClassName" }] : /* istanbul ignore next */ []));
|
|
3573
|
+
stripClassName(side) {
|
|
3574
|
+
return css(this.spotlight().strips[side]);
|
|
3575
|
+
}
|
|
3576
|
+
coverClassName = computed(() => css(this.spotlight().cover), ...(ngDevMode ? [{ debugName: "coverClassName" }] : /* istanbul ignore next */ []));
|
|
3577
|
+
fullCoverClassName = computed(() => css({
|
|
3578
|
+
inset: 0,
|
|
3579
|
+
background: this.componentOptions().scrimColor,
|
|
3580
|
+
pointerEvents: 'auto',
|
|
3581
|
+
}), ...(ngDevMode ? [{ debugName: "fullCoverClassName" }] : /* istanbul ignore next */ []));
|
|
3582
|
+
panelClassName = computed(() => {
|
|
3583
|
+
const options = this.componentOptions();
|
|
3584
|
+
const anchored = this.activeTarget() !== null;
|
|
3585
|
+
return css({
|
|
3586
|
+
...this.theme.colorPair(options.color),
|
|
3587
|
+
...this.theme.radius(options.borderRadius),
|
|
3588
|
+
...this.theme.boxShadow(options.shadow),
|
|
3589
|
+
...this.theme.typeface(options.typeface),
|
|
3590
|
+
width: options.width,
|
|
3591
|
+
maxWidth: 'calc(100vw - 32px)',
|
|
3592
|
+
padding: 0,
|
|
3593
|
+
border: 0,
|
|
3594
|
+
overflow: 'visible',
|
|
3595
|
+
// A target-less panel keeps the popover UA styles (inset:0 + margin:auto)
|
|
3596
|
+
// and centers in the viewport; anchorStyles resets them for anchoring.
|
|
3597
|
+
...(anchored
|
|
3598
|
+
? anchorStyles(this.anchorName, this.placement(), {
|
|
3599
|
+
mainAxis: options.offset + options.spotlightPadding,
|
|
3600
|
+
})
|
|
3601
|
+
: {}),
|
|
3602
|
+
...discreteOverlayTransition(options.transitionMs, { opacity: 0, translate: '0 6px' }, { opacity: 1, translate: '0 0' }),
|
|
3603
|
+
});
|
|
3604
|
+
}, ...(ngDevMode ? [{ debugName: "panelClassName" }] : /* istanbul ignore next */ []));
|
|
3605
|
+
headRowClassName = computed(() => {
|
|
3606
|
+
const options = this.componentOptions();
|
|
3607
|
+
return css({
|
|
3608
|
+
display: 'flex',
|
|
3609
|
+
alignItems: 'center',
|
|
3610
|
+
gap: 8,
|
|
3611
|
+
padding: options.padding,
|
|
3612
|
+
paddingBottom: 0,
|
|
3613
|
+
'&:not(:has(*))': { display: 'none' },
|
|
3614
|
+
});
|
|
3615
|
+
}, ...(ngDevMode ? [{ debugName: "headRowClassName" }] : /* istanbul ignore next */ []));
|
|
3616
|
+
titleClassName = computed(() => css({ ...this.theme.typeface(this.componentOptions().headerTypeface), marginRight: 'auto' }), ...(ngDevMode ? [{ debugName: "titleClassName" }] : /* istanbul ignore next */ []));
|
|
3617
|
+
bodyClassName = computed(() => {
|
|
3618
|
+
const options = this.componentOptions();
|
|
3619
|
+
return css({ padding: options.padding, paddingTop: 6, paddingBottom: 12, '&:empty': { display: 'none' } });
|
|
3620
|
+
}, ...(ngDevMode ? [{ debugName: "bodyClassName" }] : /* istanbul ignore next */ []));
|
|
3621
|
+
actionsRowClassName = computed(() => {
|
|
3622
|
+
const options = this.componentOptions();
|
|
3623
|
+
return css({
|
|
3624
|
+
display: 'flex',
|
|
3625
|
+
alignItems: 'center',
|
|
3626
|
+
gap: 8,
|
|
3627
|
+
padding: options.padding,
|
|
3628
|
+
paddingTop: 0,
|
|
3629
|
+
'&:not(:has(*))': { display: 'none' },
|
|
3630
|
+
});
|
|
3631
|
+
}, ...(ngDevMode ? [{ debugName: "actionsRowClassName" }] : /* istanbul ignore next */ []));
|
|
3632
|
+
arrowClassName = computed(() => {
|
|
3633
|
+
const options = this.componentOptions();
|
|
3634
|
+
return css({
|
|
3635
|
+
...this.theme.colorPair(options.color),
|
|
3636
|
+
...anchorArrowStyles(this.placement(), options.arrowSize),
|
|
3637
|
+
});
|
|
3638
|
+
}, ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
|
|
3639
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCalloutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3640
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniCalloutComponent, isStandalone: true, selector: "uni-callout", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, backdrop: { classPropertyName: "backdrop", publicName: "backdrop", isSignal: true, isRequired: false, transformFunction: null }, targetInteractive: { classPropertyName: "targetInteractive", publicName: "targetInteractive", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, dismissOnBackdrop: { classPropertyName: "dismissOnBackdrop", publicName: "dismissOnBackdrop", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, arrow: { classPropertyName: "arrow", publicName: "arrow", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", opened: "opened", closed: "closed", dismissed: "dismissed", panelKeydown: "panelKeydown" }, providers: [{ provide: COMPONENT_NAME, useValue: 'callout' }], viewQueries: [{ propertyName: "scrimRef", first: true, predicate: ["scrim"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "actionsRef", first: true, predicate: ["actions"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Scrim first: top-layer order is stacking order, so the panel always sits\n above it. Strips and cover are pointer-blockers only; the keyboard path\n is the duet loop on the document keydown handler. -->\n<div #scrim popover=\"manual\" [class]=\"scrimClassName()\">\n @if (scrimContent()) {\n @if (effectiveBackdrop() === 'spotlight' && activeTarget()) {\n <div [class]=\"windowClassName()\"></div>\n @for (side of stripSides; track side) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div [class]=\"stripClassName(side)\" (click)=\"onBackdropClick()\"></div>\n }\n @if (!targetInteractive()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div [class]=\"coverClassName()\" (click)=\"onBackdropClick()\"></div>\n }\n } @else if (effectiveBackdrop() === 'dim') {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div [class]=\"fullCoverClassName()\" (click)=\"onBackdropClick()\"></div>\n }\n }\n</div>\n<div\n #panel\n popover=\"manual\"\n role=\"dialog\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : header() ? headerId : bodyId\"\n [class]=\"panelClassName()\"\n>\n <ng-content select=\"[callout-media]\"></ng-content>\n <div [class]=\"headRowClassName()\">\n <ng-content select=\"[callout-header]\"></ng-content>\n @if (header()) {\n <span [id]=\"headerId\" [class]=\"titleClassName()\">{{ header() }}</span>\n }\n @if (dismissible()) {\n <button\n icon-button\n [iconName]=\"componentOptions().closeSymbol\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"close('close-button')\"\n >\n {{ closeLabel() }}\n </button>\n }\n </div>\n <div [id]=\"bodyId\" [class]=\"bodyClassName()\"><ng-content></ng-content></div>\n <div #actions [class]=\"actionsRowClassName()\"><ng-content select=\"[callout-actions]\"></ng-content></div>\n @if (arrow() && activeTarget()) {\n <span [class]=\"arrowClassName()\"></span>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3641
|
+
}
|
|
3642
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCalloutComponent, decorators: [{
|
|
3643
|
+
type: Component,
|
|
3644
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-callout', imports: [UniIconButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'callout' }], template: "<!-- Scrim first: top-layer order is stacking order, so the panel always sits\n above it. Strips and cover are pointer-blockers only; the keyboard path\n is the duet loop on the document keydown handler. -->\n<div #scrim popover=\"manual\" [class]=\"scrimClassName()\">\n @if (scrimContent()) {\n @if (effectiveBackdrop() === 'spotlight' && activeTarget()) {\n <div [class]=\"windowClassName()\"></div>\n @for (side of stripSides; track side) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div [class]=\"stripClassName(side)\" (click)=\"onBackdropClick()\"></div>\n }\n @if (!targetInteractive()) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div [class]=\"coverClassName()\" (click)=\"onBackdropClick()\"></div>\n }\n } @else if (effectiveBackdrop() === 'dim') {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div [class]=\"fullCoverClassName()\" (click)=\"onBackdropClick()\"></div>\n }\n }\n</div>\n<div\n #panel\n popover=\"manual\"\n role=\"dialog\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : header() ? headerId : bodyId\"\n [class]=\"panelClassName()\"\n>\n <ng-content select=\"[callout-media]\"></ng-content>\n <div [class]=\"headRowClassName()\">\n <ng-content select=\"[callout-header]\"></ng-content>\n @if (header()) {\n <span [id]=\"headerId\" [class]=\"titleClassName()\">{{ header() }}</span>\n }\n @if (dismissible()) {\n <button\n icon-button\n [iconName]=\"componentOptions().closeSymbol\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"close('close-button')\"\n >\n {{ closeLabel() }}\n </button>\n }\n </div>\n <div [id]=\"bodyId\" [class]=\"bodyClassName()\"><ng-content></ng-content></div>\n <div #actions [class]=\"actionsRowClassName()\"><ng-content select=\"[callout-actions]\"></ng-content></div>\n @if (arrow() && activeTarget()) {\n <span [class]=\"arrowClassName()\"></span>\n }\n</div>\n" }]
|
|
3645
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], backdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdrop", required: false }] }], targetInteractive: [{ type: i0.Input, args: [{ isSignal: true, alias: "targetInteractive", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dismissOnBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnBackdrop", required: false }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], arrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrow", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], panelKeydown: [{ type: i0.Output, args: ["panelKeydown"] }], scrimRef: [{ type: i0.ViewChild, args: ['scrim', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], actionsRef: [{ type: i0.ViewChild, args: ['actions', { isSignal: true }] }] } });
|
|
3646
|
+
|
|
3647
|
+
/**
|
|
3648
|
+
* UniCalloutComponent Barrel File
|
|
3649
|
+
*
|
|
3650
|
+
* This file exports all public-facing elements of the callout component.
|
|
3651
|
+
*/
|
|
3652
|
+
|
|
3077
3653
|
class UniCardContentComponent extends BaseComponent {
|
|
3078
3654
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCardContentComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
3079
3655
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.12", type: UniCardContentComponent, isStandalone: true, selector: "uni-card-content", providers: [{ provide: COMPONENT_NAME, useValue: 'cardContent' }], usesInheritance: true, ngImport: i0, template: "<div [style]=\"style()\">\n <ng-content></ng-content>\n</div>\n", styles: [""], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -3334,8 +3910,527 @@ class UniCheckboxComponent extends BaseComponent {
|
|
|
3334
3910
|
}
|
|
3335
3911
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCheckboxComponent, decorators: [{
|
|
3336
3912
|
type: Component,
|
|
3337
|
-
args: [{ selector: 'uni-checkbox', imports: [UniTextComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'checkbox' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<label [class]=\"checkboxLabel()\">\n <input\n type=\"checkbox\"\n [class]=\"checkboxInput()\"\n [checked]=\"checked()\"\n [indeterminate]=\"indeterminate()\"\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=\"checkbox\">\n <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <rect class=\"checkbox-box\" x=\"1\" y=\"1\" width=\"18\" height=\"18\"></rect>\n <polyline class=\"checkbox-check\" points=\"4 11 8 15 16 6\"></polyline>\n <line class=\"checkbox-dash\" x1=\"5\" y1=\"10\" x2=\"15\" y2=\"10\"></line>\n </svg>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n" }]
|
|
3338
|
-
}], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }, { type: i0.Output, args: ["indeterminateChange"] }] } });
|
|
3913
|
+
args: [{ selector: 'uni-checkbox', imports: [UniTextComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'checkbox' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<label [class]=\"checkboxLabel()\">\n <input\n type=\"checkbox\"\n [class]=\"checkboxInput()\"\n [checked]=\"checked()\"\n [indeterminate]=\"indeterminate()\"\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=\"checkbox\">\n <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <rect class=\"checkbox-box\" x=\"1\" y=\"1\" width=\"18\" height=\"18\"></rect>\n <polyline class=\"checkbox-check\" points=\"4 11 8 15 16 6\"></polyline>\n <line class=\"checkbox-dash\" x1=\"5\" y1=\"10\" x2=\"15\" y2=\"10\"></line>\n </svg>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n" }]
|
|
3914
|
+
}], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }, { type: i0.Output, args: ["indeterminateChange"] }] } });
|
|
3915
|
+
|
|
3916
|
+
/**
|
|
3917
|
+
* Style block for the popup behind every `ListboxNavigation` consumer: an
|
|
3918
|
+
* absolutely-positioned `ul[role="listbox"]` under a `position: relative`
|
|
3919
|
+
* field wrapper, with the shared option chrome and active/hover highlight.
|
|
3920
|
+
*
|
|
3921
|
+
* Extracted because four components (`uni-search-input`, `uni-tag-input`,
|
|
3922
|
+
* `uni-time-input`, `uni-combobox`) carried hand-rolled copies, and the parts
|
|
3923
|
+
* that silently drift between copies all live here: the surface/elevation
|
|
3924
|
+
* trio, and the `activeColor` pair that themes re-point when their container
|
|
3925
|
+
* tokens don't contrast (see the Wellsourced overrides).
|
|
3926
|
+
*
|
|
3927
|
+
* Compose extras with the array form — `css([listboxPopupStyles(…), {…}])` —
|
|
3928
|
+
* so a component's own `& [role="option"]` block cascades after this one
|
|
3929
|
+
* instead of replacing it (an object spread would overwrite the key).
|
|
3930
|
+
*/
|
|
3931
|
+
const listboxPopupStyles = (theme, options, { maxHeight = 280 } = {}) => ({
|
|
3932
|
+
position: 'absolute',
|
|
3933
|
+
top: '100%',
|
|
3934
|
+
left: 0,
|
|
3935
|
+
right: 0,
|
|
3936
|
+
zIndex: 20,
|
|
3937
|
+
margin: '4px 0 0',
|
|
3938
|
+
padding: 4,
|
|
3939
|
+
listStyle: 'none',
|
|
3940
|
+
maxHeight,
|
|
3941
|
+
overflowY: 'auto',
|
|
3942
|
+
...theme.colorPair((options.listColor ?? 'primary-surface')),
|
|
3943
|
+
...theme.boxShadow(options.listShadow ?? 'menu'),
|
|
3944
|
+
...theme.radius(options.listBorderRadius ?? 'xs'),
|
|
3945
|
+
'& [role="option"]': {
|
|
3946
|
+
padding: '8px 12px',
|
|
3947
|
+
cursor: 'pointer',
|
|
3948
|
+
...theme.typeface('label'),
|
|
3949
|
+
...theme.radius('xxs'),
|
|
3950
|
+
'&.active, &:not([aria-disabled="true"]):hover': {
|
|
3951
|
+
...theme.colorPair((options.activeColor ?? 'primary-container')),
|
|
3952
|
+
},
|
|
3953
|
+
},
|
|
3954
|
+
});
|
|
3955
|
+
|
|
3956
|
+
class UniInputBoxComponent extends BaseComponent {
|
|
3957
|
+
className = css({ display: 'contents' });
|
|
3958
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
3959
|
+
error = input(false, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
3960
|
+
minWidth = input('0', ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
|
|
3961
|
+
/** Override the themed field height, e.g. `'auto'` for multi-line fields. */
|
|
3962
|
+
height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
3963
|
+
color = computed(() => this.error() ? this.componentOptions().errorColor : this.componentOptions().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
3964
|
+
border = computed(() => this.error() ? this.componentOptions().errorBorder : this.componentOptions().border, ...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
|
|
3965
|
+
shadow = computed(() => this.error() ? this.componentOptions().errorShadow : this.componentOptions().shadow, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
|
|
3966
|
+
inputBoxClass = computed(() => css([
|
|
3967
|
+
this.disabled() && {
|
|
3968
|
+
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
3969
|
+
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
3970
|
+
cursor: 'not-allowed !important',
|
|
3971
|
+
},
|
|
3972
|
+
{
|
|
3973
|
+
'& input, select, textarea': {
|
|
3974
|
+
...removeInputPlatformStyling,
|
|
3975
|
+
height: '100%',
|
|
3976
|
+
...this.theme.paddingLeft(this.componentOptions().paddingLeft),
|
|
3977
|
+
...this.theme.color(this.componentOptions().textColor),
|
|
3978
|
+
// `typeFace` is the deprecated casing; themes that still set it win
|
|
3979
|
+
// only when the canonical key is absent.
|
|
3980
|
+
...this.theme.typeface(this.componentOptions().typeface ?? this.componentOptions().typeFace),
|
|
3981
|
+
},
|
|
3982
|
+
// Multi-line fields size themselves (rows/resize), not from the box.
|
|
3983
|
+
'& textarea': {
|
|
3984
|
+
height: 'auto',
|
|
3985
|
+
...this.theme.paddingTop('xs'),
|
|
3986
|
+
...this.theme.paddingBottom('xs'),
|
|
3987
|
+
},
|
|
3988
|
+
'&:has(input:disabled, select:disabled, textarea:disabled)': {
|
|
3989
|
+
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
3990
|
+
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
3991
|
+
},
|
|
3992
|
+
'& input:disabled, select:disabled, textarea:disabled': {
|
|
3993
|
+
cursor: 'not-allowed !important',
|
|
3994
|
+
},
|
|
3995
|
+
'&:has(input:focus, select:focus, textarea:focus)': {
|
|
3996
|
+
outline: this.componentOptions().focusOutline,
|
|
3997
|
+
outlineOffset: this.componentOptions().focusOutlineOffset,
|
|
3998
|
+
// Optional focus chrome (border/ring/background). It yields to the
|
|
3999
|
+
// error state, so a flagged field stays visibly flagged while the
|
|
4000
|
+
// user is in it correcting the value.
|
|
4001
|
+
...(this.error()
|
|
4002
|
+
? {}
|
|
4003
|
+
: {
|
|
4004
|
+
...this.theme.border(this.componentOptions().focusBorder),
|
|
4005
|
+
...this.theme.boxShadow(this.componentOptions().focusShadow),
|
|
4006
|
+
...this.theme.backgroundColor(this.componentOptions().focusColor),
|
|
4007
|
+
}),
|
|
4008
|
+
},
|
|
4009
|
+
},
|
|
4010
|
+
]), ...(ngDevMode ? [{ debugName: "inputBoxClass" }] : /* istanbul ignore next */ []));
|
|
4011
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4012
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniInputBoxComponent, isStandalone: true, selector: "uni-input-box", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "component", type: UniRowComponent, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4013
|
+
}
|
|
4014
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, decorators: [{
|
|
4015
|
+
type: Component,
|
|
4016
|
+
args: [{ selector: 'uni-input-box', imports: [UniRowComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n" }]
|
|
4017
|
+
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
|
|
4018
|
+
|
|
4019
|
+
/**
|
|
4020
|
+
* Form-bound, closed-set, single-select autocomplete: `FormValueControl<T | null>`
|
|
4021
|
+
* over object `Options<T>`, type-to-filter, commit-on-select. Typing produces a
|
|
4022
|
+
* draft — a filter, never a value; the value changes only when an option
|
|
4023
|
+
* commits, `clear()` runs, or the model is written from outside. Select
|
|
4024
|
+
* semantics, not search: a real label, a chevron, no magnifier, nothing
|
|
4025
|
+
* submits. For short lists with no need to type, prefer `uni-select` — the
|
|
4026
|
+
* value contract is identical, so swapping is a template-only change.
|
|
4027
|
+
*/
|
|
4028
|
+
class UniComboboxComponent extends BaseComponent {
|
|
4029
|
+
// --- Signal Forms block (explicit per AGENTS.md, not a base class) --------
|
|
4030
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
4031
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
4032
|
+
touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
4033
|
+
invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
4034
|
+
dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
4035
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
4036
|
+
ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
4037
|
+
// --- Configuration -------------------------------------------------------
|
|
4038
|
+
/** Accessible name for the field, e.g. "State"; echoed on the listbox. */
|
|
4039
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4040
|
+
placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
4041
|
+
options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
4042
|
+
/**
|
|
4043
|
+
* Equality used to match `value` against option values, called as
|
|
4044
|
+
* `compareWith(optionValue, value)`. Defaults to reference equality, which
|
|
4045
|
+
* never matches an object value rebuilt from elsewhere (e.g. a saved record
|
|
4046
|
+
* against options from a fresh fetch) — pass a key comparison like
|
|
4047
|
+
* `(a, b) => a?.id === b?.id` for object values.
|
|
4048
|
+
*/
|
|
4049
|
+
compareWith = input((a, b) => a === b, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
|
|
4050
|
+
width = input('100%', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
4051
|
+
/** Show a ✕ while a value is set; an emptied field on blur also clears. */
|
|
4052
|
+
clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
|
|
4053
|
+
/** Blur commits an exact-match draft; a non-matching draft reverts. */
|
|
4054
|
+
commitOnBlur = input(true, ...(ngDevMode ? [{ debugName: "commitOnBlur" }] : /* istanbul ignore next */ []));
|
|
4055
|
+
/** i18n-able empty row, also announced when a filter matches nothing. */
|
|
4056
|
+
emptyText = input('No matches', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
4057
|
+
// --- Filtering (local by default — the closed set is already in memory) ---
|
|
4058
|
+
/** `false` renders `options` verbatim; narrow them app-side from `query`. */
|
|
4059
|
+
filterLocally = input(true, ...(ngDevMode ? [{ debugName: "filterLocally" }] : /* istanbul ignore next */ []));
|
|
4060
|
+
/** Filter predicate; default is locale-lowercased label-contains. */
|
|
4061
|
+
filterWith = input(...(ngDevMode ? [undefined, { debugName: "filterWith" }] : /* istanbul ignore next */ []));
|
|
4062
|
+
debounceTime = input(250, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ []));
|
|
4063
|
+
// --- Events ---------------------------------------------------------------
|
|
4064
|
+
/** Debounced draft text, for async option lists. */
|
|
4065
|
+
query = output();
|
|
4066
|
+
/** An option committed. */
|
|
4067
|
+
selected = output();
|
|
4068
|
+
cleared = output();
|
|
4069
|
+
/** A commit was refused (no match); the field reverted. */
|
|
4070
|
+
rejected = output();
|
|
4071
|
+
host = inject(ElementRef);
|
|
4072
|
+
inputRef = viewChild.required('field');
|
|
4073
|
+
listRef = viewChild('listbox', ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
|
|
4074
|
+
/** Cancelled on destroy — a late tick would emit on a destroyed OutputRef. */
|
|
4075
|
+
queryTimer;
|
|
4076
|
+
constructor() {
|
|
4077
|
+
super();
|
|
4078
|
+
inject(DestroyRef).onDestroy(() => clearTimeout(this.queryTimer));
|
|
4079
|
+
}
|
|
4080
|
+
srOnly = css(visuallyHidden);
|
|
4081
|
+
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
4082
|
+
/** null → the field shows the committed label; a string is an uncommitted draft. */
|
|
4083
|
+
draft = signal(null, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
|
|
4084
|
+
/**
|
|
4085
|
+
* Popup visibility. Component-owned rather than `list.open()` — the shared
|
|
4086
|
+
* signal gates on `count() > 0`, which would hide the "No matches" row and
|
|
4087
|
+
* misreport `aria-expanded` over an empty filter.
|
|
4088
|
+
*/
|
|
4089
|
+
popupOpen = signal(false, ...(ngDevMode ? [{ debugName: "popupOpen" }] : /* istanbul ignore next */ []));
|
|
4090
|
+
/** Index in options() of the option matching value(), else -1. */
|
|
4091
|
+
committedIndex = computed(() => {
|
|
4092
|
+
const value = this.value();
|
|
4093
|
+
if (value === null || value === undefined)
|
|
4094
|
+
return -1;
|
|
4095
|
+
return this.options().findIndex((option) => this.compareWith()(option.value, value));
|
|
4096
|
+
}, ...(ngDevMode ? [{ debugName: "committedIndex" }] : /* istanbul ignore next */ []));
|
|
4097
|
+
/** A value with no matching option renders '' but is preserved — options may
|
|
4098
|
+
still be loading; the field self-heals when they arrive. */
|
|
4099
|
+
committedLabel = computed(() => {
|
|
4100
|
+
const index = this.committedIndex();
|
|
4101
|
+
return index >= 0 ? this.options()[index].label : '';
|
|
4102
|
+
}, ...(ngDevMode ? [{ debugName: "committedLabel" }] : /* istanbul ignore next */ []));
|
|
4103
|
+
displayValue = computed(() => this.draft() ?? this.committedLabel(), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
4104
|
+
/** Indices into options() surviving the filter. The draft filters; the
|
|
4105
|
+
committed label does not — reopening always shows the full set. */
|
|
4106
|
+
filteredIndices = computed(() => {
|
|
4107
|
+
const query = this.draft();
|
|
4108
|
+
const all = this.options().map((_, index) => index);
|
|
4109
|
+
if (!this.filterLocally() || query === null || query === '')
|
|
4110
|
+
return all;
|
|
4111
|
+
const filter = this.filterWith() ??
|
|
4112
|
+
((option, q) => option.label.toLocaleLowerCase().includes(q.toLocaleLowerCase()));
|
|
4113
|
+
return all.filter((index) => filter(this.options()[index], query));
|
|
4114
|
+
}, ...(ngDevMode ? [{ debugName: "filteredIndices" }] : /* istanbul ignore next */ []));
|
|
4115
|
+
/** Shared combobox bookkeeping, in filtered-list positions — the fourth
|
|
4116
|
+
consumer of the contract behind search-input, tag-input and time-input. */
|
|
4117
|
+
list = createListboxNavigation({
|
|
4118
|
+
count: () => this.filteredIndices().length,
|
|
4119
|
+
idPrefix: 'uni-combobox-listbox',
|
|
4120
|
+
disabled: (position) => !!this.options()[this.filteredIndices()[position]]?.disabled,
|
|
4121
|
+
});
|
|
4122
|
+
showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
4123
|
+
// --- Committing ------------------------------------------------------------
|
|
4124
|
+
commit(optIndex) {
|
|
4125
|
+
const option = this.options()[optIndex];
|
|
4126
|
+
if (!option || option.disabled)
|
|
4127
|
+
return false;
|
|
4128
|
+
this.value.set(option.value);
|
|
4129
|
+
this.draft.set(null);
|
|
4130
|
+
this.closeList();
|
|
4131
|
+
this.setFieldText(this.displayValue());
|
|
4132
|
+
this.announce(`${option.label} selected.`);
|
|
4133
|
+
this.selected.emit(option);
|
|
4134
|
+
return true;
|
|
4135
|
+
}
|
|
4136
|
+
clear() {
|
|
4137
|
+
this.value.set(null);
|
|
4138
|
+
this.draft.set(null);
|
|
4139
|
+
this.closeList();
|
|
4140
|
+
this.setFieldText('');
|
|
4141
|
+
this.announce('Selection cleared.');
|
|
4142
|
+
this.cleared.emit();
|
|
4143
|
+
this.inputRef().nativeElement.focus();
|
|
4144
|
+
}
|
|
4145
|
+
/**
|
|
4146
|
+
* Draft resolution, used by Enter, Tab and blur:
|
|
4147
|
+
* 1. an active option in the list → commit it;
|
|
4148
|
+
* 2. else a unique exact label match (locale-case-insensitive) → commit it;
|
|
4149
|
+
* 3. Enter only: the filter narrowed to exactly one enabled option → commit it
|
|
4150
|
+
* (on Enter the user can see the single candidate; on blur they're gone,
|
|
4151
|
+
* and committing a value they never saw confirmed is how forms grow
|
|
4152
|
+
* mystery data);
|
|
4153
|
+
* 4. else the caller keeps the list open (Enter) or reverts (Tab/blur).
|
|
4154
|
+
*/
|
|
4155
|
+
resolveDraft(enterOnly) {
|
|
4156
|
+
const filtered = this.filteredIndices();
|
|
4157
|
+
const active = this.list.activeIndex();
|
|
4158
|
+
if (this.popupOpen() && active >= 0 && active < filtered.length) {
|
|
4159
|
+
return this.commit(filtered[active]);
|
|
4160
|
+
}
|
|
4161
|
+
const draft = this.draft();
|
|
4162
|
+
if (draft === null)
|
|
4163
|
+
return true;
|
|
4164
|
+
if (draft === '') {
|
|
4165
|
+
// An emptied field on Tab/blur is a deliberate "no value" — but only
|
|
4166
|
+
// clearable fields may null the model this way.
|
|
4167
|
+
if (this.clearable() && this.value() !== null && !enterOnly) {
|
|
4168
|
+
this.value.set(null);
|
|
4169
|
+
this.announce('Selection cleared.');
|
|
4170
|
+
this.cleared.emit();
|
|
4171
|
+
}
|
|
4172
|
+
this.draft.set(null);
|
|
4173
|
+
this.setFieldText(this.displayValue());
|
|
4174
|
+
return true;
|
|
4175
|
+
}
|
|
4176
|
+
const normalize = (text) => text.toLocaleLowerCase();
|
|
4177
|
+
const exact = this.options()
|
|
4178
|
+
.map((option, index) => ({ option, index }))
|
|
4179
|
+
.filter(({ option }) => !option.disabled && normalize(option.label) === normalize(draft));
|
|
4180
|
+
if (exact.length === 1)
|
|
4181
|
+
return this.commit(exact[0].index);
|
|
4182
|
+
if (enterOnly) {
|
|
4183
|
+
const enabled = filtered.filter((index) => !this.options()[index].disabled);
|
|
4184
|
+
if (enabled.length === 1)
|
|
4185
|
+
return this.commit(enabled[0]);
|
|
4186
|
+
}
|
|
4187
|
+
return false;
|
|
4188
|
+
}
|
|
4189
|
+
revertDraft() {
|
|
4190
|
+
const draft = this.draft();
|
|
4191
|
+
if (draft === null)
|
|
4192
|
+
return null;
|
|
4193
|
+
this.draft.set(null);
|
|
4194
|
+
this.setFieldText(this.displayValue());
|
|
4195
|
+
return draft;
|
|
4196
|
+
}
|
|
4197
|
+
reject() {
|
|
4198
|
+
const query = this.revertDraft();
|
|
4199
|
+
if (query) {
|
|
4200
|
+
this.announce(`No match for “${query}”.`);
|
|
4201
|
+
this.rejected.emit({ query });
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
// --- Keyboard --------------------------------------------------------------
|
|
4205
|
+
onKeydown(event) {
|
|
4206
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
4207
|
+
event.preventDefault();
|
|
4208
|
+
if (event.altKey) {
|
|
4209
|
+
// Alt+Down opens without activating; Alt+Up closes keeping the draft.
|
|
4210
|
+
if (event.key === 'ArrowDown') {
|
|
4211
|
+
if (!this.popupOpen())
|
|
4212
|
+
this.openList(-1);
|
|
4213
|
+
}
|
|
4214
|
+
else {
|
|
4215
|
+
this.closeList();
|
|
4216
|
+
}
|
|
4217
|
+
return;
|
|
4218
|
+
}
|
|
4219
|
+
if (!this.popupOpen()) {
|
|
4220
|
+
this.popupOpen.set(true);
|
|
4221
|
+
this.list.show();
|
|
4222
|
+
if (event.key === 'ArrowDown') {
|
|
4223
|
+
const committed = this.committedFilteredPos();
|
|
4224
|
+
if (committed >= 0) {
|
|
4225
|
+
this.list.setActive(committed);
|
|
4226
|
+
this.scrollToActive();
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
// Fall through: Down lands on the first enabled option, Up on the last.
|
|
4231
|
+
}
|
|
4232
|
+
if (this.list.navigate(event))
|
|
4233
|
+
this.scrollToActive();
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4236
|
+
if (event.key === 'Home' || event.key === 'End') {
|
|
4237
|
+
// Only while the list is open — closed, they belong to the caret.
|
|
4238
|
+
if (this.popupOpen() && this.list.navigate(event))
|
|
4239
|
+
this.scrollToActive();
|
|
4240
|
+
return;
|
|
4241
|
+
}
|
|
4242
|
+
switch (event.key) {
|
|
4243
|
+
case 'Enter':
|
|
4244
|
+
// Never submits a form while the list is open.
|
|
4245
|
+
if (this.popupOpen())
|
|
4246
|
+
event.preventDefault();
|
|
4247
|
+
if (!this.resolveDraft(true)) {
|
|
4248
|
+
const count = this.filteredIndices().length;
|
|
4249
|
+
this.announce(count === 0 ? `${this.emptyText()}.` : `${count} results. Use the arrow keys.`);
|
|
4250
|
+
}
|
|
4251
|
+
return;
|
|
4252
|
+
case 'Escape':
|
|
4253
|
+
// One layer at a time: close the list, then revert the draft.
|
|
4254
|
+
// Never clears the committed value — Escape on a form control must
|
|
4255
|
+
// not be destructive.
|
|
4256
|
+
if (this.popupOpen())
|
|
4257
|
+
this.closeList();
|
|
4258
|
+
else if (this.draft() !== null)
|
|
4259
|
+
this.revertDraft();
|
|
4260
|
+
return;
|
|
4261
|
+
case 'Tab':
|
|
4262
|
+
// Resolve rules 1–2 (never rule 3), then let focus move on.
|
|
4263
|
+
if (this.commitOnBlur() && !this.resolveDraft(false))
|
|
4264
|
+
this.reject();
|
|
4265
|
+
this.closeList();
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
onInput() {
|
|
4269
|
+
const text = this.inputRef().nativeElement.value;
|
|
4270
|
+
this.draft.set(text);
|
|
4271
|
+
this.popupOpen.set(true);
|
|
4272
|
+
this.list.show();
|
|
4273
|
+
// Typing never selects — rules 2/3 make Enter still work without arrowing.
|
|
4274
|
+
this.list.setActive(-1);
|
|
4275
|
+
clearTimeout(this.queryTimer);
|
|
4276
|
+
this.queryTimer = setTimeout(() => {
|
|
4277
|
+
this.query.emit(text);
|
|
4278
|
+
// Filtering is otherwise silent to a screen reader.
|
|
4279
|
+
if (this.filterLocally() && text !== '') {
|
|
4280
|
+
const count = this.filteredIndices().length;
|
|
4281
|
+
this.announce(count === 0 ? `${this.emptyText()}.` : `${count} result${count === 1 ? '' : 's'}.`);
|
|
4282
|
+
}
|
|
4283
|
+
}, this.debounceTime());
|
|
4284
|
+
}
|
|
4285
|
+
// --- Pointer ---------------------------------------------------------------
|
|
4286
|
+
/** Click-to-browse: pointer users get the select affordance. Focus alone
|
|
4287
|
+
never opens — Tab-through forms must not spray popups. */
|
|
4288
|
+
onFieldClick() {
|
|
4289
|
+
if (this.disabled() || this.popupOpen())
|
|
4290
|
+
return;
|
|
4291
|
+
this.openList(this.committedFilteredPos());
|
|
4292
|
+
}
|
|
4293
|
+
onToggleMousedown(event) {
|
|
4294
|
+
event.preventDefault();
|
|
4295
|
+
if (this.disabled())
|
|
4296
|
+
return;
|
|
4297
|
+
this.inputRef().nativeElement.focus();
|
|
4298
|
+
if (this.popupOpen())
|
|
4299
|
+
this.closeList();
|
|
4300
|
+
else
|
|
4301
|
+
this.openList(this.committedFilteredPos());
|
|
4302
|
+
}
|
|
4303
|
+
onOptionClick(optIndex) {
|
|
4304
|
+
if (this.options()[optIndex]?.disabled)
|
|
4305
|
+
return;
|
|
4306
|
+
this.commit(optIndex);
|
|
4307
|
+
this.inputRef().nativeElement.focus();
|
|
4308
|
+
}
|
|
4309
|
+
onFocusOut(event) {
|
|
4310
|
+
const next = event.relatedTarget;
|
|
4311
|
+
if (next && this.host.nativeElement.contains(next))
|
|
4312
|
+
return;
|
|
4313
|
+
this.touched.set(true);
|
|
4314
|
+
if (this.commitOnBlur() && !this.resolveDraft(false))
|
|
4315
|
+
this.reject();
|
|
4316
|
+
else
|
|
4317
|
+
this.draft.set(null);
|
|
4318
|
+
this.closeList();
|
|
4319
|
+
}
|
|
4320
|
+
// --- Internals -------------------------------------------------------------
|
|
4321
|
+
/** Filtered position of the committed option when visible and enabled, else -1. */
|
|
4322
|
+
committedFilteredPos() {
|
|
4323
|
+
const committed = this.committedIndex();
|
|
4324
|
+
if (committed < 0 || this.options()[committed].disabled)
|
|
4325
|
+
return -1;
|
|
4326
|
+
return this.filteredIndices().indexOf(committed);
|
|
4327
|
+
}
|
|
4328
|
+
openList(activeFilteredPos) {
|
|
4329
|
+
this.popupOpen.set(true);
|
|
4330
|
+
this.list.show();
|
|
4331
|
+
this.list.setActive(activeFilteredPos);
|
|
4332
|
+
this.scrollToActive();
|
|
4333
|
+
}
|
|
4334
|
+
closeList() {
|
|
4335
|
+
this.popupOpen.set(false);
|
|
4336
|
+
this.list.hide();
|
|
4337
|
+
}
|
|
4338
|
+
scrollToActive() {
|
|
4339
|
+
const position = this.list.activeIndex();
|
|
4340
|
+
if (position < 0)
|
|
4341
|
+
return;
|
|
4342
|
+
queueMicrotask(() => this.listRef()?.nativeElement.children[position]?.scrollIntoView?.({ block: 'nearest' }));
|
|
4343
|
+
}
|
|
4344
|
+
setFieldText(text) {
|
|
4345
|
+
const element = this.inputRef()?.nativeElement;
|
|
4346
|
+
if (element)
|
|
4347
|
+
element.value = text;
|
|
4348
|
+
}
|
|
4349
|
+
announce(message) {
|
|
4350
|
+
// Re-announce identical text by breaking the string equality.
|
|
4351
|
+
this.announcement.set(this.announcement() === message ? `${message} ` : message);
|
|
4352
|
+
}
|
|
4353
|
+
// --- Styling ----------------------------------------------------------------
|
|
4354
|
+
className = computed(() => css({ display: 'block', position: 'relative', width: this.width() }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
4355
|
+
rowClass = computed(() => css({ display: 'flex', alignItems: 'center', flex: 1, width: '100%', minWidth: 0 }), ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
|
|
4356
|
+
inputClass = computed(() => css({
|
|
4357
|
+
flex: 1,
|
|
4358
|
+
minWidth: 0,
|
|
4359
|
+
border: 0,
|
|
4360
|
+
outline: 'none',
|
|
4361
|
+
background: 'transparent',
|
|
4362
|
+
color: 'inherit',
|
|
4363
|
+
font: 'inherit',
|
|
4364
|
+
}), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
|
|
4365
|
+
/** Pointer-only affordance: keyboard already has ArrowDown, and the input
|
|
4366
|
+
itself announces expanded state. */
|
|
4367
|
+
toggleClass = computed(() => css({
|
|
4368
|
+
flex: 'none',
|
|
4369
|
+
width: 28,
|
|
4370
|
+
height: 28,
|
|
4371
|
+
display: 'grid',
|
|
4372
|
+
placeItems: 'center',
|
|
4373
|
+
border: 0,
|
|
4374
|
+
padding: 0,
|
|
4375
|
+
background: 'transparent',
|
|
4376
|
+
cursor: this.disabled() ? 'not-allowed' : 'pointer',
|
|
4377
|
+
...this.theme.color('on-background-variant'),
|
|
4378
|
+
'& uni-icon': {
|
|
4379
|
+
...motionSafe({ transition: 'transform 0.15s ease' }),
|
|
4380
|
+
transform: this.popupOpen() ? 'rotate(180deg)' : 'none',
|
|
4381
|
+
},
|
|
4382
|
+
}), ...(ngDevMode ? [{ debugName: "toggleClass" }] : /* istanbul ignore next */ []));
|
|
4383
|
+
listClass = computed(() => {
|
|
4384
|
+
const options = this.componentOptions();
|
|
4385
|
+
return css([
|
|
4386
|
+
listboxPopupStyles(this.theme, options, {
|
|
4387
|
+
// A scroll height, never a cap: a closed-set control must not render a
|
|
4388
|
+
// reachable-by-keyboard-only subset (contrast searchInput.maxSuggestions).
|
|
4389
|
+
maxHeight: (options.maxVisibleOptions ?? 8) * 36 + 8,
|
|
4390
|
+
}),
|
|
4391
|
+
{
|
|
4392
|
+
'& [role="option"]': {
|
|
4393
|
+
display: 'flex',
|
|
4394
|
+
alignItems: 'center',
|
|
4395
|
+
gap: 10,
|
|
4396
|
+
minHeight: 36,
|
|
4397
|
+
boxSizing: 'border-box',
|
|
4398
|
+
padding: '5px 10px',
|
|
4399
|
+
'&.active, &:not([aria-disabled="true"]):hover': {
|
|
4400
|
+
'& .check': { color: 'inherit' },
|
|
4401
|
+
'& .desc': { color: 'inherit', opacity: 0.8 },
|
|
4402
|
+
},
|
|
4403
|
+
'&[aria-disabled="true"]': {
|
|
4404
|
+
...this.theme.color('on-disabled'),
|
|
4405
|
+
cursor: 'not-allowed',
|
|
4406
|
+
'& .desc': { color: 'inherit' },
|
|
4407
|
+
},
|
|
4408
|
+
},
|
|
4409
|
+
// Fixed-width check column so labels align with or without the icon.
|
|
4410
|
+
'& .check': { flex: 'none', width: 18, ...this.theme.color('primary') },
|
|
4411
|
+
'& .text': { minWidth: 0, flex: 1, display: 'flex', alignItems: 'baseline', gap: 8 },
|
|
4412
|
+
'& .option-label': { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
|
|
4413
|
+
'& .desc': {
|
|
4414
|
+
marginLeft: 'auto',
|
|
4415
|
+
whiteSpace: 'nowrap',
|
|
4416
|
+
fontSize: '0.85em',
|
|
4417
|
+
...this.theme.color(options.descriptionColor ?? 'on-primary-surface-variant'),
|
|
4418
|
+
},
|
|
4419
|
+
'& .empty': {
|
|
4420
|
+
padding: 10,
|
|
4421
|
+
...this.theme.typeface('label'),
|
|
4422
|
+
...this.theme.color(options.descriptionColor ?? 'on-primary-surface-variant'),
|
|
4423
|
+
},
|
|
4424
|
+
},
|
|
4425
|
+
]);
|
|
4426
|
+
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
4427
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4428
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniComboboxComponent, isStandalone: true, selector: "uni-combobox, Combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, filterLocally: { classPropertyName: "filterLocally", publicName: "filterLocally", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", selected: "selected", cleared: "cleared", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4429
|
+
}
|
|
4430
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, decorators: [{
|
|
4431
|
+
type: Component,
|
|
4432
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-combobox, Combobox', imports: [UniIconButtonComponent, UniIconComponent, UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n" }]
|
|
4433
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], filterLocally: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLocally", required: false }] }], filterWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterWith", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], query: [{ type: i0.Output, args: ["query"] }], selected: [{ type: i0.Output, args: ["selected"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
|
|
3339
4434
|
|
|
3340
4435
|
class UniDataSearchComponent extends BaseComponent {
|
|
3341
4436
|
datasource = input(...(ngDevMode ? [undefined, { debugName: "datasource" }] : /* istanbul ignore next */ []));
|
|
@@ -3795,7 +4890,7 @@ class UniDataTableComponent extends BaseComponent {
|
|
|
3795
4890
|
return name ? this.theme.theme().borders[name] : undefined;
|
|
3796
4891
|
}
|
|
3797
4892
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDataTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
3798
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDataTableComponent, isStandalone: true, selector: "uni-data-table", inputs: { datasource: { classPropertyName: "datasource", publicName: "datasource", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, detailRowTemplate: { classPropertyName: "detailRowTemplate", publicName: "detailRowTemplate", isSignal: true, isRequired: false, transformFunction: null }, useMultiSelect: { classPropertyName: "useMultiSelect", publicName: "useMultiSelect", isSignal: true, isRequired: false, transformFunction: null }, useRowClick: { classPropertyName: "useRowClick", publicName: "useRowClick", isSignal: true, isRequired: false, transformFunction: null }, highlight: { classPropertyName: "highlight", publicName: "highlight", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowSelect: "rowSelect", rowClick: "rowClick" }, providers: [{ provide: COMPONENT_NAME, useValue: 'dataTable' }], usesInheritance: true, ngImport: i0, template: "@let ds = datasource();\n@if (ds) {\n <div\n box-layout\n [color]=\"componentOptions().color\"\n [border]=\"componentOptions().border\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [elevation]=\"componentOptions().elevation\"\n overflow=\"hidden\"\n >\n <div\n box-layout\n [padding]=\"componentOptions().headerPadding\"\n [color]=\"componentOptions().headerColor\"\n [class]=\"headerClass()\"\n >\n <ng-content select=\"data-table-header\"></ng-content>\n </div>\n\n <div box-layout [height]=\"height()\" position=\"relative\" [attr.aria-busy]=\"isLoading()\">\n <div scroll-area [verticalScrollPadding]=\"0\" (scrollable)=\"handleScrollable($event)\">\n <table [class]=\"tableClass()\">\n <thead>\n <tr>\n @for (column of columns(); track column) {\n <th\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [class]=\"thClass()\"\n [class.scrollable]=\"scrollable()\"\n [class.sticky]=\"column.isSticky\"\n [style.text-align]=\"column.textAlign\"\n >\n <uni-sort-header [datasource]=\"datasource()\" [column]=\"column.columnDef\">\n <span uni-text [typeface]=\"componentOptions().thTextRole\">{{ column.header }}</span>\n </uni-sort-header>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (record of ds.records(); track
|
|
4893
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniDataTableComponent, isStandalone: true, selector: "uni-data-table", inputs: { datasource: { classPropertyName: "datasource", publicName: "datasource", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, detailRowTemplate: { classPropertyName: "detailRowTemplate", publicName: "detailRowTemplate", isSignal: true, isRequired: false, transformFunction: null }, useMultiSelect: { classPropertyName: "useMultiSelect", publicName: "useMultiSelect", isSignal: true, isRequired: false, transformFunction: null }, useRowClick: { classPropertyName: "useRowClick", publicName: "useRowClick", isSignal: true, isRequired: false, transformFunction: null }, highlight: { classPropertyName: "highlight", publicName: "highlight", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowSelect: "rowSelect", rowClick: "rowClick" }, providers: [{ provide: COMPONENT_NAME, useValue: 'dataTable' }], usesInheritance: true, ngImport: i0, template: "@let ds = datasource();\n@if (ds) {\n <div\n box-layout\n [color]=\"componentOptions().color\"\n [border]=\"componentOptions().border\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [elevation]=\"componentOptions().elevation\"\n overflow=\"hidden\"\n >\n <div\n box-layout\n [padding]=\"componentOptions().headerPadding\"\n [color]=\"componentOptions().headerColor\"\n [class]=\"headerClass()\"\n >\n <ng-content select=\"data-table-header\"></ng-content>\n </div>\n\n <div box-layout [height]=\"height()\" position=\"relative\" [attr.aria-busy]=\"isLoading()\">\n <div scroll-area [verticalScrollPadding]=\"0\" (scrollable)=\"handleScrollable($event)\">\n <table [class]=\"tableClass()\">\n <thead>\n <tr>\n @for (column of columns(); track column.columnDef) {\n <th\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [class]=\"thClass()\"\n [class.scrollable]=\"scrollable()\"\n [class.sticky]=\"column.isSticky\"\n [style.text-align]=\"column.textAlign\"\n >\n <uni-sort-header [datasource]=\"datasource()\" [column]=\"column.columnDef\">\n <span uni-text [typeface]=\"componentOptions().thTextRole\">{{ column.header }}</span>\n </uni-sort-header>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n <!-- track $index: records are arbitrary consumer objects, typically\n re-fetched as fresh references \u2014 identity tracking recreated\n every row (NG0956). Row expansion is index-addressed already. -->\n @for (record of ds.records(); track $index; let i = $index) {\n <tr\n [class]=\"trClass()\"\n [attr.tabindex]=\"useRowClick() ? 0 : null\"\n (click)=\"handleRowClick(record, $index)\"\n (keydown.enter)=\"useRowClick() && handleRowClick(record, $index)\"\n (keydown.space)=\"\n useRowClick() && handleRowClick(record, $index);\n useRowClick() && $event.preventDefault()\n \"\n >\n @for (column of columns(); track column.columnDef) {\n <td\n [class]=\"tdClass()\"\n [class.scrollable]=\"scrollable()\"\n [class.sticky]=\"column.isSticky\"\n [class.template]=\"!!column.template\"\n [style.text-align]=\"column.textAlign\"\n >\n @if (column.template) {\n <ng-container\n [ngTemplateOutlet]=\"column.template\"\n [ngTemplateOutletContext]=\"record\"\n ></ng-container>\n } @else {\n <span uni-text [typeface]=\"componentOptions().tdTextRole\">\n @if (!column.cell) {\n {{ record[column.columnDef] }}\n } @else {\n {{ column.cell(record) }}\n }\n </span>\n }\n </td>\n }\n </tr>\n @if (detailRowTemplate()) {\n <!-- Collapsed detail rows are inert: invisible to screen\n readers and unreachable by keyboard until expanded -->\n <tr\n [class]=\"detailRowClass()\"\n [class.expanded]=\"expandedIndex() === $index\"\n [attr.inert]=\"expandedIndex() === $index ? null : ''\"\n >\n <td [attr.colspan]=\"columns().length\">\n <div class=\"detail-content-wrapper\">\n <div class=\"detail-inner\">\n <ng-container\n [ngTemplateOutlet]=\"detailRowTemplate()\"\n [ngTemplateOutletContext]=\"{\n $implicit: record,\n index: i,\n }\"\n ></ng-container>\n </div>\n </div>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Loading Overlay -->\n @if (isLoading()) {\n <div\n uni-center-layout\n role=\"status\"\n aria-label=\"Loading\"\n position=\"absolute\"\n [inset]=\"0\"\n [backgroundColor]=\"componentOptions().loadingOverlayColor\"\n [class]=\"loadingOverlayClass\"\n zIndex=\"overlay\"\n >\n <!-- Loading Spinner -->\n <div\n uni-box-layout\n [height]=\"componentOptions().loadingSpinnerSize\"\n [width]=\"componentOptions().loadingSpinnerSize\"\n >\n <uni-icon name=\"spinner\" [color]=\"componentOptions().loadingSpinnerColor\" />\n </div>\n </div>\n }\n </div>\n\n <div\n box-layout\n [padding]=\"componentOptions().footerPadding\"\n [color]=\"componentOptions().footerColor\"\n [class]=\"footerClass()\"\n >\n <ng-content select=\"data-table-footer\"></ng-content>\n </div>\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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: UniScrollAreaComponent, selector: "[uni-scroll-area], [scroll-area]", inputs: ["color", "borderRadius", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "height", "width", "appearance", "autoHeightDisabled", "verticalScrollPadding"], outputs: ["scrollable"] }, { kind: "component", type: UniTextComponent, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSortHeaderComponent, selector: "uni-sort-header", inputs: ["column", "datasource"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniCenterComponent, selector: "[uni-center-layout], [center-layout]", inputs: ["display", "justifyContent", "alignItems"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3799
4894
|
}
|
|
3800
4895
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniDataTableComponent, decorators: [{
|
|
3801
4896
|
type: Component,
|
|
@@ -3807,7 +4902,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3807
4902
|
UniSortHeaderComponent,
|
|
3808
4903
|
UniIconComponent,
|
|
3809
4904
|
UniCenterComponent,
|
|
3810
|
-
], providers: [{ provide: COMPONENT_NAME, useValue: 'dataTable' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let ds = datasource();\n@if (ds) {\n <div\n box-layout\n [color]=\"componentOptions().color\"\n [border]=\"componentOptions().border\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [elevation]=\"componentOptions().elevation\"\n overflow=\"hidden\"\n >\n <div\n box-layout\n [padding]=\"componentOptions().headerPadding\"\n [color]=\"componentOptions().headerColor\"\n [class]=\"headerClass()\"\n >\n <ng-content select=\"data-table-header\"></ng-content>\n </div>\n\n <div box-layout [height]=\"height()\" position=\"relative\" [attr.aria-busy]=\"isLoading()\">\n <div scroll-area [verticalScrollPadding]=\"0\" (scrollable)=\"handleScrollable($event)\">\n <table [class]=\"tableClass()\">\n <thead>\n <tr>\n @for (column of columns(); track column) {\n <th\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [class]=\"thClass()\"\n [class.scrollable]=\"scrollable()\"\n [class.sticky]=\"column.isSticky\"\n [style.text-align]=\"column.textAlign\"\n >\n <uni-sort-header [datasource]=\"datasource()\" [column]=\"column.columnDef\">\n <span uni-text [typeface]=\"componentOptions().thTextRole\">{{ column.header }}</span>\n </uni-sort-header>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (record of ds.records(); track
|
|
4905
|
+
], providers: [{ provide: COMPONENT_NAME, useValue: 'dataTable' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let ds = datasource();\n@if (ds) {\n <div\n box-layout\n [color]=\"componentOptions().color\"\n [border]=\"componentOptions().border\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [elevation]=\"componentOptions().elevation\"\n overflow=\"hidden\"\n >\n <div\n box-layout\n [padding]=\"componentOptions().headerPadding\"\n [color]=\"componentOptions().headerColor\"\n [class]=\"headerClass()\"\n >\n <ng-content select=\"data-table-header\"></ng-content>\n </div>\n\n <div box-layout [height]=\"height()\" position=\"relative\" [attr.aria-busy]=\"isLoading()\">\n <div scroll-area [verticalScrollPadding]=\"0\" (scrollable)=\"handleScrollable($event)\">\n <table [class]=\"tableClass()\">\n <thead>\n <tr>\n @for (column of columns(); track column.columnDef) {\n <th\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [class]=\"thClass()\"\n [class.scrollable]=\"scrollable()\"\n [class.sticky]=\"column.isSticky\"\n [style.text-align]=\"column.textAlign\"\n >\n <uni-sort-header [datasource]=\"datasource()\" [column]=\"column.columnDef\">\n <span uni-text [typeface]=\"componentOptions().thTextRole\">{{ column.header }}</span>\n </uni-sort-header>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n <!-- track $index: records are arbitrary consumer objects, typically\n re-fetched as fresh references \u2014 identity tracking recreated\n every row (NG0956). Row expansion is index-addressed already. -->\n @for (record of ds.records(); track $index; let i = $index) {\n <tr\n [class]=\"trClass()\"\n [attr.tabindex]=\"useRowClick() ? 0 : null\"\n (click)=\"handleRowClick(record, $index)\"\n (keydown.enter)=\"useRowClick() && handleRowClick(record, $index)\"\n (keydown.space)=\"\n useRowClick() && handleRowClick(record, $index);\n useRowClick() && $event.preventDefault()\n \"\n >\n @for (column of columns(); track column.columnDef) {\n <td\n [class]=\"tdClass()\"\n [class.scrollable]=\"scrollable()\"\n [class.sticky]=\"column.isSticky\"\n [class.template]=\"!!column.template\"\n [style.text-align]=\"column.textAlign\"\n >\n @if (column.template) {\n <ng-container\n [ngTemplateOutlet]=\"column.template\"\n [ngTemplateOutletContext]=\"record\"\n ></ng-container>\n } @else {\n <span uni-text [typeface]=\"componentOptions().tdTextRole\">\n @if (!column.cell) {\n {{ record[column.columnDef] }}\n } @else {\n {{ column.cell(record) }}\n }\n </span>\n }\n </td>\n }\n </tr>\n @if (detailRowTemplate()) {\n <!-- Collapsed detail rows are inert: invisible to screen\n readers and unreachable by keyboard until expanded -->\n <tr\n [class]=\"detailRowClass()\"\n [class.expanded]=\"expandedIndex() === $index\"\n [attr.inert]=\"expandedIndex() === $index ? null : ''\"\n >\n <td [attr.colspan]=\"columns().length\">\n <div class=\"detail-content-wrapper\">\n <div class=\"detail-inner\">\n <ng-container\n [ngTemplateOutlet]=\"detailRowTemplate()\"\n [ngTemplateOutletContext]=\"{\n $implicit: record,\n index: i,\n }\"\n ></ng-container>\n </div>\n </div>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Loading Overlay -->\n @if (isLoading()) {\n <div\n uni-center-layout\n role=\"status\"\n aria-label=\"Loading\"\n position=\"absolute\"\n [inset]=\"0\"\n [backgroundColor]=\"componentOptions().loadingOverlayColor\"\n [class]=\"loadingOverlayClass\"\n zIndex=\"overlay\"\n >\n <!-- Loading Spinner -->\n <div\n uni-box-layout\n [height]=\"componentOptions().loadingSpinnerSize\"\n [width]=\"componentOptions().loadingSpinnerSize\"\n >\n <uni-icon name=\"spinner\" [color]=\"componentOptions().loadingSpinnerColor\" />\n </div>\n </div>\n }\n </div>\n\n <div\n box-layout\n [padding]=\"componentOptions().footerPadding\"\n [color]=\"componentOptions().footerColor\"\n [class]=\"footerClass()\"\n >\n <ng-content select=\"data-table-footer\"></ng-content>\n </div>\n </div>\n}\n" }]
|
|
3811
4906
|
}], propDecorators: { datasource: [{ type: i0.Input, args: [{ isSignal: true, alias: "datasource", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], detailRowTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailRowTemplate", required: false }] }], useMultiSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "useMultiSelect", required: false }] }], useRowClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "useRowClick", required: false }] }], highlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlight", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], rowSelect: [{ type: i0.Output, args: ["rowSelect"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }] } });
|
|
3812
4907
|
|
|
3813
4908
|
/**
|
|
@@ -4013,67 +5108,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4013
5108
|
args: ['dropdown', { static: true }]
|
|
4014
5109
|
}] } });
|
|
4015
5110
|
|
|
4016
|
-
class UniInputBoxComponent extends BaseComponent {
|
|
4017
|
-
className = css({ display: 'contents' });
|
|
4018
|
-
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
4019
|
-
error = input(false, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
4020
|
-
minWidth = input('0', ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
|
|
4021
|
-
/** Override the themed field height, e.g. `'auto'` for multi-line fields. */
|
|
4022
|
-
height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
4023
|
-
color = computed(() => this.error() ? this.componentOptions().errorColor : this.componentOptions().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
4024
|
-
border = computed(() => this.error() ? this.componentOptions().errorBorder : this.componentOptions().border, ...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
|
|
4025
|
-
shadow = computed(() => this.error() ? this.componentOptions().errorShadow : this.componentOptions().shadow, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
|
|
4026
|
-
inputBoxClass = computed(() => css([
|
|
4027
|
-
this.disabled() && {
|
|
4028
|
-
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
4029
|
-
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
4030
|
-
cursor: 'not-allowed !important',
|
|
4031
|
-
},
|
|
4032
|
-
{
|
|
4033
|
-
'& input, select, textarea': {
|
|
4034
|
-
...removeInputPlatformStyling,
|
|
4035
|
-
height: '100%',
|
|
4036
|
-
...this.theme.paddingLeft(this.componentOptions().paddingLeft),
|
|
4037
|
-
...this.theme.color(this.componentOptions().textColor),
|
|
4038
|
-
...this.theme.typeface(this.componentOptions().typeFace),
|
|
4039
|
-
},
|
|
4040
|
-
// Multi-line fields size themselves (rows/resize), not from the box.
|
|
4041
|
-
'& textarea': {
|
|
4042
|
-
height: 'auto',
|
|
4043
|
-
...this.theme.paddingTop('xs'),
|
|
4044
|
-
...this.theme.paddingBottom('xs'),
|
|
4045
|
-
},
|
|
4046
|
-
'&:has(input:disabled, select:disabled, textarea:disabled)': {
|
|
4047
|
-
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
4048
|
-
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
4049
|
-
},
|
|
4050
|
-
'& input:disabled, select:disabled, textarea:disabled': {
|
|
4051
|
-
cursor: 'not-allowed !important',
|
|
4052
|
-
},
|
|
4053
|
-
'&:has(input:focus, select:focus, textarea:focus)': {
|
|
4054
|
-
outline: this.componentOptions().focusOutline,
|
|
4055
|
-
outlineOffset: this.componentOptions().focusOutlineOffset,
|
|
4056
|
-
// Optional focus chrome (border/ring/background). It yields to the
|
|
4057
|
-
// error state, so a flagged field stays visibly flagged while the
|
|
4058
|
-
// user is in it correcting the value.
|
|
4059
|
-
...(this.error()
|
|
4060
|
-
? {}
|
|
4061
|
-
: {
|
|
4062
|
-
...this.theme.border(this.componentOptions().focusBorder),
|
|
4063
|
-
...this.theme.boxShadow(this.componentOptions().focusShadow),
|
|
4064
|
-
...this.theme.backgroundColor(this.componentOptions().focusColor),
|
|
4065
|
-
}),
|
|
4066
|
-
},
|
|
4067
|
-
},
|
|
4068
|
-
]), ...(ngDevMode ? [{ debugName: "inputBoxClass" }] : /* istanbul ignore next */ []));
|
|
4069
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4070
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: UniInputBoxComponent, isStandalone: true, selector: "uni-input-box", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], usesInheritance: true, ngImport: i0, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "component", type: UniRowComponent, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4071
|
-
}
|
|
4072
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, decorators: [{
|
|
4073
|
-
type: Component,
|
|
4074
|
-
args: [{ selector: 'uni-input-box', imports: [UniRowComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'input' }], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n row-layout\n alignItems=\"center\"\n [height]=\"height() ?? componentOptions().height\"\n [color]=\"color()\"\n [border]=\"border()\"\n [borderRadius]=\"componentOptions().borderRadius\"\n [shadow]=\"shadow()\"\n [minWidth]=\"minWidth()\"\n [class]=\"inputBoxClass()\"\n position=\"relative\"\n>\n <ng-content></ng-content>\n</div>\n" }]
|
|
4075
|
-
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
|
|
4076
|
-
|
|
4077
5111
|
/**
|
|
4078
5112
|
* Date field with free-typed parsing and a popup calendar. Type `aug 20`,
|
|
4079
5113
|
* `8/20/2026` or `2026-08-20`, or pick from the grid — the form gets the
|
|
@@ -4271,8 +5305,9 @@ class UniDateInputComponent extends BaseComponent {
|
|
|
4271
5305
|
this.dropdown()?.hideDropdown();
|
|
4272
5306
|
}
|
|
4273
5307
|
onPopupShowing() {
|
|
4274
|
-
// The grid opens on the committed value's month (or today's).
|
|
4275
|
-
|
|
5308
|
+
// The grid opens on the committed value's month (or today's). Falsy
|
|
5309
|
+
// guard: a bound '' counts as no value, like everywhere else.
|
|
5310
|
+
this.calendar()?.month.set(monthOf(this.value() || todayIso()));
|
|
4276
5311
|
this.calendar()?.focusActiveDay();
|
|
4277
5312
|
this.opened.emit();
|
|
4278
5313
|
}
|
|
@@ -4663,32 +5698,9 @@ class UniTimeInputComponent extends BaseComponent {
|
|
|
4663
5698
|
embeddedClass = computed(() => css({ display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }), ...(ngDevMode ? [{ debugName: "embeddedClass" }] : /* istanbul ignore next */ []));
|
|
4664
5699
|
listClass = computed(() => {
|
|
4665
5700
|
const options = this.componentOptions();
|
|
4666
|
-
return css({
|
|
4667
|
-
position: 'absolute',
|
|
4668
|
-
top: '100%',
|
|
4669
|
-
left: 0,
|
|
4670
|
-
right: 0,
|
|
4671
|
-
zIndex: 20,
|
|
4672
|
-
margin: '4px 0 0',
|
|
4673
|
-
padding: 4,
|
|
4674
|
-
listStyle: 'none',
|
|
5701
|
+
return css(listboxPopupStyles(this.theme, options, {
|
|
4675
5702
|
maxHeight: (options.maxVisibleOptions ?? 7) * 36,
|
|
4676
|
-
|
|
4677
|
-
...this.theme.backgroundColor(options.listColor ?? 'primary-surface'),
|
|
4678
|
-
...this.theme.boxShadow(options.listShadow ?? 'menu'),
|
|
4679
|
-
...this.theme.radius(options.listBorderRadius ?? 'xs'),
|
|
4680
|
-
'& [role="option"]': {
|
|
4681
|
-
padding: '8px 12px',
|
|
4682
|
-
cursor: 'pointer',
|
|
4683
|
-
...this.theme.typeface('label'),
|
|
4684
|
-
...this.theme.color('on-primary-surface'),
|
|
4685
|
-
...this.theme.radius('xxs'),
|
|
4686
|
-
'&.active, &:hover': {
|
|
4687
|
-
...this.theme.backgroundColor('primary-container'),
|
|
4688
|
-
...this.theme.color('on-primary-container'),
|
|
4689
|
-
},
|
|
4690
|
-
},
|
|
4691
|
-
});
|
|
5703
|
+
}));
|
|
4692
5704
|
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
4693
5705
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTimeInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4694
5706
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTimeInputComponent, isStandalone: true, selector: "uni-time-input, TimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -6092,7 +7104,11 @@ class UniMenuComponent {
|
|
|
6092
7104
|
Enter/Space activation is dispatched from onMenuKeydown. -->
|
|
6093
7105
|
<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
|
|
6094
7106
|
<div role="menu" [class]="menuClassName()" (keydown)="onMenuKeydown($event, dropdown)">
|
|
6095
|
-
|
|
7107
|
+
<!-- track $index: items carry no stable key (template items have no
|
|
7108
|
+
label, labels may repeat, dividers have neither) and consumers
|
|
7109
|
+
naturally rebuild the array each CD pass — identity tracking
|
|
7110
|
+
recreated every node and tripped NG0956. -->
|
|
7111
|
+
@for (item of menuItems(); track $index) {
|
|
6096
7112
|
@if (isDivider(item)) {
|
|
6097
7113
|
<div role="separator" [class]="dividerClassName()"></div>
|
|
6098
7114
|
} @else {
|
|
@@ -6148,7 +7164,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
6148
7164
|
Enter/Space activation is dispatched from onMenuKeydown. -->
|
|
6149
7165
|
<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->
|
|
6150
7166
|
<div role="menu" [class]="menuClassName()" (keydown)="onMenuKeydown($event, dropdown)">
|
|
6151
|
-
|
|
7167
|
+
<!-- track $index: items carry no stable key (template items have no
|
|
7168
|
+
label, labels may repeat, dividers have neither) and consumers
|
|
7169
|
+
naturally rebuild the array each CD pass — identity tracking
|
|
7170
|
+
recreated every node and tripped NG0956. -->
|
|
7171
|
+
@for (item of menuItems(); track $index) {
|
|
6152
7172
|
@if (isDivider(item)) {
|
|
6153
7173
|
<div role="separator" [class]="dividerClassName()"></div>
|
|
6154
7174
|
} @else {
|
|
@@ -6216,11 +7236,11 @@ class UniMultiSelectComponent {
|
|
|
6216
7236
|
width: '100%',
|
|
6217
7237
|
});
|
|
6218
7238
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6219
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectComponent, isStandalone: true, selector: "uni-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, flexDirection: { classPropertyName: "flexDirection", publicName: "flexDirection", isSignal: true, isRequired: false, transformFunction: null }, checkboxGap: { classPropertyName: "checkboxGap", publicName: "checkboxGap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { updates: "updates" }, host: { properties: { "class": "className" } }, ngImport: i0, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n @for (option of options(); track
|
|
7239
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectComponent, isStandalone: true, selector: "uni-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, flexDirection: { classPropertyName: "flexDirection", publicName: "flexDirection", isSignal: true, isRequired: false, transformFunction: null }, checkboxGap: { classPropertyName: "checkboxGap", publicName: "checkboxGap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { updates: "updates" }, host: { properties: { "class": "className" } }, ngImport: i0, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "component", type: UniBoxComponent, selector: "[uni-box-layout], [box-layout]", inputs: ["color", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "elevation", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6220
7240
|
}
|
|
6221
7241
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectComponent, decorators: [{
|
|
6222
7242
|
type: Component,
|
|
6223
|
-
args: [{ selector: 'uni-multi-select', imports: [UniCheckboxComponent, UniBoxComponent], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n @for (option of options(); track
|
|
7243
|
+
args: [{ selector: 'uni-multi-select', imports: [UniCheckboxComponent, UniBoxComponent], host: { '[class]': 'className' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n" }]
|
|
6224
7244
|
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], selections: [{ type: i0.Input, args: [{ isSignal: true, alias: "selections", required: false }] }], updates: [{ type: i0.Output, args: ["updates"] }], flexDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexDirection", required: false }] }], checkboxGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkboxGap", required: false }] }] } });
|
|
6225
7245
|
|
|
6226
7246
|
class UniMultiSelectDropdownComponent extends BaseComponent {
|
|
@@ -6434,7 +7454,9 @@ class UniNotificationBadgeComponent extends BaseComponent {
|
|
|
6434
7454
|
const variant = this.badgeVariant();
|
|
6435
7455
|
const color = this.color();
|
|
6436
7456
|
const position = this.position();
|
|
6437
|
-
|
|
7457
|
+
// A theme may omit `offset`; without the fallback this concatenates to
|
|
7458
|
+
// the invalid length 'undefinedpx' and the badge loses its position.
|
|
7459
|
+
const offset = (this.componentOptions().offset ?? 0) + 'px';
|
|
6438
7460
|
const positionStyles = {
|
|
6439
7461
|
'top-right': { top: offset, right: offset },
|
|
6440
7462
|
'top-left': { top: offset, left: offset },
|
|
@@ -6811,34 +7833,121 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
6811
7833
|
* This file exports all public-facing elements of the paginator component.
|
|
6812
7834
|
*/
|
|
6813
7835
|
|
|
6814
|
-
|
|
7836
|
+
/**
|
|
7837
|
+
* Anchored, top-layer panel on the native `popover` element — the browser owns
|
|
7838
|
+
* positioning (CSS anchor positioning), stacking, and, in rich mode, light
|
|
7839
|
+
* dismissal. Two modes share one implementation:
|
|
7840
|
+
*
|
|
7841
|
+
* - `rich` (default): a click-toggled disclosure. The projected `[trigger]`
|
|
7842
|
+
* carries `aria-expanded`/`aria-controls`; with no trigger content the app
|
|
7843
|
+
* drives `open` and no element claims controller ARIA. Focus stays on the
|
|
7844
|
+
* trigger (APG disclosure) unless the panel marks an `[autofocus]` field,
|
|
7845
|
+
* and returns to the trigger on close.
|
|
7846
|
+
* - `tooltip`: hover/focus-triggered, `role="tooltip"` + `aria-describedby`,
|
|
7847
|
+
* WCAG 1.4.13 dismissable/hoverable/persistent. Content must not contain
|
|
7848
|
+
* focusable elements.
|
|
7849
|
+
*
|
|
7850
|
+
* Anchoring and control are separate: `anchor` re-anchors the panel to any
|
|
7851
|
+
* element (or id) while the trigger keeps the disclosure semantics.
|
|
7852
|
+
*/
|
|
7853
|
+
class UniPopoverComponent extends BaseComponent {
|
|
6815
7854
|
renderer = inject(Renderer2);
|
|
6816
|
-
|
|
7855
|
+
destroyRef = inject(DestroyRef);
|
|
6817
7856
|
placement = input('bottom', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
6818
7857
|
/** Light-dismiss on outside click / Escape (native popover="auto"). */
|
|
6819
7858
|
autoClose = input(true, ...(ngDevMode ? [{ debugName: "autoClose" }] : /* istanbul ignore next */ []));
|
|
7859
|
+
/** Two-way open state; light dismissal and hover timers sync back into it. */
|
|
7860
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
7861
|
+
/** `rich` = click-toggled disclosure; `tooltip` = hover/focus description. */
|
|
7862
|
+
mode = input('rich', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
7863
|
+
/**
|
|
7864
|
+
* Anchor the panel to another element (or element id, resolved each time
|
|
7865
|
+
* the panel opens) while the projected trigger keeps the disclosure ARIA.
|
|
7866
|
+
* Empty string means unset.
|
|
7867
|
+
*/
|
|
7868
|
+
anchor = input(...(ngDevMode ? [undefined, { debugName: "anchor" }] : /* istanbul ignore next */ []));
|
|
7869
|
+
/** Title rendered in the header row and used as the accessible name. */
|
|
7870
|
+
header = input(...(ngDevMode ? [undefined, { debugName: "header" }] : /* istanbul ignore next */ []));
|
|
7871
|
+
/** Renders a "Close" icon button in the header row. */
|
|
7872
|
+
closable = input(false, ...(ngDevMode ? [{ debugName: "closable" }] : /* istanbul ignore next */ []));
|
|
7873
|
+
arrow = input(true, ...(ngDevMode ? [{ debugName: "arrow" }] : /* istanbul ignore next */ []));
|
|
7874
|
+
/** Panel max-width; numbers are px. Defaults to the theme's `maxWidth`. */
|
|
7875
|
+
maxWidth = input(...(ngDevMode ? [undefined, { debugName: "maxWidth" }] : /* istanbul ignore next */ []));
|
|
7876
|
+
/** Tooltip-mode hover-open delay, ms; defaults to the theme option. */
|
|
7877
|
+
openDelay = input(...(ngDevMode ? [undefined, { debugName: "openDelay" }] : /* istanbul ignore next */ []));
|
|
7878
|
+
/** Tooltip-mode pointer-leave close delay, ms; defaults to the theme option. */
|
|
7879
|
+
closeDelay = input(...(ngDevMode ? [undefined, { debugName: "closeDelay" }] : /* istanbul ignore next */ []));
|
|
7880
|
+
opened = output();
|
|
7881
|
+
closed = output();
|
|
6820
7882
|
panelId = uniqueId('uni-popover');
|
|
7883
|
+
headerId = uniqueId('uni-popover-header');
|
|
6821
7884
|
anchorName = newAnchorName();
|
|
6822
7885
|
showing = signal(false, ...(ngDevMode ? [{ debugName: "showing" }] : /* istanbul ignore next */ []));
|
|
7886
|
+
/** Whether the `[trigger]` slot projected any element content. */
|
|
7887
|
+
hasTrigger = signal(false, ...(ngDevMode ? [{ debugName: "hasTrigger" }] : /* istanbul ignore next */ []));
|
|
6823
7888
|
triggerRef = viewChild.required('trigger');
|
|
6824
7889
|
panelRef = viewChild.required('panel');
|
|
7890
|
+
openTimer = useTimer();
|
|
7891
|
+
closeTimer = useTimer();
|
|
7892
|
+
/** The element currently carrying our anchor-name. */
|
|
7893
|
+
anchoredEl = null;
|
|
7894
|
+
detachEscape = null;
|
|
6825
7895
|
constructor() {
|
|
6826
|
-
|
|
7896
|
+
super();
|
|
6827
7897
|
afterNextRender(() => {
|
|
7898
|
+
this.hasTrigger.set(this.triggerRef().nativeElement.childElementCount > 0);
|
|
7899
|
+
this.applyAnchor();
|
|
7900
|
+
});
|
|
7901
|
+
// Controller ARIA follows mode/open state; each branch removes the other
|
|
7902
|
+
// mode's attributes so a runtime mode flip never leaves stale semantics.
|
|
7903
|
+
effect(() => {
|
|
7904
|
+
if (!this.hasTrigger())
|
|
7905
|
+
return;
|
|
6828
7906
|
const target = resolveFocusTarget(this.triggerRef().nativeElement);
|
|
6829
|
-
this.
|
|
6830
|
-
|
|
7907
|
+
if (this.mode() === 'tooltip') {
|
|
7908
|
+
this.renderer.removeAttribute(target, 'aria-expanded');
|
|
7909
|
+
this.renderer.removeAttribute(target, 'aria-controls');
|
|
7910
|
+
this.renderer.setAttribute(target, 'aria-describedby', this.panelId);
|
|
7911
|
+
}
|
|
7912
|
+
else {
|
|
7913
|
+
this.renderer.removeAttribute(target, 'aria-describedby');
|
|
7914
|
+
this.renderer.setAttribute(target, 'aria-expanded', `${this.showing()}`);
|
|
7915
|
+
this.renderer.setAttribute(target, 'aria-controls', this.panelId);
|
|
7916
|
+
}
|
|
7917
|
+
});
|
|
7918
|
+
// Two-way `open`: drive the native popover when the model changes hands.
|
|
7919
|
+
effect(() => {
|
|
7920
|
+
const want = this.open();
|
|
7921
|
+
if (want === untracked(this.showing))
|
|
7922
|
+
return;
|
|
7923
|
+
if (want)
|
|
7924
|
+
this.showPopover();
|
|
7925
|
+
else
|
|
7926
|
+
this.hidePopover();
|
|
7927
|
+
});
|
|
7928
|
+
this.destroyRef.onDestroy(() => {
|
|
7929
|
+
this.detachEscape?.();
|
|
7930
|
+
if (this.anchoredEl)
|
|
7931
|
+
clearAnchorName(this.anchoredEl);
|
|
7932
|
+
try {
|
|
7933
|
+
this.panelRef().nativeElement.hidePopover();
|
|
7934
|
+
}
|
|
7935
|
+
catch {
|
|
7936
|
+
// popover was already closed or detached
|
|
7937
|
+
}
|
|
6831
7938
|
});
|
|
6832
7939
|
}
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
this.showing.set(open);
|
|
6836
|
-
this.renderer.setAttribute(resolveFocusTarget(this.triggerRef().nativeElement), 'aria-expanded', `${open}`);
|
|
6837
|
-
}
|
|
7940
|
+
/** Tooltip mode is always `manual` — its lifecycle belongs to the timers. */
|
|
7941
|
+
popoverKind = computed(() => this.mode() === 'tooltip' ? 'manual' : this.autoClose() ? 'auto' : 'manual', ...(ngDevMode ? [{ debugName: "popoverKind" }] : /* istanbul ignore next */ []));
|
|
6838
7942
|
showPopover() {
|
|
7943
|
+
if (this.showing())
|
|
7944
|
+
return;
|
|
7945
|
+
this.applyAnchor();
|
|
6839
7946
|
this.panelRef().nativeElement.showPopover();
|
|
6840
7947
|
}
|
|
6841
7948
|
hidePopover() {
|
|
7949
|
+
if (!this.showing())
|
|
7950
|
+
return;
|
|
6842
7951
|
this.panelRef().nativeElement.hidePopover();
|
|
6843
7952
|
}
|
|
6844
7953
|
togglePopover(event) {
|
|
@@ -6848,60 +7957,198 @@ class UniPopoverComponent {
|
|
|
6848
7957
|
else {
|
|
6849
7958
|
this.showPopover();
|
|
6850
7959
|
}
|
|
6851
|
-
event
|
|
7960
|
+
event?.stopPropagation();
|
|
6852
7961
|
}
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
7962
|
+
/**
|
|
7963
|
+
* The panel anchors to the detached `anchor` when one resolves (id strings
|
|
7964
|
+
* are looked up now, at open time), else to the trigger span.
|
|
7965
|
+
*/
|
|
7966
|
+
applyAnchor() {
|
|
7967
|
+
const el = resolveElement(this.anchor()) ?? this.triggerRef().nativeElement;
|
|
7968
|
+
if (this.anchoredEl === el)
|
|
7969
|
+
return;
|
|
7970
|
+
if (this.anchoredEl)
|
|
7971
|
+
clearAnchorName(this.anchoredEl);
|
|
7972
|
+
setAnchorName(el, this.anchorName);
|
|
7973
|
+
this.anchoredEl = el;
|
|
7974
|
+
}
|
|
7975
|
+
onToggle(event) {
|
|
7976
|
+
const isOpen = isToggleOpen(event);
|
|
7977
|
+
this.showing.set(isOpen);
|
|
7978
|
+
this.open.set(isOpen);
|
|
7979
|
+
const panel = this.panelRef().nativeElement;
|
|
7980
|
+
if (isOpen) {
|
|
7981
|
+
if (this.mode() === 'rich') {
|
|
7982
|
+
panel.querySelector('[autofocus]')?.focus();
|
|
7983
|
+
}
|
|
7984
|
+
else {
|
|
7985
|
+
this.attachTooltipEscape();
|
|
7986
|
+
if (typeof ngDevMode !== 'undefined' && ngDevMode && panel.querySelector(FOCUSABLE_SELECTOR)) {
|
|
7987
|
+
console.warn('[uni-popover] tooltip-mode content must not contain focusable elements — ' +
|
|
7988
|
+
'a tooltip is a description, not a surface. Use mode="rich" instead.');
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
this.opened.emit();
|
|
7992
|
+
}
|
|
7993
|
+
else {
|
|
7994
|
+
this.detachEscape?.();
|
|
7995
|
+
if (this.mode() === 'rich' && this.hasTrigger()) {
|
|
7996
|
+
restoreOverlayFocus(panel, resolveFocusTarget(this.triggerRef().nativeElement));
|
|
7997
|
+
}
|
|
7998
|
+
this.closed.emit();
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
closeFromButton() {
|
|
8002
|
+
this.hidePopover();
|
|
8003
|
+
if (this.hasTrigger())
|
|
8004
|
+
resolveFocusTarget(this.triggerRef().nativeElement).focus();
|
|
8005
|
+
}
|
|
8006
|
+
// --- rich-mode gesture ----------------------------------------------------
|
|
8007
|
+
onTriggerClick(event) {
|
|
8008
|
+
if (this.mode() !== 'rich')
|
|
8009
|
+
return;
|
|
8010
|
+
this.togglePopover(event);
|
|
8011
|
+
}
|
|
8012
|
+
// --- tooltip-mode gestures (WCAG 1.4.13) ----------------------------------
|
|
8013
|
+
openDelayMs = computed(() => this.openDelay() ?? this.componentOptions().tooltipOpenDelay, ...(ngDevMode ? [{ debugName: "openDelayMs" }] : /* istanbul ignore next */ []));
|
|
8014
|
+
closeDelayMs = computed(() => this.closeDelay() ?? this.componentOptions().tooltipCloseDelay, ...(ngDevMode ? [{ debugName: "closeDelayMs" }] : /* istanbul ignore next */ []));
|
|
8015
|
+
onTriggerEnter() {
|
|
8016
|
+
if (this.mode() !== 'tooltip')
|
|
8017
|
+
return;
|
|
8018
|
+
this.closeTimer.stop();
|
|
8019
|
+
this.openTimer.start(this.openDelayMs(), () => this.showPopover());
|
|
8020
|
+
}
|
|
8021
|
+
onTriggerLeave() {
|
|
8022
|
+
if (this.mode() !== 'tooltip')
|
|
8023
|
+
return;
|
|
8024
|
+
this.openTimer.stop();
|
|
8025
|
+
if (this.showing())
|
|
8026
|
+
this.closeTimer.start(this.closeDelayMs(), () => this.hidePopover());
|
|
8027
|
+
}
|
|
8028
|
+
onTriggerFocusIn() {
|
|
8029
|
+
if (this.mode() !== 'tooltip')
|
|
8030
|
+
return;
|
|
8031
|
+
this.closeTimer.stop();
|
|
8032
|
+
this.showPopover();
|
|
8033
|
+
}
|
|
8034
|
+
onTriggerFocusOut() {
|
|
8035
|
+
if (this.mode() !== 'tooltip')
|
|
8036
|
+
return;
|
|
8037
|
+
this.openTimer.stop();
|
|
8038
|
+
this.closeTimer.stop();
|
|
8039
|
+
this.hidePopover();
|
|
8040
|
+
}
|
|
8041
|
+
/** Resting the pointer inside the panel must not dismiss it (hoverable). */
|
|
8042
|
+
onPanelEnter() {
|
|
8043
|
+
if (this.mode() !== 'tooltip')
|
|
8044
|
+
return;
|
|
8045
|
+
this.closeTimer.stop();
|
|
8046
|
+
}
|
|
8047
|
+
onPanelLeave() {
|
|
8048
|
+
if (this.mode() !== 'tooltip')
|
|
8049
|
+
return;
|
|
8050
|
+
if (this.showing())
|
|
8051
|
+
this.closeTimer.start(this.closeDelayMs(), () => this.hidePopover());
|
|
8052
|
+
}
|
|
8053
|
+
/**
|
|
8054
|
+
* Tooltip panels are `manual`, so Escape is ours: dismiss without moving
|
|
8055
|
+
* focus, and stop propagation so an enclosing dialog stays open. Capture
|
|
8056
|
+
* phase, document-level — the pointer may be nowhere near the trigger.
|
|
8057
|
+
*/
|
|
8058
|
+
attachTooltipEscape() {
|
|
8059
|
+
const onKeydown = (event) => {
|
|
8060
|
+
if (event.key !== 'Escape')
|
|
8061
|
+
return;
|
|
8062
|
+
event.stopPropagation();
|
|
8063
|
+
this.openTimer.stop();
|
|
8064
|
+
this.closeTimer.stop();
|
|
8065
|
+
this.hidePopover();
|
|
8066
|
+
};
|
|
8067
|
+
document.addEventListener('keydown', onKeydown, true);
|
|
8068
|
+
this.detachEscape = () => {
|
|
8069
|
+
document.removeEventListener('keydown', onKeydown, true);
|
|
8070
|
+
this.detachEscape = null;
|
|
8071
|
+
};
|
|
8072
|
+
}
|
|
8073
|
+
// --- styling --------------------------------------------------------------
|
|
8074
|
+
triggerClassName = css({ display: 'inline-block' });
|
|
8075
|
+
resolvedMaxWidth = computed(() => {
|
|
8076
|
+
const width = this.maxWidth();
|
|
8077
|
+
if (width === undefined || width === '')
|
|
8078
|
+
return this.componentOptions().maxWidth;
|
|
8079
|
+
return typeof width === 'number' ? `${width}px` : width;
|
|
8080
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedMaxWidth" }] : /* istanbul ignore next */ []));
|
|
8081
|
+
popoverClassName = computed(() => {
|
|
8082
|
+
const options = this.componentOptions();
|
|
8083
|
+
return css({
|
|
8084
|
+
...this.theme.colorPair(options.color),
|
|
8085
|
+
...this.theme.radius(options.borderRadius),
|
|
8086
|
+
...this.theme.boxShadow(options.shadow),
|
|
8087
|
+
...this.theme.typeface(options.typeface),
|
|
8088
|
+
...this.theme.border(options.border),
|
|
8089
|
+
padding: 0,
|
|
6865
8090
|
width: 'max-content',
|
|
8091
|
+
maxWidth: this.resolvedMaxWidth(),
|
|
6866
8092
|
overflow: 'visible',
|
|
6867
|
-
...anchorStyles(this.anchorName, this.placement(), { mainAxis:
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
'
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
},
|
|
6881
|
-
}
|
|
6882
|
-
|
|
8093
|
+
...anchorStyles(this.anchorName, this.placement(), { mainAxis: options.offset }),
|
|
8094
|
+
...discreteOverlayTransition(250, { opacity: 0 }, { opacity: 1 }),
|
|
8095
|
+
});
|
|
8096
|
+
}, ...(ngDevMode ? [{ debugName: "popoverClassName" }] : /* istanbul ignore next */ []));
|
|
8097
|
+
/** Empty regions collapse, so bare content renders v1's single-region look. */
|
|
8098
|
+
headerRowClassName = computed(() => {
|
|
8099
|
+
const options = this.componentOptions();
|
|
8100
|
+
return css({
|
|
8101
|
+
display: 'flex',
|
|
8102
|
+
alignItems: 'center',
|
|
8103
|
+
gap: 8,
|
|
8104
|
+
padding: options.padding,
|
|
8105
|
+
paddingBottom: 0,
|
|
8106
|
+
'&:not(:has(*))': { display: 'none' },
|
|
8107
|
+
});
|
|
8108
|
+
}, ...(ngDevMode ? [{ debugName: "headerRowClassName" }] : /* istanbul ignore next */ []));
|
|
8109
|
+
titleClassName = computed(() => css({ ...this.theme.typeface(this.componentOptions().headerTypeface), marginRight: 'auto' }), ...(ngDevMode ? [{ debugName: "titleClassName" }] : /* istanbul ignore next */ []));
|
|
8110
|
+
bodyClassName = computed(() => {
|
|
8111
|
+
const options = this.componentOptions();
|
|
8112
|
+
return css({
|
|
8113
|
+
padding: this.mode() === 'tooltip' ? options.tooltipPadding : options.padding,
|
|
8114
|
+
'&:empty': { display: 'none' },
|
|
8115
|
+
});
|
|
8116
|
+
}, ...(ngDevMode ? [{ debugName: "bodyClassName" }] : /* istanbul ignore next */ []));
|
|
8117
|
+
footerRowClassName = computed(() => {
|
|
8118
|
+
const options = this.componentOptions();
|
|
8119
|
+
return css({
|
|
8120
|
+
display: 'flex',
|
|
8121
|
+
alignItems: 'center',
|
|
8122
|
+
justifyContent: 'flex-end',
|
|
8123
|
+
gap: 8,
|
|
8124
|
+
padding: options.padding,
|
|
8125
|
+
paddingTop: 0,
|
|
8126
|
+
'&:not(:has(*))': { display: 'none' },
|
|
8127
|
+
});
|
|
8128
|
+
}, ...(ngDevMode ? [{ debugName: "footerRowClassName" }] : /* istanbul ignore next */ []));
|
|
6883
8129
|
arrowClassName = computed(() => {
|
|
8130
|
+
const options = this.componentOptions();
|
|
6884
8131
|
const side = this.placement().split('-')[0];
|
|
6885
|
-
// Border on the two edges of the
|
|
8132
|
+
// Border on the two edges of the clipped half that face outward
|
|
6886
8133
|
const borders = {
|
|
6887
|
-
top: { ...this.theme.borderRight(
|
|
6888
|
-
bottom: { ...this.theme.borderLeft(
|
|
6889
|
-
left: { ...this.theme.borderRight(
|
|
6890
|
-
right: { ...this.theme.borderLeft(
|
|
8134
|
+
top: { ...this.theme.borderRight(options.border), ...this.theme.borderBottom(options.border) },
|
|
8135
|
+
bottom: { ...this.theme.borderLeft(options.border), ...this.theme.borderTop(options.border) },
|
|
8136
|
+
left: { ...this.theme.borderRight(options.border), ...this.theme.borderTop(options.border) },
|
|
8137
|
+
right: { ...this.theme.borderLeft(options.border), ...this.theme.borderBottom(options.border) },
|
|
6891
8138
|
};
|
|
6892
8139
|
return css({
|
|
6893
|
-
...this.theme.colorPair(
|
|
6894
|
-
...anchorArrowStyles(this.placement()),
|
|
8140
|
+
...this.theme.colorPair(options.color),
|
|
8141
|
+
...anchorArrowStyles(this.placement(), options.arrowSize),
|
|
6895
8142
|
...borders[side],
|
|
6896
8143
|
});
|
|
6897
8144
|
}, ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
|
|
6898
8145
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6899
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
8146
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniPopoverComponent, isStandalone: true, selector: "uni-popover", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, autoClose: { classPropertyName: "autoClose", publicName: "autoClose", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, arrow: { classPropertyName: "arrow", publicName: "arrow", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, openDelay: { classPropertyName: "openDelay", publicName: "openDelay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", opened: "opened", closed: "closed" }, providers: [{ provide: COMPONENT_NAME, useValue: 'popover' }], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Click bubbles up from the projected [trigger] content, which is expected\n to be focusable (a button); keyboard activation arrives as a click. -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n<span\n #trigger\n [class]=\"triggerClassName\"\n (click)=\"onTriggerClick($event)\"\n (mouseenter)=\"onTriggerEnter()\"\n (mouseleave)=\"onTriggerLeave()\"\n (focusin)=\"onTriggerFocusIn()\"\n (focusout)=\"onTriggerFocusOut()\"\n>\n <ng-content select=\"[trigger]\"></ng-content>\n</span>\n<div\n #panel\n [id]=\"panelId\"\n [attr.popover]=\"popoverKind()\"\n [attr.role]=\"mode() === 'tooltip' ? 'tooltip' : null\"\n [attr.aria-labelledby]=\"header() ? headerId : null\"\n [class]=\"popoverClassName()\"\n (toggle)=\"onToggle($event)\"\n (mouseenter)=\"onPanelEnter()\"\n (mouseleave)=\"onPanelLeave()\"\n>\n <div [class]=\"headerRowClassName()\">\n <ng-content select=\"[popover-header]\"></ng-content>\n @if (header()) {\n <span [id]=\"headerId\" [class]=\"titleClassName()\">{{ header() }}</span>\n }\n @if (closable()) {\n <button\n icon-button\n [iconName]=\"componentOptions().closeSymbol\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"closeFromButton()\"\n >\n Close\n </button>\n }\n </div>\n <div [class]=\"bodyClassName()\"><ng-content></ng-content></div>\n <div [class]=\"footerRowClassName()\"><ng-content select=\"[popover-footer]\"></ng-content></div>\n @if (arrow()) {\n <span [class]=\"arrowClassName()\"></span>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6900
8147
|
}
|
|
6901
8148
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniPopoverComponent, decorators: [{
|
|
6902
8149
|
type: Component,
|
|
6903
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-popover', imports: [], template: "<!-- Click bubbles up from the projected [trigger] content, which is expected\n to be focusable (a button); keyboard activation arrives as a click. -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n<span
|
|
6904
|
-
}], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], autoClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoClose", required: false }] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
8150
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-popover', imports: [UniIconButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'popover' }], template: "<!-- Click bubbles up from the projected [trigger] content, which is expected\n to be focusable (a button); keyboard activation arrives as a click. -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n<span\n #trigger\n [class]=\"triggerClassName\"\n (click)=\"onTriggerClick($event)\"\n (mouseenter)=\"onTriggerEnter()\"\n (mouseleave)=\"onTriggerLeave()\"\n (focusin)=\"onTriggerFocusIn()\"\n (focusout)=\"onTriggerFocusOut()\"\n>\n <ng-content select=\"[trigger]\"></ng-content>\n</span>\n<div\n #panel\n [id]=\"panelId\"\n [attr.popover]=\"popoverKind()\"\n [attr.role]=\"mode() === 'tooltip' ? 'tooltip' : null\"\n [attr.aria-labelledby]=\"header() ? headerId : null\"\n [class]=\"popoverClassName()\"\n (toggle)=\"onToggle($event)\"\n (mouseenter)=\"onPanelEnter()\"\n (mouseleave)=\"onPanelLeave()\"\n>\n <div [class]=\"headerRowClassName()\">\n <ng-content select=\"[popover-header]\"></ng-content>\n @if (header()) {\n <span [id]=\"headerId\" [class]=\"titleClassName()\">{{ header() }}</span>\n }\n @if (closable()) {\n <button\n icon-button\n [iconName]=\"componentOptions().closeSymbol\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"closeFromButton()\"\n >\n Close\n </button>\n }\n </div>\n <div [class]=\"bodyClassName()\"><ng-content></ng-content></div>\n <div [class]=\"footerRowClassName()\"><ng-content select=\"[popover-footer]\"></ng-content></div>\n @if (arrow()) {\n <span [class]=\"arrowClassName()\"></span>\n }\n</div>\n" }]
|
|
8151
|
+
}], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], autoClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoClose", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], arrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrow", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], openDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDelay", required: false }] }], closeDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeDelay", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
6905
8152
|
|
|
6906
8153
|
/**
|
|
6907
8154
|
* UniPopoverComponent Barrel File
|
|
@@ -7218,35 +8465,7 @@ class UniSearchInputComponent extends BaseComponent {
|
|
|
7218
8465
|
...this.theme.paddingLeft('sm'),
|
|
7219
8466
|
},
|
|
7220
8467
|
}), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
7221
|
-
listClass = computed(() => {
|
|
7222
|
-
const options = this.componentOptions();
|
|
7223
|
-
return css({
|
|
7224
|
-
position: 'absolute',
|
|
7225
|
-
top: '100%',
|
|
7226
|
-
left: 0,
|
|
7227
|
-
right: 0,
|
|
7228
|
-
zIndex: 20,
|
|
7229
|
-
margin: '4px 0 0',
|
|
7230
|
-
padding: 4,
|
|
7231
|
-
listStyle: 'none',
|
|
7232
|
-
maxHeight: 280,
|
|
7233
|
-
overflowY: 'auto',
|
|
7234
|
-
...this.theme.backgroundColor(options.listColor ?? 'primary-surface'),
|
|
7235
|
-
...this.theme.boxShadow(options.listShadow ?? 'menu'),
|
|
7236
|
-
...this.theme.radius(options.listBorderRadius ?? 'xs'),
|
|
7237
|
-
'& [role="option"]': {
|
|
7238
|
-
padding: '8px 12px',
|
|
7239
|
-
cursor: 'pointer',
|
|
7240
|
-
...this.theme.typeface('label'),
|
|
7241
|
-
...this.theme.color('on-primary-surface'),
|
|
7242
|
-
...this.theme.radius('xxs'),
|
|
7243
|
-
'&.active, &:hover': {
|
|
7244
|
-
...this.theme.backgroundColor('primary-container'),
|
|
7245
|
-
...this.theme.color('on-primary-container'),
|
|
7246
|
-
},
|
|
7247
|
-
},
|
|
7248
|
-
});
|
|
7249
|
-
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
8468
|
+
listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions())), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
7250
8469
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSearchInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
7251
8470
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSearchInputComponent, isStandalone: true, selector: "uni-search-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change", search: "search", suggestionSelected: "suggestionSelected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], viewQueries: [{ propertyName: "field", first: true, predicate: UniDebounceInputComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul [id]=\"listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniDebounceInputComponent, selector: "uni-debounce-input", inputs: ["inputName", "inputId", "debounceTime", "label", "placeholder", "disabled", "role", "ariaExpanded", "ariaControls", "ariaActivedescendant"], outputs: ["change"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7252
8471
|
}
|
|
@@ -7278,6 +8497,14 @@ class UniSelectComponent {
|
|
|
7278
8497
|
// --- CONFIGURATION ---
|
|
7279
8498
|
options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
7280
8499
|
placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
8500
|
+
/**
|
|
8501
|
+
* Equality used to match `value` against option values, called as
|
|
8502
|
+
* `compareWith(optionValue, value)`. Defaults to reference equality, which
|
|
8503
|
+
* never matches an object value rebuilt from elsewhere (e.g. a saved record
|
|
8504
|
+
* against options from a fresh fetch) — pass a key comparison like
|
|
8505
|
+
* `(a, b) => a?.id === b?.id` for object values.
|
|
8506
|
+
*/
|
|
8507
|
+
compareWith = input((a, b) => a === b, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
|
|
7281
8508
|
/** Accessible name for the select; a placeholder is not a label. */
|
|
7282
8509
|
ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
7283
8510
|
UNSELECTED = -1;
|
|
@@ -7290,7 +8517,7 @@ class UniSelectComponent {
|
|
|
7290
8517
|
if (currentVal === null || currentVal === undefined)
|
|
7291
8518
|
return this.UNSELECTED;
|
|
7292
8519
|
// Find the index of the option that contains our current value
|
|
7293
|
-
const index = this.options().findIndex((opt) => opt.value
|
|
8520
|
+
const index = this.options().findIndex((opt) => this.compareWith()(opt.value, currentVal));
|
|
7294
8521
|
return index.toString();
|
|
7295
8522
|
}, ...(ngDevMode ? [{ debugName: "currentSelectedIndex" }] : /* istanbul ignore next */ []));
|
|
7296
8523
|
showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
@@ -7322,12 +8549,12 @@ class UniSelectComponent {
|
|
|
7322
8549
|
pointerEvents: 'none' /* Crucial for clicking through */,
|
|
7323
8550
|
});
|
|
7324
8551
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7325
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option [value]=\"i\" [selected]=\"currentSelectedIndex() === i.toString()\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8552
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSelectComponent, isStandalone: true, selector: "uni-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option [value]=\"i\" [selected]=\"currentSelectedIndex() === i.toString()\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7326
8553
|
}
|
|
7327
8554
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSelectComponent, decorators: [{
|
|
7328
8555
|
type: Component,
|
|
7329
8556
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-select', imports: [UniInputBoxComponent, UniSymbolComponent], template: "<uni-input-box [error]=\"showError()\" [class]=\"selectClass\">\n <select\n [disabled]=\"disabled()\"\n [value]=\"currentSelectedIndex()\"\n (change)=\"handleSelectChange($event)\"\n (blur)=\"touched.set(true)\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n >\n @if (placeholder()) {\n <option [value]=\"UNSELECTED\" disabled selected>\n {{ placeholder() }}\n </option>\n }\n\n @for (opt of options(); track opt.label; let i = $index) {\n <!-- [selected] evaluates during the option's own render, so a selection\n pointing at an option added in the same change-detection pass still\n applies (the select-level [value] write lands before new options\n exist and is ignored by the browser). -->\n <option [value]=\"i\" [selected]=\"currentSelectedIndex() === i.toString()\">\n {{ opt.label }}\n </option>\n }\n </select>\n <uni-symbol name=\"keyboard_arrow_down\" [class]=\"arrowClass\" />\n</uni-input-box>\n" }]
|
|
7330
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
8557
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
7331
8558
|
|
|
7332
8559
|
/**
|
|
7333
8560
|
* Loading placeholder painted with surface tokens. `text` renders one or more
|
|
@@ -8508,35 +9735,7 @@ class UniTagInputComponent extends BaseComponent {
|
|
|
8508
9735
|
font: 'inherit',
|
|
8509
9736
|
padding: 0,
|
|
8510
9737
|
}), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
|
|
8511
|
-
listClass = computed(() => {
|
|
8512
|
-
const options = this.componentOptions();
|
|
8513
|
-
return css({
|
|
8514
|
-
position: 'absolute',
|
|
8515
|
-
top: '100%',
|
|
8516
|
-
left: 0,
|
|
8517
|
-
right: 0,
|
|
8518
|
-
zIndex: 20,
|
|
8519
|
-
margin: '4px 0 0',
|
|
8520
|
-
padding: 4,
|
|
8521
|
-
listStyle: 'none',
|
|
8522
|
-
maxHeight: 280,
|
|
8523
|
-
overflowY: 'auto',
|
|
8524
|
-
...this.theme.backgroundColor(options.listColor ?? 'primary-surface'),
|
|
8525
|
-
...this.theme.boxShadow(options.listShadow ?? 'menu'),
|
|
8526
|
-
...this.theme.radius(options.listBorderRadius ?? 'xs'),
|
|
8527
|
-
'& [role="option"]': {
|
|
8528
|
-
padding: '8px 12px',
|
|
8529
|
-
cursor: 'pointer',
|
|
8530
|
-
...this.theme.typeface('label'),
|
|
8531
|
-
...this.theme.color('on-primary-surface'),
|
|
8532
|
-
...this.theme.radius('xxs'),
|
|
8533
|
-
'&.active, &:hover': {
|
|
8534
|
-
...this.theme.backgroundColor('primary-container'),
|
|
8535
|
-
...this.theme.color('on-primary-container'),
|
|
8536
|
-
},
|
|
8537
|
-
},
|
|
8538
|
-
});
|
|
8539
|
-
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
9738
|
+
listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions())), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
8540
9739
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
8541
9740
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagInputComponent, isStandalone: true, selector: "uni-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, tagVariant: { classPropertyName: "tagVariant", publicName: "tagVariant", isSignal: true, isRequired: false, transformFunction: null }, tagTone: { classPropertyName: "tagTone", publicName: "tagTone", isSignal: true, isRequired: false, transformFunction: null }, tagSize: { classPropertyName: "tagSize", publicName: "tagSize", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", added: "added", removed: "removed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "chipRefs", predicate: ["chip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div style=\"position: relative\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniTagComponent, selector: "uni-tag", inputs: ["size", "tone", "label", "value", "maxWidth", "avatarSrc", "avatarName", "iconName", "symbolName", "dot", "removable", "interactive", "selected", "invalid", "disabled", "removeLabel", "controlTabIndex"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8542
9741
|
}
|
|
@@ -9477,7 +10676,7 @@ class UniThemeSwitchComponent {
|
|
|
9477
10676
|
(valueChange)="select($event)"
|
|
9478
10677
|
[ariaLabel]="ariaLabel()"
|
|
9479
10678
|
/>
|
|
9480
|
-
`, isInline: true, dependencies: [{ kind: "component", type: UniSelectComponent, selector: "uni-select", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "options", "placeholder", "ariaLabel"], outputs: ["valueChange", "touchedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10679
|
+
`, isInline: true, dependencies: [{ kind: "component", type: UniSelectComponent, selector: "uni-select", inputs: ["value", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "options", "placeholder", "compareWith", "ariaLabel"], outputs: ["valueChange", "touchedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9481
10680
|
}
|
|
9482
10681
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniThemeSwitchComponent, decorators: [{
|
|
9483
10682
|
type: Component,
|
|
@@ -9810,9 +11009,221 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
9810
11009
|
}]
|
|
9811
11010
|
}], 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 }] }] } });
|
|
9812
11011
|
|
|
11012
|
+
/**
|
|
11013
|
+
* A thin sequencer over one `uni-callout`: steps spotlight their targets, a
|
|
11014
|
+
* footer walks Next/Back with dots-or-fraction progress, and `advanceOn`
|
|
11015
|
+
* gates a step behind an interaction with its target (auto-advancing for
|
|
11016
|
+
* clicks, unlocking Next otherwise — announced through one `role="status"`
|
|
11017
|
+
* region). Escape or the close button skips the tour and reports the step.
|
|
11018
|
+
*
|
|
11019
|
+
* `active` is a two-way model, so a tour is deep-linkable and inspectable;
|
|
11020
|
+
* steps whose declared target cannot be resolved are skipped (with a dev
|
|
11021
|
+
* warning) in the direction of travel rather than erroring.
|
|
11022
|
+
*/
|
|
11023
|
+
class UniTourComponent extends BaseComponent {
|
|
11024
|
+
renderer = inject(Renderer2);
|
|
11025
|
+
destroyRef = inject(DestroyRef);
|
|
11026
|
+
steps = input.required(...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
|
|
11027
|
+
/** The presented step index, or null when the tour is not running. */
|
|
11028
|
+
active = model(null, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
11029
|
+
nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : /* istanbul ignore next */ []));
|
|
11030
|
+
backLabel = input('Back', ...(ngDevMode ? [{ debugName: "backLabel" }] : /* istanbul ignore next */ []));
|
|
11031
|
+
skipLabel = input('Skip', ...(ngDevMode ? [{ debugName: "skipLabel" }] : /* istanbul ignore next */ []));
|
|
11032
|
+
doneLabel = input('Done', ...(ngDevMode ? [{ debugName: "doneLabel" }] : /* istanbul ignore next */ []));
|
|
11033
|
+
started = output();
|
|
11034
|
+
stepChanged = output();
|
|
11035
|
+
finished = output();
|
|
11036
|
+
skipped = output();
|
|
11037
|
+
calloutOpen = signal(false, ...(ngDevMode ? [{ debugName: "calloutOpen" }] : /* istanbul ignore next */ []));
|
|
11038
|
+
satisfied = signal(true, ...(ngDevMode ? [{ debugName: "satisfied" }] : /* istanbul ignore next */ []));
|
|
11039
|
+
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
11040
|
+
resolvedTarget = signal(undefined, ...(ngDevMode ? [{ debugName: "resolvedTarget" }] : /* istanbul ignore next */ []));
|
|
11041
|
+
presentedIndex = signal(null, ...(ngDevMode ? [{ debugName: "presentedIndex" }] : /* istanbul ignore next */ []));
|
|
11042
|
+
gateCleanup = null;
|
|
11043
|
+
index = computed(() => this.presentedIndex() ?? 0, ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
11044
|
+
currentStep = computed(() => {
|
|
11045
|
+
const index = this.presentedIndex();
|
|
11046
|
+
return index === null ? undefined : this.steps()[index];
|
|
11047
|
+
}, ...(ngDevMode ? [{ debugName: "currentStep" }] : /* istanbul ignore next */ []));
|
|
11048
|
+
isLast = computed(() => this.index() >= this.steps().length - 1, ...(ngDevMode ? [{ debugName: "isLast" }] : /* istanbul ignore next */ []));
|
|
11049
|
+
clickGate = computed(() => this.currentStep()?.advanceOn?.event === 'click', ...(ngDevMode ? [{ debugName: "clickGate" }] : /* istanbul ignore next */ []));
|
|
11050
|
+
/** Gated steps force an interactive target — the gate needs the gesture. */
|
|
11051
|
+
targetInteractive = computed(() => {
|
|
11052
|
+
const step = this.currentStep();
|
|
11053
|
+
return step?.advanceOn ? true : (step?.targetInteractive ?? true);
|
|
11054
|
+
}, ...(ngDevMode ? [{ debugName: "targetInteractive" }] : /* istanbul ignore next */ []));
|
|
11055
|
+
stepAriaLabel = computed(() => {
|
|
11056
|
+
const step = this.currentStep();
|
|
11057
|
+
return step ? `${step.title}, step ${this.index() + 1} of ${this.steps().length}` : '';
|
|
11058
|
+
}, ...(ngDevMode ? [{ debugName: "stepAriaLabel" }] : /* istanbul ignore next */ []));
|
|
11059
|
+
constructor() {
|
|
11060
|
+
super();
|
|
11061
|
+
// External writes to `active` (deep links) route into the sequencer.
|
|
11062
|
+
effect(() => {
|
|
11063
|
+
const index = this.active();
|
|
11064
|
+
if (index === untracked(this.presentedIndex))
|
|
11065
|
+
return;
|
|
11066
|
+
untracked(() => (index === null ? this.stopTour() : this.present(index, 1)));
|
|
11067
|
+
});
|
|
11068
|
+
this.destroyRef.onDestroy(() => this.gateCleanup?.());
|
|
11069
|
+
}
|
|
11070
|
+
start(at = 0) {
|
|
11071
|
+
this.started.emit();
|
|
11072
|
+
this.present(at, 1);
|
|
11073
|
+
}
|
|
11074
|
+
next() {
|
|
11075
|
+
this.advance();
|
|
11076
|
+
}
|
|
11077
|
+
back() {
|
|
11078
|
+
const index = this.presentedIndex();
|
|
11079
|
+
if (index !== null && index > 0)
|
|
11080
|
+
this.present(index - 1, -1);
|
|
11081
|
+
}
|
|
11082
|
+
skip() {
|
|
11083
|
+
const step = this.currentStep();
|
|
11084
|
+
const index = this.presentedIndex();
|
|
11085
|
+
this.stopTour();
|
|
11086
|
+
if (step && index !== null)
|
|
11087
|
+
this.skipped.emit({ key: step.key, index });
|
|
11088
|
+
}
|
|
11089
|
+
advance() {
|
|
11090
|
+
const index = this.presentedIndex();
|
|
11091
|
+
if (index === null)
|
|
11092
|
+
return;
|
|
11093
|
+
if (index >= this.steps().length - 1)
|
|
11094
|
+
this.finishTour();
|
|
11095
|
+
else
|
|
11096
|
+
this.present(index + 1, 1);
|
|
11097
|
+
}
|
|
11098
|
+
/**
|
|
11099
|
+
* Present step `index`, skipping (in the direction of travel) steps whose
|
|
11100
|
+
* declared target cannot be resolved right now.
|
|
11101
|
+
*/
|
|
11102
|
+
present(index, dir) {
|
|
11103
|
+
this.gateCleanup?.();
|
|
11104
|
+
const steps = this.steps();
|
|
11105
|
+
if (index < 0 || index >= steps.length) {
|
|
11106
|
+
this.finishTour();
|
|
11107
|
+
return;
|
|
11108
|
+
}
|
|
11109
|
+
const step = steps[index];
|
|
11110
|
+
let target = null;
|
|
11111
|
+
if (step.target !== undefined && step.target !== '') {
|
|
11112
|
+
target = resolveElement(step.target);
|
|
11113
|
+
if (!target) {
|
|
11114
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
11115
|
+
console.warn(`[uni-tour] step "${step.key}" target missing — skipped`);
|
|
11116
|
+
}
|
|
11117
|
+
this.present(index + dir, dir);
|
|
11118
|
+
return;
|
|
11119
|
+
}
|
|
11120
|
+
}
|
|
11121
|
+
this.presentedIndex.set(index);
|
|
11122
|
+
this.active.set(index);
|
|
11123
|
+
this.resolvedTarget.set(target ?? undefined);
|
|
11124
|
+
this.setupGate(step, target);
|
|
11125
|
+
this.calloutOpen.set(true);
|
|
11126
|
+
this.stepChanged.emit({ key: step.key, index });
|
|
11127
|
+
}
|
|
11128
|
+
setupGate(step, target) {
|
|
11129
|
+
const gate = step.advanceOn;
|
|
11130
|
+
this.satisfied.set(!gate);
|
|
11131
|
+
if (!gate || !target)
|
|
11132
|
+
return;
|
|
11133
|
+
const auto = gate.auto ?? gate.event === 'click';
|
|
11134
|
+
const unlisten = this.renderer.listen(target, gate.event, () => {
|
|
11135
|
+
if (untracked(this.satisfied) && !auto)
|
|
11136
|
+
return;
|
|
11137
|
+
this.satisfied.set(true);
|
|
11138
|
+
if (auto) {
|
|
11139
|
+
this.gateCleanup?.();
|
|
11140
|
+
this.advance();
|
|
11141
|
+
}
|
|
11142
|
+
else {
|
|
11143
|
+
this.announcement.set('Next available');
|
|
11144
|
+
}
|
|
11145
|
+
});
|
|
11146
|
+
this.gateCleanup = () => {
|
|
11147
|
+
unlisten();
|
|
11148
|
+
this.gateCleanup = null;
|
|
11149
|
+
};
|
|
11150
|
+
}
|
|
11151
|
+
onDismissed(dismissal) {
|
|
11152
|
+
if (dismissal.reason === 'programmatic')
|
|
11153
|
+
return; // that's us, stepping or stopping
|
|
11154
|
+
const step = this.currentStep();
|
|
11155
|
+
const index = this.presentedIndex();
|
|
11156
|
+
this.stopTour();
|
|
11157
|
+
if (step && index !== null)
|
|
11158
|
+
this.skipped.emit({ key: step.key, index });
|
|
11159
|
+
}
|
|
11160
|
+
/** Arrow keys navigate only while focus is inside the panel. */
|
|
11161
|
+
onPanelKeydown(event) {
|
|
11162
|
+
if (event.key === 'ArrowRight' && this.satisfied() && !this.clickGate()) {
|
|
11163
|
+
event.preventDefault();
|
|
11164
|
+
this.advance();
|
|
11165
|
+
}
|
|
11166
|
+
else if (event.key === 'ArrowLeft' && this.index() > 0) {
|
|
11167
|
+
event.preventDefault();
|
|
11168
|
+
this.back();
|
|
11169
|
+
}
|
|
11170
|
+
}
|
|
11171
|
+
finishTour() {
|
|
11172
|
+
this.gateCleanup?.();
|
|
11173
|
+
this.calloutOpen.set(false);
|
|
11174
|
+
this.presentedIndex.set(null);
|
|
11175
|
+
this.active.set(null);
|
|
11176
|
+
this.finished.emit();
|
|
11177
|
+
}
|
|
11178
|
+
stopTour() {
|
|
11179
|
+
this.gateCleanup?.();
|
|
11180
|
+
this.calloutOpen.set(false);
|
|
11181
|
+
this.presentedIndex.set(null);
|
|
11182
|
+
this.active.set(null);
|
|
11183
|
+
}
|
|
11184
|
+
// --- styling --------------------------------------------------------------
|
|
11185
|
+
statusClassName = css(visuallyHidden);
|
|
11186
|
+
footerClassName = computed(() => css({
|
|
11187
|
+
display: 'flex',
|
|
11188
|
+
alignItems: 'center',
|
|
11189
|
+
flex: 1,
|
|
11190
|
+
gap: this.theme.spacing()[this.componentOptions().footerGap],
|
|
11191
|
+
}), ...(ngDevMode ? [{ debugName: "footerClassName" }] : /* istanbul ignore next */ []));
|
|
11192
|
+
dotsClassName = computed(() => {
|
|
11193
|
+
const colors = this.theme.colorPalette();
|
|
11194
|
+
const on = colors[this.variant()] ?? colors['primary'];
|
|
11195
|
+
return css({
|
|
11196
|
+
display: 'inline-flex',
|
|
11197
|
+
gap: 5,
|
|
11198
|
+
margin: '0 auto',
|
|
11199
|
+
'& i': {
|
|
11200
|
+
width: 6,
|
|
11201
|
+
height: 6,
|
|
11202
|
+
borderRadius: 999,
|
|
11203
|
+
background: colors['surface-variant'],
|
|
11204
|
+
border: `1px solid ${colors['outline'] ?? colors['surface-variant']}`,
|
|
11205
|
+
},
|
|
11206
|
+
'& i.on': { background: on, borderColor: on },
|
|
11207
|
+
});
|
|
11208
|
+
}, ...(ngDevMode ? [{ debugName: "dotsClassName" }] : /* istanbul ignore next */ []));
|
|
11209
|
+
fractionClassName = css({ margin: '0 auto' });
|
|
11210
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTourComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11211
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTourComponent, isStandalone: true, selector: "uni-tour", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { active: "activeChange", started: "started", stepChanged: "stepChanged", finished: "finished", skipped: "skipped" }, providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], usesInheritance: true, ngImport: i0, template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcement() }}</span>\n", dependencies: [{ kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniCalloutComponent, selector: "uni-callout", inputs: ["open", "key", "target", "placement", "backdrop", "targetInteractive", "dismissible", "dismissOnBackdrop", "header", "arrow", "ariaLabel", "closeLabel"], outputs: ["openChange", "opened", "closed", "dismissed", "panelKeydown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11212
|
+
}
|
|
11213
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTourComponent, decorators: [{
|
|
11214
|
+
type: Component,
|
|
11215
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tour', imports: [UniButtonComponent, UniCalloutComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcement() }}</span>\n" }]
|
|
11216
|
+
}], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }, { type: i0.Output, args: ["activeChange"] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], backLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLabel", required: false }] }], skipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], started: [{ type: i0.Output, args: ["started"] }], stepChanged: [{ type: i0.Output, args: ["stepChanged"] }], finished: [{ type: i0.Output, args: ["finished"] }], skipped: [{ type: i0.Output, args: ["skipped"] }] } });
|
|
11217
|
+
|
|
11218
|
+
/**
|
|
11219
|
+
* UniTourComponent Barrel File
|
|
11220
|
+
*
|
|
11221
|
+
* This file exports all public-facing elements of the tour component.
|
|
11222
|
+
*/
|
|
11223
|
+
|
|
9813
11224
|
/**
|
|
9814
11225
|
* Generated bundle index. Do not edit.
|
|
9815
11226
|
*/
|
|
9816
11227
|
|
|
9817
|
-
export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCalendarComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDateInputComponent, UniDateTimeInputComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniTimeInputComponent, UniToggleComponent, UniTooltipComponent, UniWrapComponent, acceptableFile, addDays, addMonths, anchorArrowStyles, anchorStyles, buildMonthGrid, createListboxNavigation, dayOfWeek, daysInMonth, formatDate, formatMonthHeading, formatTime, getFileExtension, inclusiveDayCount, isDivider, isValidDate, isoDate, joinDateTime, localeDatePlaceholder, localeDefaultHour12, localeFieldOrder, localeMonthNames, localeWeekStart, monthOf, motionSafe, newAnchorName, parseDateText, parseTimeText, resolveFocusTarget, splitDateTime, timeSlots, todayIso, uniqueId, useTimer, visuallyHidden, weekdayNames };
|
|
11228
|
+
export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, TRANSFORM_ORIGINS, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCalendarComponent, UniCalloutComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniComboboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDateInputComponent, UniDateTimeInputComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniTimeInputComponent, UniToggleComponent, UniTooltipComponent, UniTourComponent, UniWrapComponent, acceptableFile, addDays, addMonths, anchorArrowStyles, anchorStyles, buildMonthGrid, clearAnchorName, createListboxNavigation, dayOfWeek, daysInMonth, discreteOverlayTransition, focusableElements, formatDate, formatMonthHeading, formatTime, getFileExtension, inclusiveDayCount, isDivider, isToggleOpen, isValidDate, isoDate, joinDateTime, listboxPopupStyles, localeDatePlaceholder, localeDefaultHour12, localeFieldOrder, localeMonthNames, localeWeekStart, monthOf, motionSafe, newAnchorName, parseDateText, parseTimeText, resolveElement, resolveFocusTarget, restoreOverlayFocus, setAnchorName, splitDateTime, spotlightStyles, timeSlots, todayIso, uniqueId, useTimer, visuallyHidden, weekdayNames };
|
|
9818
11229
|
//# sourceMappingURL=uni-design-system-uni-angular.mjs.map
|