Repository URL to install this package:
|
Version:
1.0.0-next.10 ▾
|
/**
* @license
* FOURBURNER CONFIDENTIAL
* Unpublished Copyright (C) 2021 FourBurner Technologies, Inc. All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of FOURBURNER TECHNOLOGIES,
* INC. The intellectual and technical concepts contained herein are proprietary to FOURBURNER
* TECHNOLOGIES, INC. and may be covered by U.S. and Foreign Patents, patents in process, and are
* protected by trade secret or copyright law. Dissemination of this information or reproduction of
* this material is strictly forbidden unless prior written permission is obtained from FOURBURNER
* TECHNOLOGIES, INC. Access to the source code contained herein is hereby forbidden to anyone
* except current FOURBURNER TECHNOLOGIES, INC. employees, managers or contractors who have executed
* Confidentiality and Non-disclosure agreements explicitly covering such access.
*
* The copyright notice above does not evidence any actual or intended publication or disclosure of
* this source code, which includes information that is confidential and/or proprietary, and is a
* trade secret, of FOURBURNER TECHNOLOGIES, INC. ANY REPRODUCTION, MODIFICATION, DISTRIBUTION,
* PUBLIC PERFORMANCE, OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS
* WRITTEN CONSENT OF FOURBURNER TECHNOLOGIES, INC. IS STRICTLY PROHIBITED, AND IN VIOLATION OF
* APPLICABLE LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR
* RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS
* CONTENTS, OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
*/
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {BooleanInput, coerceBooleanProperty, coerceStringArray} from '@angular/cdk/coercion';
import {Platform} from '@angular/cdk/platform';
import {
AfterContentInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChildren,
ElementRef,
EventEmitter,
Inject,
InjectionToken,
Input,
Output,
QueryList,
TemplateRef,
ViewChild,
ViewEncapsulation,
OnDestroy,
Directive,
} from '@angular/core';
import {
CanDisableRipple,
TWND_OPTGROUP,
TWND_OPTION_PARENT_COMPONENT,
_TWNDOptgroupBase,
_TWNDOptionBase,
mixinDisableRipple,
TWNDOption,
TWNDOptgroup,
} from '@twnd/ux/core';
import {Subscription} from 'rxjs';
/**
* Autocomplete IDs need to be unique across components, so this counter exists outside of
* the component definition.
*/
let _uniqueAutocompleteIdCounter = 0;
/** Event object that is emitted when an autocomplete option is selected. */
export class TWNDAutocompleteSelectedEvent
{
constructor(
/** Reference to the autocomplete panel that emitted the event. */
public source: _TWNDAutocompleteBase,
/** Option that was selected. */
public option: _TWNDOptionBase,
)
{}
}
/** Event object that is emitted when an autocomplete option is activated. */
export interface TWNDAutocompleteActivatedEvent {
/** Reference to the autocomplete panel that emitted the event. */
source: _TWNDAutocompleteBase;
/** Option that was selected. */
option: _TWNDOptionBase|null;
}
// Boilerplate for applying mixins to TWNDAutocomplete.
/** @docs-private */
const _TWNDAutocompleteMixinBase = mixinDisableRipple(class {});
/** Default `twnd-autocomplete` options that can be overridden. */
export interface TWNDAutocompleteDefaultOptions {
/** Whether the first option should be highlighted when an autocomplete panel is opened. */
autoActiveFirstOption?: boolean;
/** Class or list of classes to be applied to the autocomplete's overlay panel. */
overlayPanelClass?: string|string[];
}
/** Injection token to be used to override the default options for `twnd-autocomplete`. */
export const TWND_AUTOCOMPLETE_DEFAULT_OPTIONS = new InjectionToken<TWNDAutocompleteDefaultOptions>(
'twnd-autocomplete-default-options',
{
providedIn : 'root',
factory : TWND_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY,
},
);
/** @docs-private */
export function TWND_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY(): TWNDAutocompleteDefaultOptions
{
return {autoActiveFirstOption : false};
}
/** Base class with all of the `TWNDAutocomplete` functionality. */
@Directive()
export abstract class _TWNDAutocompleteBase extends _TWNDAutocompleteMixinBase implements
AfterContentInit, CanDisableRipple, OnDestroy
{
private _activeOptionChanges = Subscription.EMPTY;
/** Class to apply to the panel when it's visible. */
protected abstract _visibleClass: string;
/** Class to apply to the panel when it's hidden. */
protected abstract _hiddenClass: string;
/** Manages active item in option list based on key events. */
_keyManager: ActiveDescendantKeyManager<_TWNDOptionBase>;
/** Whether the autocomplete panel should be visible, depending on option length. */
showPanel: boolean = false;
/** Whether the autocomplete panel is open. */
get isOpen(): boolean
{
return this._isOpen && this.showPanel;
}
_isOpen: boolean = false;
// The @ViewChild query for TemplateRef here needs to be static because some code paths
// lead to the overlay being created before change detection has finished for this component.
// Notably, another component may trigger `focus` on the autocomplete-trigger.
/** @docs-private */
@ViewChild(TemplateRef, {static : true}) template: TemplateRef<any>;
/** Element for the panel containing the autocomplete options. */
@ViewChild('panel') panel: ElementRef;
/** Reference to all options within the autocomplete. */
abstract options: QueryList<_TWNDOptionBase>;
/** Reference to all option groups within the autocomplete. */
abstract optionGroups: QueryList<_TWNDOptgroupBase>;
/** Aria label of the autocomplete. */
@Input('aria-label') ariaLabel: string;
/** Input that can be used to specify the `aria-labelledby` attribute. */
@Input('aria-labelledby') ariaLabelledby: string;
/** Function that maps an option's control value to its display value in the trigger. */
@Input() displayWith: ((value: any) => string)|null = null;
/**
* Whether the first option should be highlighted when the autocomplete panel is opened.
* Can be configured globally through the `TWND_AUTOCOMPLETE_DEFAULT_OPTIONS` token.
*/
@Input() get autoActiveFirstOption(): boolean
{
return this._autoActiveFirstOption;
}
set autoActiveFirstOption(value: boolean)
{
this._autoActiveFirstOption = coerceBooleanProperty(value);
}
private _autoActiveFirstOption: boolean;
/**
* Specify the width of the autocomplete panel. Can be any CSS sizing value, otherwise it will
* match the width of its host.
*/
@Input() panelWidth: string|number;
/** Event that is emitted whenever an option from the list is selected. */
@Output()
readonly optionSelected:
EventEmitter<TWNDAutocompleteSelectedEvent> = new EventEmitter<TWNDAutocompleteSelectedEvent>();
/** Event that is emitted when the autocomplete panel is opened. */
@Output() readonly opened: EventEmitter<void> = new EventEmitter<void>();
/** Event that is emitted when the autocomplete panel is closed. */
@Output() readonly closed: EventEmitter<void> = new EventEmitter<void>();
/** Emits whenever an option is activated using the keyboard. */
@Output()
readonly optionActivated: EventEmitter<TWNDAutocompleteActivatedEvent> =
new EventEmitter<TWNDAutocompleteActivatedEvent>();
/**
* Takes classes set on the host twnd-autocomplete element and applies them to the panel
* inside the overlay container to allow for easy styling.
*/
@Input('class') set classList(value: string|string[])
{
if (value && value.length) {
this._classList = coerceStringArray(value).reduce((classList, className) => {
classList[className] = true;
return classList;
}, {} as {[key: string] : boolean});
} else {
this._classList = {};
}
this._setVisibilityClasses(this._classList);
this._elementRef.nativeElement.className = '';
}
_classList: {[key: string]: boolean} = {};
/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
id: string = `twnd-autocomplete-${_uniqueAutocompleteIdCounter++}`;
/**
* Tells any descendant `twnd-optgroup` to use the inert a11y pattern.
* @docs-private
*/
readonly inertGroups: boolean;
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _elementRef: ElementRef<HTMLElement>,
@Inject(TWND_AUTOCOMPLETE_DEFAULT_OPTIONS) defaults: TWNDAutocompleteDefaultOptions,
platform?: Platform,
)
{
super();
// TODO(crisbeto): the problem that the `inertGroups` option resolves is only present on
// Safari using VoiceOver. We should occasionally check back to see whether the bug
// wasn't resolved in VoiceOver, and if it has, we can remove this and the `inertGroups`
// option altogether.
this.inertGroups = platform?.SAFARI || false;
this._autoActiveFirstOption = !!defaults.autoActiveFirstOption;
}
ngAfterContentInit()
{
this._keyManager = new ActiveDescendantKeyManager<_TWNDOptionBase>(this.options).withWrap();
this._activeOptionChanges = this._keyManager.change.subscribe(index => {
if (this.isOpen) {
this.optionActivated.emit({source : this, option : this.options.toArray()[index] || null});
}
});
// Set the initial visibility state.
this._setVisibility();
}
ngOnDestroy()
{
this._activeOptionChanges.unsubscribe();
}
/**
* Sets the panel scrollTop. This allows us to manually scroll to display options
* above or below the fold, as they are not actually being focused when active.
*/
_setScrollTop(scrollTop: number): void
{
if (this.panel) {
this.panel.nativeElement.scrollTop = scrollTop;
}
}
/** Returns the panel's scrollTop. */
_getScrollTop(): number
{
return this.panel ? this.panel.nativeElement.scrollTop : 0;
}
/** Panel should hide itself when the option list is empty. */
_setVisibility()
{
this.showPanel = !!this.options.length;
this._setVisibilityClasses(this._classList);
this._changeDetectorRef.markForCheck();
}
/** Emits the `select` event. */
_emitSelectEvent(option: _TWNDOptionBase): void
{
const event = new TWNDAutocompleteSelectedEvent(this, option);
this.optionSelected.emit(event);
}
/** Gets the aria-labelledby for the autocomplete panel. */
_getPanelAriaLabelledby(labelId: string|null): string|null
{
if (this.ariaLabel) {
return null;
}
const labelExpression = labelId ? labelId + ' ' : '';
return this.ariaLabelledby ? labelExpression + this.ariaLabelledby : labelId;
}
/** Sets the autocomplete visibility classes on a classlist based on the panel is visible. */
private _setVisibilityClasses(classList: {[key: string]: boolean})
{
classList[this._visibleClass] = this.showPanel;
classList[this._hiddenClass] = !this.showPanel;
}
static ngAcceptInputType_autoActiveFirstOption: BooleanInput;
static ngAcceptInputType_disableRipple: BooleanInput;
}
@Component({
selector : 'twnd-autocomplete',
templateUrl : 'autocomplete.html',
encapsulation : ViewEncapsulation.None,
changeDetection : ChangeDetectionStrategy.OnPush,
exportAs : 'twndAutocomplete',
inputs : [ 'disableRipple' ],
host : {
'class' : 'twnd-autocomplete',
},
providers : [ {provide : TWND_OPTION_PARENT_COMPONENT, useExisting : TWNDAutocomplete}
],
})
export class TWNDAutocomplete extends _TWNDAutocompleteBase
{
/** Reference to all option groups within the autocomplete. */
@ContentChildren(TWND_OPTGROUP, {descendants : true}) optionGroups: QueryList<TWNDOptgroup>;
/** Reference to all options within the autocomplete. */
@ContentChildren(TWNDOption, {descendants : true}) options: QueryList<TWNDOption>;
protected _visibleClass = 'twnd-autocomplete-visible';
protected _hiddenClass = 'twnd-autocomplete-hidden';
}