import { Component, Inject, Input, Output } from '../decorators' import WebfontLoaderService from '../font-selector/webfont-loader.service' import { IComponentController } from 'angular' @Component({ selector: 'mflyFontWeightSelector', template: require('./font-weight-selector.html') }) export default class FontWeightSelectorController implements IComponentController { @Input() fontFamily?: string @Input() fontWeight?: string @Input() fontWeightOptions?: string[] @Output('&?') onFontWeightSelect?: (param: { fontWeight: string }) => void protected interactionMode: 'mouse' | 'keyboard' = 'keyboard' protected isDropdownVisible: boolean = false protected kbSelectedIndex = -1 protected keyEventElement: HTMLElement | null = null protected fontWeightTranslationsMap: Map = new Map() private observer: MutationObserver constructor ( @Inject('$document') private $document: ng.IDocumentService, @Inject('$scope') private $scope: ng.IScope, @Inject('$element') private $element: ng.IAugmentedJQuery, @Inject('$timeout') private $timeout: ng.ITimeoutService, @Inject('translateFactory') private translateFactory, @Inject('webfontLoaderService') private webfontLoaderService: WebfontLoaderService, ) {} $onDestroy() { if (this.keyEventElement) { this.keyEventElement.removeEventListener('keydown', this.onKeyDown) } const body = (this.$document[0] as Document).body body.removeEventListener('click', this.onClickElsewhere) this.observer.disconnect() } $onInit() { const body = (this.$document[0] as Document).body body.addEventListener('click', this.onClickElsewhere) this.kbSelectedIndex = this.fontWeightOptions?.indexOf(this.fontWeight || '') || -1 for (let i = 1; i <= 9; i++) { this.fontWeightTranslationsMap.set(`${i}00`, this.translateFactory.instant(`JSUI.FONT_WEIGHT_${i}00`)) } // Load preview fonts when the font family or weight options change this.$scope.$watchCollection(() => [this.fontFamily, this.fontWeightOptions], () => { if (this.fontFamily && this.fontWeightOptions?.length) { this.webfontLoaderService.loadFontVariants(this.fontFamily, this.fontWeightOptions) } }) this.observer = new MutationObserver(() => { this.keyEventElement = this.$element.parent().find('.c-font-weight-selector__dropdown__matches')[0] if (this.keyEventElement) { this.keyEventElement.addEventListener('keydown', this.onKeyDown) this.observer.disconnect() } }) this.observer.observe(body, { attributes: true, childList: true, characterData: false, subtree: true }) } protected onClickWeightButton($event): void { this.isDropdownVisible = !this.isDropdownVisible if (this.isDropdownVisible) { this.$timeout(() => { this.keyEventElement?.focus() }, 250) } $event.stopPropagation() } // When the user mouses over the dropdown, we should remove any keyboard // highlighting, since the mouse hover provides it too. dropdownWasMousedOver() { this.interactionMode = 'mouse' this.kbSelectedIndex = -1 } protected variantWasSelected(variant: string) { this.isDropdownVisible = false this.onFontWeightSelect?.({ fontWeight: variant }) } private onClickElsewhere = ($event: MouseEvent) => { this.$timeout(() => { this.isDropdownVisible = false }) } private onKeyDown = ($event: KeyboardEvent) => this.$timeout(() => { if (!this.fontFamily || !this.fontWeightOptions?.length) { return } this.interactionMode = 'keyboard' // Enter with no selection: let the $event through if ($event.key === 'Enter' && this.kbSelectedIndex === -1) { return } // If the dropdown is closed, all we can do is arrow down to open the thing if (this.fontWeightOptions.length === 0 && $event.key !== 'ArrowDown') { return } // Escape: close the dropdown if ($event.key === 'Escape') { this.isDropdownVisible = false } // Arrow keys with dropdown open: move the selection highlight if ($event.key === 'ArrowDown') { const idx = Math.min(this.kbSelectedIndex + 1, this.fontWeightOptions.length - 1) this.kbSelectedIndex = idx this.scrollToSelection(idx) } if ($event.key === 'ArrowUp') { const idx = Math.max(this.kbSelectedIndex - 1, 0) this.kbSelectedIndex = idx this.scrollToSelection(idx) } // Enter with a selection: set the selection to the currently highlighted match if ($event.key === 'Enter' && this.kbSelectedIndex > -1) { const selectedFontWeight = this.fontWeightOptions[this.kbSelectedIndex] this.onFontWeightSelect?.({ fontWeight: selectedFontWeight }) this.isDropdownVisible = false $event.preventDefault() } $event.stopPropagation() }) private scrollToSelection(index: number) { const row = this.$element.find('.c-font-weight-selector__match-row')[index] as HTMLDivElement row?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) } }