// Copyright (c) 2019 Florian Klampfer // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . import { Observable, of } from "rxjs"; // HACK: Temporary MS Edge fix // TODO: Move rx-element into separate file or module export { getScrollHeight, getScrollLeft, getScrollTop, } from "@hydecorp/component/lib/util"; export { fromMediaQuery, fetchRx } from "@hydecorp/component/lib/creators"; export { subscribeWhen, filterWhen } from "@hydecorp/component/lib/operators"; export { createIntersectionObservable } from "@hydecorp/component/lib/observers"; const style = getComputedStyle(document.documentElement); export const BREAK_POINT_3 = `(min-width: ${style.getPropertyValue("--break-point-3")})`; export const BREAK_POINT_DYNAMIC = `(min-width: ${style.getPropertyValue("--break-point-dynamic")})`; export const CONTENT_WIDTH_5 = parseFloat( style.getPropertyValue("--content-width-5"), ); export const CONTENT_MARGIN_5 = parseFloat( style.getPropertyValue("--content-margin-5"), ); export const DRAWER_WIDTH = parseFloat( style.getPropertyValue("--sidebar-width"), ); export const HALF_CONTENT = parseFloat( style.getPropertyValue("--half-content"), ); // Check the user agent for Safari and iOS Safari, to give them some special treatment... const ua = navigator.userAgent.toLowerCase(); export const isSafari = ua.indexOf("safari") > 0 && ua.indexOf("chrome") < 0; export const isMobile = ua.indexOf("mobile") > 0; export const isMobileSafari = isSafari && isMobile; export const isUCBrowser = ua.indexOf("ucbrowser") > 0; export const isFirefox = ua.indexOf("firefox") > 0; export const isFirefoxIOS = ua.indexOf("fxios") > 0 && ua.indexOf("safari") > 0; export const hasCSSOM = "attributeStyleMap" in Element.prototype && "CSS" in window && CSS.number; export const webComponentsReady = new Promise((res) => { if ("customElements" in window) res(true); else document.addEventListener("WebComponentsReady", res); }); // FIXME: Replace with something more robust!? export const stylesheetReady = new Promise(function checkCSS( res, rej, retries = 30, ) { const drawerEl = document.querySelector("hy-drawer"); if (!drawerEl) res(true); else if (getComputedStyle(drawerEl).getPropertyValue("--hy-drawer-width")) res(true); else if (retries <= 0) rej(Error("Stylesheet not loaded within 10 seconds")); else setTimeout(() => checkCSS(res, rej, retries - 1), 1000 / 3); }); export const once = (el, eventName) => new Promise((res) => el.addEventListener(eventName, res, { once: true })); export const timeout = (t) => new Promise((res) => setTimeout(res, t)); // Takes an array of Modernizr feature tests and makes sure they all pass. export function hasFeatures(features) { if (!window.Modernizr) return true; return [...features].every((feature) => { const hasFeature = window.Modernizr[feature]; if (!hasFeature && process.env.DEBUG) console.warn(`Feature '${feature}' missing!`); return hasFeature; }); } // Some functions to hide and show content. export function show() { this.style.display = "block"; this.style.visibility = "visible"; } export function hide() { this.style.display = "none"; this.style.visibility = "hidden"; } export function unshow() { this.style.display = ""; this.style.visibility = ""; } export const unhide = unshow; // Same as `el.innerHTML = ''`, but not quite so hacky. export function empty() { while (this?.firstChild) this.removeChild(this.firstChild); } /** * An observable wrapper for the WebAnimations API. * Will return an observable that emits once when the animation finishes. * @param {HTMLElement|null} el * @param {AnimationKeyFrame | AnimationKeyFrame[] | null} effect * @param {number|AnimationEffectTiming} timing * @returns {Observable} */ export function animate(el, effect, timing) { if (!el) return of(new CustomEvent("finish")); return Observable.create((observer) => { const anim = el.animate(effect, timing); anim.addEventListener("finish", (e) => { observer.next(e); requestAnimationFrame(() => { requestAnimationFrame(() => observer.complete()); }); }); return () => { if (anim.playState !== "finished") anim.cancel(); }; }); } /** * @param {string} templateId * @returns {HTMLElement|null} */ export function importTemplate(templateId) { const template = document.getElementById(templateId); return template && document.importNode(template.content, true); } export const body = document.body || document.documentElement; export const rem = (units) => units * parseFloat(getComputedStyle(body).fontSize); export const getViewWidth = () => window.innerWidth || body.clientWidth; export const getViewHeight = () => window.innerHeight || body.clientHeight; /** * @template Q * @template S * @param {Worker} worker * @param {Q} message * @returns {Promise} */ export function postMessage(worker, message) { return new Promise((resolve, reject) => { const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = (event) => { if (event.data.error) { reject(event.data.error); } else { resolve(event.data); } }; worker.postMessage(message, [messageChannel.port2]); }); } const promisifyLoad = (loadFn) => (href) => new Promise((r) => loadFn(href).addEventListener("load", r)); /** @type {(href: string) => Promise} */ export const loadJS = promisifyLoad(window.loadJS); /** @type {(href: string) => Promise} */ export const loadCSS = promisifyLoad(window.loadCSS); /** * @param {ArrayLike} els * @param {IntersectionObserverInit} [options] * @returns {Promise} */ export function intersectOnce(els, options) { return new Promise((res) => { const io = new IntersectionObserver((entries) => { if (entries.some((x) => x.isIntersecting)) { els.forEach((el) => io.unobserve(el)); res(entries.find((x) => x.isIntersecting)); } }, options); els.forEach((el) => io.observe(el)); }); }