@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
JavaScript
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);