@filip.mazev/blocks 1.4.8 → 1.4.10

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.
@@ -3,11 +3,12 @@ import * as i0 from "@angular/core";
3
3
  import { ChangeDetectionStrategy, Component, DestroyRef, Directive, ElementRef, HostListener, InjectionToken, NgZone, PLATFORM_ID, ViewContainerRef, booleanAttribute, computed, effect, forwardRef, inject, input, output, signal } from "@angular/core";
4
4
  import { BX_I18N, BxA11yService, BxOverlayPositionService, BxShortcutService, inheritOverlayTheme, isTextWithKey, uuidv4 } from "@filip.mazev/blocks/core";
5
5
  import { Icon } from "@filip.mazev/blocks/icons";
6
+ import { AriaDescriber, FocusMonitor } from "@angular/cdk/a11y";
6
7
  import { Overlay } from "@angular/cdk/overlay";
7
8
  import { ComponentPortal, TemplatePortal } from "@angular/cdk/portal";
8
9
  import { createGlassTransition, inheritGlassLighting } from "@filip.mazev/blocks/effects/runtime";
9
- import { isPlatformBrowser } from "@angular/common";
10
10
  import { Subscription } from "rxjs";
11
+ import { isPlatformBrowser } from "@angular/common";
11
12
  var Tooltip = class Tooltip {
12
13
  i18n = inject(BX_I18N, { optional: true });
13
14
  a11y = inject(BxA11yService);
@@ -345,75 +346,103 @@ i0.ɵɵngDeclareClassMetadata({
345
346
  }]
346
347
  }
347
348
  });
