@tedi-design-system/angular 9.0.0-rc.6 → 9.0.0-rc.7

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.
@@ -487,7 +487,11 @@ function breakpointInput(input) {
487
487
  return { xs: input };
488
488
  }
489
489
 
490
- const FOCUSABLE_SELECTOR$1 = "button, a[href], [tabindex]";
490
+ /** Native controls keep their own semantics and keyboard behavior. */
491
+ const NATIVE_INTERACTIVE_SELECTOR = 'button, a[href], input:not([type="hidden"]), select, textarea';
492
+ const FOCUSABLE_SELECTOR$1 = `${NATIVE_INTERACTIVE_SELECTOR}, [tabindex]`;
493
+ /** Native controls that fire `click` on Space but not on Enter. */
494
+ const SPACE_ACTIVATED_SELECTOR = 'input[type="checkbox"], input[type="radio"]';
491
495
  class DropdownTriggerDirective {
492
496
  /**
493
497
  * The `aria-haspopup` value for the trigger, telling assistive technology
@@ -510,6 +514,8 @@ class DropdownTriggerDirective {
510
514
  * would both be tab stops, and the ARIA state would land on the wrong element.
511
515
  */
512
516
  triggerElement = signal(null, ...(ngDevMode ? [{ debugName: "triggerElement" }] : []));
517
+ /** Only a Space press that starts on the trigger may activate it on keyup. */
518
+ spacePressed = false;
513
519
  hasPopup = computed(() => {
514
520
  const explicit = this.ariaHaspopup();
515
521
  if (explicit)
@@ -545,14 +551,21 @@ class DropdownTriggerDirective {
545
551
  this.renderer.setAttribute(el, "aria-haspopup", hasPopup);
546
552
  }
547
553
  this.renderer.setAttribute(el, "aria-expanded", String(this.dropdown.isOpen()));
548
- if (!this.isNativelyFocusable(el)) {
549
- this.renderer.setAttribute(el, "role", "button");
550
- this.renderer.setAttribute(el, "tabindex", "0");
551
- }
552
554
  });
553
555
  }
554
556
  ngAfterViewInit() {
555
- this.triggerElement.set(this.resolveTriggerElement());
557
+ const el = this.resolveTriggerElement();
558
+ // Non-native triggers need button semantics, but a role or tabindex the
559
+ // consumer set (e.g. `role="checkbox"`) is theirs to keep.
560
+ if (!this.isNativelyInteractive(el)) {
561
+ if (!el.hasAttribute("role")) {
562
+ this.renderer.setAttribute(el, "role", "button");
563
+ }
564
+ if (!el.hasAttribute("tabindex")) {
565
+ this.renderer.setAttribute(el, "tabindex", "0");
566
+ }
567
+ }
568
+ this.triggerElement.set(el);
556
569
  }
557
570
  focus() {
558
571
  this.triggerElement()?.focus();
@@ -568,6 +581,20 @@ class DropdownTriggerDirective {
568
581
  onKeydown(event) {
569
582
  const key = event.key;
570
583
  switch (key) {
584
+ case "Enter":
585
+ if (!this.handlesKey(event, key))
586
+ break;
587
+ // Also keeps Enter on a checkbox from submitting its form.
588
+ event.preventDefault();
589
+ this.dropdown.toggleDropdown();
590
+ break;
591
+ case " ":
592
+ if (!this.handlesKey(event, key))
593
+ break;
594
+ // Match button timing and prevent page scrolling.
595
+ event.preventDefault();
596
+ this.spacePressed = true;
597
+ break;
571
598
  case "ArrowDown":
572
599
  if (!this.isWidgetContent())
573
600
  break;
@@ -587,17 +614,43 @@ class DropdownTriggerDirective {
587
614
  break;
588
615
  }
589
616
  }
617
+ onKeyup(event) {
618
+ if (event.key !== " " || !this.spacePressed)
619
+ return;
620
+ this.spacePressed = false;
621
+ if (event.target !== this.focusableElement)
622
+ return;
623
+ this.dropdown.toggleDropdown();
624
+ }
625
+ onFocusout(event) {
626
+ if (event.target === this.focusableElement)
627
+ this.spacePressed = false;
628
+ }
629
+ /**
630
+ * Whether the directive must activate the trigger on `key` itself, because the
631
+ * element does not turn that key into a native click: both keys for a generic
632
+ * trigger, Enter for a checkbox or radio. Other native controls handle both.
633
+ */
634
+ handlesKey(event, key) {
635
+ const el = this.focusableElement;
636
+ // Keys from other controls inside the host are theirs.
637
+ if (event.target !== el)
638
+ return false;
639
+ if (!this.isNativelyInteractive(el))
640
+ return true;
641
+ return key === "Enter" && el.matches(SPACE_ACTIVATED_SELECTOR);
642
+ }
590
643
  isWidgetContent() {
591
644
  return this.dropdown.dropdownContent().isWidget();
592
645
  }
593
646
  resolveTriggerElement() {
594
647
  const el = this.host.nativeElement;
595
- if (this.isNativelyFocusable(el))
648
+ if (this.isNativelyInteractive(el))
596
649
  return el;
597
650
  return el.querySelector(FOCUSABLE_SELECTOR$1) ?? el;
598
651
  }
599
- isNativelyFocusable(el) {
600
- return (el.tagName === "BUTTON" || (el.tagName === "A" && el.hasAttribute("href")));
652
+ isNativelyInteractive(el) {
653
+ return el.matches(NATIVE_INTERACTIVE_SELECTOR);
601
654
  }
602
655
  openAndFocusFirst() {
603
656
  if (this.dropdown.isOpen()) {
@@ -616,7 +669,7 @@ class DropdownTriggerDirective {
616
669
  }
617
670
  }
618
671
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: DropdownTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
619
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.24", type: DropdownTriggerDirective, isStandalone: true, selector: "[tedi-dropdown-trigger]", inputs: { ariaHaspopup: { classPropertyName: "ariaHaspopup", publicName: "ariaHaspopup", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()", "keydown": "onKeydown($event)" } }, hostDirectives: [{ directive: i1.CdkOverlayOrigin }], ngImport: i0 });
672
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.24", type: DropdownTriggerDirective, isStandalone: true, selector: "[tedi-dropdown-trigger]", inputs: { ariaHaspopup: { classPropertyName: "ariaHaspopup", publicName: "ariaHaspopup", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()", "keydown": "onKeydown($event)", "keyup": "onKeyup($event)", "focusout": "onFocusout($event)" } }, hostDirectives: [{ directive: i1.CdkOverlayOrigin }], ngImport: i0 });
620
673
  }
621
674
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: DropdownTriggerDirective, decorators: [{
622
675
  type: Directive,
@@ -631,6 +684,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
631
684
  }], onKeydown: [{
632
685
  type: HostListener,
633
686
  args: ["keydown", ["$event"]]
687
+ }], onKeyup: [{
688
+ type: HostListener,
689
+ args: ["keyup", ["$event"]]
690
+ }], onFocusout: [{
691
+ type: HostListener,
692
+ args: ["focusout", ["$event"]]
634
693
  }] } });
635
694
 
636
695
  const DROPDOWN_API = new InjectionToken("DropdownApi");