File

libs/ui/chip/src/lib/chip/chip.component.ts

Description

A presentational component to render a chip

Implements

FocusableOption OnDestroy

Example

<ts-chip
  id="my-id"
  [isDisabled]="false"
  [isRemovable]="true"
  [isSelectable]="false"
  theme="primary"
  [selected]="true"
  (clicked)="chipClicked($event)"
  (destroyed)="destroyed($event)"
  (blurred)="chipBlurred($event)"
  (remove)="removeChip($event)"
  (selectionChange)="selectionChange($event)"
></ts-chip>

<example-url>https://getterminus.github.io/ui-demos-release/components/chip</example-url>

Metadata

changeDetection ChangeDetectionStrategy.OnPush
encapsulation ViewEncapsulation.None
exportAs tsChip
host {
}
selector ts-chip
styleUrls ./chip.component.scss
templateUrl ./chip.component.html

Index

Properties
Methods
Inputs
Outputs
Accessors

Constructor

constructor(elementRef: ElementRef, ngZone: NgZone, documentService: TsDocumentService)
Parameters :
Name Type Optional
elementRef ElementRef<HTMLElement> No
ngZone NgZone No
documentService TsDocumentService No

Inputs

id
Type : string

Define an ID for the component

isDisabled
Default value : false

Define if the chip should be disabled

isFocusable
Default value : true

Define if the chip allows focus

isRemovable
Default value : true

Define if the chip is removable

isSelectable
Type : boolean

Whether or not the chip is selectable.

By default a chip is selectable, and it becomes non-selectable if its parent chip collection is not selectable.

selected
Type : boolean

Define if the chip is selected

theme

Define the theme for a chip

value

Define the value of the chip

Falls back to the DOM content if not set.

Outputs

blurred
Type : EventEmitter

Emitted when the chip is blurred

clicked
Type : EventEmitter

Emitted when the chip is clicked

destroyed
Type : EventEmitter

Emitted when the chip is destroyed.

remove
Type : EventEmitter

Emitted when the chip is to be removed

selectionChange
Type : EventEmitter

Emitted when the chip is selected or deselected

Methods

Public deselect
deselect()

Deselect the chip

Returns : void
Public focus
focus()

Allows for programmatic focusing of the chip.

Returns : void
Public handleBlur
handleBlur()

Defer marking the chip as not focused until the next time the zone stabilizes.

Returns : void
Public removeChip
removeChip(event?: MouseEvent | KeyboardEvent)

Allows for programmatic removal of the chip. Called by the TsChipCollectionComponent when the DELETE or BACKSPACE keys are pressed.

Informs any listeners of the removal request. Does not remove the chip from the DOM.

Parameters :
Name Type Optional
event MouseEvent | KeyboardEvent Yes
Returns : void
Public select
select()

Select the chip

Returns : void
Public toggleSelected
toggleSelected()

Toggles the current selected state of this chip.

Returns : boolean

Properties

Protected _selected
Default value : false
Public elementRef
Type : ElementRef<HTMLElement>
Public removeIcon
Default value : faTimesCircle

Define the chip close icon

Protected uid
Default value : `ts-chip-${nextUniqueId++}`

Define the default component ID

Accessors

allowMultiple
getallowMultiple()
setallowMultiple(value: boolean)

Define if multiple chips are allowed

Used by the TsAutocompleteComponent consumer

Parameters :
Name Type Optional
value boolean No
Returns : void
id
getid()
setid(value: string)

Define an ID for the component

Parameters :
Name Type Optional
value string No
Returns : void
isSelectable
getisSelectable()
setisSelectable(value: boolean)

Whether or not the chip is selectable.

By default a chip is selectable, and it becomes non-selectable if its parent chip collection is not selectable.

Parameters :
Name Type Optional
value boolean No
Returns : void
selected
getselected()
setselected(value: boolean)

Define if the chip is selected

