UNPKG

@blox/material

Version:

Material Components for Angular

1,268 lines (1,257 loc) 374 kB
import { ɵɵdefineInjectable, Injectable, ElementRef, Directive, Renderer2, HostListener, HostBinding, forwardRef, Inject, Input, ContentChildren, EventEmitter, Optional, Self, Output, ChangeDetectorRef, ContentChild, Host, SkipSelf, SimpleChange, ɵɵinject, QueryList, NgZone, NgModule } from '@angular/core'; import { DOCUMENT, CommonModule } from '@angular/common'; import { util, MDCRippleFoundation } from '@material/ripple'; import { events, ponyfill, focusTrap } from '@material/dom'; import { NgControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subject, ReplaySubject, merge } from 'rxjs'; import { takeUntil, debounceTime } from 'rxjs/operators'; import { MDCCheckboxFoundation } from '@material/checkbox'; import { MDCChipFoundation, MDCChipSetFoundation } from '@material/chips'; import { util as util$1, cssClasses, MDCDialogFoundation } from '@material/dialog'; import { MDCDismissibleDrawerFoundation, MDCModalDrawerFoundation } from '@material/drawer'; import { strings, cssClasses as cssClasses$1, MDCListFoundation } from '@material/list'; import { MDCRadioFoundation } from '@material/radio'; import { MDCFloatingLabelFoundation } from '@material/floating-label'; import { MDCFormFieldFoundation } from '@material/form-field'; import { MDCIconButtonToggleFoundation } from '@material/icon-button'; import { MDCLinearProgressFoundation } from '@material/linear-progress'; import { strings as strings$1, cssClasses as cssClasses$3, DefaultFocusState, MDCMenuFoundation } from '@material/menu'; import { cssClasses as cssClasses$2, util as util$2, MDCMenuSurfaceFoundation, Corner } from '@material/menu-surface'; import { MDCNotchedOutlineFoundation } from '@material/notched-outline'; import { MDCLineRippleFoundation } from '@material/line-ripple'; import { strings as strings$2, cssClasses as cssClasses$4, MDCSelectFoundation } from '@material/select'; import { MDCSliderFoundation } from '@material/slider'; import { util as util$3, MDCSnackbarFoundation, numbers } from '@material/snackbar'; import { MDCSwitchFoundation } from '@material/switch'; import { MDCTabBarFoundation } from '@material/tab-bar'; import { util as util$4, MDCTabScrollerFoundation } from '@material/tab-scroller'; import { MDCTabFoundation } from '@material/tab'; import { MDCFadingTabIndicatorFoundation, MDCSlidingTabIndicatorFoundation } from '@material/tab-indicator'; import { NavigationEnd, Router, RouterLink, RouterLinkWithHref } from '@angular/router'; import { MDCTextFieldIconFoundation, MDCTextFieldHelperTextFoundation, MDCTextFieldFoundation } from '@material/textfield'; import { MDCShortTopAppBarFoundation, MDCFixedTopAppBarFoundation, MDCTopAppBarFoundation } from '@material/top-app-bar'; function asBoolean(value) { return value != null && `${value}` !== 'false'; } function asBooleanOrNull(value) { if (value == null) return value; return `${value}` !== 'false'; } function asNumberOrNull(value) { if (value == null) return value; return +value; } const unlisteners = new Map(); class MdcEventRegistry { constructor() { } listen(renderer, type, listener, ref, options) { this.listenElm(renderer, type, listener, ref.nativeElement, options); } listenElm(renderer, type, listener, el, options) { el.addEventListener(type, listener, options); const unlistener = function () { el.removeEventListener(type, listener, options); }; this.registerUnlisten(type, listener, unlistener); } registerUnlisten(type, listener, unlistener) { if (!unlisteners.has(type)) unlisteners.set(type, new WeakMap()); unlisteners.get(type).set(listener, unlistener); } unlisten(type, listener) { if (!unlisteners.has(type)) return; const unlistenerMap = unlisteners.get(type); if (!unlistenerMap.has(listener)) return; unlistenerMap.get(listener)(); unlistenerMap.delete(listener); } } MdcEventRegistry.ɵprov = ɵɵdefineInjectable({ factory: function MdcEventRegistry_Factory() { return new MdcEventRegistry(); }, token: MdcEventRegistry, providedIn: "root" }); MdcEventRegistry.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; MdcEventRegistry.ctorParameters = () => []; /** @docs-private */ class AbstractMdcRipple { constructor(_rippleElm, _renderer, _registry, doc) { this._rippleElm = _rippleElm; this._renderer = _renderer; this._registry = _registry; this.mdcRippleAdapter = { browserSupportsCssVars: () => util.supportsCssVariables(this.document.defaultView), isUnbounded: () => this._unbounded, isSurfaceActive: () => this.isRippleSurfaceActive(), isSurfaceDisabled: () => this.isRippleSurfaceDisabled(), addClass: (className) => this.addClassToRipple(className), removeClass: (className) => this.removeClassFromRipple(className), containsEventTarget: (target) => this._rippleElm.nativeElement.contains(target), registerInteractionHandler: (type, handler) => { if (this.getRippleInteractionElement()) this._registry.listenElm(this._renderer, type, handler, this.getRippleInteractionElement().nativeElement, events.applyPassive()); }, deregisterInteractionHandler: (type, handler) => { this._registry.unlisten(type, handler); }, registerDocumentInteractionHandler: (type, handler) => this._registry.listenElm(this._renderer, type, handler, this.document, events.applyPassive()), deregisterDocumentInteractionHandler: (type, handler) => this._registry.unlisten(type, handler), registerResizeHandler: (handler) => { this._registry.listenElm(this._renderer, 'resize', handler, this.document.defaultView); }, deregisterResizeHandler: (handler) => { this._registry.unlisten('resize', handler); }, updateCssVariable: (name, value) => { this.getRippleStylingElement().nativeElement.style.setProperty(name, value); }, computeBoundingRect: () => this.computeRippleBoundingRect(), getWindowPageOffset: () => ({ x: this.document.defaultView.pageXOffset, y: this.document.defaultView.pageYOffset }) }; /** @internal */ this._rippleFoundation = null; this._unbounded = false; this._rippleSurface = null; // workaround compiler bug when using ViewEngine. Type Document fails compilation this.document = doc; } /** @internal */ initRipple(unbounded = false) { if (this._rippleFoundation) throw new Error('initRipple() is called multiple times'); this._unbounded = unbounded; this._rippleFoundation = new MDCRippleFoundation(this.mdcRippleAdapter); this._rippleFoundation.init(); } /** @internal */ destroyRipple() { if (this._rippleFoundation) { this._rippleFoundation.destroy(); this._rippleFoundation = null; } } /** @internal */ reinitRipple() { if (this._rippleFoundation) { this.destroyRipple(); this.initRipple(this._unbounded); } } /** @internal */ isRippleInitialized() { return this._rippleFoundation != null; } /** @internal */ addRippleSurface(clazz, firstElement = false) { this.destroyRippleSurface(); this._rippleSurface = this._renderer.createElement('div'); this._renderer.addClass(this._rippleSurface, clazz); if (firstElement && this._rippleElm.nativeElement.children.length > 0) { const firstChild = this._rippleElm.nativeElement.children.item(0); this._renderer.insertBefore(this._rippleElm.nativeElement, this._rippleSurface, firstChild); } else this._renderer.appendChild(this._rippleElm.nativeElement, this._rippleSurface); return this._rippleSurface; } /** @internal */ destroyRippleSurface() { if (this._rippleSurface) { this._renderer.removeChild(this._rippleElm.nativeElement, this._rippleSurface); this._rippleSurface = null; } } activateRipple() { if (this._rippleFoundation) this._rippleFoundation.activate(); } deactivateRipple() { if (this._rippleFoundation) this._rippleFoundation.deactivate(); } layout() { if (this._rippleFoundation) this._rippleFoundation.layout(); } get rippleSurface() { return new ElementRef(this._rippleSurface); } getRippleInteractionElement() { return this._rippleElm; } getRippleStylingElement() { return this._rippleElm; } isRippleUnbounded() { return this._unbounded; } /** @internal */ setRippleUnbounded(value) { if (!!value !== this._unbounded) { this._unbounded = !!value; // despite what the documentation seems to indicate, you can't // just change the unbounded property of an already initialized // ripple. The initialization registers different handlers, and won't // change those registrations when you change the unbounded property. // Hence we destroy and re-init the whole thing: this.reinitRipple(); } } isRippleSurfaceActive() { let interactionElm = this.getRippleInteractionElement(); return !!interactionElm && this.isActiveElement(interactionElm.nativeElement); } isActiveElement(element) { return element == null ? false : ponyfill.matches(element, ':active'); } isRippleSurfaceDisabled() { let interactionElm = this.getRippleInteractionElement(); return !!interactionElm && !!interactionElm.nativeElement.attributes.getNamedItem('disabled'); } /** @internal */ addClassToRipple(name) { this._renderer.addClass(this.getRippleStylingElement().nativeElement, name); } /** @internal */ removeClassFromRipple(name) { this._renderer.removeClass(this.getRippleStylingElement().nativeElement, name); } computeRippleBoundingRect() { return this._rippleElm.nativeElement.getBoundingClientRect(); } /** @internal */ onFocus() { if (this._rippleFoundation) this._rippleFoundation.handleFocus(); } /** @internal */ onBlur() { if (this._rippleFoundation) this._rippleFoundation.handleBlur(); } } AbstractMdcRipple.decorators = [ { type: Directive } ]; AbstractMdcRipple.ctorParameters = () => [ { type: ElementRef }, { type: Renderer2 }, { type: MdcEventRegistry }, { type: undefined } ]; AbstractMdcRipple.propDecorators = { onFocus: [{ type: HostListener, args: ['focusin',] }], onBlur: [{ type: HostListener, args: ['focusout',] }] }; /** * Use this directive for adding an icon to an <code>mdcButton</code>. This directive can be * added to font-style icons (such as <a href="https://material.io/icons/" target="_blank">material icons</a> * from Google fonts), or with <code>svg</code> elements for svg based icons. */ class MdcButtonIconDirective { constructor() { /** @internal */ this._cls = true; /** @internal */ this._ariaHidden = true; } } MdcButtonIconDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcButtonIcon]' },] } ]; MdcButtonIconDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-button__icon',] }], _ariaHidden: [{ type: HostBinding, args: ['attr.aria-hidden',] }] }; /** * Directive for the label of an <code>mdcButton</code>. Must be a direct child * of <code>mdcButton</code>. */ class MdcButtonLabelDirective { constructor() { /** @internal */ this._cls = true; } } MdcButtonLabelDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcButtonLabel]' },] } ]; MdcButtonLabelDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-button__label',] }] }; /** * Material design button. Anchors can also be styled as buttons with this directive. * Defaults to a button that is flushed with the surface. * Use the input modifiers to alter the styling, or create your own style * based on the provided sass-mixins. * * For buttons with a trailing icon, you must put the label inside an `mdcButtonLabel` * directive. For all other buttons it is also recommnded to put the label inside * an `mdcButtonLabel`, because future version of material-components-web may make * it's use mandatory. * * A ripple (and the required DOM elements for the ripple) will be added automatically. */ class MdcButtonDirective extends AbstractMdcRipple { constructor(_elm, renderer, registry, doc) { super(_elm, renderer, registry, doc); this._elm = _elm; /** @internal */ this._cls = true; this._raised = false; this._unelevated = false; this._outlined = false; this.addRippleSurface('mdc-button__ripple'); } ngAfterContentInit() { this.initRipple(); } ngOnDestroy() { this.destroyRipple(); } /** * When this input is defined and does not have value false, the button will be elevated * upon the surface. */ get raised() { return this._raised; } set raised(val) { this._raised = asBoolean(val); } /** * When this input is defined and does not have value false, the button will be styled * flush with the surface and have a visible border. */ get outlined() { return this._outlined; } set outlined(val) { this._outlined = asBoolean(val); } /** * Set this property to a non false value for a contained button * flush with the surface. */ get unelevated() { return this._unelevated; } set unelevated(val) { this._unelevated = asBoolean(val); } } MdcButtonDirective.decorators = [ { type: Directive, args: [{ selector: 'button[mdcButton],a[mdcButton]', providers: [{ provide: AbstractMdcRipple, useExisting: forwardRef(() => MdcButtonDirective) }] },] } ]; MdcButtonDirective.ctorParameters = () => [ { type: ElementRef }, { type: Renderer2 }, { type: MdcEventRegistry }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; MdcButtonDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-button',] }], raised: [{ type: HostBinding, args: ['class.mdc-button--raised',] }, { type: Input }], outlined: [{ type: HostBinding, args: ['class.mdc-button--outlined',] }, { type: Input }], unelevated: [{ type: HostBinding, args: ['class.mdc-button--unelevated',] }, { type: Input }] }; const BUTTON_DIRECTIVES = [ MdcButtonIconDirective, MdcButtonLabelDirective, MdcButtonDirective ]; /** @docs-private */ class AbstractMdcIcon extends AbstractMdcRipple { constructor(_elm, renderer, registry, doc) { super(_elm, renderer, registry, doc); this._elm = _elm; } } AbstractMdcIcon.decorators = [ { type: Directive } ]; AbstractMdcIcon.ctorParameters = () => [ { type: ElementRef }, { type: Renderer2 }, { type: MdcEventRegistry }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; /** * Directive for an area that displays a custom background-image. See the <code>size</code> * property for the sizing of the image. * If used, this directive should be put inside the card itself (<code>MdcCardDirective</code>). * Add an <code>mdcCardMediaContent</code> as sub-element for displaying a title, text, * or icon on top of the background image. */ class MdcCardMediaDirective { constructor() { /** @internal */ this._cls = true; this._size = 'cover'; } /** @internal */ get _square() { return this._size === 'square'; } /** @internal */ get _size2() { return this._size === '16:9'; } /** * Directive to select size to which this element's background-image should * be scaled. Can be one of 'cover', '16:9', or 'square'. The default value * is 'cover'. */ get size() { return this._size; } set size(val) { this._size = val; } } MdcCardMediaDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCardMedia]', },] } ]; MdcCardMediaDirective.ctorParameters = () => []; MdcCardMediaDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-card__media',] }], _square: [{ type: HostBinding, args: ['class.mdc-card__media--square',] }], _size2: [{ type: HostBinding, args: ['class.mdc-card__media--16-9',] }], size: [{ type: Input }] }; /** * Directive for displaying text on top of a <code>mdcCardMedia</code> element. * This directive should be used as child element of the <code>mdcCardMedia</code>, and * creates an absolutely positioned box the same size as the media area. */ class MdcCardMediaContentDirective { constructor() { /** @internal */ this._cls = true; } } MdcCardMediaContentDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCardMediaContent]' },] } ]; MdcCardMediaContentDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-card__media-content',] }] }; /** * Directive for displaying the button card actions. Composed of one or more * card actions, which must be buttons that have the <code>MdcButtonDirective</code>. * This directive should be placed inside an <code>MdcCardActionsDirective</code>. */ class MdcCardActionButtonsDirective { constructor() { /** @internal */ this._cls = true; } } MdcCardActionButtonsDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCardActionButtons]' },] } ]; MdcCardActionButtonsDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-card__action-buttons',] }] }; /** * Directive for displaying the icon card actions. Composed of one or more * card actions, which must be icons (for instance <code>mdcIconButton</code>. * This directive should be placed inside an <code>MdcCardActionsDirective</code>. */ class MdcCardActionIconsDirective { constructor() { /** @internal */ this._cls = true; } } MdcCardActionIconsDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCardActionIcons]' },] } ]; MdcCardActionIconsDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-card__action-icons',] }] }; /** * Directive for showing the different actions a user can take. Use * <code>mdcButton</code>, or <code>mdcIconButton</code> as child elements. * If you want to use both buttons and icons in the same row, wrap them in * <code>mdcCardActionButtons</code>, and <code>mdcCardActionIcons</code> directives. */ class MdcCardActionsDirective { constructor(renderer) { this.renderer = renderer; /** @internal */ this._cls = true; this._initialized = false; this._fullBleed = false; } ngAfterContentInit() { this._initialized = true; this._initButtons(); this._initIcons(); this._buttons.changes.subscribe(() => { this._initButtons(); }); this._icons.changes.subscribe(() => { this._initIcons(); }); } _initButtons() { if (this._initialized) this._buttons.forEach(btn => { this.renderer.addClass(btn._elm.nativeElement, 'mdc-card__action'); this.renderer.addClass(btn._elm.nativeElement, 'mdc-card__action--button'); }); } _initIcons() { if (this._initialized) this._icons.forEach(icon => { this.renderer.addClass(icon._elm.nativeElement, 'mdc-card__action'); this.renderer.addClass(icon._elm.nativeElement, 'mdc-card__action--icon'); }); } /** * When this input is defined and does not have value false, the contained * button takes up the entire width of the action row. This should be used only when * there is a single button contained in the directive. */ get fullBleed() { return this._fullBleed; } set fullBleed(val) { this._fullBleed = asBoolean(val); } } MdcCardActionsDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCardActions]', },] } ]; MdcCardActionsDirective.ctorParameters = () => [ { type: Renderer2 } ]; MdcCardActionsDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-card__actions',] }], _buttons: [{ type: ContentChildren, args: [MdcButtonDirective, { descendants: true },] }], _icons: [{ type: ContentChildren, args: [AbstractMdcIcon, { descendants: true },] }], fullBleed: [{ type: HostBinding, args: ['class.mdc-card__actions--full-bleed',] }, { type: Input }] }; /** * Directive for the main tappable area of the card (so should be a child of <code>mdcCard</code>). * Typically contains most (or all) card content except <code>mdcCardActions</code>. * Only applicable to cards that have a primary action that the main surface should trigger. */ class MdcCardPrimaryActionDirective extends AbstractMdcRipple { constructor(elm, renderer, registry, doc) { super(elm, renderer, registry, doc); this.elm = elm; /** @internal */ this._cls = true; } ngAfterContentInit() { if (!this.elm.nativeElement.hasAttribute('tabindex')) // unless overridden, make the action tabbable: this.elm.nativeElement.tabIndex = 0; this.initRipple(); } ngOnDestroy() { this.destroyRipple(); } } MdcCardPrimaryActionDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCardPrimaryAction]', },] } ]; MdcCardPrimaryActionDirective.ctorParameters = () => [ { type: ElementRef }, { type: Renderer2 }, { type: MdcEventRegistry }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; MdcCardPrimaryActionDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-card__primary-action',] }] }; /** * Directive for a material designed card. The card can be composed with the following directives: * <code>MdcCardMediaDirective</code>, <code>MdcCardActionsDirective</code> */ class MdcCardDirective { constructor() { /** @internal */ this._cls = true; this._outlined = false; } /** * When this input is set to a value other than false, the card will have a * hairline stroke instead of a shadow. */ get outlined() { return this._outlined; } set outlined(val) { this._outlined = asBoolean(val); } } MdcCardDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCard]' },] } ]; MdcCardDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-card',] }], outlined: [{ type: HostBinding, args: ['class.mdc-card--outlined',] }, { type: Input }] }; const CARD_DIRECTIVES = [ MdcCardMediaDirective, MdcCardMediaContentDirective, MdcCardActionButtonsDirective, MdcCardActionIconsDirective, MdcCardActionsDirective, MdcCardPrimaryActionDirective, MdcCardDirective ]; /** @docs-private */ class AbstractMdcInput { } /** * Directive for the input element of an <code>MdcCheckboxDirective</code>. */ class MdcCheckboxInputDirective extends AbstractMdcInput { constructor(_elm, _cntr) { super(); this._elm = _elm; this._cntr = _cntr; /** @internal */ this._cls = true; this.onDestroy$ = new Subject(); this._id = null; this._disabled = false; this._checked = false; this._indeterminate = false; /** @internal */ this._checkedChange = new EventEmitter(); /** @internal */ this._indeterminateChange = new EventEmitter(); /** @internal */ this._disabledChange = new EventEmitter(); } ngOnInit() { var _a; (_a = this._cntr) === null || _a === void 0 ? void 0 : _a.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((value) => { this.updateValue(value, true); }); } ngOnDestroy() { this.onDestroy$.next(); this.onDestroy$.complete(); } /** @docs-private */ get id() { return this._id; } set id(value) { this._id = value; } /** @docs-private */ get disabled() { return this._cntr ? !!this._cntr.disabled : this._disabled; } set disabled(value) { const newVal = asBoolean(value); if (newVal != this._disabled) { this._disabled = asBoolean(newVal); this._disabledChange.emit(newVal); } } /** @docs-private */ get checked() { return this._checked; } set checked(value) { this.updateValue(value, false); } updateValue(value, fromControl) { // When the 'checked' property is the source of the change, we want to coerce boolean // values using asBoolean, so that initializing with an attribute with no value works // as expected. // When the NgControl is the source of the change we don't want that. The value should // be interpreted like NgControl/NgForms handles non-boolean values when binding. const newVal = fromControl ? !!value : asBoolean(value); if (newVal !== this._checked) { this._checked = newVal; this._checkedChange.emit(newVal); } if (!fromControl && this._cntr && newVal !== this._cntr.value) { this._cntr.control.setValue(newVal); } } /** @docs-private */ get indeterminate() { return this._indeterminate; } set indeterminate(value) { const newVal = asBoolean(value); if (newVal !== this._indeterminate) { this._indeterminate = newVal; Promise.resolve().then(() => this._indeterminateChange.emit(newVal)); } } // We listen to click-event instead of change-event, because IE doesn't fire the // change-event when an indeterminate checkbox is clicked. There's no need to // also listen to change-events. _onChange() { // only update the checked state from click if there is no control for which we already // listen to value changes: if (!this._cntr) this.checked = this._elm.nativeElement.checked; this.indeterminate = this._elm.nativeElement.indeterminate; } } MdcCheckboxInputDirective.decorators = [ { type: Directive, args: [{ selector: 'input[mdcCheckboxInput][type=checkbox]', providers: [{ provide: AbstractMdcInput, useExisting: forwardRef(() => MdcCheckboxInputDirective) }] },] } ]; MdcCheckboxInputDirective.ctorParameters = () => [ { type: ElementRef }, { type: NgControl, decorators: [{ type: Optional }, { type: Self }] } ]; MdcCheckboxInputDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-checkbox__native-control',] }], _checkedChange: [{ type: Output }], _indeterminateChange: [{ type: Output }], _disabledChange: [{ type: Output }], id: [{ type: HostBinding }, { type: Input }], disabled: [{ type: HostBinding }, { type: Input }], checked: [{ type: HostBinding }, { type: Input }], indeterminate: [{ type: HostBinding }, { type: Input }], _onChange: [{ type: HostListener, args: ['click',] }] }; /** * Directive for creating a Material Design checkbox. The checkbox is driven by an * underlying native checkbox input, which must use the <code>MdcCheckboxInputDirective</code> * directive. * The current implementation will add all other required DOM elements (such as the * background and ripple). * Future implementations will also support supplying (customized) background * elements. * * This directive can be used together with an <code>mdcFormField</code> to * easily position checkboxes and their labels, see * <a href="/components/form-field">mdcFormField</a>. */ class MdcCheckboxDirective extends AbstractMdcRipple { constructor(renderer, root, registry, doc) { super(root, renderer, registry, doc); this.root = root; /** @internal */ this._cls = true; this.onDestroy$ = new Subject(); this.onInputChange$ = new Subject(); this.mdcAdapter = { addClass: (className) => this._renderer.addClass(this.root.nativeElement, className), removeClass: (className) => this._renderer.removeClass(this.root.nativeElement, className), setNativeControlAttr: (attr, value) => this._renderer.setAttribute(this._input._elm.nativeElement, attr, value), removeNativeControlAttr: (attr) => this._renderer.removeAttribute(this._input._elm.nativeElement, attr), forceLayout: () => this.root.nativeElement.offsetWidth, isAttachedToDOM: () => !!this._input, hasNativeControl: () => !!this._input, isChecked: () => this._input._elm.nativeElement.checked, isIndeterminate: () => this._input._elm.nativeElement.indeterminate, setNativeControlDisabled: (disabled) => this._input.disabled = disabled }; /** @internal */ this._foundation = null; this.addRippleSurface('mdc-checkbox__ripple'); } ngAfterContentInit() { MdcCheckboxDirective.addBackground(this._rippleElm, this._renderer); this.initRipple(true); if (this._input) { this._foundation = new MDCCheckboxFoundation(this.mdcAdapter); this._foundation.init(); } this._inputs.changes.pipe(takeUntil(this.onDestroy$)).subscribe(() => { this.reinitRipple(); if (this._foundation) this._foundation.destroy(); if (this._input) { this._foundation = new MDCCheckboxFoundation(this.mdcAdapter); this._foundation.init(); } else this._foundation = null; this.subscribeInputChanges(); }); this.subscribeInputChanges(); } ngOnDestroy() { this.onInputChange$.next(); this.onInputChange$.complete(); this.onDestroy$.next(); this.onDestroy$.complete(); if (this._foundation) { this._foundation.destroy(); this._foundation = null; } this.destroyRipple(); } subscribeInputChanges() { var _a, _b, _c; this.onInputChange$.next(); (_a = this._input) === null || _a === void 0 ? void 0 : _a._indeterminateChange.asObservable().pipe(takeUntil(this.onInputChange$)).subscribe(() => { var _a; return (_a = this._foundation) === null || _a === void 0 ? void 0 : _a.handleChange(); }); (_b = this._input) === null || _b === void 0 ? void 0 : _b._checkedChange.asObservable().pipe(takeUntil(this.onInputChange$)).subscribe(() => { var _a; return (_a = this._foundation) === null || _a === void 0 ? void 0 : _a.handleChange(); }); (_c = this._input) === null || _c === void 0 ? void 0 : _c._disabledChange.asObservable().pipe(takeUntil(this.onInputChange$)).subscribe(val => { var _a; return (_a = this._foundation) === null || _a === void 0 ? void 0 : _a.setDisabled(val); }); } static addBackground(elm, renderer) { let path = renderer.createElement('path', 'svg'); renderer.addClass(path, 'mdc-checkbox__checkmark-path'); renderer.setAttribute(path, 'fill', 'none'); renderer.setAttribute(path, 'd', 'M1.73,12.91 8.1,19.28 22.79,4.59'); let svg = renderer.createElement('svg', 'svg'); renderer.appendChild(svg, path); renderer.addClass(svg, 'mdc-checkbox__checkmark'); renderer.setAttribute(svg, 'viewBox', '0 0 24 24'); let mixedmark = renderer.createElement('div'); renderer.addClass(mixedmark, 'mdc-checkbox__mixedmark'); let bg = renderer.createElement('div'); renderer.appendChild(bg, svg); renderer.appendChild(bg, mixedmark); renderer.addClass(bg, 'mdc-checkbox__background'); renderer.appendChild(elm.nativeElement, bg); } /** @internal */ getRippleInteractionElement() { var _a; return (_a = this._input) === null || _a === void 0 ? void 0 : _a._elm; } /** @internal */ onAnimationEnd() { var _a; (_a = this._foundation) === null || _a === void 0 ? void 0 : _a.handleAnimationEnd(); } /** @internal */ get _input() { return this._inputs && this._inputs.length > 0 ? this._inputs.first : null; } } MdcCheckboxDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcCheckbox]' },] } ]; MdcCheckboxDirective.ctorParameters = () => [ { type: Renderer2 }, { type: ElementRef }, { type: MdcEventRegistry }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; MdcCheckboxDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-checkbox',] }], _inputs: [{ type: ContentChildren, args: [MdcCheckboxInputDirective,] }], onAnimationEnd: [{ type: HostListener, args: ['animationend',] }] }; const CHECKBOX_DIRECTIVES = [ MdcCheckboxInputDirective, MdcCheckboxDirective ]; /** * @license * Copyright 2020 Google Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * Priorities for the announce function */ var AnnouncerPriority; (function (AnnouncerPriority) { AnnouncerPriority["POLITE"] = "polite"; AnnouncerPriority["ASSERTIVE"] = "assertive"; })(AnnouncerPriority || (AnnouncerPriority = {})); /** * Data attribute added to live region element. */ const DATA_MDC_DOM_ANNOUNCE = 'data-mdc-dom-announce'; /** * Announces the given message with optional priority, defaulting to "polite" */ function announce(message, priority) { Announcer.getInstance().say(message, priority); } class Announcer { // Constructor made private to ensure only the singleton is used constructor() { this.liveRegions = new Map(); } static getInstance() { if (!Announcer.instance) { Announcer.instance = new Announcer(); } return Announcer.instance; } say(message, priority = AnnouncerPriority.POLITE) { const liveRegion = this.getLiveRegion(priority); // Reset the region to pick up the message, even if the message is the // exact same as before. liveRegion.textContent = ''; // Timeout is necessary for screen readers like NVDA and VoiceOver. setTimeout(() => { liveRegion.textContent = message; document.addEventListener('click', clearLiveRegion); }, 1); function clearLiveRegion() { liveRegion.textContent = ''; document.removeEventListener('click', clearLiveRegion); } } getLiveRegion(priority) { const existingLiveRegion = this.liveRegions.get(priority); if (existingLiveRegion && document.body.contains(existingLiveRegion)) { return existingLiveRegion; } const liveRegion = this.createLiveRegion(priority); this.liveRegions.set(priority, liveRegion); return liveRegion; } createLiveRegion(priority) { const el = document.createElement('div'); el.style.position = 'absolute'; el.style.top = '-9999px'; el.style.left = '-9999px'; el.style.height = '1px'; el.style.overflow = 'hidden'; el.setAttribute('aria-atomic', 'true'); el.setAttribute('aria-live', priority); el.setAttribute(DATA_MDC_DOM_ANNOUNCE, 'true'); document.body.appendChild(el); return el; } } var ChipEventSource; (function (ChipEventSource) { ChipEventSource[ChipEventSource["primary"] = 0] = "primary"; ChipEventSource[ChipEventSource["trailing"] = 1] = "trailing"; ChipEventSource[ChipEventSource["none"] = 2] = "none"; })(ChipEventSource || (ChipEventSource = {})); ; /** * Directive for the (optional) leading or trailing icon of an `mdcChip`. * Depending on the position within the `mdcChip` the icon will determine * whether it is a leading or trailing icon. * Trailing icons must implement the functionality to remove the chip from the set, and * must only be added to input chips (`mdcChipSet="input"`). Chips with a trailing * icon must implement the `remove` event. Trailing icons should be wrapped * inside an `mdcChipCell`. */ class MdcChipIconDirective { constructor(_elm, _rndr, _cdRef) { this._elm = _elm; this._rndr = _rndr; this._cdRef = _cdRef; /** @internal */ this._cls = true; /** @internal */ this._leading = false; /** * Event emitted for trailing icon user interactions. Please note that chip removal should be * handled by the `remove` output of the chip, *not* by a handler for this output. */ this.interact = new EventEmitter(); /** @internal */ this._trailing = false; this._originalTabindex = null; this.__tabindex = -1; this.__role = null; /** @internal */ this._chip = null; this.__role = _elm.nativeElement.getAttribute('role'); let tabIndex = _elm.nativeElement.getAttribute('tabindex'); if (tabIndex) { this._originalTabindex = +tabIndex; this.__tabindex = +tabIndex; } } ngOnDestroy() { this._chip = null; } /** @internal */ get _tabindex() { return this._trailing ? this.__tabindex : this._originalTabindex; } /** @internal */ set _tabindex(val) { this.__tabindex = val == null ? -1 : val; } /** @internal */ get _role() { if (this.__role) return this.__role; return this._trailing ? 'button' : null; } /** @internal */ _handleClick(event) { var _a; if (this._chip && this._trailing) (_a = this._chip._foundation) === null || _a === void 0 ? void 0 : _a.handleTrailingIconInteraction(event); } /** @internal */ _handleInteraction(event) { var _a; if (this._chip && this._trailing) (_a = this._chip._foundation) === null || _a === void 0 ? void 0 : _a.handleTrailingIconInteraction(event); } } MdcChipIconDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcChipIcon]' },] } ]; MdcChipIconDirective.ctorParameters = () => [ { type: ElementRef }, { type: Renderer2 }, { type: ChangeDetectorRef } ]; MdcChipIconDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-chip__icon',] }], _leading: [{ type: HostBinding, args: ['class.mdc-chip__icon--leading',] }], interact: [{ type: Output }], _trailing: [{ type: HostBinding, args: ['class.mdc-chip__icon--trailing',] }], _tabindex: [{ type: HostBinding, args: ['attr.tabindex',] }], _role: [{ type: HostBinding, args: ['attr.role',] }], _handleClick: [{ type: HostListener, args: ['click', ['$event'],] }], _handleInteraction: [{ type: HostListener, args: ['keydown', ['$event'],] }] }; /** * Directive for the text of an `mdcChip`. Must be contained in an `mdcChipPrimaryAction` * directive. */ class MdcChipTextDirective { constructor(_elm) { this._elm = _elm; this._cls = true; } } MdcChipTextDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcChipText]' },] } ]; MdcChipTextDirective.ctorParameters = () => [ { type: ElementRef } ]; MdcChipTextDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-chip__text',] }] }; /** * Directive for the primary action element of a chip. The `mdcChipPrimaryAction` must * contain the `mdcChipText` directive, and be contained by an `mdcChipCell` directive. */ class MdcChipPrimaryActionDirective { constructor(_elm) { this._elm = _elm; /** @internal */ this._cls = true; this.__tabindex = -1; /** @internal */ this.__role = 'button'; this.__tabindex = +(this._elm.nativeElement.getAttribute('tabindex') || -1); } /** @internal */ get _role() { return this.__role; } /** @internal */ get _tabindex() { return this.__tabindex; } /** @internal */ set _tabindex(val) { this.__tabindex = val; } } MdcChipPrimaryActionDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcChipPrimaryAction]' },] } ]; MdcChipPrimaryActionDirective.ctorParameters = () => [ { type: ElementRef } ]; MdcChipPrimaryActionDirective.propDecorators = { _cls: [{ type: HostBinding, args: ['class.mdc-chip__primary-action',] }], _role: [{ type: HostBinding, args: ['attr.role',] }], _tabindex: [{ type: HostBinding, args: ['attr.tabindex',] }] }; /** * Directive for the main content of a chip, or for an optional trailing * action on `input` chips. This directive must contain an * `mdcChipPrimaryActione` or an `mdcChipIcon` (when used for the trailing action). * An `mdcChipCell` must always be the direct child of an `mdcChip`. */ class MdcChipCellDirective { constructor(_elm) { this._elm = _elm; /** @internal */ this._role = 'gridcell'; } } MdcChipCellDirective.decorators = [ { type: Directive, args: [{ selector: '[mdcChipCell]' },] } ]; MdcChipCellDirective.ctorParameters = () => [ { type: ElementRef } ]; MdcChipCellDirective.propDecorators = { _role: [{ type: HostBinding, args: ['attr.role',] }] }; /** * Directive for a chip. Chips must be child elements of an `mdcChipSet`, * and must contain an `mdcChipCell`, and may additionally contain an `mdcChipIcon` for * the leading icon. An optional trailing icon must be wrapped in a second `mdcChipCell`. */ class MdcChipDirective extends AbstractMdcRipple { constructor(_elm, rndr, registry, doc) { super(_elm, rndr, registry, doc); this._elm = _elm; /** @internal */ this._cls = true; /** @internal */ this._role = 'row'; this.initialized = false; this.selectedMem = false; this.removableMem = true; this._value = null; /** * Event emitted for user interaction with the chip. */ this.interact = new EventEmitter(); /** * Event emitted when the user has removed (by clicking the trailing icon) the chip. * This event must be implemented when the `removable` property is set, and the chip * has a trailing icon. The implementation must remove the chip from the set. * Without such implementation the directive will animate the chip out of vision, * but will not remove the chip from the DOM. */ this.remove = new EventEmitter(); /** * Event emitted when a navigation event has occured. */ this.navigation = new EventEmitter(); /** * Event emitted when the chip changes from not-selected to selected state or vice versa * (for filter and choice chips). */ this.selectedChange = new EventEmitter(); // Like selectedChange, but only the events that should go to the chipset (i.e. not including the ones initiated by the chipset) /** @internal */ this._selectedForChipSet = new EventEmitter(); /** @internal */ this._notifyRemoval = new EventEmitter(); /** @internal */ this._set = null; this._checkmark = null; this._leadingIcon = null; this._trailingIcon = null; this._adapter = { addClass: (className) => { const hasClass = this._elm.nativeElement.classList.contains(className); this._renderer.addClass(this._elm.nativeElement, className); if (!hasClass && className === 'mdc-chip--selected') { this.selectedMem = true; this.selectedChange.emit(true); } }, removeClass: (className) => { const hasClass = this._elm.nativeElement.classList.contains(className); this._renderer.removeClass(this._elm.nativeElement, className); if (hasClass && className === 'mdc-chip--selected') { this.selectedMem = false; this.selectedChange.emit(false); } }, hasClass: (className) => this._elm.nativeElement.classList.contains(className), addClassToLeadingIcon: (className) => this._leadingIcon && this._renderer.addClass(this._leadingIcon._elm.nativeElement, className), removeClassFromLeadingIcon: (className) => this._leadingIcon && this._renderer.removeClass(this._leadingIcon._elm.nativeElement, className), eventTargetHasClass: (target, className) => !!target && target.classList.contains(className), getAttribute: (attr) => this._elm.nativeElement.getAttribute(attr), notifyInteraction: () => this.interact.emit(), notifySelection: (selected, chipSetShouldIgnore) => { if (!chipSetShouldIgnore) this._selectedForChipSet.emit(selected); }, notifyTrailingIconInteraction: () => this._trailingIcon.interact.emit(), notifyRemoval: (removedAnnouncement) => this._notifyRemoval.emit({ removedAnnouncement }), notifyNavigation: (key, source) => this.navigation.emit({ key, source: source }), getComputedStyleValue: (propertyName) => getComputedStyle(this._elm.nativeElement).getPropertyValue(propertyName), setStyleProperty: (style, value) => this._renderer.setStyle(this._elm.nativeElement, style, value), hasLeadingIcon: () => !!this._leadingIcon, getRootBoundingClientRect: () => this._elm.nativeElement.getBoundingClientRect(), getCheckmarkBoundingClientRect: () => { var _a; return ((_a = this._checkmark) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) || null; }, setPrimaryActionAttr: (attr, value) => this._primaryAction && this._renderer.setAttribute(this._primaryAction._elm.nativeElement, attr, value), focusPrimaryAction: () => { var _a; return (_a = this._primaryAction) === null || _a === void 0 ? void 0 : _a._elm.nativeElement.focus(); }, hasTrailingAction: () => !!this._trailingIcon, setTrailingActionAttr: (attr, value) => this._trailingIcon && this._renderer.setAttribute(this._trailingIcon._elm.nativeElement, attr, value), focusTrailingAction: () => { var _a; return (_a = this._trailingIcon) === null || _a === void 0 ? void 0 : _a._elm.nativeElement.focus(); }, isRTL: () => getComputedStyle(this._elm.nativeElement).getPropertyValue('direction') === 'rtl' }; /** @internal */ this._foundation = new MDCChipFoundation(this._adapter); this._uniqueValue = `mdc-chip-${MdcChipDirective.nextValue++}`; } ngAfterContentInit() { this.initActions(); this.initIcons(); this._icons.changes.subscribe(() => this._reInit()); this._texts.changes.subscribe(() => this._reInit()); this._primaryActions.changes.subscribe(() => this._reInit()); this._chipCells.changes.subscribe(() => this._reInit()); this.addRippleSurface('mdc-chip__ripple'); this.initCheckMark(); this.initRipple(); this._foundation.init(); if (this.selectedMem) // triggers setting the foundation selected state (and possibly for other [choice] chips too): this.selected = this.selectedMem; if (!this.removableMem) this._foundation.setShouldRemoveOnTrailingIconClick(this.removableMem); this.initialized = true; }