348
- var TooltipDirective = class TooltipDirective {
349
- elementRef = inject(ElementRef);
349
+ const BX_OVERLAY_CONTEXT = new InjectionToken("BX_OVERLAY_CONTEXT");
350
+ var ContextMenuDirective = class ContextMenuDirective {
350
351
  overlay = inject(Overlay);
351
352
  positionBuilder = inject(BxOverlayPositionService);
353
+ overlayContext = inject(BX_OVERLAY_CONTEXT, { optional: true });
354
+ elementRef = inject(ElementRef);
352
355
  a11y = inject(BxA11yService);
353
356
  zone = inject(NgZone);
354
- id = `bx-tooltip-${uuidv4()}`;
355
- bxTooltip = input.required(...ngDevMode ? [{ debugName: "bxTooltip" }] : /* istanbul ignore next */ []);
356
- placement = input("bottom", ...ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []);
357
+ vcr = inject(ViewContainerRef);
358
+ bxContextMenu = input.required(...ngDevMode ? [{ debugName: "bxContextMenu" }] : /* istanbul ignore next */ []);
359
+ bxContextMenuLongPress = input(true, {
360
+ ...ngDevMode ? { debugName: "bxContextMenuLongPress" } : /* istanbul ignore next */ {},
361
+ transform: booleanAttribute
362
+ });
363
+ onPointerDown = (event) => {
364
+ this.lastPointerType = event.pointerType;
365
+ this.cancelTouchPress();
366
+ if (event.isPrimary) this.clearTouchGuard();
367
+ if (event.pointerType !== "touch" || !event.isPrimary || event.button !== 0 || !this.bxContextMenuLongPress() || this.hasNativeTouchAction(event)) return;
368
+ const doc = this.elementRef.nativeElement.ownerDocument;
369
+ const win = doc.defaultView;
370
+ if (!win || this.hasTextSelection()) return;
371
+ const press = {
372
+ pointerId: event.pointerId,
373
+ x: event.clientX,
374
+ y: event.clientY,
375
+ timer: 0,
376
+ opened: false,
377
+ events: new AbortController()
378
+ };
379
+ this.touchPress = press;
380
+ const options = {
381
+ capture: true,
382
+ passive: true,
383
+ signal: press.events.signal
384
+ };
385
+ const finish = () => this.cancelTouchPress();
386
+ doc.addEventListener("pointermove", (move) => {
387
+ if (move.pointerId === press.pointerId && !press.opened && Math.hypot(move.clientX - press.x, move.clientY - press.y) > 10) finish();
388
+ }, options);
389
+ for (const type of ["pointerup", "pointercancel"]) doc.addEventListener(type, (end) => {
390
+ if (end.pointerId === press.pointerId) finish();
391
+ }, options);
392
+ doc.addEventListener("pointerdown", (next) => {
393
+ if (next.pointerId !== press.pointerId) finish();
394
+ }, options);
395
+ doc.addEventListener("scroll", finish, options);
396
+ doc.addEventListener("visibilitychange", () => {
397
+ if (doc.hidden) finish();
398
+ }, options);
399
+ win.addEventListener("blur", finish, { signal: press.events.signal });
400
+ press.timer = win.setTimeout(() => this.zone.run(() => this.openTouchMenu(press)), 500);
401
+ };
357
402
  overlayRef = null;
358
403
  transition;
404
+ point;
359
405
  disconnectTheme;
360
406
  disconnectLighting;
361
- positionSubscription;
362
- keyboardSubscription;
407
+ subscriptions = new Subscription();
363
408
  revision = 0;
364
- hovered = false;
365
- focused = false;
409
+ touchPress;
410
+ touchGuard;
411
+ touchGuardTimer;
412
+ lastPointerType = "";
366
413
  constructor() {
367
- inject(DestroyRef).onDestroy(() => this.dispose());
368
- }
369
- show() {
370
- if (!this.bxTooltip()) return;
371
- const revision = ++this.revision;
372
- if (!this.overlayRef) {
373
- const strategy = this.positionBuilder.getElementStrategy(this.elementRef.nativeElement, this.placement()).withFlexibleDimensions(false);
374
- this.overlayRef = this.overlay.create({
375
- positionStrategy: strategy,
376
- scrollStrategy: this.overlay.scrollStrategies.reposition(),
377
- hasBackdrop: false,
378
- panelClass: "bx-tooltip-pane"
379
- });
380
- const pane = this.overlayRef.overlayElement;
381
- pane.id = this.id;
382
- pane.setAttribute("role", "tooltip");
383
- this.disconnectTheme = this.zone.runOutsideAngular(() => inheritOverlayTheme(this.elementRef.nativeElement, pane));
384
- this.disconnectLighting = inheritGlassLighting(pane, this.elementRef.nativeElement);
385
- const component = this.overlayRef.attach(new ComponentPortal(Tooltip));
386
- pane.style.pointerEvents = "none";
387
- this.keyboardSubscription = this.overlayRef.keydownEvents().subscribe((event) => this.onEscape(event));
388
- component.setInput("text", this.bxTooltip());
389
- component.changeDetectorRef.detectChanges();
390
- const positionArrow = () => {
391
- const trigger = this.elementRef.nativeElement.getBoundingClientRect();
392
- const bounds = pane.getBoundingClientRect();
393
- pane.style.setProperty("--bx-tooltip-arrow-x", `${trigger.left + trigger.width / 2 - bounds.left}px`);
394
- pane.style.setProperty("--bx-tooltip-arrow-y", `${trigger.top + trigger.height / 2 - bounds.top}px`);
395
- this.transition?.refresh();
396
- };
397
- this.positionSubscription = strategy.positionChanges.subscribe(positionArrow);
398
- this.overlayRef.updatePosition();
399
- positionArrow();
400
- if (!this.a11y.isReducedMotion()) this.transition = this.zone.runOutsideAngular(() => createGlassTransition(this.elementRef.nativeElement, pane));
401
- }
402
414
  const source = this.elementRef.nativeElement;
403
- const ids = new Set((source.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean));
404
- ids.add(this.id);
405
- source.setAttribute("aria-describedby", [...ids].join(" "));
406
- Promise.resolve(this.zone.runOutsideAngular(() => this.transition?.enter())).catch(() => {
407
- if (revision === this.revision) {
408
- this.transition?.destroy();
409
- this.transition = void 0;
410
- }
415
+ if (source.ownerDocument.defaultView) this.zone.runOutsideAngular(() => source.addEventListener("pointerdown", this.onPointerDown, { passive: true }));
416
+ inject(DestroyRef).onDestroy(() => {
417
+ ++this.revision;
418
+ source.removeEventListener("pointerdown", this.onPointerDown);
419
+ this.cancelTouchPress();
420
+ this.clearTouchGuard();
421
+ this.dispose();
411
422
  });
412
423
  }
413
- hide() {
414
- const revision = ++this.revision;
415
- this.removeDescription();
424
+ open(event) {
425
+ if (event.type === "contextmenu" && this.lastPointerType === "touch") {
426
+ const press = this.touchPress;
427
+ if (!press) return;
428
+ if (!this.bxContextMenuLongPress() || this.hasNativeTouchAction(event) || this.hasTextSelection()) {
429
+ this.cancelTouchPress();
430
+ return;
431
+ }
432
+ event.preventDefault();
433
+ this.openTouchMenu(press);
434
+ return;
435
+ }
436
+ event.preventDefault();
437
+ this.cancelTouchPress();
438
+ this.clearTouchGuard();
439
+ this.openAt(event.clientX, event.clientY);
440
+ }
441
+ close(restoreFocus = false) {
416
442
  if (!this.overlayRef) return;
443
+ const revision = ++this.revision;
444
+ if (restoreFocus || this.overlayRef.overlayElement.contains(this.elementRef.nativeElement.ownerDocument.activeElement)) this.elementRef.nativeElement.focus({ preventScroll: true });
445
+ this.overlayRef.overlayElement.inert = true;
417
446
  const transition = this.transition;
418
447
  if (transition && !this.a11y.isReducedMotion()) this.zone.runOutsideAngular(() => transition.exit()).then(() => {
419
448
  if (revision === this.revision) this.dispose();
@@ -422,87 +451,212 @@ var TooltipDirective = class TooltipDirective {
422
451
  });
423
452
  else this.dispose();
424
453
  }
425
- onEnter() {
426
- this.hovered = true;
427
- this.show();
454
+ onKey(event) {
455
+ if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
456
+ event.preventDefault();
457
+ this.lastPointerType = "";
458
+ this.cancelTouchPress();
459
+ this.clearTouchGuard();
460
+ const rect = this.elementRef.nativeElement.getBoundingClientRect();
461
+ this.openAt(rect.left + Math.min(24, rect.width / 2), rect.bottom);
462
+ this.items()[0]?.focus();
428
463
  }
429
- onLeave() {
430
- this.hovered = false;
431
- if (!this.focused) this.hide();
464
+ onDocumentClick(event) {
465
+ const target = event.target;
466
+ if (target && this.overlayRef?.overlayElement.contains(target) && target.closest("button, a, [role=\"menuitem\"]")) this.close();
432
467
  }
433
- onFocus() {
434
- this.focused = true;
435
- this.show();
468
+ hasNativeTouchAction(event) {
469
+ const source = this.elementRef.nativeElement;
470
+ for (const node of event.composedPath()) if (node instanceof HTMLElement) {
471
+ if (node.isContentEditable || node.matches("input, textarea, select, :disabled, [aria-disabled=\"true\"], [inert]")) return true;
472
+ if (node === source) break;
473
+ }
474
+ return !!source.closest(":disabled, [aria-disabled=\"true\"], [inert]");
436
475
  }
437
- onBlur(event) {
438
- if (event.relatedTarget instanceof Node && this.elementRef.nativeElement.contains(event.relatedTarget)) return;
439
- this.focused = false;
440
- if (!this.hovered) this.hide();
476
+ hasTextSelection() {
477
+ const source = this.elementRef.nativeElement;
478
+ const selection = source.ownerDocument.getSelection();
479
+ return !!selection && !selection.isCollapsed && (source.contains(selection.anchorNode) || source.contains(selection.focusNode));
441
480
  }
442
- onEscape(event) {
443
- if (event.key === "Escape" && this.overlayRef) {
444
- event.stopPropagation();
445
- this.hovered = false;
446
- this.focused = false;
447
- this.hide();
481
+ openTouchMenu(press) {
482
+ if (this.touchPress !== press || press.opened) return;
483
+ const source = this.elementRef.nativeElement;
484
+ if (!this.bxContextMenuLongPress() || !source.isConnected || source.isContentEditable || source.closest(":disabled, [aria-disabled=\"true\"], [inert]") || this.hasTextSelection()) {
485
+ this.cancelTouchPress();
486
+ return;
448
487
  }
488
+ press.opened = true;
489
+ this.elementRef.nativeElement.ownerDocument.defaultView?.clearTimeout(press.timer);
490
+ this.guardTouchRelease();
491
+ this.openAt(press.x, press.y + 12);
449
492
  }
450
- removeDescription() {
451
- const source = this.elementRef.nativeElement;
452
- const ids = (source.getAttribute("aria-describedby") ?? "").split(/\s+/).filter((id) => id && id !== this.id);
453
- if (ids.length) source.setAttribute("aria-describedby", ids.join(" "));
454
- else source.removeAttribute("aria-describedby");
493
+ cancelTouchPress() {
494
+ const press = this.touchPress;
495
+ if (!press) return;
496
+ this.touchPress = void 0;
497
+ const win = this.elementRef.nativeElement.ownerDocument.defaultView;
498
+ win?.clearTimeout(press.timer);
499
+ press.events.abort();
500
+ if (press.opened && win) this.touchGuardTimer = win.setTimeout(() => this.clearTouchGuard(), 800);
455
501
  }
456
- dispose() {
457
- this.removeDescription();
458
- this.keyboardSubscription?.unsubscribe();
459
- this.keyboardSubscription = void 0;
460
- this.positionSubscription?.unsubscribe();
461
- this.positionSubscription = void 0;
462
- this.disconnectTheme?.();
463
- this.disconnectTheme = void 0;
464
- this.transition?.destroy();
465
- this.transition = void 0;
466
- this.overlayRef?.dispose();
467
- this.overlayRef = null;
468
- this.disconnectLighting?.();
469
- this.disconnectLighting = void 0;
502
+ guardTouchRelease() {
503
+ this.clearTouchGuard();
504
+ const doc = this.elementRef.nativeElement.ownerDocument;
505
+ const guard = new AbortController();
506
+ this.touchGuard = guard;
507
+ const options = {
508
+ capture: true,
509
+ signal: guard.signal
510
+ };
511
+ doc.addEventListener("click", (event) => {
512
+ if (event.detail === 0) return;
513
+ event.preventDefault();
514
+ event.stopImmediatePropagation();
515
+ this.clearTouchGuard();
516
+ }, options);
517
+ doc.addEventListener("contextmenu", (event) => {
518
+ event.preventDefault();
519
+ event.stopImmediatePropagation();
520
+ }, options);
521
+ doc.addEventListener("pointerdown", (event) => {
522
+ if (event.isPrimary) this.clearTouchGuard();
523
+ }, {
524
+ ...options,
525
+ passive: true
526
+ });
527
+ doc.addEventListener("keydown", () => this.clearTouchGuard(), options);
470
528
  }
471
- static ɵfac = i0.ɵɵngDeclareFactory({
472
- minVersion: "12.0.0",
473
- version: "22.2.3",
474
- ngImport: i0,
475
- type: TooltipDirective,
476
- deps: [],
477
- target: i0.ɵɵFactoryTarget.Directive
478
- });
479
- static ɵdir = i0.ɵɵngDeclareDirective({
480
- minVersion: "17.1.0",
481
- version: "22.2.3",
482
- type: TooltipDirective,
483
- isStandalone: true,
484
- selector: "[bxTooltip]",
485
- inputs: {
486
- bxTooltip: {
487
- classPropertyName: "bxTooltip",
488
- publicName: "bxTooltip",
489
- isSignal: true,
490
- isRequired: true,
491
- transformFunction: null
492
- },
493
- placement: {
494
- classPropertyName: "placement",
495
- publicName: "placement",
496
- isSignal: true,
497
- isRequired: false,
498
- transformFunction: null
529
+ clearTouchGuard() {
530
+ this.touchGuard?.abort();
531
+ this.touchGuard = void 0;
532
+ this.elementRef.nativeElement.ownerDocument.defaultView?.clearTimeout(this.touchGuardTimer);
533
+ this.touchGuardTimer = void 0;
534
+ }
535
+ openAt(x, y) {
536
+ this.dispose();
537
+ ++this.revision;
538
+ const source = this.elementRef.nativeElement;
539
+ this.overlayRef = this.overlay.create({
540
+ positionStrategy: this.positionBuilder.getCoordinateStrategy(x, y),
541
+ scrollStrategy: this.overlay.scrollStrategies.noop(),
542
+ usePopover: !!this.overlayContext?.useTopLayer || !!source.closest("dialog, [role=\"dialog\"], [role=\"alertdialog\"], bx-modal, bx-modal-core, [popover]"),
543
+ hasBackdrop: true,
544
+ backdropClass: "cdk-overlay-transparent-backdrop"
545
+ });
546
+ this.subscriptions = new Subscription();
547
+ const win = source.ownerDocument.defaultView;
548
+ if (win) this.zone.runOutsideAngular(() => {
549
+ const closeOnScroll = (event) => {
550
+ const target = event.target;
551
+ if (this.overlayRef?.overlayElement.inert) return;
552
+ if (target === win || target instanceof Node && target.contains(source)) this.zone.run(() => this.close());
553
+ };
554
+ win.addEventListener("scroll", closeOnScroll, {
555
+ capture: true,
556
+ passive: true
557
+ });
558
+ this.subscriptions.add(() => win.removeEventListener("scroll", closeOnScroll, true));
559
+ });
560
+ this.subscriptions.add(this.overlayRef.backdropClick().subscribe(() => this.close()));
561
+ this.subscriptions.add(this.overlayRef.detachments().subscribe(() => this.dispose()));
562
+ this.subscriptions.add(this.overlayRef.keydownEvents().subscribe((event) => this.onMenuKey(event)));
563
+ const pane = this.overlayRef.overlayElement;
564
+ this.disconnectTheme = this.zone.runOutsideAngular(() => inheritOverlayTheme(source, pane));
565
+ this.disconnectLighting = inheritGlassLighting(pane, source);
566
+ this.overlayRef.attach(new TemplatePortal(this.bxContextMenu(), this.vcr)).detectChanges();
567
+ this.overlayRef.updatePosition();
568
+ if (!this.a11y.isReducedMotion()) {
569
+ const point = source.ownerDocument.createElement("span");
570
+ point.setAttribute("aria-hidden", "true");
571
+ Object.assign(point.style, {
572
+ position: "fixed",
573
+ left: `${x}px`,
574
+ top: `${y}px`,
575
+ width: "1px",
576
+ height: "1px",
577
+ pointerEvents: "none"
578
+ });
579
+ source.ownerDocument.body.append(point);
580
+ this.point = point;
581
+ const transition = this.zone.runOutsideAngular(() => createGlassTransition(point, pane));
582
+ this.transition = transition;
583
+ this.zone.runOutsideAngular(() => transition.enter()).catch(() => {
584
+ this.transition?.destroy();
585
+ this.transition = void 0;
586
+ });
587
+ }
588
+ }
589
+ items() {
590
+ return Array.from(this.overlayRef?.overlayElement.querySelectorAll("button:not(:disabled), a[href], [role=\"menuitem\"]") ?? []).filter((item) => !item.matches("[disabled], [aria-disabled=\"true\"]") && !item.closest("[hidden], [inert]"));
591
+ }
592
+ onMenuKey(event) {
593
+ if (event.key === "Escape") {
594
+ event.preventDefault();
595
+ event.stopPropagation();
596
+ this.close(true);
597
+ return;
598
+ }
599
+ if (event.target?.closest("input,textarea,[contenteditable=\"true\"]")) return;
600
+ if (![
601
+ "ArrowDown",
602
+ "ArrowUp",
603
+ "Home",
604
+ "End"
605
+ ].includes(event.key)) return;
606
+ const items = this.items();
607
+ if (!items.length) return;
608
+ event.preventDefault();
609
+ const current = items.indexOf(this.elementRef.nativeElement.ownerDocument.activeElement);
610
+ items[event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : event.key === "ArrowDown" ? (current + 1) % items.length : current < 0 ? items.length - 1 : (current - 1 + items.length) % items.length].focus();
611
+ }
612
+ dispose() {
613
+ this.subscriptions.unsubscribe();
614
+ this.disconnectTheme?.();
615
+ this.disconnectTheme = void 0;
616
+ this.transition?.destroy();
617
+ this.transition = void 0;
618
+ this.point?.remove();
619
+ this.point = void 0;
620
+ const overlay = this.overlayRef;
621
+ this.overlayRef = null;
622
+ overlay?.dispose();
623
+ this.disconnectLighting?.();
624
+ this.disconnectLighting = void 0;
625
+ }
626
+ static ɵfac = i0.ɵɵngDeclareFactory({
627
+ minVersion: "12.0.0",
628
+ version: "22.2.3",
629
+ ngImport: i0,
630
+ type: ContextMenuDirective,
631
+ deps: [],
632
+ target: i0.ɵɵFactoryTarget.Directive
633
+ });
634
+ static ɵdir = i0.ɵɵngDeclareDirective({
635
+ minVersion: "17.1.0",
636
+ version: "22.2.3",
637
+ type: ContextMenuDirective,
638
+ isStandalone: true,
639
+ selector: "[bxContextMenu]",
640
+ inputs: {
641
+ bxContextMenu: {
642
+ classPropertyName: "bxContextMenu",
643
+ publicName: "bxContextMenu",
644
+ isSignal: true,
645
+ isRequired: true,
646
+ transformFunction: null
647
+ },
648
+ bxContextMenuLongPress: {
649
+ classPropertyName: "bxContextMenuLongPress",
650
+ publicName: "bxContextMenuLongPress",
651
+ isSignal: true,
652
+ isRequired: false,
653
+ transformFunction: null
499
654
  }
500
655
  },
501
656
  host: { listeners: {
502
- "mouseenter": "onEnter()",
503
- "mouseleave": "onLeave()",
504
- "focusin": "onFocus()",
505
- "focusout": "onBlur($event)"
657
+ "contextmenu": "open($event)",
658
+ "keydown": "onKey($event)",
659
+ "document:click": "onDocumentClick($event)"
506
660
  } },
507
661
  ngImport: i0
508
662
  });
@@ -511,266 +665,304 @@ i0.ɵɵngDeclareClassMetadata({
511
665
  minVersion: "12.0.0",
512
666
  version: "22.2.3",
513
667
  ngImport: i0,
514
- type: TooltipDirective,
668
+ type: ContextMenuDirective,
515
669
  decorators: [{
516
670
  type: Directive,
517
- args: [{ selector: "[bxTooltip]" }]
671
+ args: [{ selector: "[bxContextMenu]" }]
518
672
  }],
519
673
  ctorParameters: () => [],
520
674
  propDecorators: {
521
- bxTooltip: [{
675
+ bxContextMenu: [{
522
676
  type: i0.Input,
523
677
  args: [{
524
678
  isSignal: true,
525
- alias: "bxTooltip",
679
+ alias: "bxContextMenu",
526
680
  required: true
527
681
  }]
528
682
  }],
529
- placement: [{
683
+ bxContextMenuLongPress: [{
530
684
  type: i0.Input,
531
685
  args: [{
532
686
  isSignal: true,
533
- alias: "placement",
687
+ alias: "bxContextMenuLongPress",
534
688
  required: false
535
689
  }]
536
690
  }],
537
- onEnter: [{
538
- type: HostListener,
539
- args: ["mouseenter"]
540
- }],
541
- onLeave: [{
691
+ open: [{
542
692
  type: HostListener,
543
- args: ["mouseleave"]
693
+ args: ["contextmenu", ["$event"]]
544
694
  }],
545
- onFocus: [{
695
+ onKey: [{
546
696
  type: HostListener,
547
- args: ["focusin"]
697
+ args: ["keydown", ["$event"]]
548
698
  }],
549
- onBlur: [{
699
+ onDocumentClick: [{
550
700
  type: HostListener,
551
- args: ["focusout", ["$event"]]
701
+ args: ["document:click", ["$event"]]
552
702
  }]
553
703
  }
554
704
  });
555
- const BX_OVERLAY_CONTEXT = new InjectionToken("BX_OVERLAY_CONTEXT");
556
- var DropdownDirective = class DropdownDirective {
705
+ var TooltipDirective = class TooltipDirective {
557
706
  elementRef = inject(ElementRef);
558
707
  overlay = inject(Overlay);
559
708
  positionBuilder = inject(BxOverlayPositionService);
560
- overlayContext = inject(BX_OVERLAY_CONTEXT, { optional: true });
561
709
  a11y = inject(BxA11yService);
562
710
  zone = inject(NgZone);
563
- vcr = inject(ViewContainerRef);
564
- isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
565
- _isOpen = signal(false, ...ngDevMode ? [{ debugName: "_isOpen" }] : /* istanbul ignore next */ []);
566
- overlayId = signal(null, ...ngDevMode ? [{ debugName: "overlayId" }] : /* istanbul ignore next */ []);
567
- isOpen = this._isOpen.asReadonly();
568
- _state = signal("closed", ...ngDevMode ? [{ debugName: "_state" }] : /* istanbul ignore next */ []);
569
- state = this._state.asReadonly();
570
- bxDropdown = input.required(...ngDevMode ? [{ debugName: "bxDropdown" }] : /* istanbul ignore next */ []);
711
+ focusMonitor = inject(FocusMonitor);
712
+ describer = inject(AriaDescriber);
713
+ i18n = inject(BX_I18N, { optional: true });
714
+ contextMenu = inject(ContextMenuDirective, { optional: true });
715
+ id = `bx-tooltip-${uuidv4()}`;
716
+ bxTooltip = input.required(...ngDevMode ? [{ debugName: "bxTooltip" }] : /* istanbul ignore next */ []);
571
717
  placement = input("bottom", ...ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []);
572
- bxTransition = input("fade", ...ngDevMode ? [{ debugName: "bxTransition" }] : /* istanbul ignore next */ []);
718
+ bxTooltipTouch = input("none", ...ngDevMode ? [{ debugName: "bxTooltipTouch" }] : /* istanbul ignore next */ []);
573
719
  overlayRef = null;
574
- transition = null;
575
- subscriptions = new Subscription();
576
- revision = 0;
720
+ transition;
577
721
  disconnectTheme;
578
722
  disconnectLighting;
723
+ positionSubscription;
724
+ keyboardSubscription;
725
+ revision = 0;
726
+ hovered = false;
727
+ focused = false;
728
+ lastPointerType = "";
729
+ touchEvents;
730
+ holdTimer;
731
+ touchHideTimer;
732
+ touchVisible = false;
733
+ closeTouchTap = false;
579
734
  constructor() {
735
+ const source = this.elementRef.nativeElement;
736
+ const focus = source.ownerDocument.defaultView ? this.focusMonitor.monitor(source, true).subscribe((origin) => this.zone.run(() => {
737
+ this.focused = origin === "keyboard";
738
+ if (this.focused) {
739
+ this.clearTouchInteraction();
740
+ this.show();
741
+ } else if (!this.hovered && !this.touchEvents) this.hide();
742
+ })) : void 0;
743
+ effect((onCleanup) => {
744
+ this.i18n?.version?.();
745
+ const text = this.bxTooltip();
746
+ const description = isTextWithKey(text) ? this.i18n?.translate(text.key) ?? text.key : text;
747
+ if (!description) return;
748
+ this.describer.describe(source, description, "tooltip");
749
+ onCleanup(() => this.describer.removeDescription(source, description, "tooltip"));
750
+ });
580
751
  inject(DestroyRef).onDestroy(() => {
581
752
  ++this.revision;
753
+ focus?.unsubscribe();
754
+ if (focus) this.focusMonitor.stopMonitoring(source);
755
+ this.clearTouchInteraction();
582
756
  this.dispose();
583
757
  });
584
- effect(() => {
585
- if (this.a11y.isReducedMotion() && this.transition) {
586
- ++this.revision;
587
- this.transition.destroy();
588
- this.transition = null;
589
- if (this.isOpen()) this._state.set("open");
590
- else this.dispose();
591
- }
592
- });
593
- }
594
- toggle(event) {
595
- if (this.isDisabled()) return;
596
- event?.stopPropagation();
597
- if (this.isOpen()) this.close();
598
- else this.open();
599
- }
600
- onTriggerKeydown(event) {
601
- if (this.bxTransition() === "none" || this.isDisabled() || this.isEditing(event.target)) return;
602
- if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
603
- event.preventDefault();
604
- event.stopPropagation();
605
- this.open();
606
- const items = this.menuItems();
607
- (event.key === "ArrowUp" ? items[items.length - 1] : items[0])?.focus({ preventScroll: true });
608
758
  }
609
- open() {
610
- if (!this.isBrowser || this.isOpen() || this.isDisabled()) return;
759
+ show() {
760
+ if (!this.bxTooltip()) return;
611
761
  const revision = ++this.revision;
612
- this._isOpen.set(true);
613
- this._state.set("opening");
614
- if (!this.overlayRef) this.attachOverlay();
615
- const overlay = this.overlayRef;
616
- if (!overlay) return;
617
- overlay.overlayElement.inert = false;
618
- overlay.overlayElement.style.removeProperty("pointer-events");
619
- if (this.bxTransition() !== "none" && !this.a11y.isReducedMotion()) {
620
- const transition = this.transition ??= this.zone.runOutsideAngular(() => createGlassTransition(this.elementRef.nativeElement, overlay.overlayElement));
621
- this.zone.runOutsideAngular(() => transition.enter()).then(() => {
622
- if (revision === this.revision && this.isOpen()) this.zone.run(() => this._state.set("open"));
623
- }).catch(() => {
624
- if (revision === this.revision && this.isOpen()) {
625
- this.transition?.destroy();
626
- this.transition = null;
627
- this._state.set("open");
628
- }
762
+ if (!this.overlayRef) {
763
+ const strategy = this.positionBuilder.getElementStrategy(this.elementRef.nativeElement, this.placement()).withFlexibleDimensions(false);
764
+ this.overlayRef = this.overlay.create({
765
+ positionStrategy: strategy,
766
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
767
+ hasBackdrop: false,
768
+ panelClass: "bx-tooltip-pane"
629
769
  });
630
- } else this._state.set("open");
770
+ const pane = this.overlayRef.overlayElement;
771
+ pane.id = this.id;
772
+ pane.setAttribute("role", "tooltip");
773
+ pane.setAttribute("aria-hidden", "true");
774
+ this.disconnectTheme = this.zone.runOutsideAngular(() => inheritOverlayTheme(this.elementRef.nativeElement, pane));
775
+ this.disconnectLighting = inheritGlassLighting(pane, this.elementRef.nativeElement);
776
+ const component = this.overlayRef.attach(new ComponentPortal(Tooltip));
777
+ pane.style.pointerEvents = "none";
778
+ this.keyboardSubscription = this.overlayRef.keydownEvents().subscribe((event) => this.onEscape(event));
779
+ component.setInput("text", this.bxTooltip());
780
+ component.changeDetectorRef.detectChanges();
781
+ const positionArrow = () => {
782
+ const trigger = this.elementRef.nativeElement.getBoundingClientRect();
783
+ const bounds = pane.getBoundingClientRect();
784
+ pane.style.setProperty("--bx-tooltip-arrow-x", `${trigger.left + trigger.width / 2 - bounds.left}px`);
785
+ pane.style.setProperty("--bx-tooltip-arrow-y", `${trigger.top + trigger.height / 2 - bounds.top}px`);
786
+ this.transition?.refresh();
787
+ };
788
+ this.positionSubscription = strategy.positionChanges.subscribe(positionArrow);
789
+ this.overlayRef.updatePosition();
790
+ positionArrow();
791
+ if (!this.a11y.isReducedMotion()) this.transition = this.zone.runOutsideAngular(() => createGlassTransition(this.elementRef.nativeElement, pane));
792
+ }
793
+ Promise.resolve(this.zone.runOutsideAngular(() => this.transition?.enter())).catch(() => {
794
+ if (revision === this.revision) {
795
+ this.transition?.destroy();
796
+ this.transition = void 0;
797
+ }
798
+ });
631
799
  }
632
- close(restoreFocus = false) {
633
- if (!this.overlayRef || !this.isOpen()) return;
800
+ hide() {
801
+ this.clearTouchInteraction();
634
802
  const revision = ++this.revision;
635
- const overlay = this.overlayRef;
636
- this._isOpen.set(false);
637
- this._state.set("closing");
638
- const source = this.elementRef.nativeElement;
639
- if (restoreFocus || overlay.overlayElement.contains(source.ownerDocument.activeElement)) source.focus({ preventScroll: true });
640
- overlay.overlayElement.inert = true;
641
- overlay.overlayElement.style.pointerEvents = "none";
803
+ if (!this.overlayRef) return;
642
804
  const transition = this.transition;
643
805
  if (transition && !this.a11y.isReducedMotion()) this.zone.runOutsideAngular(() => transition.exit()).then(() => {
644
- if (revision === this.revision && !this.isOpen()) this.zone.run(() => this.dispose());
806
+ if (revision === this.revision) this.dispose();
645
807
  }).catch(() => {
646
- if (revision === this.revision && !this.isOpen()) this.dispose();
808
+ if (revision === this.revision) this.dispose();
647
809
  });
648
810
  else this.dispose();
649
811
  }
650
- onDocumentClick(event) {
651
- if (!this.overlayRef?.hasAttached() || !this.isOpen()) return;
652
- const target = event.target;
653
- if (!target || this.elementRef.nativeElement.contains(target)) return;
654
- if (!this.overlayRef.overlayElement.contains(target) || this.bxTransition() === "none" || target instanceof Element && target.closest("button, a, [role=\"menuitem\"]")) this.close();
812
+ onEnter(event) {
813
+ if (event.pointerType !== "mouse") return;
814
+ this.clearTouchInteraction();
815
+ this.hovered = true;
816
+ this.show();
655
817
  }
656
- attachOverlay() {
657
- const positionStrategy = this.positionBuilder.getElementStrategy(this.elementRef.nativeElement, this.placement(), 4);
658
- const overlay = this.overlay.create({
659
- positionStrategy,
660
- scrollStrategy: this.overlay.scrollStrategies.noop(),
661
- usePopover: !!this.overlayContext?.useTopLayer || !!this.elementRef.nativeElement.closest("dialog, [role=\"dialog\"], [role=\"alertdialog\"], bx-modal, bx-modal-core, [popover]"),
662
- hasBackdrop: this.bxTransition() === "none",
663
- backdropClass: "cdk-overlay-transparent-backdrop"
664
- });
665
- this.overlayRef = overlay;
666
- this.overlayId.set(overlay.overlayElement.id || null);
667
- this.disconnectTheme = this.zone.runOutsideAngular(() => inheritOverlayTheme(this.elementRef.nativeElement, overlay.overlayElement));
668
- this.disconnectLighting = inheritGlassLighting(overlay.overlayElement, this.elementRef.nativeElement);
669
- this.subscriptions = new Subscription();
670
- this.trackScroll(overlay, positionStrategy);
671
- this.subscriptions.add(overlay.backdropClick().subscribe(() => this.close()));
672
- this.subscriptions.add(overlay.outsidePointerEvents().subscribe((event) => {
673
- if (!this.elementRef.nativeElement.contains(event.target)) this.close();
674
- }));
675
- this.subscriptions.add(overlay.keydownEvents().subscribe((event) => {
676
- if (event.key === "Escape" && this.isOpen()) {
677
- event.preventDefault();
678
- event.stopPropagation();
679
- this.close(true);
680
- } else if (!event.defaultPrevented && this.bxTransition() !== "none" && this.isOpen()) this.navigateMenu(event);
681
- }));
682
- this.subscriptions.add(positionStrategy.positionChanges.subscribe(() => {
683
- this.zone.runOutsideAngular(() => this.transition?.refresh());
684
- }));
685
- overlay.attach(new TemplatePortal(this.bxDropdown(), this.vcr)).detectChanges();
686
- overlay.updatePosition();
818
+ onLeave(event) {
819
+ if (event.pointerType !== "mouse") return;
820
+ this.hovered = false;
821
+ if (!this.focused) this.hide();
687
822
  }
688
- trackScroll(overlay, positionStrategy) {
689
- const source = this.elementRef.nativeElement;
690
- const view = source.ownerDocument.defaultView;
691
- if (!view) return;
692
- this.zone.runOutsideAngular(() => {
693
- const reposition = (event) => {
694
- const target = event.target;
695
- if (overlay.hasAttached() && (target === view || target instanceof Node && target.contains(source))) positionStrategy.reapplyLastPosition();
696
- };
697
- view.addEventListener("scroll", reposition, {
698
- capture: true,
699
- passive: true
700
- });
701
- this.subscriptions.add(() => view.removeEventListener("scroll", reposition, true));
702
- });
823
+ onPointerDown(event) {
824
+ this.lastPointerType = event.pointerType;
825
+ if (event.pointerType !== "touch") return;
826
+ this.closeTouchTap = this.touchVisible && this.bxTooltipTouch() === "tap";
827
+ this.hovered = false;
828
+ this.focused = false;
829
+ this.hide();
830
+ if (this.bxTooltipTouch() !== "longpress" || !event.isPrimary || event.button !== 0 || !this.canUseTouch(event)) return;
831
+ this.zone.runOutsideAngular(() => this.startTouchInteraction(event));
703
832
  }
704
- isDisabled() {
705
- const element = this.elementRef.nativeElement;
706
- return element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true";
833
+ onClick(event) {
834
+ if (event.detail === 0 || this.lastPointerType !== "touch" || this.bxTooltipTouch() !== "tap" || !this.canUseTouch(event)) return;
835
+ if (this.closeTouchTap) {
836
+ this.closeTouchTap = false;
837
+ return;
838
+ }
839
+ this.zone.runOutsideAngular(() => this.startTouchInteraction());
707
840
  }
708
- menuItems() {
709
- const pane = this.overlayRef?.overlayElement;
710
- if (!pane) return [];
711
- return Array.from(pane.querySelectorAll("button, a[href], [role=\"menuitem\"]")).filter((item) => {
712
- if (item.matches(":disabled, [disabled], [aria-disabled=\"true\"]") || item.closest("[aria-disabled=\"true\"]")) return false;
713
- if (item.closest("[hidden], [inert], [aria-hidden=\"true\"]")) return false;
714
- if (item.tabIndex < 0 && !item.hasAttribute("tabindex")) return false;
715
- const style = item.ownerDocument.defaultView?.getComputedStyle(item);
716
- return !!style && style.display !== "none" && style.visibility !== "hidden" && item.getClientRects().length > 0;
717
- });
841
+ onEscape(event) {
842
+ if (event.key === "Escape" && this.overlayRef) {
843
+ event.stopPropagation();
844
+ this.hovered = false;
845
+ this.focused = false;
846
+ this.hide();
847
+ }
718
848
  }
719
- navigateMenu(event) {
720
- if (this.isEditing(event.target) || !(event.target instanceof Node) || !this.overlayRef?.overlayElement.contains(event.target)) return;
721
- if (![
722
- "ArrowDown",
723
- "ArrowUp",
724
- "Home",
725
- "End"
726
- ].includes(event.key)) return;
727
- const items = this.menuItems();
728
- if (!items.length) return;
729
- const current = items.indexOf(this.elementRef.nativeElement.ownerDocument.activeElement);
730
- let index;
731
- if (event.key === "Home") index = 0;
732
- else if (event.key === "End") index = items.length - 1;
733
- else if (event.key === "ArrowDown") index = current < 0 ? 0 : (current + 1) % items.length;
734
- else index = current < 0 ? items.length - 1 : (current - 1 + items.length) % items.length;
735
- event.preventDefault();
736
- event.stopPropagation();
737
- items[index].focus({ preventScroll: true });
849
+ canUseTouch(event) {
850
+ if (this.contextMenu && this.bxTooltipTouch() === "longpress") return false;
851
+ if (this.elementRef.nativeElement.closest(":disabled, [aria-disabled=\"true\"], [inert]")) return false;
852
+ return !event.composedPath().some((node) => node instanceof HTMLElement && (node.isContentEditable || node.matches("input, textarea, select")));
738
853
  }
739
- isEditing(target) {
740
- return target instanceof Element && !!target.closest("input, textarea, select, [contenteditable]:not([contenteditable=\"false\"]), [role=\"textbox\"]");
854
+ hasTextSelection() {
855
+ const source = this.elementRef.nativeElement;
856
+ const selection = source.ownerDocument.getSelection();
857
+ return !!selection && !selection.isCollapsed && (source.contains(selection.anchorNode) || source.contains(selection.focusNode));
858
+ }
859
+ startTouchInteraction(press) {
860
+ const source = this.elementRef.nativeElement;
861
+ const doc = source.ownerDocument;
862
+ const win = doc.defaultView;
863
+ if (!win || this.hasTextSelection()) return;
864
+ this.clearTouchInteraction();
865
+ const events = new AbortController();
866
+ this.touchEvents = events;
867
+ const options = {
868
+ capture: true,
869
+ passive: true,
870
+ signal: events.signal
871
+ };
872
+ const dismiss = () => this.zone.run(() => this.hide());
873
+ let suppressReleaseClick = false;
874
+ doc.addEventListener("pointerdown", (event) => {
875
+ if (!press && event.target instanceof Node && source.contains(event.target)) return;
876
+ dismiss();
877
+ }, options);
878
+ doc.addEventListener("scroll", dismiss, options);
879
+ doc.addEventListener("visibilitychange", () => {
880
+ if (doc.hidden) dismiss();
881
+ }, options);
882
+ win.addEventListener("blur", dismiss, { signal: events.signal });
883
+ if (!press) {
884
+ this.touchVisible = true;
885
+ this.zone.run(() => this.show());
886
+ return;
887
+ }
888
+ doc.addEventListener("pointermove", (event) => {
889
+ if (event.pointerId === press.pointerId && !this.touchVisible && Math.hypot(event.clientX - press.clientX, event.clientY - press.clientY) > 10) dismiss();
890
+ }, options);
891
+ doc.addEventListener("pointerup", (event) => {
892
+ if (event.pointerId !== press.pointerId) return;
893
+ if (!this.touchVisible) dismiss();
894
+ else this.touchHideTimer = win.setTimeout(dismiss, 1500);
895
+ }, options);
896
+ doc.addEventListener("pointercancel", (event) => {
897
+ if (event.pointerId === press.pointerId) dismiss();
898
+ }, options);
899
+ doc.addEventListener("click", (event) => {
900
+ if (!suppressReleaseClick || event.detail === 0) return;
901
+ event.preventDefault();
902
+ event.stopImmediatePropagation();
903
+ suppressReleaseClick = false;
904
+ }, {
905
+ ...options,
906
+ passive: false
907
+ });
908
+ doc.addEventListener("contextmenu", (event) => {
909
+ if (event.target instanceof Node && source.contains(event.target) && !this.hasTextSelection()) event.preventDefault();
910
+ }, {
911
+ ...options,
912
+ passive: false
913
+ });
914
+ this.holdTimer = win.setTimeout(() => {
915
+ if (this.bxTooltipTouch() !== "longpress" || !source.isConnected || source.closest(":disabled, [aria-disabled=\"true\"], [inert]") || this.hasTextSelection()) {
916
+ dismiss();
917
+ return;
918
+ }
919
+ this.touchVisible = true;
920
+ suppressReleaseClick = true;
921
+ this.zone.run(() => this.show());
922
+ }, 500);
923
+ }
924
+ clearTouchInteraction() {
925
+ this.touchEvents?.abort();
926
+ this.touchEvents = void 0;
927
+ const win = this.elementRef.nativeElement.ownerDocument.defaultView;
928
+ win?.clearTimeout(this.holdTimer);
929
+ win?.clearTimeout(this.touchHideTimer);
930
+ this.holdTimer = void 0;
931
+ this.touchHideTimer = void 0;
932
+ this.touchVisible = false;
741
933
  }
742
934
  dispose() {
935
+ this.keyboardSubscription?.unsubscribe();
936
+ this.keyboardSubscription = void 0;
937
+ this.positionSubscription?.unsubscribe();
938
+ this.positionSubscription = void 0;
743
939
  this.disconnectTheme?.();
744
940
  this.disconnectTheme = void 0;
745
941
  this.transition?.destroy();
746
- this.transition = null;
747
- this.subscriptions.unsubscribe();
942
+ this.transition = void 0;
748
943
  this.overlayRef?.dispose();
749
944
  this.overlayRef = null;
750
945
  this.disconnectLighting?.();
751
946
  this.disconnectLighting = void 0;
752
- this.overlayId.set(null);
753
- this._isOpen.set(false);
754
- this._state.set("closed");
755
947
  }
756
948
  static ɵfac = i0.ɵɵngDeclareFactory({
757
949
  minVersion: "12.0.0",
758
950
  version: "22.2.3",
759
951
  ngImport: i0,
760
- type: DropdownDirective,
952
+ type: TooltipDirective,
761
953
  deps: [],
762
954
  target: i0.ɵɵFactoryTarget.Directive
763
955
  });
764
956
  static ɵdir = i0.ɵɵngDeclareDirective({
765
957
  minVersion: "17.1.0",
766
958
  version: "22.2.3",
767
- type: DropdownDirective,
959
+ type: TooltipDirective,
768
960
  isStandalone: true,
769
- selector: "[bxDropdown]",
961
+ selector: "[bxTooltip]",
770
962
  inputs: {
771
- bxDropdown: {
772
- classPropertyName: "bxDropdown",
773
- publicName: "bxDropdown",
963
+ bxTooltip: {
964
+ classPropertyName: "bxTooltip",
965
+ publicName: "bxTooltip",
774
966
  isSignal: true,
775
967
  isRequired: true,
776
968
  transformFunction: null
@@ -782,27 +974,21 @@ var DropdownDirective = class DropdownDirective {
782
974
  isRequired: false,
783
975
  transformFunction: null
784
976
  },
785
- bxTransition: {
786
- classPropertyName: "bxTransition",
787
- publicName: "bxTransition",
977
+ bxTooltipTouch: {
978
+ classPropertyName: "bxTooltipTouch",
979
+ publicName: "bxTooltipTouch",
788
980
  isSignal: true,
789
981
  isRequired: false,
790
982
  transformFunction: null
791
983
  }
792
984
  },
793
- host: {
794
- listeners: {
795
- "click": "toggle($event)",
796
- "keydown": "onTriggerKeydown($event)",
797
- "document:click": "onDocumentClick($event)"
798
- },
799
- properties: {
800
- "attr.aria-expanded": "isOpen()",
801
- "attr.aria-haspopup": "\"menu\"",
802
- "attr.aria-controls": "overlayId()"
803
- }
804
- },
805
- exportAs: ["bxDropdown"],
985
+ host: { listeners: {
986
+ "pointerenter": "onEnter($event)",
987
+ "pointerleave": "onLeave($event)",
988
+ "pointerdown": "onPointerDown($event)",
989
+ "click": "onClick($event)",
990
+ "document:keydown": "onEscape($event)"
991
+ } },
806
992
  ngImport: i0
807
993
  });
808
994
  };
@@ -810,27 +996,18 @@ i0.ɵɵngDeclareClassMetadata({
810
996
  minVersion: "12.0.0",
811
997
  version: "22.2.3",
812
998
  ngImport: i0,
813
- type: DropdownDirective,
999
+ type: TooltipDirective,
814
1000
  decorators: [{
815
1001
  type: Directive,
816
- args: [{
817
- selector: "[bxDropdown]",
818
- exportAs: "bxDropdown",
819
- standalone: true,
820
- host: {
821
- "[attr.aria-expanded]": "isOpen()",
822
- "[attr.aria-haspopup]": "\"menu\"",
823
- "[attr.aria-controls]": "overlayId()"
824
- }
825
- }]
1002
+ args: [{ selector: "[bxTooltip]" }]
826
1003
  }],
827
1004
  ctorParameters: () => [],
828
1005
  propDecorators: {
829
- bxDropdown: [{
1006
+ bxTooltip: [{
830
1007
  type: i0.Input,
831
1008
  args: [{
832
1009
  isSignal: true,
833
- alias: "bxDropdown",
1010
+ alias: "bxTooltip",
834
1011
  required: true
835
1012
  }]
836
1013
  }],
@@ -842,339 +1019,286 @@ i0.ɵɵngDeclareClassMetadata({
842
1019
  required: false
843
1020
  }]
844
1021
  }],
845
- bxTransition: [{
1022
+ bxTooltipTouch: [{
846
1023
  type: i0.Input,
847
1024
  args: [{
848
1025
  isSignal: true,
849
- alias: "bxTransition",
1026
+ alias: "bxTooltipTouch",
850
1027
  required: false
851
1028
  }]
852
1029
  }],
853
- toggle: [{
1030
+ onEnter: [{
854
1031
  type: HostListener,
855
- args: ["click", ["$event"]]
1032
+ args: ["pointerenter", ["$event"]]
856
1033
  }],
857
- onTriggerKeydown: [{
1034
+ onLeave: [{
858
1035
  type: HostListener,
859
- args: ["keydown", ["$event"]]
1036
+ args: ["pointerleave", ["$event"]]
860
1037
  }],
861
- onDocumentClick: [{
1038
+ onPointerDown: [{
862
1039
  type: HostListener,
863
- args: ["document:click", ["$event"]]
1040
+ args: ["pointerdown", ["$event"]]
1041
+ }],
1042
+ onClick: [{
1043
+ type: HostListener,
1044
+ args: ["click", ["$event"]]
1045
+ }],
1046
+ onEscape: [{
1047
+ type: HostListener,
1048
+ args: ["document:keydown", ["$event"]]
864
1049
  }]
865
1050
  }
866
1051
  });
867
- var ContextMenuDirective = class ContextMenuDirective {
1052
+ var DropdownDirective = class DropdownDirective {
1053
+ elementRef = inject(ElementRef);
868
1054
  overlay = inject(Overlay);
869
1055
  positionBuilder = inject(BxOverlayPositionService);
870
1056
  overlayContext = inject(BX_OVERLAY_CONTEXT, { optional: true });
871
- elementRef = inject(ElementRef);
872
1057
  a11y = inject(BxA11yService);
873
1058
  zone = inject(NgZone);
874
1059
  vcr = inject(ViewContainerRef);
875
- bxContextMenu = input.required(...ngDevMode ? [{ debugName: "bxContextMenu" }] : /* istanbul ignore next */ []);
876
- bxContextMenuLongPress = input(true, {
877
- ...ngDevMode ? { debugName: "bxContextMenuLongPress" } : /* istanbul ignore next */ {},
878
- transform: booleanAttribute
879
- });
880
- onPointerDown = (event) => {
881
- this.lastPointerType = event.pointerType;
882
- this.cancelTouchPress();
883
- if (event.isPrimary) this.clearTouchGuard();
884
- if (event.pointerType !== "touch" || !event.isPrimary || event.button !== 0 || !this.bxContextMenuLongPress() || this.hasNativeTouchAction(event)) return;
885
- const doc = this.elementRef.nativeElement.ownerDocument;
886
- const win = doc.defaultView;
887
- if (!win || this.hasTextSelection()) return;
888
- const press = {
889
- pointerId: event.pointerId,
890
- x: event.clientX,
891
- y: event.clientY,
892
- timer: 0,
893
- opened: false,
894
- events: new AbortController()
895
- };
896
- this.touchPress = press;
897
- const options = {
898
- capture: true,
899
- passive: true,
900
- signal: press.events.signal
901
- };
902
- const finish = () => this.cancelTouchPress();
903
- doc.addEventListener("pointermove", (move) => {
904
- if (move.pointerId === press.pointerId && !press.opened && Math.hypot(move.clientX - press.x, move.clientY - press.y) > 10) finish();
905
- }, options);
906
- for (const type of ["pointerup", "pointercancel"]) doc.addEventListener(type, (end) => {
907
- if (end.pointerId === press.pointerId) finish();
908
- }, options);
909
- doc.addEventListener("pointerdown", (next) => {
910
- if (next.pointerId !== press.pointerId) finish();
911
- }, options);
912
- doc.addEventListener("scroll", finish, options);
913
- doc.addEventListener("visibilitychange", () => {
914
- if (doc.hidden) finish();
915
- }, options);
916
- win.addEventListener("blur", finish, { signal: press.events.signal });
917
- press.timer = win.setTimeout(() => this.zone.run(() => this.openTouchMenu(press)), 500);
918
- };
1060
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
1061
+ _isOpen = signal(false, ...ngDevMode ? [{ debugName: "_isOpen" }] : /* istanbul ignore next */ []);
1062
+ overlayId = signal(null, ...ngDevMode ? [{ debugName: "overlayId" }] : /* istanbul ignore next */ []);
1063
+ isOpen = this._isOpen.asReadonly();
1064
+ _state = signal("closed", ...ngDevMode ? [{ debugName: "_state" }] : /* istanbul ignore next */ []);
1065
+ state = this._state.asReadonly();
1066
+ bxDropdown = input.required(...ngDevMode ? [{ debugName: "bxDropdown" }] : /* istanbul ignore next */ []);
1067
+ placement = input("bottom", ...ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []);
1068
+ bxTransition = input("fade", ...ngDevMode ? [{ debugName: "bxTransition" }] : /* istanbul ignore next */ []);
919
1069
  overlayRef = null;
920
- transition;
921
- point;
922
- disconnectTheme;
923
- disconnectLighting;
1070
+ transition = null;
924
1071
  subscriptions = new Subscription();
925
1072
  revision = 0;
926
- touchPress;
927
- touchGuard;
928
- touchGuardTimer;
929
- lastPointerType = "";
1073
+ disconnectTheme;
1074
+ disconnectLighting;
930
1075
  constructor() {
931
- const source = this.elementRef.nativeElement;
932
- if (source.ownerDocument.defaultView) this.zone.runOutsideAngular(() => source.addEventListener("pointerdown", this.onPointerDown, { passive: true }));
933
1076
  inject(DestroyRef).onDestroy(() => {
934
1077
  ++this.revision;
935
- source.removeEventListener("pointerdown", this.onPointerDown);
936
- this.cancelTouchPress();
937
- this.clearTouchGuard();
938
1078
  this.dispose();
939
1079
  });
940
- }
941
- open(event) {
942
- if (event.type === "contextmenu" && this.lastPointerType === "touch") {
943
- const press = this.touchPress;
944
- if (!press) return;
945
- if (!this.bxContextMenuLongPress() || this.hasNativeTouchAction(event) || this.hasTextSelection()) {
946
- this.cancelTouchPress();
947
- return;
1080
+ effect(() => {
1081
+ if (this.a11y.isReducedMotion() && this.transition) {
1082
+ ++this.revision;
1083
+ this.transition.destroy();
1084
+ this.transition = null;
1085
+ if (this.isOpen()) this._state.set("open");
1086
+ else this.dispose();
948
1087
  }
949
- event.preventDefault();
950
- this.openTouchMenu(press);
951
- return;
952
- }
953
- event.preventDefault();
954
- this.cancelTouchPress();
955
- this.clearTouchGuard();
956
- this.openAt(event.clientX, event.clientY);
957
- }
958
- close(restoreFocus = false) {
959
- if (!this.overlayRef) return;
960
- const revision = ++this.revision;
961
- if (restoreFocus || this.overlayRef.overlayElement.contains(this.elementRef.nativeElement.ownerDocument.activeElement)) this.elementRef.nativeElement.focus({ preventScroll: true });
962
- this.overlayRef.overlayElement.inert = true;
963
- const transition = this.transition;
964
- if (transition && !this.a11y.isReducedMotion()) this.zone.runOutsideAngular(() => transition.exit()).then(() => {
965
- if (revision === this.revision) this.dispose();
966
- }).catch(() => {
967
- if (revision === this.revision) this.dispose();
968
1088
  });
969
- else this.dispose();
970
- }
971
- onKey(event) {
972
- if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
973
- event.preventDefault();
974
- this.lastPointerType = "";
975
- this.cancelTouchPress();
976
- this.clearTouchGuard();
977
- const rect = this.elementRef.nativeElement.getBoundingClientRect();
978
- this.openAt(rect.left + Math.min(24, rect.width / 2), rect.bottom);
979
- this.items()[0]?.focus();
980
- }
981
- onDocumentClick(event) {
982
- const target = event.target;
983
- if (target && this.overlayRef?.overlayElement.contains(target) && target.closest("button, a, [role=\"menuitem\"]")) this.close();
984
- }
985
- hasNativeTouchAction(event) {
986
- const source = this.elementRef.nativeElement;
987
- for (const node of event.composedPath()) if (node instanceof HTMLElement) {
988
- if (node.isContentEditable || node.matches("input, textarea, select, :disabled, [aria-disabled=\"true\"], [inert]")) return true;
989
- if (node === source) break;
990
- }
991
- return !!source.closest(":disabled, [aria-disabled=\"true\"], [inert]");
992
- }
993
- hasTextSelection() {
994
- const source = this.elementRef.nativeElement;
995
- const selection = source.ownerDocument.getSelection();
996
- return !!selection && !selection.isCollapsed && (source.contains(selection.anchorNode) || source.contains(selection.focusNode));
997
1089
  }
998
- openTouchMenu(press) {
999
- if (this.touchPress !== press || press.opened) return;
1000
- const source = this.elementRef.nativeElement;
1001
- if (!this.bxContextMenuLongPress() || !source.isConnected || source.isContentEditable || source.closest(":disabled, [aria-disabled=\"true\"], [inert]") || this.hasTextSelection()) {
1002
- this.cancelTouchPress();
1003
- return;
1004
- }
1005
- press.opened = true;
1006
- this.elementRef.nativeElement.ownerDocument.defaultView?.clearTimeout(press.timer);
1007
- this.guardTouchRelease();
1008
- this.openAt(press.x, press.y + 12);
1090
+ toggle(event) {
1091
+ if (this.isDisabled()) return;
1092
+ event?.stopPropagation();
1093
+ if (this.isOpen()) this.close();
1094
+ else this.open();
1009
1095
  }
1010
- cancelTouchPress() {
1011
- const press = this.touchPress;
1012
- if (!press) return;
1013
- this.touchPress = void 0;
1014
- const win = this.elementRef.nativeElement.ownerDocument.defaultView;
1015
- win?.clearTimeout(press.timer);
1016
- press.events.abort();
1017
- if (press.opened && win) this.touchGuardTimer = win.setTimeout(() => this.clearTouchGuard(), 800);
1096
+ onTriggerKeydown(event) {
1097
+ if (this.bxTransition() === "none" || this.isDisabled() || this.isEditing(event.target)) return;
1098
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
1099
+ event.preventDefault();
1100
+ event.stopPropagation();
1101
+ this.open();
1102
+ const items = this.menuItems();
1103
+ (event.key === "ArrowUp" ? items[items.length - 1] : items[0])?.focus({ preventScroll: true });
1018
1104
  }
1019
- guardTouchRelease() {
1020
- this.clearTouchGuard();
1021
- const doc = this.elementRef.nativeElement.ownerDocument;
1022
- const guard = new AbortController();
1023
- this.touchGuard = guard;
1024
- const options = {
1025
- capture: true,
1026
- signal: guard.signal
1027
- };
1028
- doc.addEventListener("click", (event) => {
1029
- if (event.detail === 0) return;
1030
- event.preventDefault();
1031
- event.stopImmediatePropagation();
1032
- this.clearTouchGuard();
1033
- }, options);
1034
- doc.addEventListener("contextmenu", (event) => {
1035
- event.preventDefault();
1036
- event.stopImmediatePropagation();
1037
- }, options);
1038
- doc.addEventListener("pointerdown", (event) => {
1039
- if (event.isPrimary) this.clearTouchGuard();
1040
- }, {
1041
- ...options,
1042
- passive: true
1105
+ open() {
1106
+ if (!this.isBrowser || this.isOpen() || this.isDisabled()) return;
1107
+ const revision = ++this.revision;
1108
+ this._isOpen.set(true);
1109
+ this._state.set("opening");
1110
+ if (!this.overlayRef) this.attachOverlay();
1111
+ const overlay = this.overlayRef;
1112
+ if (!overlay) return;
1113
+ overlay.overlayElement.inert = false;
1114
+ overlay.overlayElement.style.removeProperty("pointer-events");
1115
+ if (this.bxTransition() !== "none" && !this.a11y.isReducedMotion()) {
1116
+ const transition = this.transition ??= this.zone.runOutsideAngular(() => createGlassTransition(this.elementRef.nativeElement, overlay.overlayElement));
1117
+ this.zone.runOutsideAngular(() => transition.enter()).then(() => {
1118
+ if (revision === this.revision && this.isOpen()) this.zone.run(() => this._state.set("open"));
1119
+ }).catch(() => {
1120
+ if (revision === this.revision && this.isOpen()) {
1121
+ this.transition?.destroy();
1122
+ this.transition = null;
1123
+ this._state.set("open");
1124
+ }
1125
+ });
1126
+ } else this._state.set("open");
1127
+ }
1128
+ close(restoreFocus = false) {
1129
+ if (!this.overlayRef || !this.isOpen()) return;
1130
+ const revision = ++this.revision;
1131
+ const overlay = this.overlayRef;
1132
+ this._isOpen.set(false);
1133
+ this._state.set("closing");
1134
+ const source = this.elementRef.nativeElement;
1135
+ if (restoreFocus || overlay.overlayElement.contains(source.ownerDocument.activeElement)) source.focus({ preventScroll: true });
1136
+ overlay.overlayElement.inert = true;
1137
+ overlay.overlayElement.style.pointerEvents = "none";
1138
+ const transition = this.transition;
1139
+ if (transition && !this.a11y.isReducedMotion()) this.zone.runOutsideAngular(() => transition.exit()).then(() => {
1140
+ if (revision === this.revision && !this.isOpen()) this.zone.run(() => this.dispose());
1141
+ }).catch(() => {
1142
+ if (revision === this.revision && !this.isOpen()) this.dispose();
1043
1143
  });
1044
- doc.addEventListener("keydown", () => this.clearTouchGuard(), options);
1144
+ else this.dispose();
1045
1145
  }
1046
- clearTouchGuard() {
1047
- this.touchGuard?.abort();
1048
- this.touchGuard = void 0;
1049
- this.elementRef.nativeElement.ownerDocument.defaultView?.clearTimeout(this.touchGuardTimer);
1050
- this.touchGuardTimer = void 0;
1146
+ onDocumentClick(event) {
1147
+ if (!this.overlayRef?.hasAttached() || !this.isOpen()) return;
1148
+ const target = event.target;
1149
+ if (!target || this.elementRef.nativeElement.contains(target)) return;
1150
+ if (!this.overlayRef.overlayElement.contains(target) || this.bxTransition() === "none" || target instanceof Element && target.closest("button, a, [role=\"menuitem\"]")) this.close();
1051
1151
  }
1052
- openAt(x, y) {
1053
- this.dispose();
1054
- ++this.revision;
1055
- const source = this.elementRef.nativeElement;
1056
- this.overlayRef = this.overlay.create({
1057
- positionStrategy: this.positionBuilder.getCoordinateStrategy(x, y),
1152
+ attachOverlay() {
1153
+ const positionStrategy = this.positionBuilder.getElementStrategy(this.elementRef.nativeElement, this.placement(), 4);
1154
+ const overlay = this.overlay.create({
1155
+ positionStrategy,
1058
1156
  scrollStrategy: this.overlay.scrollStrategies.noop(),
1059
- usePopover: !!this.overlayContext?.useTopLayer || !!source.closest("dialog, [role=\"dialog\"], [role=\"alertdialog\"], bx-modal, bx-modal-core, [popover]"),
1060
- hasBackdrop: true,
1157
+ usePopover: !!this.overlayContext?.useTopLayer || !!this.elementRef.nativeElement.closest("dialog, [role=\"dialog\"], [role=\"alertdialog\"], bx-modal, bx-modal-core, [popover]"),
1158
+ hasBackdrop: this.bxTransition() === "none",
1061
1159
  backdropClass: "cdk-overlay-transparent-backdrop"
1062
1160
  });
1161
+ this.overlayRef = overlay;
1162
+ this.overlayId.set(overlay.overlayElement.id || null);
1163
+ this.disconnectTheme = this.zone.runOutsideAngular(() => inheritOverlayTheme(this.elementRef.nativeElement, overlay.overlayElement));
1164
+ this.disconnectLighting = inheritGlassLighting(overlay.overlayElement, this.elementRef.nativeElement);
1063
1165
  this.subscriptions = new Subscription();
1064
- const win = source.ownerDocument.defaultView;
1065
- if (win) this.zone.runOutsideAngular(() => {
1066
- const closeOnScroll = (event) => {
1166
+ this.trackScroll(overlay, positionStrategy);
1167
+ this.subscriptions.add(overlay.backdropClick().subscribe(() => this.close()));
1168
+ this.subscriptions.add(overlay.outsidePointerEvents().subscribe((event) => {
1169
+ if (!this.elementRef.nativeElement.contains(event.target)) this.close();
1170
+ }));
1171
+ this.subscriptions.add(overlay.keydownEvents().subscribe((event) => {
1172
+ if (event.key === "Escape" && this.isOpen()) {
1173
+ event.preventDefault();
1174
+ event.stopPropagation();
1175
+ this.close(true);
1176
+ } else if (!event.defaultPrevented && this.bxTransition() !== "none" && this.isOpen()) this.navigateMenu(event);
1177
+ }));
1178
+ this.subscriptions.add(positionStrategy.positionChanges.subscribe(() => {
1179
+ this.zone.runOutsideAngular(() => this.transition?.refresh());
1180
+ }));
1181
+ overlay.attach(new TemplatePortal(this.bxDropdown(), this.vcr)).detectChanges();
1182
+ overlay.updatePosition();
1183
+ }
1184
+ trackScroll(overlay, positionStrategy) {
1185
+ const source = this.elementRef.nativeElement;
1186
+ const view = source.ownerDocument.defaultView;
1187
+ if (!view) return;
1188
+ this.zone.runOutsideAngular(() => {
1189
+ const reposition = (event) => {
1067
1190
  const target = event.target;
1068
- if (this.overlayRef?.overlayElement.inert) return;
1069
- if (target === win || target instanceof Node && target.contains(source)) this.zone.run(() => this.close());
1191
+ if (overlay.hasAttached() && (target === view || target instanceof Node && target.contains(source))) positionStrategy.reapplyLastPosition();
1070
1192
  };
1071
- win.addEventListener("scroll", closeOnScroll, {
1193
+ view.addEventListener("scroll", reposition, {
1072
1194
  capture: true,
1073
1195
  passive: true
1074
1196
  });
1075
- this.subscriptions.add(() => win.removeEventListener("scroll", closeOnScroll, true));
1197
+ this.subscriptions.add(() => view.removeEventListener("scroll", reposition, true));
1076
1198
  });
1077
- this.subscriptions.add(this.overlayRef.backdropClick().subscribe(() => this.close()));
1078
- this.subscriptions.add(this.overlayRef.detachments().subscribe(() => this.dispose()));
1079
- this.subscriptions.add(this.overlayRef.keydownEvents().subscribe((event) => this.onMenuKey(event)));
1080
- const pane = this.overlayRef.overlayElement;
1081
- this.disconnectTheme = this.zone.runOutsideAngular(() => inheritOverlayTheme(source, pane));
1082
- this.disconnectLighting = inheritGlassLighting(pane, source);
1083
- this.overlayRef.attach(new TemplatePortal(this.bxContextMenu(), this.vcr)).detectChanges();
1084
- this.overlayRef.updatePosition();
1085
- if (!this.a11y.isReducedMotion()) {
1086
- const point = source.ownerDocument.createElement("span");
1087
- point.setAttribute("aria-hidden", "true");
1088
- Object.assign(point.style, {
1089
- position: "fixed",
1090
- left: `${x}px`,
1091
- top: `${y}px`,
1092
- width: "1px",
1093
- height: "1px",
1094
- pointerEvents: "none"
1095
- });
1096
- source.ownerDocument.body.append(point);
1097
- this.point = point;
1098
- const transition = this.zone.runOutsideAngular(() => createGlassTransition(point, pane));
1099
- this.transition = transition;
1100
- this.zone.runOutsideAngular(() => transition.enter()).catch(() => {
1101
- this.transition?.destroy();
1102
- this.transition = void 0;
1103
- });
1104
- }
1105
1199
  }
1106
- items() {
1107
- return Array.from(this.overlayRef?.overlayElement.querySelectorAll("button:not(:disabled), a[href], [role=\"menuitem\"]") ?? []).filter((item) => !item.matches("[disabled], [aria-disabled=\"true\"]") && !item.closest("[hidden], [inert]"));
1200
+ isDisabled() {
1201
+ const element = this.elementRef.nativeElement;
1202
+ return element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true";
1108
1203
  }
1109
- onMenuKey(event) {
1110
- if (event.key === "Escape") {
1111
- event.preventDefault();
1112
- event.stopPropagation();
1113
- this.close(true);
1114
- return;
1115
- }
1116
- if (event.target?.closest("input,textarea,[contenteditable=\"true\"]")) return;
1204
+ menuItems() {
1205
+ const pane = this.overlayRef?.overlayElement;
1206
+ if (!pane) return [];
1207
+ return Array.from(pane.querySelectorAll("button, a[href], [role=\"menuitem\"]")).filter((item) => {
1208
+ if (item.matches(":disabled, [disabled], [aria-disabled=\"true\"]") || item.closest("[aria-disabled=\"true\"]")) return false;
1209
+ if (item.closest("[hidden], [inert], [aria-hidden=\"true\"]")) return false;
1210
+ if (item.tabIndex < 0 && !item.hasAttribute("tabindex")) return false;
1211
+ const style = item.ownerDocument.defaultView?.getComputedStyle(item);
1212
+ return !!style && style.display !== "none" && style.visibility !== "hidden" && item.getClientRects().length > 0;
1213
+ });
1214
+ }
1215
+ navigateMenu(event) {
1216
+ if (this.isEditing(event.target) || !(event.target instanceof Node) || !this.overlayRef?.overlayElement.contains(event.target)) return;
1117
1217
  if (![
1118
1218
  "ArrowDown",
1119
1219
  "ArrowUp",
1120
1220
  "Home",
1121
1221
  "End"
1122
1222
  ].includes(event.key)) return;
1123
- const items = this.items();
1223
+ const items = this.menuItems();
1124
1224
  if (!items.length) return;
1125
- event.preventDefault();
1126
1225
  const current = items.indexOf(this.elementRef.nativeElement.ownerDocument.activeElement);
1127
- items[event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : event.key === "ArrowDown" ? (current + 1) % items.length : current < 0 ? items.length - 1 : (current - 1 + items.length) % items.length].focus();
1226
+ let index;
1227
+ if (event.key === "Home") index = 0;
1228
+ else if (event.key === "End") index = items.length - 1;
1229
+ else if (event.key === "ArrowDown") index = current < 0 ? 0 : (current + 1) % items.length;
1230
+ else index = current < 0 ? items.length - 1 : (current - 1 + items.length) % items.length;
1231
+ event.preventDefault();
1232
+ event.stopPropagation();
1233
+ items[index].focus({ preventScroll: true });
1234
+ }
1235
+ isEditing(target) {
1236
+ return target instanceof Element && !!target.closest("input, textarea, select, [contenteditable]:not([contenteditable=\"false\"]), [role=\"textbox\"]");
1128
1237
  }
1129
1238
  dispose() {
1130
- this.subscriptions.unsubscribe();
1131
1239
  this.disconnectTheme?.();
1132
1240
  this.disconnectTheme = void 0;
1133
1241
  this.transition?.destroy();
1134
- this.transition = void 0;
1135
- this.point?.remove();
1136
- this.point = void 0;
1137
- const overlay = this.overlayRef;
1242
+ this.transition = null;
1243
+ this.subscriptions.unsubscribe();
1244
+ this.overlayRef?.dispose();
1138
1245
  this.overlayRef = null;
1139
- overlay?.dispose();
1140
1246
  this.disconnectLighting?.();
1141
1247
  this.disconnectLighting = void 0;
1248
+ this.overlayId.set(null);
1249
+ this._isOpen.set(false);
1250
+ this._state.set("closed");
1142
1251
  }
1143
1252
  static ɵfac = i0.ɵɵngDeclareFactory({
1144
1253
  minVersion: "12.0.0",
1145
1254
  version: "22.2.3",
1146
1255
  ngImport: i0,
1147
- type: ContextMenuDirective,
1256
+ type: DropdownDirective,
1148
1257
  deps: [],
1149
1258
  target: i0.ɵɵFactoryTarget.Directive
1150
1259
  });
1151
1260
  static ɵdir = i0.ɵɵngDeclareDirective({
1152
1261
  minVersion: "17.1.0",
1153
1262
  version: "22.2.3",
1154
- type: ContextMenuDirective,
1263
+ type: DropdownDirective,
1155
1264
  isStandalone: true,
1156
- selector: "[bxContextMenu]",
1265
+ selector: "[bxDropdown]",
1157
1266
  inputs: {
1158
- bxContextMenu: {
1159
- classPropertyName: "bxContextMenu",
1160
- publicName: "bxContextMenu",
1267
+ bxDropdown: {
1268
+ classPropertyName: "bxDropdown",
1269
+ publicName: "bxDropdown",
1161
1270
  isSignal: true,
1162
1271
  isRequired: true,
1163
1272
  transformFunction: null
1164
1273
  },
1165
- bxContextMenuLongPress: {
1166
- classPropertyName: "bxContextMenuLongPress",
1167
- publicName: "bxContextMenuLongPress",
1274
+ placement: {
1275
+ classPropertyName: "placement",
1276
+ publicName: "placement",
1277
+ isSignal: true,
1278
+ isRequired: false,
1279
+ transformFunction: null
1280
+ },
1281
+ bxTransition: {
1282
+ classPropertyName: "bxTransition",
1283
+ publicName: "bxTransition",
1168
1284
  isSignal: true,
1169
1285
  isRequired: false,
1170
1286
  transformFunction: null
1171
1287
  }
1172
1288
  },
1173
- host: { listeners: {
1174
- "contextmenu": "open($event)",
1175
- "keydown": "onKey($event)",
1176
- "document:click": "onDocumentClick($event)"
1177
- } },
1289
+ host: {
1290
+ listeners: {
1291
+ "click": "toggle($event)",
1292
+ "keydown": "onTriggerKeydown($event)",
1293
+ "document:click": "onDocumentClick($event)"
1294
+ },
1295
+ properties: {
1296
+ "attr.aria-expanded": "isOpen()",
1297
+ "attr.aria-haspopup": "\"menu\"",
1298
+ "attr.aria-controls": "overlayId()"
1299
+ }
1300
+ },
1301
+ exportAs: ["bxDropdown"],
1178
1302
  ngImport: i0
1179
1303
  });
1180
1304
  };
@@ -1182,34 +1306,51 @@ i0.ɵɵngDeclareClassMetadata({
1182
1306
  minVersion: "12.0.0",
1183
1307
  version: "22.2.3",
1184
1308
  ngImport: i0,
1185
- type: ContextMenuDirective,
1309
+ type: DropdownDirective,
1186
1310
  decorators: [{
1187
1311
  type: Directive,
1188
- args: [{ selector: "[bxContextMenu]" }]
1312
+ args: [{
1313
+ selector: "[bxDropdown]",
1314
+ exportAs: "bxDropdown",
1315
+ standalone: true,
1316
+ host: {
1317
+ "[attr.aria-expanded]": "isOpen()",
1318
+ "[attr.aria-haspopup]": "\"menu\"",
1319
+ "[attr.aria-controls]": "overlayId()"
1320
+ }
1321
+ }]
1189
1322
  }],
1190
1323
  ctorParameters: () => [],
1191
1324
  propDecorators: {
1192
- bxContextMenu: [{
1325
+ bxDropdown: [{
1193
1326
  type: i0.Input,
1194
1327
  args: [{
1195
1328
  isSignal: true,
1196
- alias: "bxContextMenu",
1329
+ alias: "bxDropdown",
1197
1330
  required: true
1198
1331
  }]
1199
1332
  }],
1200
- bxContextMenuLongPress: [{
1333
+ placement: [{
1201
1334
  type: i0.Input,
1202
1335
  args: [{
1203
1336
  isSignal: true,
1204
- alias: "bxContextMenuLongPress",
1337
+ alias: "placement",
1205
1338
  required: false
1206
1339
  }]
1207
1340
  }],
1208
- open: [{
1341
+ bxTransition: [{
1342
+ type: i0.Input,
1343
+ args: [{
1344
+ isSignal: true,
1345
+ alias: "bxTransition",
1346
+ required: false
1347
+ }]
1348
+ }],
1349
+ toggle: [{
1209
1350
  type: HostListener,
1210
- args: ["contextmenu", ["$event"]]
1351
+ args: ["click", ["$event"]]
1211
1352
  }],
1212
- onKey: [{
1353
+ onTriggerKeydown: [{
1213
1354
  type: HostListener,
1214
1355
  args: ["keydown", ["$event"]]
1215
1356
  }],