Parameters :
Name Type Optional
value boolean No
Returns : void
value
getvalue()
setvalue(value)

Define the value of the chip

Falls back to the DOM content if not set.

Parameters :
Name Optional
value No
Returns : void
theme
gettheme()
settheme(value)

Define the theme for a chip

Parameters :
Name Optional
value No
Returns : void
import { FocusableOption } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  EventEmitter,
  Input,
  NgZone,
  OnDestroy,
  Output,
  ViewChild,
  ViewEncapsulation,
} from '@angular/core';
import { faTimesCircle } from '@fortawesome/pro-solid-svg-icons/faTimesCircle';
import { Subject } from 'rxjs';
import { take } from 'rxjs/operators';

import {
  isUndefined,
  KEYS,
  TsDocumentService,
} from '@terminus/fe-utilities';
import { TsStyleThemeTypes } from '@terminus/ui-utilities';

// Unique ID for each instance
// @internal
let nextUniqueId = 0;

/**
 * Represents an event fired on an individual {@link TsChipComponent}
 */
export class TsChipEvent {
  constructor(
    public chip: TsChipComponent,
  ) {}
}

/**
 * Represents an event fired when clicking an individual {@link TsChipComponent}
 */
export class TsChipClickEvent {
  constructor(
    public chip: TsChipComponent,
    event: MouseEvent,
  ) {}
}

/**
 * Event object emitted by {@link TsChipComponent} when selected or deselected
 */
export class TsChipSelectionChange {
  constructor(
    // Reference to the chip that emitted the event
    public source: TsChipComponent,
    // Whether the chip that emitted the event is selected
    public selected: boolean,
  ) { }
}


/**
 * A presentational component to render a chip
 *
 * @example
 * <ts-chip
 *              id="my-id"
 *              [isDisabled]="false"
 *              [isRemovable]="true"
 *              [isSelectable]="false"
 *              theme="primary"
 *              [selected]="true"
 *              (clicked)="chipClicked($event)"
 *              (destroyed)="destroyed($event)"
 *              (blurred)="chipBlurred($event)"
 *              (remove)="removeChip($event)"
 *              (selectionChange)="selectionChange($event)"
 * ></ts-chip>
 *
 * <example-url>https://getterminus.github.io/ui-demos-release/components/chip</example-url>
 */
@Component({
  selector: 'ts-chip',
  templateUrl: './chip.component.html',
  styleUrls: ['./chip.component.scss'],
  host: {
    'class': 'ts-chip',
    '[class.ts-chip--primary]': 'theme === "primary"',
    '[class.ts-chip--accent]': 'theme === "accent"',
    '[class.ts-chip--warn]': 'theme === "warn"',
    '[attr.tabindex]': 'isDisabled ? null : -1',
    '[class.ts-chip--selected]': 'selected',
    '[class.ts-chip--disabled]': 'isDisabled',
    '[attr.disabled]': 'isDisabled || null',
    '[attr.aria-disabled]': 'isDisabled',
    '[attr.aria-selected]': 'ariaSelected',
    'role': 'option',
    '(blur)': 'handleBlur()',
    '(click)': 'handleClick($event)',
    '(focus)': 'focus()',
    '(keydown)': 'handleKeydown($event)',
  },
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  exportAs: 'tsChip',
})
export class TsChipComponent implements FocusableOption, OnDestroy {
  /**
   * Define if multiple chips are allowed
   *
   * Used by the {@link TsAutocompleteComponent} consumer
   *
   * @param value
   */
  public set allowMultiple(value: boolean) {
    this._allowMultiple = value;
  }
  public get allowMultiple(): boolean {
    return this._allowMultiple;
  }
  private _allowMultiple = false;

  /**
   * Define the default component ID
   */
  protected uid = `ts-chip-${nextUniqueId++}`;

  /**
   * Emits when the chip is focused
   *
   * @internal
   */
  public readonly onFocus = new Subject<TsChipEvent>();

