UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

241 lines (240 loc) 8.94 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { ESLBaseElement } from '../../esl-base-element/core'; import { hasHover } from '../../esl-utils/environment/device-detector'; import { isElement } from '../../esl-utils/dom/api'; import { setAttr } from '../../esl-utils/dom/attr'; import { CSSClassUtils } from '../../esl-utils/dom/class'; import { ENTER, SPACE, ESC } from '../../esl-utils/dom/keys'; import { attr, boolAttr, prop, listen } from '../../esl-utils/decorators'; import { parseBoolean, parseNumber, toBooleanAttribute } from '../../esl-utils/misc/format'; import { ESLMediaQuery } from '../../esl-media-query/core'; /** Base class for elements that should trigger {@link ESLToggleable} instance */ export class ESLBaseTrigger extends ESLBaseElement { /** Element target to setup aria attributes */ get $a11yTarget() { return this; } /** Value to setup aria-label */ get a11yLabel() { if (!this.$target) return null; return (this.isTargetActive ? this.a11yLabelActive : this.a11yLabelInactive) || null; } /** Marker to allow track hover */ get allowHover() { return hasHover && ESLMediaQuery.for(this.trackHover).matches; } /** Marker to allow track clicks */ get allowClick() { return ESLMediaQuery.for(this.trackClick).matches; } /** Checks that the target is in active state */ get isTargetActive() { var _a; return !!((_a = this.$target) === null || _a === void 0 ? void 0 : _a.open); } connectedCallback() { super.connectedCallback(); this.initA11y(); } /** Check if the event target should be ignored */ isTargetIgnored(target) { return !isElement(target); } /** Merge params to pass to the toggleable */ mergeToggleableParams(...params) { return Object.assign({ initiator: 'trigger', activator: this }, ...params); } /** Show target toggleable with passed params */ showTarget(params = {}) { const actionParams = this.mergeToggleableParams({ delay: parseNumber(this.showDelay) }, params); if (this.$target && typeof this.$target.show === 'function') { this.$target.show(actionParams); } } /** Hide target toggleable with passed params */ hideTarget(params = {}) { const actionParams = this.mergeToggleableParams({ delay: parseNumber(this.hideDelay) }, params); if (this.$target && typeof this.$target.hide === 'function') { this.$target.hide(actionParams); } } /** Toggles target toggleable with passed params */ toggleTarget(params = {}, state = !this.active) { state ? this.showTarget(params) : this.hideTarget(params); } /** * Updates trigger state according to toggleable state * Does not produce `esl:change:active` event */ updateState() { const { active, isTargetActive } = this; this.$$attr('no-target', !this.$target); this.$$attr('active', isTargetActive); const $target = this.$$find(this.activeClassTarget); $target && CSSClassUtils.toggle($target, this.activeClass, isTargetActive); this.updateA11y(); return isTargetActive !== active; } /** Handles target primary (observed) event */ _onPrimaryEvent(event) { if (this.stopPropagation) event.stopPropagation(); switch (this.mode) { case 'show': return this.showTarget({ event }); case 'hide': return this.hideTarget({ event }); default: return this.toggleTarget({ event }); } } /** Handles ESLToggleable state change */ _onTargetStateChange(originalEvent) { if (!this.updateState()) return; const detail = { active: this.active, originalEvent }; this.$$fire(this.CHANGE_EVENT, { detail }); } /** Handles `click` event */ _onClick(event) { if (!this.allowClick || this.isTargetIgnored(event.target)) return; event.preventDefault(); this._onPrimaryEvent(event); } /** Handles `keydown` event */ _onKeydown(event) { if (![ENTER, SPACE, ESC].includes(event.key) || this.isTargetIgnored(event.target)) return; event.preventDefault(); if (event.key === ESC) { if (this.ignoreEsc) return; this.hideTarget({ event }); } else { this._onPrimaryEvent(event); } } /** Handles hover `mouseenter` event */ _onMouseEnter(event) { if (!this.allowHover) return; const delay = parseNumber(this.hoverShowDelay); this.toggleTarget({ event, delay }, this.mode !== 'hide'); event.preventDefault(); } /** Handles hover `mouseleave` event */ _onMouseLeave(event) { if (!this.allowHover) return; if (this.mode === 'show' || this.mode === 'hide') return; const delay = parseNumber(this.hoverHideDelay); this.hideTarget({ event, delay, trackHover: true }); event.preventDefault(); } /** Set initial a11y attributes. Do nothing if trigger contains actionable element */ initA11y() { if (this.$a11yTarget !== this) return; if (!this.hasAttribute('role')) this.setAttribute('role', 'button'); if (this.getAttribute('role') === 'button' && !this.hasAttribute('tabindex')) { this.setAttribute('tabindex', '0'); } } /** Update aria attributes */ updateA11y() { const target = this.$a11yTarget; if (!target) return; if (this.a11yLabelActive !== null || this.a11yLabelInactive !== null) { setAttr(target, 'aria-label', this.a11yLabel); } setAttr(target, 'aria-expanded', String(this.active)); if (this.$target && this.$target.id) { setAttr(target, 'aria-controls', this.$target.id); } } } __decorate([ prop('esl:change:active') ], ESLBaseTrigger.prototype, "CHANGE_EVENT", void 0); __decorate([ prop('esl:show esl:hide') ], ESLBaseTrigger.prototype, "OBSERVED_EVENTS", void 0); __decorate([ boolAttr({ readonly: true }) ], ESLBaseTrigger.prototype, "active", void 0); __decorate([ attr({ defaultValue: '' }) ], ESLBaseTrigger.prototype, "activeClass", void 0); __decorate([ attr({ defaultValue: '' }) ], ESLBaseTrigger.prototype, "activeClassTarget", void 0); __decorate([ attr({ defaultValue: 'all' }) ], ESLBaseTrigger.prototype, "trackClick", void 0); __decorate([ attr({ defaultValue: 'not all' }) ], ESLBaseTrigger.prototype, "trackHover", void 0); __decorate([ attr({ defaultValue: null }) ], ESLBaseTrigger.prototype, "a11yLabelActive", void 0); __decorate([ attr({ defaultValue: null }) ], ESLBaseTrigger.prototype, "a11yLabelInactive", void 0); __decorate([ attr({ defaultValue: 'none' }) ], ESLBaseTrigger.prototype, "showDelay", void 0); __decorate([ attr({ defaultValue: 'none' }) ], ESLBaseTrigger.prototype, "hideDelay", void 0); __decorate([ attr({ defaultValue: '0' }) ], ESLBaseTrigger.prototype, "hoverShowDelay", void 0); __decorate([ attr({ defaultValue: '0' }) ], ESLBaseTrigger.prototype, "hoverHideDelay", void 0); __decorate([ attr({ parser: parseBoolean, serializer: toBooleanAttribute }) ], ESLBaseTrigger.prototype, "ignoreEsc", void 0); __decorate([ attr({ defaultValue: true, parser: parseBoolean, serializer: toBooleanAttribute }) ], ESLBaseTrigger.prototype, "stopPropagation", void 0); __decorate([ prop('toggle') ], ESLBaseTrigger.prototype, "mode", void 0); __decorate([ listen({ event: (that) => that.OBSERVED_EVENTS, target: (that) => that.$target, condition: (that) => !!that.$target }) ], ESLBaseTrigger.prototype, "_onTargetStateChange", null); __decorate([ listen('click') ], ESLBaseTrigger.prototype, "_onClick", null); __decorate([ listen('keydown') ], ESLBaseTrigger.prototype, "_onKeydown", null); __decorate([ listen('mouseenter') ], ESLBaseTrigger.prototype, "_onMouseEnter", null); __decorate([ listen('mouseleave') ], ESLBaseTrigger.prototype, "_onMouseLeave", null);