import { LitElement, html, css } from "lit"; import { customElement, property } from "lit/decorators.js"; import "../utils/resize-observer"; import { BreakpointSize } from "../config"; export const MediaSizes = Object.values(BreakpointSize); declare global { interface HTMLElement { startResizeListener(): void; stopResizeListener(): void; } } const _window = (typeof window !== "undefined" ? window : {}) as any; @customElement("flex-media") export class FlexMedia extends LitElement { _targetEl: HTMLElement | Window | null = null; _resizeListener: any = null; static override styles = css` :host { box-sizing: border-box; display: block; } `; override connectedCallback() { super.connectedCallback(); this._targetEl = this.getTargetElement(); if (this._targetEl instanceof Window) { this._resizeListener = (event: any) => { this.onResize((event.target as Window).innerWidth); }; _window?.addEventListener("resize", this._resizeListener); this.onResize(_window?.innerWidth); } else { if (this._targetEl) { this._targetEl.startResizeListener(); } this._resizeListener = (event: any) => { this.onResize((event.detail as DOMRectReadOnly).width); }; if (this._targetEl) { this._targetEl.addEventListener("resize", this._resizeListener); } } } onResize(width: number) { this.setMedia(width); } getTargetElement(): HTMLElement | null | Window { if (this.target === "window") return _window; if (this.target) { return document.querySelector(this.target) as HTMLElement; } return this; } @property({ type: String, reflect: true }) breakpoints: string | null = null; @property({ type: String, reflect: true }) target: string | null = null; @property({ type: String, reflect: true }) sizes: string | null = null; override disconnectedCallback() { super.disconnectedCallback(); if (this._targetEl && this._targetEl instanceof HTMLElement) { this._targetEl.stopResizeListener(); } else if (this._resizeListener) { _window?.removeEventListener("resize", this._resizeListener); } } getMediaString(): Array<{ width: number; mode: string; }> | null { if (!this.breakpoints) { return null; } const regex = /^(\d+\s*,\s*)*\d+$/; if (!regex.test(this.breakpoints)) { throw new Error( "Invalid breakpoints string, must be comma separated list of numbers" ); } let availableNames = MediaSizes; const nums = this.breakpoints.split(",").map((item) => { return parseInt(item.trim()); }); nums.unshift(0); if (nums.length > availableNames.length) { console.error( "Too many breakpoints defined, max is " + (availableNames.length - 1) + " from sm to xxl" ); } let mediaString = []; for (let i = 0; i < nums.length; i++) { mediaString.push({ width: nums[i], mode: availableNames[i], }); } return mediaString; } setMedia(currentWidth: number) { let mediaString = this.getMediaString(); if (mediaString) { let sizes = []; for (let i = 0; i < mediaString.length; i++) { let { width, mode } = mediaString[i]; if (currentWidth >= width) { sizes.push(mode); } else { break; } } if (sizes.length) { let str = sizes.map((el) => `[${el}]`).join(""); this.sizes = str; } } else { this.sizes = null; } } override render() { return html``; } }