UNPKG

@exadel/esl

Version:

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

256 lines (255 loc) 10.7 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; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var ESLCarouselAutoplayMixin_1; import { ExportNs } from '../../../esl-utils/environment/export-ns'; import { listen, memoize } from '../../../esl-utils/decorators'; import { parseTime } from '../../../esl-utils/misc/format'; import { CSSClassUtils } from '../../../esl-utils/dom/class'; import { ESLMediaRuleList } from '../../../esl-media-query/core'; import { ESLIntersectionTarget, ESLIntersectionEvent } from '../../../esl-event-listener/core'; import { ESLCarouselPlugin } from '../esl-carousel.plugin'; import { ESLCarouselSlideEvent } from '../../core/esl-carousel.events'; import { ESLCarouselAutoplayEvent } from './esl-carousel.autoplay.event'; /** * Autoplay plugin mixin for {@link ESLCarousel}. * Schedules slide navigation by timeout while allowed by viewport, interaction and config constraints. */ let ESLCarouselAutoplayMixin = ESLCarouselAutoplayMixin_1 = class ESLCarouselAutoplayMixin extends ESLCarouselPlugin { constructor() { super(...arguments); /** User suspension flag (inverse of manual enable state) */ this._suspended = false; /** Last known viewport intersection state */ this._inViewport = false; /** Active cycle timeout id (null if no cycle scheduled) */ this._timeout = null; } /** True when a navigation timeout is currently scheduled */ get active() { return !!this._timeout; } /** * Effective enabled state. * True when user did not suspend and global duration is non-negative / valid. * (duration = 0 keeps plugin enabled but suppresses default scheduling unless slide overrides). */ get enabled() { return !this._suspended && this.duration >= 0; } /** Manually enable / disable (suspend) autoplay */ set enabled(value) { this._suspended = !value; this.update(); } /** Global base duration in ms (raw config parsed). Negative / NaN considered as disabled */ get duration() { return parseTime(this.config.duration); } /** * Effective current slide duration. * Tries active slide attribute; falls back to global duration. * Non-positive result pauses cycle for the slide only (unless global invalid disables plugin). */ get effectiveDuration() { const { $activeSlide } = this.$host; if (!$activeSlide) return this.duration; const value = $activeSlide.getAttribute(ESLCarouselAutoplayMixin_1.SLIDE_DURATION_ATTRIBUTE); if (!value) return this.duration; const parsed = ESLMediaRuleList.parse(value, this.$host.media, parseTime); if (typeof parsed.value === 'undefined' || isNaN(parsed.value)) return this.duration; return parsed.value; } /** Control elements collection (memoized) */ get $controls() { const sel = this.config.control; return sel ? this.$$findAll(sel) : []; } /** Interaction scope elements (memoized) */ get $interactionScope() { const sel = this.config.interactionScope; return sel ? this.$$findAll(sel) : [this.$host]; } /** True if active slide contains any blocking items */ get hasActiveBlockingItems() { const { blockerSelector } = this.config; return !!blockerSelector && !!this.$$find(blockerSelector); } /** True if any scope element is hovered */ get hovered() { return this.$interactionScope.some(($el) => $el.matches('*:hover')); } /** True if keyboard-visible focus is within scope */ get focused() { var _a; if (!((_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.matches('*:focus-visible'))) return false; return this.$interactionScope.some(($el) => $el.matches('*:focus-within')); } /** Runtime allowance: enabled + in viewport + no blocking interaction (if tracked) */ get allowed() { if (!this.enabled) return false; if (!this._inViewport) return false; if (this.hasActiveBlockingItems) return false; if (this.config.trackInteraction) return !this.hovered && !this.focused; return true; } /** Init lifecycle hook */ onInit() { this.update(); } /** React to config changes (rebuild memoized queries, re-evaluate state) */ onConfigChange() { super.onConfigChange(); memoize.clear(this, ['$controls', '$interactionScope']); this.$$off(this._onBlockingEvent); this.update(); } /** Suspend & cleanup on disconnect */ disconnectedCallback() { this._suspended = true; this.updateMarkers(); this.refresh(); super.disconnectedCallback(); } /** Update classes and listeners, then re-validate cycle */ update() { this.updateMarkers(); this.$$on({ group: 'state' }); this.refresh(); } /** Update UI markers (CSS classes) reflecting effective enable state */ updateMarkers() { const { $container } = this.$host; CSSClassUtils.toggle(this.$controls, this.config.controlCls, this.enabled); $container && CSSClassUtils.toggle($container, this.config.containerCls, this.enabled); } /** Re-evaluate cycle scheduling (optionally force restart) */ refresh(restart = false) { if (!this.allowed || restart) { this._timeout && window.clearTimeout(this._timeout); this._timeout = null; ESLCarouselAutoplayEvent.dispatch(this, 0); } if (this.allowed && !this.active) this._onCycle(); } /** Internal cycle handler (exec step then schedule next) */ _onCycle(exec) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; this._timeout && window.clearTimeout(this._timeout); this._timeout = null; if (exec) yield ((_a = this.$host) === null || _a === void 0 ? void 0 : _a.goTo(this.config.command, { activator: this }).catch(console.debug)); if (!this.allowed || this.active) return; const { effectiveDuration } = this; if (effectiveDuration > 0 && ((_b = this.$host) === null || _b === void 0 ? void 0 : _b.canNavigate(this.config.command))) { this._timeout = window.setTimeout(() => this._onCycle(true), effectiveDuration); } ESLCarouselAutoplayEvent.dispatch(this, effectiveDuration); }); } /** Viewport intersection listener controlling runtime allowance */ _onIntersection(e) { this._inViewport = e.isIntersecting; this.refresh(); } /** Hover/focus interaction listener toggling pause state */ _onInteract() { this.refresh(); } /** Slide change listener (forces cycle restart) */ _onSlideChange() { if (this.enabled) this.refresh(true); } /** Control click handler toggling manual enabled state */ _onToggle(e) { this.enabled = !this.enabled; e.preventDefault(); } /** Subscribe to events that block autoplay */ _onBlockingEvent() { this.refresh(); } }; ESLCarouselAutoplayMixin.is = 'esl-carousel-autoplay'; ESLCarouselAutoplayMixin.DEFAULT_CONFIG = { duration: 10000, command: 'slide:next', intersection: 0.25, trackInteraction: true, blockerSelector: '::find(esl-share[active], esl-note[active])', watchEvents: 'esl:change:active' }; ESLCarouselAutoplayMixin.DEFAULT_CONFIG_KEY = 'duration'; /** Per-slide override attribute name for timeout */ ESLCarouselAutoplayMixin.SLIDE_DURATION_ATTRIBUTE = ESLCarouselAutoplayMixin_1.is + '-timeout'; __decorate([ memoize() ], ESLCarouselAutoplayMixin.prototype, "$controls", null); __decorate([ memoize() ], ESLCarouselAutoplayMixin.prototype, "$interactionScope", null); __decorate([ listen({ inherit: true }) ], ESLCarouselAutoplayMixin.prototype, "onConfigChange", null); __decorate([ listen({ group: 'state', condition: ($this) => $this.enabled, event: ESLIntersectionEvent.TYPE, target: ($this) => ESLIntersectionTarget.for($this.$host, { threshold: [$this.config.intersection] }) }) ], ESLCarouselAutoplayMixin.prototype, "_onIntersection", null); __decorate([ listen({ group: 'state', event: 'mouseleave mouseenter focusin focusout', target: ($this) => $this.$interactionScope, condition: ($this) => $this.enabled && $this.config.trackInteraction }) ], ESLCarouselAutoplayMixin.prototype, "_onInteract", null); __decorate([ listen(ESLCarouselSlideEvent.AFTER) ], ESLCarouselAutoplayMixin.prototype, "_onSlideChange", null); __decorate([ listen({ event: 'click', target: ($this) => $this.$controls, condition: ($this) => !!$this.$controls.length }) ], ESLCarouselAutoplayMixin.prototype, "_onToggle", null); __decorate([ listen({ group: 'state', event: ($this) => $this.config.watchEvents, target: ($this) => $this.$interactionScope, condition: ($this) => $this.enabled }) ], ESLCarouselAutoplayMixin.prototype, "_onBlockingEvent", null); ESLCarouselAutoplayMixin = ESLCarouselAutoplayMixin_1 = __decorate([ ExportNs('Carousel.Autoplay') ], ESLCarouselAutoplayMixin); export { ESLCarouselAutoplayMixin };