  /**
   * Whether the chip has focus
   *
   * @internal
   */
  public hasFocus = false;

  /**
   * Whether the chip collection is selectable
   *
   * @internal
   */
  public chipCollectionSelectable = true;

  /**
   * Whether the chip collection allows chip removable
   *
   * @internal
   */
  public chipCollectionRemovable = true;

  /**
   * Whether the chip collection is in multi-selection mode.
   *
   * @internal
   */
  public chipCollectionMultiple = false;

  /**
   * Define the chip close icon
   */
  public removeIcon = faTimesCircle;

  /**
   * The ARIA selected applied to the chip.
   *
   * @internal
   */
  public get ariaSelected(): string | null {
    // NOTE: Remove the `aria-selected` when the chip is deselected in single-selection mode, because
    // it adds noise to NVDA users where "not selected" will be read out for each chip.
    return this.isSelectable && (this.chipCollectionMultiple || this.selected)
      ? this.selected.toString() : null;
  }

  /**
   * Access to container for chip contents
   */
  @ViewChild('content', { static: true })
  private content!: ElementRef<HTMLElement>;

  /**
   * Define an ID for the component
   *
   * @param value
   */
  @Input()
  public set id(value: string) {
    this._id = value || this.uid;
  }
  public get id(): string {
    return this._id;
  }
  private _id: string = this.uid;

  /**
   * Define if the chip should be disabled
   */
  @Input()
  public isDisabled = false;

  /**
   * Define if the chip allows focus
   */
  @Input()
  public isFocusable = true;

  /**
   * Define if the chip is removable
   */
  @Input()
  public isRemovable = true;

  /**
   * Whether or not the chip is selectable.
   *
   * By default a chip is selectable, and it becomes non-selectable if its parent chip collection is not selectable.
   *
   * @param value
   */
  @Input()
  public set isSelectable(value: boolean) {
    this._selectable = value;
  }
  public get isSelectable(): boolean {
    return this._selectable && this.chipCollectionSelectable;
  }
  private _selectable = true;

  /**
   * Define if the chip is selected
   *
   * @param value
   */
  @Input()
  public set selected(value: boolean) {
    const coercedValue = coerceBooleanProperty(value);

    if (coercedValue !== this._selected) {
      this._selected = coercedValue;
      this.dispatchSelectionChange();
    }
  }
  public get selected(): boolean {
    return this._selected;
  }
  protected _selected = false;

  /**
   * Define the value of the chip
   *
   * Falls back to the DOM content if not set.
   *
   * @param value
   */
  @Input()
  public set value(value: string | undefined) {
    this._value = value;
  }
  // NOTE: Despite the return type, this getter will only ever return a string
  public get value(): string | undefined {
    if (isUndefined(this._value)) {
      return (this.content.nativeElement.textContent || '').trim();
    }

    return this._value;
  }
  private _value;

  /**
   * Define the theme for a chip
   *
   * @param value
   */
  @Input()
  public set theme(value: TsStyleThemeTypes) {
    this._theme = value || 'primary';
  }
  public get theme(): TsStyleThemeTypes {
    return this._theme;
  }
  private _theme: TsStyleThemeTypes = 'primary';

  /**
   * Emitted when the chip is clicked
   */
  @Output()
  public readonly clicked = new EventEmitter<TsChipClickEvent>();

  /**
   * Emitted when the chip is destroyed.
   */
  @Output()
  public readonly destroyed = new EventEmitter<TsChipEvent>();

  /**
   * Emitted when the chip is blurred
   */
  @Output()
  public readonly blurred = new EventEmitter<void>();

  /**
   * Emitted when the chip is to be removed
   */
  @Output()
  public readonly remove = new EventEmitter<TsChipEvent>();

  /**
   * Emitted when the chip is selected or deselected
   */
  @Output()
  public readonly selectionChange = new EventEmitter<TsChipSelectionChange>();

