@uni-design-system/uni-angular 8.2.0 → 8.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -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,181 @@ 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
|
+
* Transform origin for a panel's open/close scale animation, derived from
|
|
642
|
+
* where the panel **actually** rendered relative to its anchor — not from the
|
|
643
|
+
* requested placement. `position-try-fallbacks` lets the browser flip a panel
|
|
644
|
+
* at viewport edges, and a statically mapped origin then animates from the
|
|
645
|
+
* wrong corner (a `bottom-end` picker flipped above its field would still
|
|
646
|
+
* scale from `top right`). Measure after the popover is shown and apply the
|
|
647
|
+
* result as an inline style.
|
|
648
|
+
*
|
|
649
|
+
* Returns keyword pairs like `'top right'` / `'bottom center'`, or `null`
|
|
650
|
+
* when the panel has no box yet (e.g. `display: none`, or jsdom).
|
|
651
|
+
*/
|
|
652
|
+
function transformOriginFor(panel, trigger) {
|
|
653
|
+
if (!panel.width && !panel.height)
|
|
654
|
+
return null;
|
|
655
|
+
// Which side of the trigger the panel sits on wins; when it overlaps on an
|
|
656
|
+
// axis (aligned placements), the closer-aligned edge is the anchored one.
|
|
657
|
+
const axis = (panelStart, panelEnd, triggerStart, triggerEnd) => {
|
|
658
|
+
if (panelStart >= triggerEnd)
|
|
659
|
+
return 'start'; // panel after the trigger: grows away from its start edge
|
|
660
|
+
if (panelEnd <= triggerStart)
|
|
661
|
+
return 'end'; // panel before the trigger: grows toward its end edge
|
|
662
|
+
const startGap = Math.abs(panelStart - triggerStart);
|
|
663
|
+
const endGap = Math.abs(panelEnd - triggerEnd);
|
|
664
|
+
if (Math.abs(startGap - endGap) <= 1)
|
|
665
|
+
return 'center';
|
|
666
|
+
return startGap < endGap ? 'start' : 'end';
|
|
667
|
+
};
|
|
668
|
+
const y = axis(panel.top, panel.bottom, trigger.top, trigger.bottom);
|
|
669
|
+
const x = axis(panel.left, panel.right, trigger.left, trigger.right);
|
|
670
|
+
const vertical = y === 'start' ? 'top' : y === 'end' ? 'bottom' : 'center';
|
|
671
|
+
const horizontal = x === 'start' ? 'left' : x === 'end' ? 'right' : 'center';
|
|
672
|
+
return `${vertical} ${horizontal}`;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Shared plumbing for top-layer overlays built on the native `popover`
|
|
677
|
+
* attribute (dropdown, popover, callout). Pure functions and data — no DOM
|
|
678
|
+
* ownership — so each component keeps its own template while the anchor
|
|
679
|
+
* bookkeeping, discrete-transition block, and focus-restore rule stay single-
|
|
680
|
+
* sourced.
|
|
681
|
+
*/
|
|
682
|
+
/** Placement → `transform-origin`, so scale animations grow from the anchor. */
|
|
683
|
+
const TRANSFORM_ORIGINS = {
|
|
684
|
+
top: 'bottom center',
|
|
685
|
+
right: 'center left',
|
|
686
|
+
bottom: 'top center',
|
|
687
|
+
left: 'center right',
|
|
688
|
+
'top-start': 'bottom left',
|
|
689
|
+
'top-end': 'bottom right',
|
|
690
|
+
'right-start': 'top left',
|
|
691
|
+
'right-end': 'bottom left',
|
|
692
|
+
'bottom-start': 'top left',
|
|
693
|
+
'bottom-end': 'top right',
|
|
694
|
+
'left-start': 'top right',
|
|
695
|
+
'left-end': 'bottom right',
|
|
696
|
+
};
|
|
697
|
+
/** Write a CSS `anchor-name` onto an element (detached anchors and targets). */
|
|
698
|
+
function setAnchorName(el, name) {
|
|
699
|
+
el.style.setProperty('anchor-name', name);
|
|
700
|
+
}
|
|
701
|
+
function clearAnchorName(el) {
|
|
702
|
+
el.style.removeProperty('anchor-name');
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Resolve an element-or-id reference. Id strings are looked up at call time —
|
|
706
|
+
* never cache the result across opens, the element may have been re-rendered.
|
|
707
|
+
* `''` and null-ish mean "unset" (falsy guard: an empty string is the only
|
|
708
|
+
* typeable empty for a string-typed input).
|
|
709
|
+
*/
|
|
710
|
+
function resolveElement(ref) {
|
|
711
|
+
if (!ref)
|
|
712
|
+
return null;
|
|
713
|
+
return typeof ref === 'string' ? document.getElementById(ref) : ref;
|
|
714
|
+
}
|
|
715
|
+
/** True when a native `toggle` event reports the overlay opening. */
|
|
716
|
+
function isToggleOpen(event) {
|
|
717
|
+
return event.newState === 'open';
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* The discrete-transition block that animates an element into and out of the
|
|
721
|
+
* top layer: `transition-behavior: allow-discrete` over the `hidden` keys plus
|
|
722
|
+
* `display`/`overlay`, the shown state under `:popover-open`, and
|
|
723
|
+
* `@starting-style` so entry transitions run from the hidden state.
|
|
724
|
+
*/
|
|
725
|
+
function discreteOverlayTransition(durationMs, hidden, shown) {
|
|
726
|
+
return {
|
|
727
|
+
transitionProperty: [...Object.keys(hidden), 'display', 'overlay']
|
|
728
|
+
.map((key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`))
|
|
729
|
+
.join(', '),
|
|
730
|
+
transitionDuration: `${durationMs}ms`,
|
|
731
|
+
transitionBehavior: 'allow-discrete',
|
|
732
|
+
...hidden,
|
|
733
|
+
'&:popover-open': shown,
|
|
734
|
+
'@starting-style': {
|
|
735
|
+
'&:popover-open': hidden,
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Returns focus to `target` when an overlay closes while focus was inside its
|
|
741
|
+
* panel (or was dropped on `<body>` by the top layer closing), so keyboard
|
|
742
|
+
* users are never stranded (WCAG 2.4.3). Focus resting anywhere else is left
|
|
743
|
+
* alone.
|
|
744
|
+
*/
|
|
745
|
+
function restoreOverlayFocus(panel, target) {
|
|
746
|
+
const active = document.activeElement;
|
|
747
|
+
if (active === document.body || (active && panel.contains(active))) {
|
|
748
|
+
target?.focus();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* The focusable elements inside `root`, in DOM order. Filters disabled
|
|
753
|
+
* controls, and hidden ones where the environment can tell: jsdom reports
|
|
754
|
+
* `offsetParent` as null for every element, so the visibility filter applies
|
|
755
|
+
* only when the document actually lays out (some element has an offsetParent).
|
|
756
|
+
*/
|
|
757
|
+
function focusableElements(root) {
|
|
758
|
+
const all = Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => !el.disabled);
|
|
759
|
+
const laidOut = all.some((el) => el.offsetParent !== null);
|
|
760
|
+
return laidOut ? all.filter((el) => el.offsetParent !== null) : all;
|
|
761
|
+
}
|
|
540
762
|
|
|
541
763
|
class UniBaseDatasource {
|
|
542
764
|
selections = signal([], ...(ngDevMode ? [{ debugName: "selections" }] : /* istanbul ignore next */ []));
|
|
@@ -3079,6 +3301,389 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3079
3301
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-calendar, Calendar', imports: [UniIconButtonComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'calendar' }], host: { '[class]': 'className()', '(focusout)': 'onHostFocusOut($event)' }, template: "<div [class]=\"navClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navPrevSymbol ?? 'chevron_left'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(-1)\"\n >\n Previous month\n </button>\n <!-- aria-live so PageUp/Down narrates the new month without refocusing. -->\n <div [class]=\"headingClass()\" [id]=\"headingId\" aria-live=\"polite\">{{ heading() }}</div>\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [symbolName]=\"componentOptions().navNextSymbol ?? 'chevron_right'\"\n [disable]=\"disabled()\"\n (click)=\"onNav(1)\"\n >\n Next month\n </button>\n</div>\n\n<!-- One tab stop: the roving tabindex lives on the day buttons, and the grid\n itself only relays their bubbling keys (same delegation as tag-input). -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n role=\"grid\"\n [class]=\"gridClass()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : headingId\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div role=\"row\" [class]=\"rowClass()\">\n @for (weekday of weekdays(); track weekday.full) {\n <div role=\"columnheader\" [class]=\"weekdayClass()\">\n <abbr [attr.title]=\"weekday.full\">{{ weekday.label }}</abbr>\n </div>\n }\n </div>\n\n @for (week of gridWeeks(); track week[0].date) {\n <div role=\"row\" [class]=\"rowClass()\">\n @for (cell of week; track cell.date) {\n @if (cell.outside) {\n <!-- Outside days: hidden placeholders by default (geometry kept),\n muted and non-interactive under the showOutsideDays option. -->\n <div role=\"gridcell\" aria-hidden=\"true\" [class]=\"cell.cellClass\">\n @if (showOutside()) {\n <span [class]=\"outsideDayClass()\">{{ cell.day }}</span>\n }\n </div>\n } @else {\n <div\n role=\"gridcell\"\n [class]=\"cell.cellClass\"\n [attr.aria-selected]=\"cell.selected || cell.inBand ? true : null\"\n >\n <button\n type=\"button\"\n [class]=\"cell.dayClass\"\n [attr.data-date]=\"cell.date\"\n [tabindex]=\"cell.tabIndex\"\n [attr.aria-label]=\"cell.ariaLabel\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n [disabled]=\"cell.disabled || disabled()\"\n (click)=\"select(cell.date)\"\n (mouseenter)=\"onDayHover(cell.date)\"\n >\n {{ cell.day }}\n @if (cell.markers.length) {\n <span [class]=\"dotsClass()\">\n @for (marker of cell.markers; track $index) {\n <span data-dot [class]=\"dotClassFor(marker.variant)\"></span>\n }\n </span>\n }\n </button>\n </div>\n }\n }\n </div>\n }\n</div>\n\n<!-- Selections and range progress are otherwise silent for a screen reader. -->\n<span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n" }]
|
|
3080
3302
|
}], 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"] }] } });
|
|
3081
3303
|
|
|
3304
|
+
/**
|
|
3305
|
+
* Anchored coach-mark panel that dims the page and cuts a spotlight hole
|
|
3306
|
+
* around its target. Both the scrim and the panel are `popover="manual"`
|
|
3307
|
+
* elements shown in order, so they stack deterministically in the top layer
|
|
3308
|
+
* above every app z-index, and every scrim piece is CSS-anchor-positioned to
|
|
3309
|
+
* the target — the hole tracks scroll/resize/layout with zero listeners.
|
|
3310
|
+
*
|
|
3311
|
+
* The panel is a non-modal `role="dialog"` (deliberately no `aria-modal`):
|
|
3312
|
+
* focus moves into it on open, Tab runs a "duet loop" over the panel's
|
|
3313
|
+
* focusables plus the spotlit target while it stays interactive, and on close
|
|
3314
|
+
* focus returns to the pre-open element — unless the user moved into the
|
|
3315
|
+
* target, where it stays.
|
|
3316
|
+
*
|
|
3317
|
+
* Storage-free by rule: `key` and the `dismissed` output are the "don't show
|
|
3318
|
+
* again" hooks; persistence is the app's business (e.g. cdk local-storage).
|
|
3319
|
+
*/
|
|
3320
|
+
class UniCalloutComponent extends BaseComponent {
|
|
3321
|
+
destroyRef = inject(DestroyRef);
|
|
3322
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
3323
|
+
/** Identifies this callout in `dismissed` payloads. Empty string = unset. */
|
|
3324
|
+
key = input(...(ngDevMode ? [undefined, { debugName: "key" }] : /* istanbul ignore next */ []));
|
|
3325
|
+
/** Element (or id, resolved at open time) to spotlight. '' = unset. */
|
|
3326
|
+
target = input(...(ngDevMode ? [undefined, { debugName: "target" }] : /* istanbul ignore next */ []));
|
|
3327
|
+
placement = input('bottom', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
3328
|
+
/** Defaults to `spotlight` when a target resolves, else `dim`. */
|
|
3329
|
+
backdrop = input(...(ngDevMode ? [undefined, { debugName: "backdrop" }] : /* istanbul ignore next */ []));
|
|
3330
|
+
/** When false, a transparent cover blocks the spotlit target. */
|
|
3331
|
+
targetInteractive = input(true, ...(ngDevMode ? [{ debugName: "targetInteractive" }] : /* istanbul ignore next */ []));
|
|
3332
|
+
/** Gates Escape and the close button. */
|
|
3333
|
+
dismissible = input(true, ...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []));
|
|
3334
|
+
/** When true, scrim clicks close (reason `backdrop`); else the panel pulses. */
|
|
3335
|
+
dismissOnBackdrop = input(false, ...(ngDevMode ? [{ debugName: "dismissOnBackdrop" }] : /* istanbul ignore next */ []));
|
|
3336
|
+
header = input(...(ngDevMode ? [undefined, { debugName: "header" }] : /* istanbul ignore next */ []));
|
|
3337
|
+
arrow = input(true, ...(ngDevMode ? [{ debugName: "arrow" }] : /* istanbul ignore next */ []));
|
|
3338
|
+
/** Wins over the header/body labelling; the tour sets "{title}, step n of N". */
|
|
3339
|
+
ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
3340
|
+
/** Accessible name of the dismiss button (the tour passes its skip label). */
|
|
3341
|
+
closeLabel = input('Close', ...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
|
|
3342
|
+
opened = output();
|
|
3343
|
+
closed = output();
|
|
3344
|
+
dismissed = output();
|
|
3345
|
+
/** Keys pressed while focus is inside the panel (the tour's arrow-key hook). */
|
|
3346
|
+
panelKeydown = output();
|
|
3347
|
+
panelId = uniqueId('uni-callout');
|
|
3348
|
+
headerId = uniqueId('uni-callout-header');
|
|
3349
|
+
bodyId = uniqueId('uni-callout-body');
|
|
3350
|
+
anchorName = newAnchorName();
|
|
3351
|
+
showing = signal(false, ...(ngDevMode ? [{ debugName: "showing" }] : /* istanbul ignore next */ []));
|
|
3352
|
+
activeTarget = signal(null, ...(ngDevMode ? [{ debugName: "activeTarget" }] : /* istanbul ignore next */ []));
|
|
3353
|
+
scrimShown = signal(false, ...(ngDevMode ? [{ debugName: "scrimShown" }] : /* istanbul ignore next */ []));
|
|
3354
|
+
/**
|
|
3355
|
+
* Keeps the spotlight pieces rendered and the panel anchored through the
|
|
3356
|
+
* close fade — tearing the anchor down at close time made the panel jump
|
|
3357
|
+
* and the scrim vanish mid-transition.
|
|
3358
|
+
*/
|
|
3359
|
+
scrimContent = signal(false, ...(ngDevMode ? [{ debugName: "scrimContent" }] : /* istanbul ignore next */ []));
|
|
3360
|
+
pendingTeardown = null;
|
|
3361
|
+
prevFocus = null;
|
|
3362
|
+
detachKeydown = null;
|
|
3363
|
+
stripSides = ['top', 'bottom', 'left', 'right'];
|
|
3364
|
+
scrimRef = viewChild.required('scrim');
|
|
3365
|
+
panelRef = viewChild.required('panel');
|
|
3366
|
+
actionsRef = viewChild.required('actions');
|
|
3367
|
+
constructor() {
|
|
3368
|
+
super();
|
|
3369
|
+
effect(() => {
|
|
3370
|
+
const want = this.open();
|
|
3371
|
+
if (want === untracked(this.showing))
|
|
3372
|
+
return;
|
|
3373
|
+
if (want)
|
|
3374
|
+
this.show();
|
|
3375
|
+
else
|
|
3376
|
+
this.close('programmatic');
|
|
3377
|
+
});
|
|
3378
|
+
// Re-seat an open callout when its target or backdrop changes (a tour
|
|
3379
|
+
// stepping) instead of closing and reopening — the panel stays up, the
|
|
3380
|
+
// original pre-open focus is preserved for the eventual close, and the
|
|
3381
|
+
// duet loop re-seats onto the new step's content.
|
|
3382
|
+
effect(() => {
|
|
3383
|
+
const ref = this.target();
|
|
3384
|
+
this.backdrop();
|
|
3385
|
+
if (!untracked(this.showing))
|
|
3386
|
+
return;
|
|
3387
|
+
untracked(() => this.retarget(resolveElement(ref)));
|
|
3388
|
+
});
|
|
3389
|
+
this.destroyRef.onDestroy(() => {
|
|
3390
|
+
this.detachKeydown?.();
|
|
3391
|
+
if (this.pendingTeardown)
|
|
3392
|
+
clearTimeout(this.pendingTeardown);
|
|
3393
|
+
const target = untracked(this.activeTarget);
|
|
3394
|
+
if (target)
|
|
3395
|
+
clearAnchorName(target);
|
|
3396
|
+
for (const ref of [this.panelRef, this.scrimRef]) {
|
|
3397
|
+
try {
|
|
3398
|
+
ref().nativeElement.hidePopover();
|
|
3399
|
+
}
|
|
3400
|
+
catch {
|
|
3401
|
+
// popover was already closed or detached
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
effectiveBackdrop = computed(() => this.backdrop() ?? (this.activeTarget() ? 'spotlight' : 'dim'), ...(ngDevMode ? [{ debugName: "effectiveBackdrop" }] : /* istanbul ignore next */ []));
|
|
3407
|
+
show() {
|
|
3408
|
+
if (this.showing())
|
|
3409
|
+
return;
|
|
3410
|
+
if (this.pendingTeardown) {
|
|
3411
|
+
clearTimeout(this.pendingTeardown);
|
|
3412
|
+
this.pendingTeardown = null;
|
|
3413
|
+
}
|
|
3414
|
+
const target = resolveElement(this.target());
|
|
3415
|
+
const stale = this.activeTarget();
|
|
3416
|
+
if (stale && stale !== target)
|
|
3417
|
+
clearAnchorName(stale); // a close was mid-fade
|
|
3418
|
+
this.activeTarget.set(target);
|
|
3419
|
+
this.scrimContent.set(true);
|
|
3420
|
+
this.prevFocus = document.activeElement;
|
|
3421
|
+
// Anchor insets need the target on screen before the scrim paints; the
|
|
3422
|
+
// hole then tracks any further movement natively.
|
|
3423
|
+
target?.scrollIntoView?.({ block: 'center', behavior: 'instant' });
|
|
3424
|
+
if (target)
|
|
3425
|
+
setAnchorName(target, this.anchorName);
|
|
3426
|
+
// Scrim first, panel second: top-layer order is stacking order.
|
|
3427
|
+
if ((this.backdrop() ?? (target ? 'spotlight' : 'dim')) !== 'none') {
|
|
3428
|
+
this.scrimRef().nativeElement.showPopover();
|
|
3429
|
+
this.scrimShown.set(true);
|
|
3430
|
+
}
|
|
3431
|
+
this.panelRef().nativeElement.showPopover();
|
|
3432
|
+
const onKeydown = (event) => this.onKeydown(event);
|
|
3433
|
+
document.addEventListener('keydown', onKeydown, true);
|
|
3434
|
+
this.detachKeydown = () => {
|
|
3435
|
+
document.removeEventListener('keydown', onKeydown, true);
|
|
3436
|
+
this.detachKeydown = null;
|
|
3437
|
+
};
|
|
3438
|
+
this.showing.set(true);
|
|
3439
|
+
this.open.set(true);
|
|
3440
|
+
this.focusInitial();
|
|
3441
|
+
this.opened.emit();
|
|
3442
|
+
}
|
|
3443
|
+
close(reason) {
|
|
3444
|
+
if (!this.showing())
|
|
3445
|
+
return;
|
|
3446
|
+
this.detachKeydown?.();
|
|
3447
|
+
const target = this.activeTarget();
|
|
3448
|
+
const active = document.activeElement;
|
|
3449
|
+
const focusInTarget = !!target && (target === active || target.contains(active));
|
|
3450
|
+
for (const ref of [this.panelRef, this.scrimRef]) {
|
|
3451
|
+
try {
|
|
3452
|
+
ref().nativeElement.hidePopover();
|
|
3453
|
+
}
|
|
3454
|
+
catch {
|
|
3455
|
+
// scrim may not have been shown (backdrop="none")
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
this.showing.set(false);
|
|
3459
|
+
this.scrimShown.set(false);
|
|
3460
|
+
this.open.set(false);
|
|
3461
|
+
// The close fade is still running: the panel must stay anchored and the
|
|
3462
|
+
// spotlight pieces rendered until it finishes, or everything jumps.
|
|
3463
|
+
this.pendingTeardown = setTimeout(() => {
|
|
3464
|
+
this.pendingTeardown = null;
|
|
3465
|
+
if (target)
|
|
3466
|
+
clearAnchorName(target);
|
|
3467
|
+
this.activeTarget.set(null);
|
|
3468
|
+
this.scrimContent.set(false);
|
|
3469
|
+
}, this.componentOptions().transitionMs);
|
|
3470
|
+
// The duet loop may have sent the user into the target on purpose — don't
|
|
3471
|
+
// yank them back out of it.
|
|
3472
|
+
if (!focusInTarget && this.prevFocus && document.contains(this.prevFocus)) {
|
|
3473
|
+
this.prevFocus.focus();
|
|
3474
|
+
}
|
|
3475
|
+
this.dismissed.emit({ key: this.key() || undefined, reason });
|
|
3476
|
+
this.closed.emit();
|
|
3477
|
+
}
|
|
3478
|
+
/** Move an open callout to a new target without a close/open round trip. */
|
|
3479
|
+
retarget(target) {
|
|
3480
|
+
const prev = this.activeTarget();
|
|
3481
|
+
if (prev && prev !== target)
|
|
3482
|
+
clearAnchorName(prev);
|
|
3483
|
+
target?.scrollIntoView?.({ block: 'center', behavior: 'instant' });
|
|
3484
|
+
if (target)
|
|
3485
|
+
setAnchorName(target, this.anchorName);
|
|
3486
|
+
this.activeTarget.set(target);
|
|
3487
|
+
const wantScrim = (this.backdrop() ?? (target ? 'spotlight' : 'dim')) !== 'none';
|
|
3488
|
+
if (wantScrim !== this.scrimShown()) {
|
|
3489
|
+
const scrim = this.scrimRef().nativeElement;
|
|
3490
|
+
const panel = this.panelRef().nativeElement;
|
|
3491
|
+
try {
|
|
3492
|
+
if (wantScrim) {
|
|
3493
|
+
// Top-layer order is stacking order: re-show the panel above the
|
|
3494
|
+
// late-arriving scrim.
|
|
3495
|
+
scrim.showPopover();
|
|
3496
|
+
panel.hidePopover();
|
|
3497
|
+
panel.showPopover();
|
|
3498
|
+
}
|
|
3499
|
+
else {
|
|
3500
|
+
scrim.hidePopover();
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
catch {
|
|
3504
|
+
// a piece was already in the requested state
|
|
3505
|
+
}
|
|
3506
|
+
this.scrimShown.set(wantScrim);
|
|
3507
|
+
}
|
|
3508
|
+
this.focusInitial();
|
|
3509
|
+
}
|
|
3510
|
+
/** `[autofocus]` → first action → first panel focusable → the panel itself. */
|
|
3511
|
+
focusInitial() {
|
|
3512
|
+
const panel = this.panelRef().nativeElement;
|
|
3513
|
+
const first = panel.querySelector('[autofocus]') ??
|
|
3514
|
+
focusableElements(this.actionsRef().nativeElement)[0] ??
|
|
3515
|
+
focusableElements(panel)[0] ??
|
|
3516
|
+
panel;
|
|
3517
|
+
first.focus({ preventScroll: true });
|
|
3518
|
+
}
|
|
3519
|
+
/**
|
|
3520
|
+
* Capture phase so Escape works from inside the spotlit target and the tab
|
|
3521
|
+
* loop preempts the page. Non-Tab keys surface through `panelKeydown`.
|
|
3522
|
+
*/
|
|
3523
|
+
onKeydown(event) {
|
|
3524
|
+
if (event.key === 'Escape') {
|
|
3525
|
+
if (this.dismissible()) {
|
|
3526
|
+
event.preventDefault();
|
|
3527
|
+
this.close('escape');
|
|
3528
|
+
}
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
if (event.key !== 'Tab') {
|
|
3532
|
+
if (this.panelRef().nativeElement.contains(document.activeElement)) {
|
|
3533
|
+
this.panelKeydown.emit(event);
|
|
3534
|
+
}
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
if (this.effectiveBackdrop() === 'none')
|
|
3538
|
+
return; // nothing is blocked — tab naturally
|
|
3539
|
+
// Duet loop: panel focusables plus the interactive target.
|
|
3540
|
+
const loop = focusableElements(this.panelRef().nativeElement);
|
|
3541
|
+
const target = this.activeTarget();
|
|
3542
|
+
if (target && this.targetInteractive())
|
|
3543
|
+
loop.push(target);
|
|
3544
|
+
if (!loop.length)
|
|
3545
|
+
return;
|
|
3546
|
+
const index = loop.indexOf(document.activeElement);
|
|
3547
|
+
event.preventDefault();
|
|
3548
|
+
const next = event.shiftKey
|
|
3549
|
+
? loop[(index <= 0 ? loop.length : index) - 1]
|
|
3550
|
+
: loop[(index + 1) % loop.length];
|
|
3551
|
+
next.focus();
|
|
3552
|
+
}
|
|
3553
|
+
/** A stray click shouldn't kill onboarding: nudge the panel instead. */
|
|
3554
|
+
onBackdropClick() {
|
|
3555
|
+
if (this.dismissOnBackdrop()) {
|
|
3556
|
+
this.close('backdrop');
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
try {
|
|
3560
|
+
if (matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
3561
|
+
return;
|
|
3562
|
+
this.panelRef().nativeElement.animate?.([
|
|
3563
|
+
{ translate: '0 0' },
|
|
3564
|
+
{ translate: '0 -3px', offset: 0.3 },
|
|
3565
|
+
{ translate: '0 2px', offset: 0.6 },
|
|
3566
|
+
{ translate: '0 0' },
|
|
3567
|
+
], { duration: 400, easing: 'ease' });
|
|
3568
|
+
}
|
|
3569
|
+
catch {
|
|
3570
|
+
// animation is decoration; environments without WAAPI just skip it
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
// --- styling --------------------------------------------------------------
|
|
3574
|
+
spotlight = computed(() => {
|
|
3575
|
+
const options = this.componentOptions();
|
|
3576
|
+
return spotlightStyles(this.anchorName, {
|
|
3577
|
+
pad: options.spotlightPadding,
|
|
3578
|
+
ringWidth: options.ringWidth,
|
|
3579
|
+
scrimColor: options.scrimColor,
|
|
3580
|
+
});
|
|
3581
|
+
}, ...(ngDevMode ? [{ debugName: "spotlight" }] : /* istanbul ignore next */ []));
|
|
3582
|
+
scrimClassName = computed(() => {
|
|
3583
|
+
const options = this.componentOptions();
|
|
3584
|
+
return css({
|
|
3585
|
+
position: 'fixed',
|
|
3586
|
+
inset: 0,
|
|
3587
|
+
width: '100%',
|
|
3588
|
+
height: '100%',
|
|
3589
|
+
margin: 0,
|
|
3590
|
+
border: 0,
|
|
3591
|
+
padding: 0,
|
|
3592
|
+
background: 'transparent',
|
|
3593
|
+
overflow: 'visible',
|
|
3594
|
+
pointerEvents: 'none',
|
|
3595
|
+
'& > *': { position: 'fixed' },
|
|
3596
|
+
...discreteOverlayTransition(options.transitionMs, { opacity: 0 }, { opacity: 1 }),
|
|
3597
|
+
});
|
|
3598
|
+
}, ...(ngDevMode ? [{ debugName: "scrimClassName" }] : /* istanbul ignore next */ []));
|
|
3599
|
+
windowClassName = computed(() => {
|
|
3600
|
+
const ring = this.theme.colorPalette()[this.variant()] ?? this.theme.colorPalette()['primary'];
|
|
3601
|
+
return css({
|
|
3602
|
+
...this.spotlight().window,
|
|
3603
|
+
...this.theme.radius(this.componentOptions().spotlightRadius),
|
|
3604
|
+
borderColor: ring,
|
|
3605
|
+
});
|
|
3606
|
+
}, ...(ngDevMode ? [{ debugName: "windowClassName" }] : /* istanbul ignore next */ []));
|
|
3607
|
+
stripClassName(side) {
|
|
3608
|
+
return css(this.spotlight().strips[side]);
|
|
3609
|
+
}
|
|
3610
|
+
coverClassName = computed(() => css(this.spotlight().cover), ...(ngDevMode ? [{ debugName: "coverClassName" }] : /* istanbul ignore next */ []));
|
|
3611
|
+
fullCoverClassName = computed(() => css({
|
|
3612
|
+
inset: 0,
|
|
3613
|
+
background: this.componentOptions().scrimColor,
|
|
3614
|
+
pointerEvents: 'auto',
|
|
3615
|
+
}), ...(ngDevMode ? [{ debugName: "fullCoverClassName" }] : /* istanbul ignore next */ []));
|
|
3616
|
+
panelClassName = computed(() => {
|
|
3617
|
+
const options = this.componentOptions();
|
|
3618
|
+
const anchored = this.activeTarget() !== null;
|
|
3619
|
+
return css({
|
|
3620
|
+
...this.theme.colorPair(options.color),
|
|
3621
|
+
...this.theme.radius(options.borderRadius),
|
|
3622
|
+
...this.theme.boxShadow(options.shadow),
|
|
3623
|
+
...this.theme.typeface(options.typeface),
|
|
3624
|
+
width: options.width,
|
|
3625
|
+
maxWidth: 'calc(100vw - 32px)',
|
|
3626
|
+
padding: 0,
|
|
3627
|
+
border: 0,
|
|
3628
|
+
overflow: 'visible',
|
|
3629
|
+
// A target-less panel keeps the popover UA styles (inset:0 + margin:auto)
|
|
3630
|
+
// and centers in the viewport; anchorStyles resets them for anchoring.
|
|
3631
|
+
...(anchored
|
|
3632
|
+
? anchorStyles(this.anchorName, this.placement(), {
|
|
3633
|
+
mainAxis: options.offset + options.spotlightPadding,
|
|
3634
|
+
})
|
|
3635
|
+
: {}),
|
|
3636
|
+
...discreteOverlayTransition(options.transitionMs, { opacity: 0, translate: '0 6px' }, { opacity: 1, translate: '0 0' }),
|
|
3637
|
+
});
|
|
3638
|
+
}, ...(ngDevMode ? [{ debugName: "panelClassName" }] : /* istanbul ignore next */ []));
|
|
3639
|
+
headRowClassName = computed(() => {
|
|
3640
|
+
const options = this.componentOptions();
|
|
3641
|
+
return css({
|
|
3642
|
+
display: 'flex',
|
|
3643
|
+
alignItems: 'center',
|
|
3644
|
+
gap: 8,
|
|
3645
|
+
padding: options.padding,
|
|
3646
|
+
paddingBottom: 0,
|
|
3647
|
+
'&:not(:has(*))': { display: 'none' },
|
|
3648
|
+
});
|
|
3649
|
+
}, ...(ngDevMode ? [{ debugName: "headRowClassName" }] : /* istanbul ignore next */ []));
|
|
3650
|
+
titleClassName = computed(() => css({ ...this.theme.typeface(this.componentOptions().headerTypeface), marginRight: 'auto' }), ...(ngDevMode ? [{ debugName: "titleClassName" }] : /* istanbul ignore next */ []));
|
|
3651
|
+
bodyClassName = computed(() => {
|
|
3652
|
+
const options = this.componentOptions();
|
|
3653
|
+
return css({ padding: options.padding, paddingTop: 6, paddingBottom: 12, '&:empty': { display: 'none' } });
|
|
3654
|
+
}, ...(ngDevMode ? [{ debugName: "bodyClassName" }] : /* istanbul ignore next */ []));
|
|
3655
|
+
actionsRowClassName = computed(() => {
|
|
3656
|
+
const options = this.componentOptions();
|
|
3657
|
+
return css({
|
|
3658
|
+
display: 'flex',
|
|
3659
|
+
alignItems: 'center',
|
|
3660
|
+
gap: 8,
|
|
3661
|
+
padding: options.padding,
|
|
3662
|
+
paddingTop: 0,
|
|
3663
|
+
'&:not(:has(*))': { display: 'none' },
|
|
3664
|
+
});
|
|
3665
|
+
}, ...(ngDevMode ? [{ debugName: "actionsRowClassName" }] : /* istanbul ignore next */ []));
|
|
3666
|
+
arrowClassName = computed(() => {
|
|
3667
|
+
const options = this.componentOptions();
|
|
3668
|
+
return css({
|
|
3669
|
+
...this.theme.colorPair(options.color),
|
|
3670
|
+
...anchorArrowStyles(this.placement(), options.arrowSize),
|
|
3671
|
+
});
|
|
3672
|
+
}, ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
|
|
3673
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCalloutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3674
|
+
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 });
|
|
3675
|
+
}
|
|
3676
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCalloutComponent, decorators: [{
|
|
3677
|
+
type: Component,
|
|
3678
|
+
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" }]
|
|
3679
|
+
}], 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 }] }] } });
|
|
3680
|
+
|
|
3681
|
+
/**
|
|
3682
|
+
* UniCalloutComponent Barrel File
|
|
3683
|
+
*
|
|
3684
|
+
* This file exports all public-facing elements of the callout component.
|
|
3685
|
+
*/
|
|
3686
|
+
|
|
3082
3687
|
class UniCardContentComponent extends BaseComponent {
|
|
3083
3688
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCardContentComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
3084
3689
|
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 });
|
|
@@ -3342,6 +3947,525 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3342
3947
|
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" }]
|
|
3343
3948
|
}], 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"] }] } });
|
|
3344
3949
|
|
|
3950
|
+
/**
|
|
3951
|
+
* Style block for the popup behind every `ListboxNavigation` consumer: an
|
|
3952
|
+
* absolutely-positioned `ul[role="listbox"]` under a `position: relative`
|
|
3953
|
+
* field wrapper, with the shared option chrome and active/hover highlight.
|
|
3954
|
+
*
|
|
3955
|
+
* Extracted because four components (`uni-search-input`, `uni-tag-input`,
|
|
3956
|
+
* `uni-time-input`, `uni-combobox`) carried hand-rolled copies, and the parts
|
|
3957
|
+
* that silently drift between copies all live here: the surface/elevation
|
|
3958
|
+
* trio, and the `activeColor` pair that themes re-point when their container
|
|
3959
|
+
* tokens don't contrast (see the Wellsourced overrides).
|
|
3960
|
+
*
|
|
3961
|
+
* Compose extras with the array form — `css([listboxPopupStyles(…), {…}])` —
|
|
3962
|
+
* so a component's own `& [role="option"]` block cascades after this one
|
|
3963
|
+
* instead of replacing it (an object spread would overwrite the key).
|
|
3964
|
+
*/
|
|
3965
|
+
const listboxPopupStyles = (theme, options, { maxHeight = 280 } = {}) => ({
|
|
3966
|
+
position: 'absolute',
|
|
3967
|
+
top: '100%',
|
|
3968
|
+
left: 0,
|
|
3969
|
+
right: 0,
|
|
3970
|
+
zIndex: 20,
|
|
3971
|
+
margin: '4px 0 0',
|
|
3972
|
+
padding: 4,
|
|
3973
|
+
listStyle: 'none',
|
|
3974
|
+
maxHeight,
|
|
3975
|
+
overflowY: 'auto',
|
|
3976
|
+
...theme.colorPair((options.listColor ?? 'primary-surface')),
|
|
3977
|
+
...theme.boxShadow(options.listShadow ?? 'menu'),
|
|
3978
|
+
...theme.radius(options.listBorderRadius ?? 'xs'),
|
|
3979
|
+
'& [role="option"]': {
|
|
3980
|
+
padding: '8px 12px',
|
|
3981
|
+
cursor: 'pointer',
|
|
3982
|
+
...theme.typeface('label'),
|
|
3983
|
+
...theme.radius('xxs'),
|
|
3984
|
+
'&.active, &:not([aria-disabled="true"]):hover': {
|
|
3985
|
+
...theme.colorPair((options.activeColor ?? 'primary-container')),
|
|
3986
|
+
},
|
|
3987
|
+
},
|
|
3988
|
+
});
|
|
3989
|
+
|
|
3990
|
+
class UniInputBoxComponent extends BaseComponent {
|
|
3991
|
+
className = css({ display: 'contents' });
|
|
3992
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
3993
|
+
error = input(false, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
3994
|
+
minWidth = input('0', ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
|
|
3995
|
+
/** Override the themed field height, e.g. `'auto'` for multi-line fields. */
|
|
3996
|
+
height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
3997
|
+
color = computed(() => this.error() ? this.componentOptions().errorColor : this.componentOptions().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
3998
|
+
border = computed(() => this.error() ? this.componentOptions().errorBorder : this.componentOptions().border, ...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
|
|
3999
|
+
shadow = computed(() => this.error() ? this.componentOptions().errorShadow : this.componentOptions().shadow, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
|
|
4000
|
+
inputBoxClass = computed(() => css([
|
|
4001
|
+
this.disabled() && {
|
|
4002
|
+
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
4003
|
+
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
4004
|
+
cursor: 'not-allowed !important',
|
|
4005
|
+
},
|
|
4006
|
+
{
|
|
4007
|
+
'& input, select, textarea': {
|
|
4008
|
+
...removeInputPlatformStyling,
|
|
4009
|
+
height: '100%',
|
|
4010
|
+
...this.theme.paddingLeft(this.componentOptions().paddingLeft),
|
|
4011
|
+
...this.theme.color(this.componentOptions().textColor),
|
|
4012
|
+
// `typeFace` is the deprecated casing; themes that still set it win
|
|
4013
|
+
// only when the canonical key is absent.
|
|
4014
|
+
...this.theme.typeface(this.componentOptions().typeface ?? this.componentOptions().typeFace),
|
|
4015
|
+
},
|
|
4016
|
+
// Multi-line fields size themselves (rows/resize), not from the box.
|
|
4017
|
+
'& textarea': {
|
|
4018
|
+
height: 'auto',
|
|
4019
|
+
...this.theme.paddingTop('xs'),
|
|
4020
|
+
...this.theme.paddingBottom('xs'),
|
|
4021
|
+
},
|
|
4022
|
+
'&:has(input:disabled, select:disabled, textarea:disabled)': {
|
|
4023
|
+
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
4024
|
+
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
4025
|
+
},
|
|
4026
|
+
'& input:disabled, select:disabled, textarea:disabled': {
|
|
4027
|
+
cursor: 'not-allowed !important',
|
|
4028
|
+
},
|
|
4029
|
+
'&:has(input:focus, select:focus, textarea:focus)': {
|
|
4030
|
+
outline: this.componentOptions().focusOutline,
|
|
4031
|
+
outlineOffset: this.componentOptions().focusOutlineOffset,
|
|
4032
|
+
// Optional focus chrome (border/ring/background). It yields to the
|
|
4033
|
+
// error state, so a flagged field stays visibly flagged while the
|
|
4034
|
+
// user is in it correcting the value.
|
|
4035
|
+
...(this.error()
|
|
4036
|
+
? {}
|
|
4037
|
+
: {
|
|
4038
|
+
...this.theme.border(this.componentOptions().focusBorder),
|
|
4039
|
+
...this.theme.boxShadow(this.componentOptions().focusShadow),
|
|
4040
|
+
...this.theme.backgroundColor(this.componentOptions().focusColor),
|
|
4041
|
+
}),
|
|
4042
|
+
},
|
|
4043
|
+
},
|
|
4044
|
+
]), ...(ngDevMode ? [{ debugName: "inputBoxClass" }] : /* istanbul ignore next */ []));
|
|
4045
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4046
|
+
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 });
|
|
4047
|
+
}
|
|
4048
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, decorators: [{
|
|
4049
|
+
type: Component,
|
|
4050
|
+
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" }]
|
|
4051
|
+
}], 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 }] }] } });
|
|
4052
|
+
|
|
4053
|
+
/**
|
|
4054
|
+
* Form-bound, closed-set, single-select autocomplete: `FormValueControl<T | null>`
|
|
4055
|
+
* over object `Options<T>`, type-to-filter, commit-on-select. Typing produces a
|
|
4056
|
+
* draft — a filter, never a value; the value changes only when an option
|
|
4057
|
+
* commits, `clear()` runs, or the model is written from outside. Select
|
|
4058
|
+
* semantics, not search: a real label, a chevron, no magnifier, nothing
|
|
4059
|
+
* submits. For short lists with no need to type, prefer `uni-select` — the
|
|
4060
|
+
* value contract is identical, so swapping is a template-only change.
|
|
4061
|
+
*/
|
|
4062
|
+
class UniComboboxComponent extends BaseComponent {
|
|
4063
|
+
// --- Signal Forms block (explicit per AGENTS.md, not a base class) --------
|
|
4064
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
4065
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
4066
|
+
touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
4067
|
+
invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
4068
|
+
dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
4069
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
4070
|
+
ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
4071
|
+
// --- Configuration -------------------------------------------------------
|
|
4072
|
+
/** Accessible name for the field, e.g. "State"; echoed on the listbox. */
|
|
4073
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4074
|
+
placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
4075
|
+
options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
4076
|
+
/**
|
|
4077
|
+
* Equality used to match `value` against option values, called as
|
|
4078
|
+
* `compareWith(optionValue, value)`. Defaults to reference equality, which
|
|
4079
|
+
* never matches an object value rebuilt from elsewhere (e.g. a saved record
|
|
4080
|
+
* against options from a fresh fetch) — pass a key comparison like
|
|
4081
|
+
* `(a, b) => a?.id === b?.id` for object values.
|
|
4082
|
+
*/
|
|
4083
|
+
compareWith = input((a, b) => a === b, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
|
|
4084
|
+
width = input('100%', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
4085
|
+
/** Show a ✕ while a value is set; an emptied field on blur also clears. */
|
|
4086
|
+
clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
|
|
4087
|
+
/** Blur commits an exact-match draft; a non-matching draft reverts. */
|
|
4088
|
+
commitOnBlur = input(true, ...(ngDevMode ? [{ debugName: "commitOnBlur" }] : /* istanbul ignore next */ []));
|
|
4089
|
+
/** i18n-able empty row, also announced when a filter matches nothing. */
|
|
4090
|
+
emptyText = input('No matches', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
4091
|
+
// --- Filtering (local by default — the closed set is already in memory) ---
|
|
4092
|
+
/** `false` renders `options` verbatim; narrow them app-side from `query`. */
|
|
4093
|
+
filterLocally = input(true, ...(ngDevMode ? [{ debugName: "filterLocally" }] : /* istanbul ignore next */ []));
|
|
4094
|
+
/** Filter predicate; default is locale-lowercased label-contains. */
|
|
4095
|
+
filterWith = input(...(ngDevMode ? [undefined, { debugName: "filterWith" }] : /* istanbul ignore next */ []));
|
|
4096
|
+
debounceTime = input(250, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ []));
|
|
4097
|
+
// --- Events ---------------------------------------------------------------
|
|
4098
|
+
/** Debounced draft text, for async option lists. */
|
|
4099
|
+
query = output();
|
|
4100
|
+
/** An option committed. */
|
|
4101
|
+
selected = output();
|
|
4102
|
+
cleared = output();
|
|
4103
|
+
/** A commit was refused (no match); the field reverted. */
|
|
4104
|
+
rejected = output();
|
|
4105
|
+
host = inject(ElementRef);
|
|
4106
|
+
inputRef = viewChild.required('field');
|
|
4107
|
+
listRef = viewChild('listbox', ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
|
|
4108
|
+
/** Cancelled on destroy — a late tick would emit on a destroyed OutputRef. */
|
|
4109
|
+
queryTimer;
|
|
4110
|
+
constructor() {
|
|
4111
|
+
super();
|
|
4112
|
+
inject(DestroyRef).onDestroy(() => clearTimeout(this.queryTimer));
|
|
4113
|
+
}
|
|
4114
|
+
srOnly = css(visuallyHidden);
|
|
4115
|
+
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
4116
|
+
/** null → the field shows the committed label; a string is an uncommitted draft. */
|
|
4117
|
+
draft = signal(null, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
|
|
4118
|
+
/**
|
|
4119
|
+
* Popup visibility. Component-owned rather than `list.open()` — the shared
|
|
4120
|
+
* signal gates on `count() > 0`, which would hide the "No matches" row and
|
|
4121
|
+
* misreport `aria-expanded` over an empty filter.
|
|
4122
|
+
*/
|
|
4123
|
+
popupOpen = signal(false, ...(ngDevMode ? [{ debugName: "popupOpen" }] : /* istanbul ignore next */ []));
|
|
4124
|
+
/** Index in options() of the option matching value(), else -1. */
|
|
4125
|
+
committedIndex = computed(() => {
|
|
4126
|
+
const value = this.value();
|
|
4127
|
+
if (value === null || value === undefined)
|
|
4128
|
+
return -1;
|
|
4129
|
+
return this.options().findIndex((option) => this.compareWith()(option.value, value));
|
|
4130
|
+
}, ...(ngDevMode ? [{ debugName: "committedIndex" }] : /* istanbul ignore next */ []));
|
|
4131
|
+
/** A value with no matching option renders '' but is preserved — options may
|
|
4132
|
+
still be loading; the field self-heals when they arrive. */
|
|
4133
|
+
committedLabel = computed(() => {
|
|
4134
|
+
const index = this.committedIndex();
|
|
4135
|
+
return index >= 0 ? this.options()[index].label : '';
|
|
4136
|
+
}, ...(ngDevMode ? [{ debugName: "committedLabel" }] : /* istanbul ignore next */ []));
|
|
4137
|
+
displayValue = computed(() => this.draft() ?? this.committedLabel(), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
4138
|
+
/** Indices into options() surviving the filter. The draft filters; the
|
|
4139
|
+
committed label does not — reopening always shows the full set. */
|
|
4140
|
+
filteredIndices = computed(() => {
|
|
4141
|
+
const query = this.draft();
|
|
4142
|
+
const all = this.options().map((_, index) => index);
|
|
4143
|
+
if (!this.filterLocally() || query === null || query === '')
|
|
4144
|
+
return all;
|
|
4145
|
+
const filter = this.filterWith() ??
|
|
4146
|
+
((option, q) => option.label.toLocaleLowerCase().includes(q.toLocaleLowerCase()));
|
|
4147
|
+
return all.filter((index) => filter(this.options()[index], query));
|
|
4148
|
+
}, ...(ngDevMode ? [{ debugName: "filteredIndices" }] : /* istanbul ignore next */ []));
|
|
4149
|
+
/** Shared combobox bookkeeping, in filtered-list positions — the fourth
|
|
4150
|
+
consumer of the contract behind search-input, tag-input and time-input. */
|
|
4151
|
+
list = createListboxNavigation({
|
|
4152
|
+
count: () => this.filteredIndices().length,
|
|
4153
|
+
idPrefix: 'uni-combobox-listbox',
|
|
4154
|
+
disabled: (position) => !!this.options()[this.filteredIndices()[position]]?.disabled,
|
|
4155
|
+
});
|
|
4156
|
+
showError = computed(() => this.invalid() && (this.touched() || this.dirty()), ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
4157
|
+
// --- Committing ------------------------------------------------------------
|
|
4158
|
+
commit(optIndex) {
|
|
4159
|
+
const option = this.options()[optIndex];
|
|
4160
|
+
if (!option || option.disabled)
|
|
4161
|
+
return false;
|
|
4162
|
+
this.value.set(option.value);
|
|
4163
|
+
this.draft.set(null);
|
|
4164
|
+
this.closeList();
|
|
4165
|
+
this.setFieldText(this.displayValue());
|
|
4166
|
+
this.announce(`${option.label} selected.`);
|
|
4167
|
+
this.selected.emit(option);
|
|
4168
|
+
return true;
|
|
4169
|
+
}
|
|
4170
|
+
clear() {
|
|
4171
|
+
this.value.set(null);
|
|
4172
|
+
this.draft.set(null);
|
|
4173
|
+
this.closeList();
|
|
4174
|
+
this.setFieldText('');
|
|
4175
|
+
this.announce('Selection cleared.');
|
|
4176
|
+
this.cleared.emit();
|
|
4177
|
+
this.inputRef().nativeElement.focus();
|
|
4178
|
+
}
|
|
4179
|
+
/**
|
|
4180
|
+
* Draft resolution, used by Enter, Tab and blur:
|
|
4181
|
+
* 1. an active option in the list → commit it;
|
|
4182
|
+
* 2. else a unique exact label match (locale-case-insensitive) → commit it;
|
|
4183
|
+
* 3. Enter only: the filter narrowed to exactly one enabled option → commit it
|
|
4184
|
+
* (on Enter the user can see the single candidate; on blur they're gone,
|
|
4185
|
+
* and committing a value they never saw confirmed is how forms grow
|
|
4186
|
+
* mystery data);
|
|
4187
|
+
* 4. else the caller keeps the list open (Enter) or reverts (Tab/blur).
|
|
4188
|
+
*/
|
|
4189
|
+
resolveDraft(enterOnly) {
|
|
4190
|
+
const filtered = this.filteredIndices();
|
|
4191
|
+
const active = this.list.activeIndex();
|
|
4192
|
+
if (this.popupOpen() && active >= 0 && active < filtered.length) {
|
|
4193
|
+
return this.commit(filtered[active]);
|
|
4194
|
+
}
|
|
4195
|
+
const draft = this.draft();
|
|
4196
|
+
if (draft === null)
|
|
4197
|
+
return true;
|
|
4198
|
+
if (draft === '') {
|
|
4199
|
+
// An emptied field on Tab/blur is a deliberate "no value" — but only
|
|
4200
|
+
// clearable fields may null the model this way.
|
|
4201
|
+
if (this.clearable() && this.value() !== null && !enterOnly) {
|
|
4202
|
+
this.value.set(null);
|
|
4203
|
+
this.announce('Selection cleared.');
|
|
4204
|
+
this.cleared.emit();
|
|
4205
|
+
}
|
|
4206
|
+
this.draft.set(null);
|
|
4207
|
+
this.setFieldText(this.displayValue());
|
|
4208
|
+
return true;
|
|
4209
|
+
}
|
|
4210
|
+
const normalize = (text) => text.toLocaleLowerCase();
|
|
4211
|
+
const exact = this.options()
|
|
4212
|
+
.map((option, index) => ({ option, index }))
|
|
4213
|
+
.filter(({ option }) => !option.disabled && normalize(option.label) === normalize(draft));
|
|
4214
|
+
if (exact.length === 1)
|
|
4215
|
+
return this.commit(exact[0].index);
|
|
4216
|
+
if (enterOnly) {
|
|
4217
|
+
const enabled = filtered.filter((index) => !this.options()[index].disabled);
|
|
4218
|
+
if (enabled.length === 1)
|
|
4219
|
+
return this.commit(enabled[0]);
|
|
4220
|
+
}
|
|
4221
|
+
return false;
|
|
4222
|
+
}
|
|
4223
|
+
revertDraft() {
|
|
4224
|
+
const draft = this.draft();
|
|
4225
|
+
if (draft === null)
|
|
4226
|
+
return null;
|
|
4227
|
+
this.draft.set(null);
|
|
4228
|
+
this.setFieldText(this.displayValue());
|
|
4229
|
+
return draft;
|
|
4230
|
+
}
|
|
4231
|
+
reject() {
|
|
4232
|
+
const query = this.revertDraft();
|
|
4233
|
+
if (query) {
|
|
4234
|
+
this.announce(`No match for “${query}”.`);
|
|
4235
|
+
this.rejected.emit({ query });
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
// --- Keyboard --------------------------------------------------------------
|
|
4239
|
+
onKeydown(event) {
|
|
4240
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
4241
|
+
event.preventDefault();
|
|
4242
|
+
if (event.altKey) {
|
|
4243
|
+
// Alt+Down opens without activating; Alt+Up closes keeping the draft.
|
|
4244
|
+
if (event.key === 'ArrowDown') {
|
|
4245
|
+
if (!this.popupOpen())
|
|
4246
|
+
this.openList(-1);
|
|
4247
|
+
}
|
|
4248
|
+
else {
|
|
4249
|
+
this.closeList();
|
|
4250
|
+
}
|
|
4251
|
+
return;
|
|
4252
|
+
}
|
|
4253
|
+
if (!this.popupOpen()) {
|
|
4254
|
+
this.popupOpen.set(true);
|
|
4255
|
+
this.list.show();
|
|
4256
|
+
if (event.key === 'ArrowDown') {
|
|
4257
|
+
const committed = this.committedFilteredPos();
|
|
4258
|
+
if (committed >= 0) {
|
|
4259
|
+
this.list.setActive(committed);
|
|
4260
|
+
this.scrollToActive();
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
// Fall through: Down lands on the first enabled option, Up on the last.
|
|
4265
|
+
}
|
|
4266
|
+
if (this.list.navigate(event))
|
|
4267
|
+
this.scrollToActive();
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
4270
|
+
if (event.key === 'Home' || event.key === 'End') {
|
|
4271
|
+
// Only while the list is open — closed, they belong to the caret.
|
|
4272
|
+
if (this.popupOpen() && this.list.navigate(event))
|
|
4273
|
+
this.scrollToActive();
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
switch (event.key) {
|
|
4277
|
+
case 'Enter':
|
|
4278
|
+
// Never submits a form while the list is open.
|
|
4279
|
+
if (this.popupOpen())
|
|
4280
|
+
event.preventDefault();
|
|
4281
|
+
if (!this.resolveDraft(true)) {
|
|
4282
|
+
const count = this.filteredIndices().length;
|
|
4283
|
+
this.announce(count === 0 ? `${this.emptyText()}.` : `${count} results. Use the arrow keys.`);
|
|
4284
|
+
}
|
|
4285
|
+
return;
|
|
4286
|
+
case 'Escape':
|
|
4287
|
+
// One layer at a time: close the list, then revert the draft.
|
|
4288
|
+
// Never clears the committed value — Escape on a form control must
|
|
4289
|
+
// not be destructive.
|
|
4290
|
+
if (this.popupOpen())
|
|
4291
|
+
this.closeList();
|
|
4292
|
+
else if (this.draft() !== null)
|
|
4293
|
+
this.revertDraft();
|
|
4294
|
+
return;
|
|
4295
|
+
case 'Tab':
|
|
4296
|
+
// Resolve rules 1–2 (never rule 3), then let focus move on.
|
|
4297
|
+
if (this.commitOnBlur() && !this.resolveDraft(false))
|
|
4298
|
+
this.reject();
|
|
4299
|
+
this.closeList();
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
onInput() {
|
|
4303
|
+
const text = this.inputRef().nativeElement.value;
|
|
4304
|
+
this.draft.set(text);
|
|
4305
|
+
this.popupOpen.set(true);
|
|
4306
|
+
this.list.show();
|
|
4307
|
+
// Typing never selects — rules 2/3 make Enter still work without arrowing.
|
|
4308
|
+
this.list.setActive(-1);
|
|
4309
|
+
clearTimeout(this.queryTimer);
|
|
4310
|
+
this.queryTimer = setTimeout(() => {
|
|
4311
|
+
this.query.emit(text);
|
|
4312
|
+
// Filtering is otherwise silent to a screen reader.
|
|
4313
|
+
if (this.filterLocally() && text !== '') {
|
|
4314
|
+
const count = this.filteredIndices().length;
|
|
4315
|
+
this.announce(count === 0 ? `${this.emptyText()}.` : `${count} result${count === 1 ? '' : 's'}.`);
|
|
4316
|
+
}
|
|
4317
|
+
}, this.debounceTime());
|
|
4318
|
+
}
|
|
4319
|
+
// --- Pointer ---------------------------------------------------------------
|
|
4320
|
+
/** Click-to-browse: pointer users get the select affordance. Focus alone
|
|
4321
|
+
never opens — Tab-through forms must not spray popups. */
|
|
4322
|
+
onFieldClick() {
|
|
4323
|
+
if (this.disabled() || this.popupOpen())
|
|
4324
|
+
return;
|
|
4325
|
+
this.openList(this.committedFilteredPos());
|
|
4326
|
+
}
|
|
4327
|
+
onToggleMousedown(event) {
|
|
4328
|
+
event.preventDefault();
|
|
4329
|
+
if (this.disabled())
|
|
4330
|
+
return;
|
|
4331
|
+
this.inputRef().nativeElement.focus();
|
|
4332
|
+
if (this.popupOpen())
|
|
4333
|
+
this.closeList();
|
|
4334
|
+
else
|
|
4335
|
+
this.openList(this.committedFilteredPos());
|
|
4336
|
+
}
|
|
4337
|
+
onOptionClick(optIndex) {
|
|
4338
|
+
if (this.options()[optIndex]?.disabled)
|
|
4339
|
+
return;
|
|
4340
|
+
this.commit(optIndex);
|
|
4341
|
+
this.inputRef().nativeElement.focus();
|
|
4342
|
+
}
|
|
4343
|
+
onFocusOut(event) {
|
|
4344
|
+
const next = event.relatedTarget;
|
|
4345
|
+
if (next && this.host.nativeElement.contains(next))
|
|
4346
|
+
return;
|
|
4347
|
+
this.touched.set(true);
|
|
4348
|
+
if (this.commitOnBlur() && !this.resolveDraft(false))
|
|
4349
|
+
this.reject();
|
|
4350
|
+
else
|
|
4351
|
+
this.draft.set(null);
|
|
4352
|
+
this.closeList();
|
|
4353
|
+
}
|
|
4354
|
+
// --- Internals -------------------------------------------------------------
|
|
4355
|
+
/** Filtered position of the committed option when visible and enabled, else -1. */
|
|
4356
|
+
committedFilteredPos() {
|
|
4357
|
+
const committed = this.committedIndex();
|
|
4358
|
+
if (committed < 0 || this.options()[committed].disabled)
|
|
4359
|
+
return -1;
|
|
4360
|
+
return this.filteredIndices().indexOf(committed);
|
|
4361
|
+
}
|
|
4362
|
+
openList(activeFilteredPos) {
|
|
4363
|
+
this.popupOpen.set(true);
|
|
4364
|
+
this.list.show();
|
|
4365
|
+
this.list.setActive(activeFilteredPos);
|
|
4366
|
+
this.scrollToActive();
|
|
4367
|
+
}
|
|
4368
|
+
closeList() {
|
|
4369
|
+
this.popupOpen.set(false);
|
|
4370
|
+
this.list.hide();
|
|
4371
|
+
}
|
|
4372
|
+
scrollToActive() {
|
|
4373
|
+
const position = this.list.activeIndex();
|
|
4374
|
+
if (position < 0)
|
|
4375
|
+
return;
|
|
4376
|
+
queueMicrotask(() => this.listRef()?.nativeElement.children[position]?.scrollIntoView?.({ block: 'nearest' }));
|
|
4377
|
+
}
|
|
4378
|
+
setFieldText(text) {
|
|
4379
|
+
const element = this.inputRef()?.nativeElement;
|
|
4380
|
+
if (element)
|
|
4381
|
+
element.value = text;
|
|
4382
|
+
}
|
|
4383
|
+
announce(message) {
|
|
4384
|
+
// Re-announce identical text by breaking the string equality.
|
|
4385
|
+
this.announcement.set(this.announcement() === message ? `${message} ` : message);
|
|
4386
|
+
}
|
|
4387
|
+
// --- Styling ----------------------------------------------------------------
|
|
4388
|
+
className = computed(() => css({ display: 'block', position: 'relative', width: this.width() }), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
4389
|
+
rowClass = computed(() => css({ display: 'flex', alignItems: 'center', flex: 1, width: '100%', minWidth: 0 }), ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
|
|
4390
|
+
inputClass = computed(() => css({
|
|
4391
|
+
flex: 1,
|
|
4392
|
+
minWidth: 0,
|
|
4393
|
+
border: 0,
|
|
4394
|
+
outline: 'none',
|
|
4395
|
+
background: 'transparent',
|
|
4396
|
+
color: 'inherit',
|
|
4397
|
+
font: 'inherit',
|
|
4398
|
+
}), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
|
|
4399
|
+
/** Pointer-only affordance: keyboard already has ArrowDown, and the input
|
|
4400
|
+
itself announces expanded state. */
|
|
4401
|
+
toggleClass = computed(() => css({
|
|
4402
|
+
flex: 'none',
|
|
4403
|
+
width: 28,
|
|
4404
|
+
height: 28,
|
|
4405
|
+
display: 'grid',
|
|
4406
|
+
placeItems: 'center',
|
|
4407
|
+
border: 0,
|
|
4408
|
+
padding: 0,
|
|
4409
|
+
background: 'transparent',
|
|
4410
|
+
cursor: this.disabled() ? 'not-allowed' : 'pointer',
|
|
4411
|
+
...this.theme.color('on-background-variant'),
|
|
4412
|
+
'& uni-icon': {
|
|
4413
|
+
...motionSafe({ transition: 'transform 0.15s ease' }),
|
|
4414
|
+
transform: this.popupOpen() ? 'rotate(180deg)' : 'none',
|
|
4415
|
+
},
|
|
4416
|
+
}), ...(ngDevMode ? [{ debugName: "toggleClass" }] : /* istanbul ignore next */ []));
|
|
4417
|
+
listClass = computed(() => {
|
|
4418
|
+
const options = this.componentOptions();
|
|
4419
|
+
return css([
|
|
4420
|
+
listboxPopupStyles(this.theme, options, {
|
|
4421
|
+
// A scroll height, never a cap: a closed-set control must not render a
|
|
4422
|
+
// reachable-by-keyboard-only subset (contrast searchInput.maxSuggestions).
|
|
4423
|
+
maxHeight: (options.maxVisibleOptions ?? 8) * 36 + 8,
|
|
4424
|
+
}),
|
|
4425
|
+
{
|
|
4426
|
+
'& [role="option"]': {
|
|
4427
|
+
display: 'flex',
|
|
4428
|
+
alignItems: 'center',
|
|
4429
|
+
gap: 10,
|
|
4430
|
+
minHeight: 36,
|
|
4431
|
+
boxSizing: 'border-box',
|
|
4432
|
+
padding: '5px 10px',
|
|
4433
|
+
'&.active, &:not([aria-disabled="true"]):hover': {
|
|
4434
|
+
'& .check': { color: 'inherit' },
|
|
4435
|
+
'& .desc': { color: 'inherit', opacity: 0.8 },
|
|
4436
|
+
},
|
|
4437
|
+
'&[aria-disabled="true"]': {
|
|
4438
|
+
...this.theme.color('on-disabled'),
|
|
4439
|
+
cursor: 'not-allowed',
|
|
4440
|
+
'& .desc': { color: 'inherit' },
|
|
4441
|
+
},
|
|
4442
|
+
},
|
|
4443
|
+
// Fixed-width check column so labels align with or without the icon.
|
|
4444
|
+
'& .check': { flex: 'none', width: 18, ...this.theme.color('primary') },
|
|
4445
|
+
'& .text': { minWidth: 0, flex: 1, display: 'flex', alignItems: 'baseline', gap: 8 },
|
|
4446
|
+
'& .option-label': { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
|
|
4447
|
+
'& .desc': {
|
|
4448
|
+
marginLeft: 'auto',
|
|
4449
|
+
whiteSpace: 'nowrap',
|
|
4450
|
+
fontSize: '0.85em',
|
|
4451
|
+
...this.theme.color(options.descriptionColor ?? 'on-primary-surface-variant'),
|
|
4452
|
+
},
|
|
4453
|
+
'& .empty': {
|
|
4454
|
+
padding: 10,
|
|
4455
|
+
...this.theme.typeface('label'),
|
|
4456
|
+
...this.theme.color(options.descriptionColor ?? 'on-primary-surface-variant'),
|
|
4457
|
+
},
|
|
4458
|
+
},
|
|
4459
|
+
]);
|
|
4460
|
+
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
4461
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4462
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniComboboxComponent, isStandalone: true, selector: "uni-combobox, Combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, filterLocally: { classPropertyName: "filterLocally", publicName: "filterLocally", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", selected: "selected", cleared: "cleared", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniIconComponent, selector: "uni-icon", inputs: ["color", "name", "size"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4463
|
+
}
|
|
4464
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniComboboxComponent, decorators: [{
|
|
4465
|
+
type: Component,
|
|
4466
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-combobox, Combobox', imports: [UniIconButtonComponent, UniIconComponent, UniInputBoxComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'combobox' }], host: { '[class]': 'className()' }, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n resolves only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\">\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [value]=\"displayValue()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"popupOpen()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onKeydown($event)\"\n (input)=\"onInput()\"\n (click)=\"onFieldClick()\"\n />\n @if (clearable() && value() !== null && !disabled()) {\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n [iconName]=\"componentOptions().clearIcon ?? 'close'\"\n (click)=\"clear()\"\n >\n Clear {{ label() }}\n </button>\n }\n <!-- Pointer-only: keyboard has ArrowDown, the input announces expanded. -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"toggleClass()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"onToggleMousedown($event)\"\n >\n <uni-icon [name]=\"componentOptions().toggleIcon ?? 'chevronDown'\" size=\"20\" />\n </button>\n </div>\n </uni-input-box>\n\n @if (popupOpen()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @if (filteredIndices().length === 0) {\n <!-- Non-interactive: not an option, not navigable, no id. -->\n <li class=\"empty\">{{ emptyText() }}</li>\n } @else {\n @for (optIndex of filteredIndices(); track optIndex; let i = $index) {\n <!-- aria-selected marks the *committed* option; the active one is\n carried by aria-activedescendant \u2014 they are different facts. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"optIndex === committedIndex()\"\n [attr.aria-disabled]=\"options()[optIndex].disabled ? true : null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(optIndex)\"\n >\n <span class=\"check\" aria-hidden=\"true\">\n @if (optIndex === committedIndex()) {\n <uni-icon [name]=\"componentOptions().selectedIcon ?? 'check'\" size=\"18\" />\n }\n </span>\n <span class=\"text\">\n <span class=\"option-label\">{{ options()[optIndex].label }}</span>\n @if (options()[optIndex].description) {\n <span class=\"desc\">{{ options()[optIndex].description }}</span>\n }\n </span>\n </li>\n }\n }\n </ul>\n }\n\n <!-- Commits, clears, refusals and result counts are otherwise silent. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n" }]
|
|
4467
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], commitOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOnBlur", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], filterLocally: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLocally", required: false }] }], filterWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterWith", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], query: [{ type: i0.Output, args: ["query"] }], selected: [{ type: i0.Output, args: ["selected"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], inputRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['listbox', { isSignal: true }] }] } });
|
|
4468
|
+
|
|
3345
4469
|
class UniDataSearchComponent extends BaseComponent {
|
|
3346
4470
|
datasource = input(...(ngDevMode ? [undefined, { debugName: "datasource" }] : /* istanbul ignore next */ []));
|
|
3347
4471
|
placeholder = input('Search', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
@@ -3857,6 +4981,9 @@ class UniDropdownComponent extends BaseComponent {
|
|
|
3857
4981
|
get _dropdown() {
|
|
3858
4982
|
return this.dropdownRef.nativeElement;
|
|
3859
4983
|
}
|
|
4984
|
+
// Pre-measure default only: the requested placement's corner. The real
|
|
4985
|
+
// origin is measured per toggle (syncTransformOrigin), because
|
|
4986
|
+
// position-try fallbacks may have flipped the panel.
|
|
3860
4987
|
transformOriginMap = {
|
|
3861
4988
|
top: 'bottom center',
|
|
3862
4989
|
right: 'center left',
|
|
@@ -3930,6 +5057,10 @@ class UniDropdownComponent extends BaseComponent {
|
|
|
3930
5057
|
// Sync state if user invokes light-dismiss via outside click or Escape key
|
|
3931
5058
|
this.renderer.listen(this._dropdown, 'toggle', (event) => {
|
|
3932
5059
|
const isOpened = event.newState === 'open';
|
|
5060
|
+
// Both edges: on open so the entry scale grows out of the trigger, and
|
|
5061
|
+
// on close-start so a panel the browser flipped while open (scroll near
|
|
5062
|
+
// a viewport edge) still collapses back toward the trigger.
|
|
5063
|
+
this.syncTransformOrigin();
|
|
3933
5064
|
this.showing.set(isOpened);
|
|
3934
5065
|
this.renderer.setAttribute(this._focusTarget, 'aria-expanded', `${isOpened}`);
|
|
3935
5066
|
if (isOpened) {
|
|
@@ -3941,6 +5072,19 @@ class UniDropdownComponent extends BaseComponent {
|
|
|
3941
5072
|
}
|
|
3942
5073
|
});
|
|
3943
5074
|
}
|
|
5075
|
+
/**
|
|
5076
|
+
* Scale the open/close animation from the corner touching the trigger,
|
|
5077
|
+
* wherever the browser actually placed the panel. The static
|
|
5078
|
+
* `transformOriginMap` covers only the *requested* placement; with
|
|
5079
|
+
* `position-try-fallbacks` the panel may have flipped at a viewport edge,
|
|
5080
|
+
* and a `bottom-end` picker rendered above its field would otherwise still
|
|
5081
|
+
* animate from the top-right corner.
|
|
5082
|
+
*/
|
|
5083
|
+
syncTransformOrigin() {
|
|
5084
|
+
const origin = transformOriginFor(this._dropdown.getBoundingClientRect(), this._trigger.getBoundingClientRect());
|
|
5085
|
+
if (origin)
|
|
5086
|
+
this.renderer.setStyle(this._dropdown, 'transform-origin', origin);
|
|
5087
|
+
}
|
|
3944
5088
|
/**
|
|
3945
5089
|
* Returns focus to the trigger when the popover closes while focus was
|
|
3946
5090
|
* inside it (or was dropped on <body> by the top layer closing), so
|
|
@@ -4018,69 +5162,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4018
5162
|
args: ['dropdown', { static: true }]
|
|
4019
5163
|
}] } });
|
|
4020
5164
|
|
|
4021
|
-
class UniInputBoxComponent extends BaseComponent {
|
|
4022
|
-
className = css({ display: 'contents' });
|
|
4023
|
-
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
4024
|
-
error = input(false, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
4025
|
-
minWidth = input('0', ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
|
|
4026
|
-
/** Override the themed field height, e.g. `'auto'` for multi-line fields. */
|
|
4027
|
-
height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
4028
|
-
color = computed(() => this.error() ? this.componentOptions().errorColor : this.componentOptions().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
4029
|
-
border = computed(() => this.error() ? this.componentOptions().errorBorder : this.componentOptions().border, ...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
|
|
4030
|
-
shadow = computed(() => this.error() ? this.componentOptions().errorShadow : this.componentOptions().shadow, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
|
|
4031
|
-
inputBoxClass = computed(() => css([
|
|
4032
|
-
this.disabled() && {
|
|
4033
|
-
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
4034
|
-
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
4035
|
-
cursor: 'not-allowed !important',
|
|
4036
|
-
},
|
|
4037
|
-
{
|
|
4038
|
-
'& input, select, textarea': {
|
|
4039
|
-
...removeInputPlatformStyling,
|
|
4040
|
-
height: '100%',
|
|
4041
|
-
...this.theme.paddingLeft(this.componentOptions().paddingLeft),
|
|
4042
|
-
...this.theme.color(this.componentOptions().textColor),
|
|
4043
|
-
// `typeFace` is the deprecated casing; themes that still set it win
|
|
4044
|
-
// only when the canonical key is absent.
|
|
4045
|
-
...this.theme.typeface(this.componentOptions().typeface ?? this.componentOptions().typeFace),
|
|
4046
|
-
},
|
|
4047
|
-
// Multi-line fields size themselves (rows/resize), not from the box.
|
|
4048
|
-
'& textarea': {
|
|
4049
|
-
height: 'auto',
|
|
4050
|
-
...this.theme.paddingTop('xs'),
|
|
4051
|
-
...this.theme.paddingBottom('xs'),
|
|
4052
|
-
},
|
|
4053
|
-
'&:has(input:disabled, select:disabled, textarea:disabled)': {
|
|
4054
|
-
...this.theme.color(this.componentOptions().disabledTextColor),
|
|
4055
|
-
...this.theme.backgroundColor(this.componentOptions().disabledColor),
|
|
4056
|
-
},
|
|
4057
|
-
'& input:disabled, select:disabled, textarea:disabled': {
|
|
4058
|
-
cursor: 'not-allowed !important',
|
|
4059
|
-
},
|
|
4060
|
-
'&:has(input:focus, select:focus, textarea:focus)': {
|
|
4061
|
-
outline: this.componentOptions().focusOutline,
|
|
4062
|
-
outlineOffset: this.componentOptions().focusOutlineOffset,
|
|
4063
|
-
// Optional focus chrome (border/ring/background). It yields to the
|
|
4064
|
-
// error state, so a flagged field stays visibly flagged while the
|
|
4065
|
-
// user is in it correcting the value.
|
|
4066
|
-
...(this.error()
|
|
4067
|
-
? {}
|
|
4068
|
-
: {
|
|
4069
|
-
...this.theme.border(this.componentOptions().focusBorder),
|
|
4070
|
-
...this.theme.boxShadow(this.componentOptions().focusShadow),
|
|
4071
|
-
...this.theme.backgroundColor(this.componentOptions().focusColor),
|
|
4072
|
-
}),
|
|
4073
|
-
},
|
|
4074
|
-
},
|
|
4075
|
-
]), ...(ngDevMode ? [{ debugName: "inputBoxClass" }] : /* istanbul ignore next */ []));
|
|
4076
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4077
|
-
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 });
|
|
4078
|
-
}
|
|
4079
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniInputBoxComponent, decorators: [{
|
|
4080
|
-
type: Component,
|
|
4081
|
-
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" }]
|
|
4082
|
-
}], 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 }] }] } });
|
|
4083
|
-
|
|
4084
5165
|
/**
|
|
4085
5166
|
* Date field with free-typed parsing and a popup calendar. Type `aug 20`,
|
|
4086
5167
|
* `8/20/2026` or `2026-08-20`, or pick from the grid — the form gets the
|
|
@@ -4671,32 +5752,9 @@ class UniTimeInputComponent extends BaseComponent {
|
|
|
4671
5752
|
embeddedClass = computed(() => css({ display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }), ...(ngDevMode ? [{ debugName: "embeddedClass" }] : /* istanbul ignore next */ []));
|
|
4672
5753
|
listClass = computed(() => {
|
|
4673
5754
|
const options = this.componentOptions();
|
|
4674
|
-
return css({
|
|
4675
|
-
position: 'absolute',
|
|
4676
|
-
top: '100%',
|
|
4677
|
-
left: 0,
|
|
4678
|
-
right: 0,
|
|
4679
|
-
zIndex: 20,
|
|
4680
|
-
margin: '4px 0 0',
|
|
4681
|
-
padding: 4,
|
|
4682
|
-
listStyle: 'none',
|
|
5755
|
+
return css(listboxPopupStyles(this.theme, options, {
|
|
4683
5756
|
maxHeight: (options.maxVisibleOptions ?? 7) * 36,
|
|
4684
|
-
|
|
4685
|
-
...this.theme.backgroundColor(options.listColor ?? 'primary-surface'),
|
|
4686
|
-
...this.theme.boxShadow(options.listShadow ?? 'menu'),
|
|
4687
|
-
...this.theme.radius(options.listBorderRadius ?? 'xs'),
|
|
4688
|
-
'& [role="option"]': {
|
|
4689
|
-
padding: '8px 12px',
|
|
4690
|
-
cursor: 'pointer',
|
|
4691
|
-
...this.theme.typeface('label'),
|
|
4692
|
-
...this.theme.color('on-primary-surface'),
|
|
4693
|
-
...this.theme.radius('xxs'),
|
|
4694
|
-
'&.active, &:hover': {
|
|
4695
|
-
...this.theme.backgroundColor('primary-container'),
|
|
4696
|
-
...this.theme.color('on-primary-container'),
|
|
4697
|
-
},
|
|
4698
|
-
},
|
|
4699
|
-
});
|
|
5757
|
+
}));
|
|
4700
5758
|
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
4701
5759
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTimeInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4702
5760
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTimeInputComponent, isStandalone: true, selector: "uni-time-input, TimeInput", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'timeInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listbox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes and the draft\n commits only when focus leaves the whole field. -->\n<div (focusout)=\"onFocusOut($event)\">\n <ng-template #parts>\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"displayText()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"ariaDescribedBy() ?? null\"\n [attr.aria-invalid]=\"showError() || draftInvalid() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput()\"\n />\n <span [class]=\"toggleWrapClass()\">\n <button\n icon-button\n type=\"button\"\n size=\"sm\"\n aria-haspopup=\"listbox\"\n [symbolName]=\"componentOptions().toggleSymbol ?? 'schedule'\"\n [attr.aria-expanded]=\"list.open()\"\n [disable]=\"disabled()\"\n (click)=\"onToggle()\"\n >\n Choose time\n </button>\n </span>\n </ng-template>\n\n @if (embedded()) {\n <!-- A composer (uni-date-time-input) owns the field chrome. -->\n <div [class]=\"embeddedClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n } @else {\n <uni-input-box [error]=\"showError() || draftInvalid()\" [disabled]=\"disabled()\">\n <div [class]=\"rowClass()\"><ng-container [ngTemplateOutlet]=\"parts\" /></div>\n </uni-input-box>\n }\n\n @if (list.open()) {\n <ul #listbox [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (time of options(); track time; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"time === value()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectOption(time)\"\n >\n {{ optionLabels()[i] }}\n </li>\n }\n </ul>\n }\n\n <!-- Commits and rejections are otherwise a silent reformat. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -6829,34 +7887,121 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
6829
7887
|
* This file exports all public-facing elements of the paginator component.
|
|
6830
7888
|
*/
|
|
6831
7889
|
|
|
6832
|
-
|
|
7890
|
+
/**
|
|
7891
|
+
* Anchored, top-layer panel on the native `popover` element — the browser owns
|
|
7892
|
+
* positioning (CSS anchor positioning), stacking, and, in rich mode, light
|
|
7893
|
+
* dismissal. Two modes share one implementation:
|
|
7894
|
+
*
|
|
7895
|
+
* - `rich` (default): a click-toggled disclosure. The projected `[trigger]`
|
|
7896
|
+
* carries `aria-expanded`/`aria-controls`; with no trigger content the app
|
|
7897
|
+
* drives `open` and no element claims controller ARIA. Focus stays on the
|
|
7898
|
+
* trigger (APG disclosure) unless the panel marks an `[autofocus]` field,
|
|
7899
|
+
* and returns to the trigger on close.
|
|
7900
|
+
* - `tooltip`: hover/focus-triggered, `role="tooltip"` + `aria-describedby`,
|
|
7901
|
+
* WCAG 1.4.13 dismissable/hoverable/persistent. Content must not contain
|
|
7902
|
+
* focusable elements.
|
|
7903
|
+
*
|
|
7904
|
+
* Anchoring and control are separate: `anchor` re-anchors the panel to any
|
|
7905
|
+
* element (or id) while the trigger keeps the disclosure semantics.
|
|
7906
|
+
*/
|
|
7907
|
+
class UniPopoverComponent extends BaseComponent {
|
|
6833
7908
|
renderer = inject(Renderer2);
|
|
6834
|
-
|
|
7909
|
+
destroyRef = inject(DestroyRef);
|
|
6835
7910
|
placement = input('bottom', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
6836
7911
|
/** Light-dismiss on outside click / Escape (native popover="auto"). */
|
|
6837
7912
|
autoClose = input(true, ...(ngDevMode ? [{ debugName: "autoClose" }] : /* istanbul ignore next */ []));
|
|
7913
|
+
/** Two-way open state; light dismissal and hover timers sync back into it. */
|
|
7914
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
7915
|
+
/** `rich` = click-toggled disclosure; `tooltip` = hover/focus description. */
|
|
7916
|
+
mode = input('rich', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
7917
|
+
/**
|
|
7918
|
+
* Anchor the panel to another element (or element id, resolved each time
|
|
7919
|
+
* the panel opens) while the projected trigger keeps the disclosure ARIA.
|
|
7920
|
+
* Empty string means unset.
|
|
7921
|
+
*/
|
|
7922
|
+
anchor = input(...(ngDevMode ? [undefined, { debugName: "anchor" }] : /* istanbul ignore next */ []));
|
|
7923
|
+
/** Title rendered in the header row and used as the accessible name. */
|
|
7924
|
+
header = input(...(ngDevMode ? [undefined, { debugName: "header" }] : /* istanbul ignore next */ []));
|
|
7925
|
+
/** Renders a "Close" icon button in the header row. */
|
|
7926
|
+
closable = input(false, ...(ngDevMode ? [{ debugName: "closable" }] : /* istanbul ignore next */ []));
|
|
7927
|
+
arrow = input(true, ...(ngDevMode ? [{ debugName: "arrow" }] : /* istanbul ignore next */ []));
|
|
7928
|
+
/** Panel max-width; numbers are px. Defaults to the theme's `maxWidth`. */
|
|
7929
|
+
maxWidth = input(...(ngDevMode ? [undefined, { debugName: "maxWidth" }] : /* istanbul ignore next */ []));
|
|
7930
|
+
/** Tooltip-mode hover-open delay, ms; defaults to the theme option. */
|
|
7931
|
+
openDelay = input(...(ngDevMode ? [undefined, { debugName: "openDelay" }] : /* istanbul ignore next */ []));
|
|
7932
|
+
/** Tooltip-mode pointer-leave close delay, ms; defaults to the theme option. */
|
|
7933
|
+
closeDelay = input(...(ngDevMode ? [undefined, { debugName: "closeDelay" }] : /* istanbul ignore next */ []));
|
|
7934
|
+
opened = output();
|
|
7935
|
+
closed = output();
|
|
6838
7936
|
panelId = uniqueId('uni-popover');
|
|
7937
|
+
headerId = uniqueId('uni-popover-header');
|
|
6839
7938
|
anchorName = newAnchorName();
|
|
6840
7939
|
showing = signal(false, ...(ngDevMode ? [{ debugName: "showing" }] : /* istanbul ignore next */ []));
|
|
7940
|
+
/** Whether the `[trigger]` slot projected any element content. */
|
|
7941
|
+
hasTrigger = signal(false, ...(ngDevMode ? [{ debugName: "hasTrigger" }] : /* istanbul ignore next */ []));
|
|
6841
7942
|
triggerRef = viewChild.required('trigger');
|
|
6842
7943
|
panelRef = viewChild.required('panel');
|
|
7944
|
+
openTimer = useTimer();
|
|
7945
|
+
closeTimer = useTimer();
|
|
7946
|
+
/** The element currently carrying our anchor-name. */
|
|
7947
|
+
anchoredEl = null;
|
|
7948
|
+
detachEscape = null;
|
|
6843
7949
|
constructor() {
|
|
6844
|
-
|
|
7950
|
+
super();
|
|
6845
7951
|
afterNextRender(() => {
|
|
7952
|
+
this.hasTrigger.set(this.triggerRef().nativeElement.childElementCount > 0);
|
|
7953
|
+
this.applyAnchor();
|
|
7954
|
+
});
|
|
7955
|
+
// Controller ARIA follows mode/open state; each branch removes the other
|
|
7956
|
+
// mode's attributes so a runtime mode flip never leaves stale semantics.
|
|
7957
|
+
effect(() => {
|
|
7958
|
+
if (!this.hasTrigger())
|
|
7959
|
+
return;
|
|
6846
7960
|
const target = resolveFocusTarget(this.triggerRef().nativeElement);
|
|
6847
|
-
this.
|
|
6848
|
-
|
|
7961
|
+
if (this.mode() === 'tooltip') {
|
|
7962
|
+
this.renderer.removeAttribute(target, 'aria-expanded');
|
|
7963
|
+
this.renderer.removeAttribute(target, 'aria-controls');
|
|
7964
|
+
this.renderer.setAttribute(target, 'aria-describedby', this.panelId);
|
|
7965
|
+
}
|
|
7966
|
+
else {
|
|
7967
|
+
this.renderer.removeAttribute(target, 'aria-describedby');
|
|
7968
|
+
this.renderer.setAttribute(target, 'aria-expanded', `${this.showing()}`);
|
|
7969
|
+
this.renderer.setAttribute(target, 'aria-controls', this.panelId);
|
|
7970
|
+
}
|
|
7971
|
+
});
|
|
7972
|
+
// Two-way `open`: drive the native popover when the model changes hands.
|
|
7973
|
+
effect(() => {
|
|
7974
|
+
const want = this.open();
|
|
7975
|
+
if (want === untracked(this.showing))
|
|
7976
|
+
return;
|
|
7977
|
+
if (want)
|
|
7978
|
+
this.showPopover();
|
|
7979
|
+
else
|
|
7980
|
+
this.hidePopover();
|
|
7981
|
+
});
|
|
7982
|
+
this.destroyRef.onDestroy(() => {
|
|
7983
|
+
this.detachEscape?.();
|
|
7984
|
+
if (this.anchoredEl)
|
|
7985
|
+
clearAnchorName(this.anchoredEl);
|
|
7986
|
+
try {
|
|
7987
|
+
this.panelRef().nativeElement.hidePopover();
|
|
7988
|
+
}
|
|
7989
|
+
catch {
|
|
7990
|
+
// popover was already closed or detached
|
|
7991
|
+
}
|
|
6849
7992
|
});
|
|
6850
7993
|
}
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
this.showing.set(open);
|
|
6854
|
-
this.renderer.setAttribute(resolveFocusTarget(this.triggerRef().nativeElement), 'aria-expanded', `${open}`);
|
|
6855
|
-
}
|
|
7994
|
+
/** Tooltip mode is always `manual` — its lifecycle belongs to the timers. */
|
|
7995
|
+
popoverKind = computed(() => this.mode() === 'tooltip' ? 'manual' : this.autoClose() ? 'auto' : 'manual', ...(ngDevMode ? [{ debugName: "popoverKind" }] : /* istanbul ignore next */ []));
|
|
6856
7996
|
showPopover() {
|
|
7997
|
+
if (this.showing())
|
|
7998
|
+
return;
|
|
7999
|
+
this.applyAnchor();
|
|
6857
8000
|
this.panelRef().nativeElement.showPopover();
|
|
6858
8001
|
}
|
|
6859
8002
|
hidePopover() {
|
|
8003
|
+
if (!this.showing())
|
|
8004
|
+
return;
|
|
6860
8005
|
this.panelRef().nativeElement.hidePopover();
|
|
6861
8006
|
}
|
|
6862
8007
|
togglePopover(event) {
|
|
@@ -6866,60 +8011,198 @@ class UniPopoverComponent {
|
|
|
6866
8011
|
else {
|
|
6867
8012
|
this.showPopover();
|
|
6868
8013
|
}
|
|
6869
|
-
event
|
|
8014
|
+
event?.stopPropagation();
|
|
6870
8015
|
}
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
8016
|
+
/**
|
|
8017
|
+
* The panel anchors to the detached `anchor` when one resolves (id strings
|
|
8018
|
+
* are looked up now, at open time), else to the trigger span.
|
|
8019
|
+
*/
|
|
8020
|
+
applyAnchor() {
|
|
8021
|
+
const el = resolveElement(this.anchor()) ?? this.triggerRef().nativeElement;
|
|
8022
|
+
if (this.anchoredEl === el)
|
|
8023
|
+
return;
|
|
8024
|
+
if (this.anchoredEl)
|
|
8025
|
+
clearAnchorName(this.anchoredEl);
|
|
8026
|
+
setAnchorName(el, this.anchorName);
|
|
8027
|
+
this.anchoredEl = el;
|
|
8028
|
+
}
|
|
8029
|
+
onToggle(event) {
|
|
8030
|
+
const isOpen = isToggleOpen(event);
|
|
8031
|
+
this.showing.set(isOpen);
|
|
8032
|
+
this.open.set(isOpen);
|
|
8033
|
+
const panel = this.panelRef().nativeElement;
|
|
8034
|
+
if (isOpen) {
|
|
8035
|
+
if (this.mode() === 'rich') {
|
|
8036
|
+
panel.querySelector('[autofocus]')?.focus();
|
|
8037
|
+
}
|
|
8038
|
+
else {
|
|
8039
|
+
this.attachTooltipEscape();
|
|
8040
|
+
if (typeof ngDevMode !== 'undefined' && ngDevMode && panel.querySelector(FOCUSABLE_SELECTOR)) {
|
|
8041
|
+
console.warn('[uni-popover] tooltip-mode content must not contain focusable elements — ' +
|
|
8042
|
+
'a tooltip is a description, not a surface. Use mode="rich" instead.');
|
|
8043
|
+
}
|
|
8044
|
+
}
|
|
8045
|
+
this.opened.emit();
|
|
8046
|
+
}
|
|
8047
|
+
else {
|
|
8048
|
+
this.detachEscape?.();
|
|
8049
|
+
if (this.mode() === 'rich' && this.hasTrigger()) {
|
|
8050
|
+
restoreOverlayFocus(panel, resolveFocusTarget(this.triggerRef().nativeElement));
|
|
8051
|
+
}
|
|
8052
|
+
this.closed.emit();
|
|
8053
|
+
}
|
|
8054
|
+
}
|
|
8055
|
+
closeFromButton() {
|
|
8056
|
+
this.hidePopover();
|
|
8057
|
+
if (this.hasTrigger())
|
|
8058
|
+
resolveFocusTarget(this.triggerRef().nativeElement).focus();
|
|
8059
|
+
}
|
|
8060
|
+
// --- rich-mode gesture ----------------------------------------------------
|
|
8061
|
+
onTriggerClick(event) {
|
|
8062
|
+
if (this.mode() !== 'rich')
|
|
8063
|
+
return;
|
|
8064
|
+
this.togglePopover(event);
|
|
8065
|
+
}
|
|
8066
|
+
// --- tooltip-mode gestures (WCAG 1.4.13) ----------------------------------
|
|
8067
|
+
openDelayMs = computed(() => this.openDelay() ?? this.componentOptions().tooltipOpenDelay, ...(ngDevMode ? [{ debugName: "openDelayMs" }] : /* istanbul ignore next */ []));
|
|
8068
|
+
closeDelayMs = computed(() => this.closeDelay() ?? this.componentOptions().tooltipCloseDelay, ...(ngDevMode ? [{ debugName: "closeDelayMs" }] : /* istanbul ignore next */ []));
|
|
8069
|
+
onTriggerEnter() {
|
|
8070
|
+
if (this.mode() !== 'tooltip')
|
|
8071
|
+
return;
|
|
8072
|
+
this.closeTimer.stop();
|
|
8073
|
+
this.openTimer.start(this.openDelayMs(), () => this.showPopover());
|
|
8074
|
+
}
|
|
8075
|
+
onTriggerLeave() {
|
|
8076
|
+
if (this.mode() !== 'tooltip')
|
|
8077
|
+
return;
|
|
8078
|
+
this.openTimer.stop();
|
|
8079
|
+
if (this.showing())
|
|
8080
|
+
this.closeTimer.start(this.closeDelayMs(), () => this.hidePopover());
|
|
8081
|
+
}
|
|
8082
|
+
onTriggerFocusIn() {
|
|
8083
|
+
if (this.mode() !== 'tooltip')
|
|
8084
|
+
return;
|
|
8085
|
+
this.closeTimer.stop();
|
|
8086
|
+
this.showPopover();
|
|
8087
|
+
}
|
|
8088
|
+
onTriggerFocusOut() {
|
|
8089
|
+
if (this.mode() !== 'tooltip')
|
|
8090
|
+
return;
|
|
8091
|
+
this.openTimer.stop();
|
|
8092
|
+
this.closeTimer.stop();
|
|
8093
|
+
this.hidePopover();
|
|
8094
|
+
}
|
|
8095
|
+
/** Resting the pointer inside the panel must not dismiss it (hoverable). */
|
|
8096
|
+
onPanelEnter() {
|
|
8097
|
+
if (this.mode() !== 'tooltip')
|
|
8098
|
+
return;
|
|
8099
|
+
this.closeTimer.stop();
|
|
8100
|
+
}
|
|
8101
|
+
onPanelLeave() {
|
|
8102
|
+
if (this.mode() !== 'tooltip')
|
|
8103
|
+
return;
|
|
8104
|
+
if (this.showing())
|
|
8105
|
+
this.closeTimer.start(this.closeDelayMs(), () => this.hidePopover());
|
|
8106
|
+
}
|
|
8107
|
+
/**
|
|
8108
|
+
* Tooltip panels are `manual`, so Escape is ours: dismiss without moving
|
|
8109
|
+
* focus, and stop propagation so an enclosing dialog stays open. Capture
|
|
8110
|
+
* phase, document-level — the pointer may be nowhere near the trigger.
|
|
8111
|
+
*/
|
|
8112
|
+
attachTooltipEscape() {
|
|
8113
|
+
const onKeydown = (event) => {
|
|
8114
|
+
if (event.key !== 'Escape')
|
|
8115
|
+
return;
|
|
8116
|
+
event.stopPropagation();
|
|
8117
|
+
this.openTimer.stop();
|
|
8118
|
+
this.closeTimer.stop();
|
|
8119
|
+
this.hidePopover();
|
|
8120
|
+
};
|
|
8121
|
+
document.addEventListener('keydown', onKeydown, true);
|
|
8122
|
+
this.detachEscape = () => {
|
|
8123
|
+
document.removeEventListener('keydown', onKeydown, true);
|
|
8124
|
+
this.detachEscape = null;
|
|
8125
|
+
};
|
|
8126
|
+
}
|
|
8127
|
+
// --- styling --------------------------------------------------------------
|
|
8128
|
+
triggerClassName = css({ display: 'inline-block' });
|
|
8129
|
+
resolvedMaxWidth = computed(() => {
|
|
8130
|
+
const width = this.maxWidth();
|
|
8131
|
+
if (width === undefined || width === '')
|
|
8132
|
+
return this.componentOptions().maxWidth;
|
|
8133
|
+
return typeof width === 'number' ? `${width}px` : width;
|
|
8134
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedMaxWidth" }] : /* istanbul ignore next */ []));
|
|
8135
|
+
popoverClassName = computed(() => {
|
|
8136
|
+
const options = this.componentOptions();
|
|
8137
|
+
return css({
|
|
8138
|
+
...this.theme.colorPair(options.color),
|
|
8139
|
+
...this.theme.radius(options.borderRadius),
|
|
8140
|
+
...this.theme.boxShadow(options.shadow),
|
|
8141
|
+
...this.theme.typeface(options.typeface),
|
|
8142
|
+
...this.theme.border(options.border),
|
|
8143
|
+
padding: 0,
|
|
6883
8144
|
width: 'max-content',
|
|
8145
|
+
maxWidth: this.resolvedMaxWidth(),
|
|
6884
8146
|
overflow: 'visible',
|
|
6885
|
-
...anchorStyles(this.anchorName, this.placement(), { mainAxis:
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
'
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
},
|
|
6899
|
-
}
|
|
6900
|
-
|
|
8147
|
+
...anchorStyles(this.anchorName, this.placement(), { mainAxis: options.offset }),
|
|
8148
|
+
...discreteOverlayTransition(250, { opacity: 0 }, { opacity: 1 }),
|
|
8149
|
+
});
|
|
8150
|
+
}, ...(ngDevMode ? [{ debugName: "popoverClassName" }] : /* istanbul ignore next */ []));
|
|
8151
|
+
/** Empty regions collapse, so bare content renders v1's single-region look. */
|
|
8152
|
+
headerRowClassName = computed(() => {
|
|
8153
|
+
const options = this.componentOptions();
|
|
8154
|
+
return css({
|
|
8155
|
+
display: 'flex',
|
|
8156
|
+
alignItems: 'center',
|
|
8157
|
+
gap: 8,
|
|
8158
|
+
padding: options.padding,
|
|
8159
|
+
paddingBottom: 0,
|
|
8160
|
+
'&:not(:has(*))': { display: 'none' },
|
|
8161
|
+
});
|
|
8162
|
+
}, ...(ngDevMode ? [{ debugName: "headerRowClassName" }] : /* istanbul ignore next */ []));
|
|
8163
|
+
titleClassName = computed(() => css({ ...this.theme.typeface(this.componentOptions().headerTypeface), marginRight: 'auto' }), ...(ngDevMode ? [{ debugName: "titleClassName" }] : /* istanbul ignore next */ []));
|
|
8164
|
+
bodyClassName = computed(() => {
|
|
8165
|
+
const options = this.componentOptions();
|
|
8166
|
+
return css({
|
|
8167
|
+
padding: this.mode() === 'tooltip' ? options.tooltipPadding : options.padding,
|
|
8168
|
+
'&:empty': { display: 'none' },
|
|
8169
|
+
});
|
|
8170
|
+
}, ...(ngDevMode ? [{ debugName: "bodyClassName" }] : /* istanbul ignore next */ []));
|
|
8171
|
+
footerRowClassName = computed(() => {
|
|
8172
|
+
const options = this.componentOptions();
|
|
8173
|
+
return css({
|
|
8174
|
+
display: 'flex',
|
|
8175
|
+
alignItems: 'center',
|
|
8176
|
+
justifyContent: 'flex-end',
|
|
8177
|
+
gap: 8,
|
|
8178
|
+
padding: options.padding,
|
|
8179
|
+
paddingTop: 0,
|
|
8180
|
+
'&:not(:has(*))': { display: 'none' },
|
|
8181
|
+
});
|
|
8182
|
+
}, ...(ngDevMode ? [{ debugName: "footerRowClassName" }] : /* istanbul ignore next */ []));
|
|
6901
8183
|
arrowClassName = computed(() => {
|
|
8184
|
+
const options = this.componentOptions();
|
|
6902
8185
|
const side = this.placement().split('-')[0];
|
|
6903
|
-
// Border on the two edges of the
|
|
8186
|
+
// Border on the two edges of the clipped half that face outward
|
|
6904
8187
|
const borders = {
|
|
6905
|
-
top: { ...this.theme.borderRight(
|
|
6906
|
-
bottom: { ...this.theme.borderLeft(
|
|
6907
|
-
left: { ...this.theme.borderRight(
|
|
6908
|
-
right: { ...this.theme.borderLeft(
|
|
8188
|
+
top: { ...this.theme.borderRight(options.border), ...this.theme.borderBottom(options.border) },
|
|
8189
|
+
bottom: { ...this.theme.borderLeft(options.border), ...this.theme.borderTop(options.border) },
|
|
8190
|
+
left: { ...this.theme.borderRight(options.border), ...this.theme.borderTop(options.border) },
|
|
8191
|
+
right: { ...this.theme.borderLeft(options.border), ...this.theme.borderBottom(options.border) },
|
|
6909
8192
|
};
|
|
6910
8193
|
return css({
|
|
6911
|
-
...this.theme.colorPair(
|
|
6912
|
-
...anchorArrowStyles(this.placement()),
|
|
8194
|
+
...this.theme.colorPair(options.color),
|
|
8195
|
+
...anchorArrowStyles(this.placement(), options.arrowSize),
|
|
6913
8196
|
...borders[side],
|
|
6914
8197
|
});
|
|
6915
8198
|
}, ...(ngDevMode ? [{ debugName: "arrowClassName" }] : /* istanbul ignore next */ []));
|
|
6916
8199
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6917
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
8200
|
+
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 });
|
|
6918
8201
|
}
|
|
6919
8202
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniPopoverComponent, decorators: [{
|
|
6920
8203
|
type: Component,
|
|
6921
|
-
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
|
|
6922
|
-
}], 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 }] }] } });
|
|
8204
|
+
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" }]
|
|
8205
|
+
}], 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 }] }] } });
|
|
6923
8206
|
|
|
6924
8207
|
/**
|
|
6925
8208
|
* UniPopoverComponent Barrel File
|
|
@@ -7236,35 +8519,7 @@ class UniSearchInputComponent extends BaseComponent {
|
|
|
7236
8519
|
...this.theme.paddingLeft('sm'),
|
|
7237
8520
|
},
|
|
7238
8521
|
}), ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
7239
|
-
listClass = computed(() => {
|
|
7240
|
-
const options = this.componentOptions();
|
|
7241
|
-
return css({
|
|
7242
|
-
position: 'absolute',
|
|
7243
|
-
top: '100%',
|
|
7244
|
-
left: 0,
|
|
7245
|
-
right: 0,
|
|
7246
|
-
zIndex: 20,
|
|
7247
|
-
margin: '4px 0 0',
|
|
7248
|
-
padding: 4,
|
|
7249
|
-
listStyle: 'none',
|
|
7250
|
-
maxHeight: 280,
|
|
7251
|
-
overflowY: 'auto',
|
|
7252
|
-
...this.theme.backgroundColor(options.listColor ?? 'primary-surface'),
|
|
7253
|
-
...this.theme.boxShadow(options.listShadow ?? 'menu'),
|
|
7254
|
-
...this.theme.radius(options.listBorderRadius ?? 'xs'),
|
|
7255
|
-
'& [role="option"]': {
|
|
7256
|
-
padding: '8px 12px',
|
|
7257
|
-
cursor: 'pointer',
|
|
7258
|
-
...this.theme.typeface('label'),
|
|
7259
|
-
...this.theme.color('on-primary-surface'),
|
|
7260
|
-
...this.theme.radius('xxs'),
|
|
7261
|
-
'&.active, &:hover': {
|
|
7262
|
-
...this.theme.backgroundColor('primary-container'),
|
|
7263
|
-
...this.theme.color('on-primary-container'),
|
|
7264
|
-
},
|
|
7265
|
-
},
|
|
7266
|
-
});
|
|
7267
|
-
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
8522
|
+
listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions())), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
7268
8523
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniSearchInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
7269
8524
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniSearchInputComponent, isStandalone: true, selector: "uni-search-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change", search: "search", suggestionSelected: "suggestionSelected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'searchInput' }], viewQueries: [{ propertyName: "field", first: true, predicate: UniDebounceInputComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Keydown/focusout ride the bubbling events from the inner input; the\n wrapper itself stays out of the tab order. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div (keydown)=\"onKeydown($event)\" (focusout)=\"onFocusOut($event)\">\n <uni-debounce-input\n #searchField\n [label]=\"label()\"\n [placeholder]=\"placeholder() ?? label()\"\n [debounceTime]=\"debounceTime()\"\n role=\"combobox\"\n [ariaExpanded]=\"list.open()\"\n [ariaControls]=\"listboxId\"\n [ariaActivedescendant]=\"list.activeDescendantId() ?? undefined\"\n (change)=\"handleChange($event)\"\n >\n <uni-symbol pre-input class=\"uni-search-lead\" name=\"{{ componentOptions().searchSymbol ?? 'search' }}\" aria-hidden=\"true\" />\n @if (hasQuery()) {\n <button\n post-input\n icon-button\n [iconName]=\"componentOptions().clearSymbol ?? 'close'\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"clear()\"\n >\n Clear search\n </button>\n }\n </uni-debounce-input>\n\n @if (list.open()) {\n <ul [id]=\"listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(suggestion)\"\n >\n {{ suggestion }}\n </li>\n }\n </ul>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniDebounceInputComponent, selector: "uni-debounce-input", inputs: ["inputName", "inputId", "debounceTime", "label", "placeholder", "disabled", "role", "ariaExpanded", "ariaControls", "ariaActivedescendant"], outputs: ["change"] }, { kind: "component", type: UniIconButtonComponent, selector: "button[uni-icon-button], button[icon-button]", inputs: ["ariaLabel", "iconName", "symbolName", "variant", "size", "disable", "loading", "opticalSize"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7270
8525
|
}
|
|
@@ -8534,35 +9789,7 @@ class UniTagInputComponent extends BaseComponent {
|
|
|
8534
9789
|
font: 'inherit',
|
|
8535
9790
|
padding: 0,
|
|
8536
9791
|
}), ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
|
|
8537
|
-
listClass = computed(() => {
|
|
8538
|
-
const options = this.componentOptions();
|
|
8539
|
-
return css({
|
|
8540
|
-
position: 'absolute',
|
|
8541
|
-
top: '100%',
|
|
8542
|
-
left: 0,
|
|
8543
|
-
right: 0,
|
|
8544
|
-
zIndex: 20,
|
|
8545
|
-
margin: '4px 0 0',
|
|
8546
|
-
padding: 4,
|
|
8547
|
-
listStyle: 'none',
|
|
8548
|
-
maxHeight: 280,
|
|
8549
|
-
overflowY: 'auto',
|
|
8550
|
-
...this.theme.backgroundColor(options.listColor ?? 'primary-surface'),
|
|
8551
|
-
...this.theme.boxShadow(options.listShadow ?? 'menu'),
|
|
8552
|
-
...this.theme.radius(options.listBorderRadius ?? 'xs'),
|
|
8553
|
-
'& [role="option"]': {
|
|
8554
|
-
padding: '8px 12px',
|
|
8555
|
-
cursor: 'pointer',
|
|
8556
|
-
...this.theme.typeface('label'),
|
|
8557
|
-
...this.theme.color('on-primary-surface'),
|
|
8558
|
-
...this.theme.radius('xxs'),
|
|
8559
|
-
'&.active, &:hover': {
|
|
8560
|
-
...this.theme.backgroundColor('primary-container'),
|
|
8561
|
-
...this.theme.color('on-primary-container'),
|
|
8562
|
-
},
|
|
8563
|
-
},
|
|
8564
|
-
});
|
|
8565
|
-
}, ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
9792
|
+
listClass = computed(() => css(listboxPopupStyles(this.theme, this.componentOptions())), ...(ngDevMode ? [{ debugName: "listClass" }] : /* istanbul ignore next */ []));
|
|
8566
9793
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTagInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
8567
9794
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTagInputComponent, isStandalone: true, selector: "uni-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, commitOnBlur: { classPropertyName: "commitOnBlur", publicName: "commitOnBlur", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, tagVariant: { classPropertyName: "tagVariant", publicName: "tagVariant", isSignal: true, isRequired: false, transformFunction: null }, tagTone: { classPropertyName: "tagTone", publicName: "tagTone", isSignal: true, isRequired: false, transformFunction: null }, tagSize: { classPropertyName: "tagSize", publicName: "tagSize", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", query: "query", added: "added", removed: "removed", rejected: "rejected" }, host: { properties: { "class": "className()" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'tagInput' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "chipRefs", predicate: ["chip"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Focusout rides the bubbling event so the list closes only when focus\n leaves the whole field, not while moving between chips and the input. -->\n<div style=\"position: relative\" (focusout)=\"onFocusOut($event)\">\n <uni-input-box [error]=\"showError()\" height=\"auto\">\n <ul [class]=\"fieldClass()\">\n @for (item of value(); track item.value; let i = $index) {\n <!-- The focusable element is the chip's own button; this <li> only\n receives keys bubbling up from it, so it must stay out of the tab\n order (same delegation the menu uses for its roving focus). -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <li #chip (keydown)=\"onChipKeydown($event, i)\" (dblclick)=\"editChip(i)\">\n <uni-tag\n [label]=\"labelOf(item)\"\n [value]=\"item.value\"\n [avatarSrc]=\"item.avatarSrc\"\n [variant]=\"item.invalid ? 'warn' : tagVariant()\"\n [tone]=\"tagTone()\"\n [size]=\"tagSize() ?? componentOptions().chipSize ?? 'md'\"\n [invalid]=\"!!item.invalid\"\n [disabled]=\"!!item.disabled || disabled()\"\n [interactive]=\"true\"\n [removable]=\"!item.disabled && !disabled()\"\n [controlTabIndex]=\"-1\"\n (removed)=\"removeAt(i)\"\n />\n </li>\n }\n\n <li [class]=\"inputClass()\">\n <!-- The field is one tab stop: chips carry tabindex=\"-1\" and are\n entered with Backspace/ArrowLeft, so Tab never walks through\n eight recipients to reach the next control. -->\n <input\n #field\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n [class]=\"inputClass()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() ?? ''\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"list.open()\"\n [attr.aria-controls]=\"list.listboxId\"\n [attr.aria-activedescendant]=\"list.activeDescendantId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n (keydown)=\"onInputKeydown($event)\"\n (input)=\"onInput($any($event.target).value)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n </li>\n </ul>\n </uni-input-box>\n\n @if (list.open()) {\n <ul [id]=\"list.listboxId\" role=\"listbox\" [attr.aria-label]=\"label()\" [class]=\"listClass()\">\n @for (suggestion of visibleSuggestions(); track suggestion.value; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"list.optionId(i)\"\n [class.active]=\"i === list.activeIndex()\"\n [attr.aria-selected]=\"i === list.activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"selectSuggestion(suggestion)\"\n >\n {{ labelOf(suggestion) }}\n @if (suggestion.description) {\n <span [class]=\"srOnly\">{{ suggestion.description }}</span>\n }\n </li>\n }\n </ul>\n }\n\n <!-- Said once for the whole field, rather than every chip advertising it. -->\n <span [id]=\"hintId\" [class]=\"srOnly\">\n Press Backspace to reach the last entry, then Backspace again to remove it.\n </span>\n\n <!-- Adds and removes are otherwise silent for a screen reader. -->\n <span role=\"status\" aria-live=\"polite\" [class]=\"srOnly\">{{ announcement() }}</span>\n</div>\n", dependencies: [{ kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height"] }, { kind: "component", type: UniTagComponent, selector: "uni-tag", inputs: ["size", "tone", "label", "value", "maxWidth", "avatarSrc", "avatarName", "iconName", "symbolName", "dot", "removable", "interactive", "selected", "invalid", "disabled", "removeLabel", "controlTabIndex"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8568
9795
|
}
|
|
@@ -9836,9 +11063,221 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
9836
11063
|
}]
|
|
9837
11064
|
}], 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 }] }] } });
|
|
9838
11065
|
|
|
11066
|
+
/**
|
|
11067
|
+
* A thin sequencer over one `uni-callout`: steps spotlight their targets, a
|
|
11068
|
+
* footer walks Next/Back with dots-or-fraction progress, and `advanceOn`
|
|
11069
|
+
* gates a step behind an interaction with its target (auto-advancing for
|
|
11070
|
+
* clicks, unlocking Next otherwise — announced through one `role="status"`
|
|
11071
|
+
* region). Escape or the close button skips the tour and reports the step.
|
|
11072
|
+
*
|
|
11073
|
+
* `active` is a two-way model, so a tour is deep-linkable and inspectable;
|
|
11074
|
+
* steps whose declared target cannot be resolved are skipped (with a dev
|
|
11075
|
+
* warning) in the direction of travel rather than erroring.
|
|
11076
|
+
*/
|
|
11077
|
+
class UniTourComponent extends BaseComponent {
|
|
11078
|
+
renderer = inject(Renderer2);
|
|
11079
|
+
destroyRef = inject(DestroyRef);
|
|
11080
|
+
steps = input.required(...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
|
|
11081
|
+
/** The presented step index, or null when the tour is not running. */
|
|
11082
|
+
active = model(null, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
11083
|
+
nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : /* istanbul ignore next */ []));
|
|
11084
|
+
backLabel = input('Back', ...(ngDevMode ? [{ debugName: "backLabel" }] : /* istanbul ignore next */ []));
|
|
11085
|
+
skipLabel = input('Skip', ...(ngDevMode ? [{ debugName: "skipLabel" }] : /* istanbul ignore next */ []));
|
|
11086
|
+
doneLabel = input('Done', ...(ngDevMode ? [{ debugName: "doneLabel" }] : /* istanbul ignore next */ []));
|
|
11087
|
+
started = output();
|
|
11088
|
+
stepChanged = output();
|
|
11089
|
+
finished = output();
|
|
11090
|
+
skipped = output();
|
|
11091
|
+
calloutOpen = signal(false, ...(ngDevMode ? [{ debugName: "calloutOpen" }] : /* istanbul ignore next */ []));
|
|
11092
|
+
satisfied = signal(true, ...(ngDevMode ? [{ debugName: "satisfied" }] : /* istanbul ignore next */ []));
|
|
11093
|
+
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
11094
|
+
resolvedTarget = signal(undefined, ...(ngDevMode ? [{ debugName: "resolvedTarget" }] : /* istanbul ignore next */ []));
|
|
11095
|
+
presentedIndex = signal(null, ...(ngDevMode ? [{ debugName: "presentedIndex" }] : /* istanbul ignore next */ []));
|
|
11096
|
+
gateCleanup = null;
|
|
11097
|
+
index = computed(() => this.presentedIndex() ?? 0, ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
11098
|
+
currentStep = computed(() => {
|
|
11099
|
+
const index = this.presentedIndex();
|
|
11100
|
+
return index === null ? undefined : this.steps()[index];
|
|
11101
|
+
}, ...(ngDevMode ? [{ debugName: "currentStep" }] : /* istanbul ignore next */ []));
|
|
11102
|
+
isLast = computed(() => this.index() >= this.steps().length - 1, ...(ngDevMode ? [{ debugName: "isLast" }] : /* istanbul ignore next */ []));
|
|
11103
|
+
clickGate = computed(() => this.currentStep()?.advanceOn?.event === 'click', ...(ngDevMode ? [{ debugName: "clickGate" }] : /* istanbul ignore next */ []));
|
|
11104
|
+
/** Gated steps force an interactive target — the gate needs the gesture. */
|
|
11105
|
+
targetInteractive = computed(() => {
|
|
11106
|
+
const step = this.currentStep();
|
|
11107
|
+
return step?.advanceOn ? true : (step?.targetInteractive ?? true);
|
|
11108
|
+
}, ...(ngDevMode ? [{ debugName: "targetInteractive" }] : /* istanbul ignore next */ []));
|
|
11109
|
+
stepAriaLabel = computed(() => {
|
|
11110
|
+
const step = this.currentStep();
|
|
11111
|
+
return step ? `${step.title}, step ${this.index() + 1} of ${this.steps().length}` : '';
|
|
11112
|
+
}, ...(ngDevMode ? [{ debugName: "stepAriaLabel" }] : /* istanbul ignore next */ []));
|
|
11113
|
+
constructor() {
|
|
11114
|
+
super();
|
|
11115
|
+
// External writes to `active` (deep links) route into the sequencer.
|
|
11116
|
+
effect(() => {
|
|
11117
|
+
const index = this.active();
|
|
11118
|
+
if (index === untracked(this.presentedIndex))
|
|
11119
|
+
return;
|
|
11120
|
+
untracked(() => (index === null ? this.stopTour() : this.present(index, 1)));
|
|
11121
|
+
});
|
|
11122
|
+
this.destroyRef.onDestroy(() => this.gateCleanup?.());
|
|
11123
|
+
}
|
|
11124
|
+
start(at = 0) {
|
|
11125
|
+
this.started.emit();
|
|
11126
|
+
this.present(at, 1);
|
|
11127
|
+
}
|
|
11128
|
+
next() {
|
|
11129
|
+
this.advance();
|
|
11130
|
+
}
|
|
11131
|
+
back() {
|
|
11132
|
+
const index = this.presentedIndex();
|
|
11133
|
+
if (index !== null && index > 0)
|
|
11134
|
+
this.present(index - 1, -1);
|
|
11135
|
+
}
|
|
11136
|
+
skip() {
|
|
11137
|
+
const step = this.currentStep();
|
|
11138
|
+
const index = this.presentedIndex();
|
|
11139
|
+
this.stopTour();
|
|
11140
|
+
if (step && index !== null)
|
|
11141
|
+
this.skipped.emit({ key: step.key, index });
|
|
11142
|
+
}
|
|
11143
|
+
advance() {
|
|
11144
|
+
const index = this.presentedIndex();
|
|
11145
|
+
if (index === null)
|
|
11146
|
+
return;
|
|
11147
|
+
if (index >= this.steps().length - 1)
|
|
11148
|
+
this.finishTour();
|
|
11149
|
+
else
|
|
11150
|
+
this.present(index + 1, 1);
|
|
11151
|
+
}
|
|
11152
|
+
/**
|
|
11153
|
+
* Present step `index`, skipping (in the direction of travel) steps whose
|
|
11154
|
+
* declared target cannot be resolved right now.
|
|
11155
|
+
*/
|
|
11156
|
+
present(index, dir) {
|
|
11157
|
+
this.gateCleanup?.();
|
|
11158
|
+
const steps = this.steps();
|
|
11159
|
+
if (index < 0 || index >= steps.length) {
|
|
11160
|
+
this.finishTour();
|
|
11161
|
+
return;
|
|
11162
|
+
}
|
|
11163
|
+
const step = steps[index];
|
|
11164
|
+
let target = null;
|
|
11165
|
+
if (step.target !== undefined && step.target !== '') {
|
|
11166
|
+
target = resolveElement(step.target);
|
|
11167
|
+
if (!target) {
|
|
11168
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
11169
|
+
console.warn(`[uni-tour] step "${step.key}" target missing — skipped`);
|
|
11170
|
+
}
|
|
11171
|
+
this.present(index + dir, dir);
|
|
11172
|
+
return;
|
|
11173
|
+
}
|
|
11174
|
+
}
|
|
11175
|
+
this.presentedIndex.set(index);
|
|
11176
|
+
this.active.set(index);
|
|
11177
|
+
this.resolvedTarget.set(target ?? undefined);
|
|
11178
|
+
this.setupGate(step, target);
|
|
11179
|
+
this.calloutOpen.set(true);
|
|
11180
|
+
this.stepChanged.emit({ key: step.key, index });
|
|
11181
|
+
}
|
|
11182
|
+
setupGate(step, target) {
|
|
11183
|
+
const gate = step.advanceOn;
|
|
11184
|
+
this.satisfied.set(!gate);
|
|
11185
|
+
if (!gate || !target)
|
|
11186
|
+
return;
|
|
11187
|
+
const auto = gate.auto ?? gate.event === 'click';
|
|
11188
|
+
const unlisten = this.renderer.listen(target, gate.event, () => {
|
|
11189
|
+
if (untracked(this.satisfied) && !auto)
|
|
11190
|
+
return;
|
|
11191
|
+
this.satisfied.set(true);
|
|
11192
|
+
if (auto) {
|
|
11193
|
+
this.gateCleanup?.();
|
|
11194
|
+
this.advance();
|
|
11195
|
+
}
|
|
11196
|
+
else {
|
|
11197
|
+
this.announcement.set('Next available');
|
|
11198
|
+
}
|
|
11199
|
+
});
|
|
11200
|
+
this.gateCleanup = () => {
|
|
11201
|
+
unlisten();
|
|
11202
|
+
this.gateCleanup = null;
|
|
11203
|
+
};
|
|
11204
|
+
}
|
|
11205
|
+
onDismissed(dismissal) {
|
|
11206
|
+
if (dismissal.reason === 'programmatic')
|
|
11207
|
+
return; // that's us, stepping or stopping
|
|
11208
|
+
const step = this.currentStep();
|
|
11209
|
+
const index = this.presentedIndex();
|
|
11210
|
+
this.stopTour();
|
|
11211
|
+
if (step && index !== null)
|
|
11212
|
+
this.skipped.emit({ key: step.key, index });
|
|
11213
|
+
}
|
|
11214
|
+
/** Arrow keys navigate only while focus is inside the panel. */
|
|
11215
|
+
onPanelKeydown(event) {
|
|
11216
|
+
if (event.key === 'ArrowRight' && this.satisfied() && !this.clickGate()) {
|
|
11217
|
+
event.preventDefault();
|
|
11218
|
+
this.advance();
|
|
11219
|
+
}
|
|
11220
|
+
else if (event.key === 'ArrowLeft' && this.index() > 0) {
|
|
11221
|
+
event.preventDefault();
|
|
11222
|
+
this.back();
|
|
11223
|
+
}
|
|
11224
|
+
}
|
|
11225
|
+
finishTour() {
|
|
11226
|
+
this.gateCleanup?.();
|
|
11227
|
+
this.calloutOpen.set(false);
|
|
11228
|
+
this.presentedIndex.set(null);
|
|
11229
|
+
this.active.set(null);
|
|
11230
|
+
this.finished.emit();
|
|
11231
|
+
}
|
|
11232
|
+
stopTour() {
|
|
11233
|
+
this.gateCleanup?.();
|
|
11234
|
+
this.calloutOpen.set(false);
|
|
11235
|
+
this.presentedIndex.set(null);
|
|
11236
|
+
this.active.set(null);
|
|
11237
|
+
}
|
|
11238
|
+
// --- styling --------------------------------------------------------------
|
|
11239
|
+
statusClassName = css(visuallyHidden);
|
|
11240
|
+
footerClassName = computed(() => css({
|
|
11241
|
+
display: 'flex',
|
|
11242
|
+
alignItems: 'center',
|
|
11243
|
+
flex: 1,
|
|
11244
|
+
gap: this.theme.spacing()[this.componentOptions().footerGap],
|
|
11245
|
+
}), ...(ngDevMode ? [{ debugName: "footerClassName" }] : /* istanbul ignore next */ []));
|
|
11246
|
+
dotsClassName = computed(() => {
|
|
11247
|
+
const colors = this.theme.colorPalette();
|
|
11248
|
+
const on = colors[this.variant()] ?? colors['primary'];
|
|
11249
|
+
return css({
|
|
11250
|
+
display: 'inline-flex',
|
|
11251
|
+
gap: 5,
|
|
11252
|
+
margin: '0 auto',
|
|
11253
|
+
'& i': {
|
|
11254
|
+
width: 6,
|
|
11255
|
+
height: 6,
|
|
11256
|
+
borderRadius: 999,
|
|
11257
|
+
background: colors['surface-variant'],
|
|
11258
|
+
border: `1px solid ${colors['outline'] ?? colors['surface-variant']}`,
|
|
11259
|
+
},
|
|
11260
|
+
'& i.on': { background: on, borderColor: on },
|
|
11261
|
+
});
|
|
11262
|
+
}, ...(ngDevMode ? [{ debugName: "dotsClassName" }] : /* istanbul ignore next */ []));
|
|
11263
|
+
fractionClassName = css({ margin: '0 auto' });
|
|
11264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTourComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11265
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniTourComponent, isStandalone: true, selector: "uni-tour", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { active: "activeChange", started: "started", stepChanged: "stepChanged", finished: "finished", skipped: "skipped" }, providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], usesInheritance: true, ngImport: i0, template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcement() }}</span>\n", dependencies: [{ kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "component", type: UniCalloutComponent, selector: "uni-callout", inputs: ["open", "key", "target", "placement", "backdrop", "targetInteractive", "dismissible", "dismissOnBackdrop", "header", "arrow", "ariaLabel", "closeLabel"], outputs: ["openChange", "opened", "closed", "dismissed", "panelKeydown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11266
|
+
}
|
|
11267
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniTourComponent, decorators: [{
|
|
11268
|
+
type: Component,
|
|
11269
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'uni-tour', imports: [UniButtonComponent, UniCalloutComponent], providers: [{ provide: COMPONENT_NAME, useValue: 'tour' }], template: "<uni-callout\n [(open)]=\"calloutOpen\"\n [key]=\"currentStep()?.key ?? ''\"\n [target]=\"resolvedTarget()\"\n [placement]=\"currentStep()?.placement ?? 'bottom'\"\n [backdrop]=\"currentStep()?.backdrop\"\n [targetInteractive]=\"targetInteractive()\"\n [header]=\"currentStep()?.title ?? ''\"\n [ariaLabel]=\"stepAriaLabel()\"\n [closeLabel]=\"skipLabel()\"\n (dismissed)=\"onDismissed($event)\"\n (panelKeydown)=\"onPanelKeydown($event)\"\n>\n <p>{{ currentStep()?.body }}</p>\n <div callout-actions [class]=\"footerClassName()\">\n @if (index() > 0) {\n <button text-button variant=\"ghost\" (click)=\"back()\">{{ backLabel() }}</button>\n }\n @if (componentOptions().progressStyle === 'fraction') {\n <span aria-hidden=\"true\" [class]=\"fractionClassName\">{{ index() + 1 }} of {{ steps().length }}</span>\n } @else {\n <span aria-hidden=\"true\" [class]=\"dotsClassName()\">\n @for (step of steps(); track $index) {\n <i [class.on]=\"$index === index()\"></i>\n }\n </span>\n }\n <!-- A click-gate advances by using the target itself \u2014 a Next button\n would be a lie, so none renders. -->\n @if (!clickGate()) {\n <button text-button [disable]=\"!satisfied()\" (click)=\"advance()\">\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n }\n </div>\n</uni-callout>\n<!-- One live region per tour: gate unlocks are announced here. -->\n<span role=\"status\" [class]=\"statusClassName\">{{ announcement() }}</span>\n" }]
|
|
11270
|
+
}], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }, { type: i0.Output, args: ["activeChange"] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], backLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLabel", required: false }] }], skipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], started: [{ type: i0.Output, args: ["started"] }], stepChanged: [{ type: i0.Output, args: ["stepChanged"] }], finished: [{ type: i0.Output, args: ["finished"] }], skipped: [{ type: i0.Output, args: ["skipped"] }] } });
|
|
11271
|
+
|
|
11272
|
+
/**
|
|
11273
|
+
* UniTourComponent Barrel File
|
|
11274
|
+
*
|
|
11275
|
+
* This file exports all public-facing elements of the tour component.
|
|
11276
|
+
*/
|
|
11277
|
+
|
|
9839
11278
|
/**
|
|
9840
11279
|
* Generated bundle index. Do not edit.
|
|
9841
11280
|
*/
|
|
9842
11281
|
|
|
9843
|
-
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 };
|
|
11282
|
+
export { BodyRenderDirective, ConfirmationDialogComponent, DragAndDropDirective, FOCUSABLE_SELECTOR, ListboxNavigation, LocalStorageService, NotificationService, NotificationsComponent, RippleDirective, TRANSFORM_ORIGINS, ThemeService, UNI_THEMES, UniAlertComponent, UniAppBarComponent, UniAvatarComponent, UniAvatarGroupComponent, UniBackgroundComponent, UniBadgeComponent, UniBaseDatasource, UniBoxComponent, UniBreadcrumbComponent, UniButtonComponent, UniButtonGroupComponent, UniCalendarComponent, UniCalloutComponent, UniCardComponent, UniCardContentComponent, UniCardHeaderComponent, UniCenterComponent, UniCheckboxComponent, UniComboboxComponent, UniDataSearchComponent, UniDataTableComponent, UniDateInputComponent, UniDateTimeInputComponent, UniDebounceInputComponent, UniDialogButtonsComponent, UniDialogComponent, UniDialogHeaderComponent, UniDividerComponent, UniDrawerComponent, UniDropdownComponent, UniExpandAreaComponent, UniExpandComponent, UniExpandToggleComponent, UniFileDropZoneComponent, UniGridAreaComponent, UniGridComponent, UniIconButtonComponent, UniIconComponent, UniInputBoxComponent, UniInputComponent, UniJsonViewComponent, UniMenuComponent, UniMultiSelectComponent, UniMultiSelectDropdownComponent, UniNotificationBadgeComponent, UniPaginatorComponent, UniPopoverComponent, UniProgressBarComponent, UniProgressGaugeComponent, UniRadioComponent, UniRecordDatasource, UniRowComponent, UniScrollAreaComponent, UniSearchInputComponent, UniSelectComponent, UniServerSideDatasource, UniSkeletonComponent, UniSliderComponent, UniSnackbarComponent, UniSortHeaderComponent, UniStackComponent, UniStatComponent, UniSymbolComponent, UniTabComponent, UniTabsComponent, UniTagComponent, UniTagInputComponent, UniTextComponent, UniTextareaComponent, UniThemeBuilderComponent, UniThemeSwitchComponent, UniTimeInputComponent, UniToggleComponent, UniTooltipComponent, UniTourComponent, UniWrapComponent, acceptableFile, addDays, addMonths, anchorArrowStyles, anchorStyles, buildMonthGrid, clearAnchorName, createListboxNavigation, dayOfWeek, daysInMonth, discreteOverlayTransition, focusableElements, formatDate, formatMonthHeading, formatTime, getFileExtension, inclusiveDayCount, isDivider, isToggleOpen, isValidDate, isoDate, joinDateTime, listboxPopupStyles, localeDatePlaceholder, localeDefaultHour12, localeFieldOrder, localeMonthNames, localeWeekStart, monthOf, motionSafe, newAnchorName, parseDateText, parseTimeText, resolveElement, resolveFocusTarget, restoreOverlayFocus, setAnchorName, splitDateTime, spotlightStyles, timeSlots, todayIso, transformOriginFor, uniqueId, useTimer, visuallyHidden, weekdayNames };
|
|
9844
11283
|
//# sourceMappingURL=uni-design-system-uni-angular.mjs.map
|