UNPKG

@exadel/esl

Version:

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

159 lines (158 loc) 6.39 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 { ExportNs } from '../../esl-utils/environment/export-ns'; import { ESLMixinElement } from '../../esl-mixin-element/core'; import { attr, boolAttr, safe, listen, memoize, prop } from '../../esl-utils/decorators'; import { isElement, isRelativeNode } from '../../esl-utils/dom'; import { ESLResizeObserverTarget } from '../../esl-event-listener/core'; import { ESLMediaRuleList } from '../../esl-media-query/core'; function roundLineHeight(value, precision = 0.1) { return Math.round(value / precision) * precision; } /** * ESLLineClamp mixin element * @author Dmytro Shovchko * * ESLLineClamp is a custom mixin element that applies line clamping to its content based on the specified number of lines. * It uses CSS custom properties to control the number of lines and applies truncation when necessary. * The element also handles focus management and scroll behavior to ensure a smooth user experience. */ let ESLLineClamp = class ESLLineClamp extends ESLMixinElement { constructor() { super(...arguments); this.lastHeight = 0; } /** * Returns parsed media rule list for line clamping configuration * @returns ESLMediaRuleList instance for managing responsive line limits */ get linesQuery() { return ESLMediaRuleList.parse(this.lines, this.mask); } get lineHeight() { const styles = getComputedStyle(this.$host); const lineHeight = styles.lineHeight; if (lineHeight !== 'normal') return parseFloat(lineHeight); return parseFloat(styles.fontSize) * 1.2; } get linesExpected() { const lines = this.linesQuery.value; if ((lines === null || lines === void 0 ? void 0 : lines.length) === 0) return null; if (lines === 'auto') { const { maxHeight } = getComputedStyle(this.$host); const parsedLineHeight = roundLineHeight(this.lineHeight); const parsedMaxHeight = parseFloat(maxHeight); const autoLines = Math.floor(parsedMaxHeight / parsedLineHeight); if (!Number.isFinite(autoLines)) return null; return autoLines > 0 ? String(autoLines) : null; } return lines !== null && lines !== void 0 ? lines : null; } connectedCallback() { super.connectedCallback(); this.onQueryChange(); } disconnectedCallback() { super.disconnectedCallback(); memoize.clear(this, 'linesQuery'); } attributeChangedCallback() { memoize.clear(this, 'linesQuery'); this.$$on(this.onQueryChange); this.onQueryChange(); } updateLines() { const lines = this.linesExpected; if (lines) { this.$host.style.setProperty('--esl-line-clamp', lines); } else { this.$host.style.removeProperty('--esl-line-clamp'); } } /** Handles query change and sets the CSS custom property for line clamping */ onQueryChange() { this.updateLines(); } /** Handles focus out event and resets scroll position */ onFocusOut(e) { const $relatedTarget = e.relatedTarget; if (!isElement($relatedTarget) || this.$host.contains($relatedTarget)) return; const $target = e.target; if (isElement($target.$target) && $target.$target.contains($relatedTarget)) return; this.$host.scrollTo(0, 0); } /** Handles resize events to update clamped state */ onResize() { const { height } = this.$host.getBoundingClientRect(); if (this.linesQuery.value === 'auto' && this.lastHeight !== height) { this.lastHeight = height; this.updateLines(); } const { clientHeight, clientWidth, scrollHeight, scrollWidth } = this.$host; const clampedY = scrollHeight - clientHeight > this.lineHeight * this.TOLERANCE; const clampedX = scrollWidth - clientWidth > 1; this.$$attr('clamped', clampedY || clampedX); } /** Observes custom broadcast 'esl:refresh' event to force refresh */ onRefresh({ target }) { if (isElement(target) && !isRelativeNode(target, this.$host)) return; this.updateLines(); } /** Handles show request events to scroll target into view */ onShowRequest(e) { const $target = e.target; $target.scrollIntoView(); } }; ESLLineClamp.is = 'esl-line-clamp'; /** Default mask for media query parsing, can be overridden by setting the static property */ ESLLineClamp.DEFAULT_MASK = ''; __decorate([ prop(0.5) ], ESLLineClamp.prototype, "TOLERANCE", void 0); __decorate([ boolAttr({ name: 'clamped', readonly: true }) ], ESLLineClamp.prototype, "clamped", void 0); __decorate([ attr({ name: ESLLineClamp.is, defaultValue: '' }) ], ESLLineClamp.prototype, "lines", void 0); __decorate([ attr({ name: ESLLineClamp.is + '-mask', defaultValue: () => ESLLineClamp.DEFAULT_MASK }) ], ESLLineClamp.prototype, "mask", void 0); __decorate([ memoize(), safe(ESLMediaRuleList.empty()) ], ESLLineClamp.prototype, "linesQuery", null); __decorate([ listen({ event: 'change', target: ($this) => $this.linesQuery }) ], ESLLineClamp.prototype, "onQueryChange", null); __decorate([ listen('focusout') ], ESLLineClamp.prototype, "onFocusOut", null); __decorate([ listen({ event: 'resize', target: ESLResizeObserverTarget.for }) ], ESLLineClamp.prototype, "onResize", null); __decorate([ listen({ event: 'esl:refresh', target: window }) ], ESLLineClamp.prototype, "onRefresh", null); __decorate([ listen('esl:show:request') ], ESLLineClamp.prototype, "onShowRequest", null); ESLLineClamp = __decorate([ ExportNs('LineClamp') ], ESLLineClamp); export { ESLLineClamp };