  constructor(
    public elementRef: ElementRef<HTMLElement>,
    private ngZone: NgZone,
    private documentService: TsDocumentService,
  ) {}

  /**
   * Alert consumers about destruction
   */
  public ngOnDestroy(): void {
    this.destroyed.emit({ chip: this });
  }

  /**
   * Emit the 'clicked' event
   *
   * @internal
   * @param event
   */
  public click(event: MouseEvent): void {
    this.clicked.emit(new TsChipClickEvent(this, event));
  }

  /**
   * Select the chip
   */
  public select(): void {
    if (!this.selected) {
      this.selected = true;
      this.dispatchSelectionChange();
    }
  }

  /**
   * Deselect the chip
   */
  public deselect(): void {
    if (this.selected) {
      this.selected = false;
      this.dispatchSelectionChange();
    }
  }

  /**
   * Toggles the current selected state of this chip.
   */
  public toggleSelected(): boolean {
    this._selected = !this.selected;
    this.dispatchSelectionChange();
    return this.selected;
  }

  /**
   * Allows for programmatic focusing of the chip.
   */
  public focus(): void {
    // istanbul ignore else
    if (!this.hasFocus && this.isFocusable) {
      this.hasFocus = true;
      this.elementRef.nativeElement.focus();
      this.onFocus.next(new TsChipEvent(this));
    }
  }

  /**
   * Allows for programmatic removal of the chip. Called by the {@link TsChipCollectionComponent} when the DELETE or BACKSPACE keys are
   * pressed.
   *
   * Informs any listeners of the removal request. Does not remove the chip from the DOM.
   *
   * @param event
   */
  public removeChip(event?: MouseEvent | KeyboardEvent): void {
    // istanbul ignore else
    if (this.isRemovable) {
      this.remove.emit(new TsChipEvent(this));
    }

    // NOTE: We stop propagation here so clicking a chip does not bubble up to an autocomplete instance
    // istanbul ignore else
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
  }

  /**
   * Handles click events on the chip.
   *
   * @internal
   * @param event - click event
   */
  public handleClick(event: MouseEvent): void {
    const shiftKey = event.shiftKey;

    if (this.allowMultiple && this.isSelectable && shiftKey) {
      // NOTE: This is needed to disable text highlight when shift clicking chips
      this.documentService.document.onselectstart = () => false;
      this.toggleSelected();
    }

    this.clicked.emit(new TsChipClickEvent(this, event));

    if (this.isDisabled) {
      event.preventDefault();
    } else {
      event.stopPropagation();
    }
  }

  /**
   * Handle custom key presses.
   *
   * @internal
   * @param event - keyboard event
   */
  public handleKeydown(event: KeyboardEvent): void {
    const code = event.code;
    if (this.isDisabled) {
      return;
    }

    switch (code) {
      case KEYS.DELETE.code:
      case KEYS.BACKSPACE.code:
        this.removeChip(event);
        break;
      case KEYS.SPACE.code:
        // istanbul ignore else
        if (this.isSelectable) {
          this.toggleSelected();
        }
        break;
        // skip default - no default logic
    }

    // Always prevent so page navigation does not occur and to prevent space from scrolling the page since the list has focus
    event.preventDefault();
  }

  /**
   * Defer marking the chip as not focused until the next time the zone stabilizes.
   */
  public handleBlur(): void {
    this.ngZone.onStable
      .asObservable()
      .pipe(take(1))
      .subscribe(() => {
        this.ngZone.run(() => {
          this.hasFocus = false;
          this.blurred.emit();
        });
      });
  }

  /**
   * When selection change action dispatched, emit selectionChange eventEmitter.
   */
  private dispatchSelectionChange(): void {
    this.selectionChange.emit(new TsChipSelectionChange(this, this.selected));
  }
}

result-matching ""

    No results matching ""