← back to Dw Theme Boost Fix
assets/is-land.min.js
196 lines
// MIT License
// Copyright (c) 2022–2023 Zach Leatherman @zachleat
// https://github.com/11ty/is-land
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
class Island extends HTMLElement {
static tagName = 'is-land'
static prefix = 'is-land--'
static attr = { template: 'data-island', ready: 'ready', defer: 'defer-hydration' }
static onceCache = new Map()
static onReady = new Map()
static fallback = {
':not(is-land,:defined,[defer-hydration])': (readyPromise, node, prefix) => {
let cloned = document.createElement(prefix + node.localName)
for (let attr of node.getAttributeNames()) cloned.setAttribute(attr, node.getAttribute(attr))
let shadowroot = node.shadowRoot
if (!shadowroot) {
let tmpl = node.querySelector(':scope > template:is([shadowrootmode], [shadowroot])')
if (tmpl) {
let mode = tmpl.getAttribute('shadowrootmode') || tmpl.getAttribute('shadowroot') || 'closed'
;(shadowroot = node.attachShadow({ mode: mode })), shadowroot.appendChild(tmpl.content.cloneNode(!0))
}
}
return (
shadowroot && cloned.attachShadow({ mode: shadowroot.mode }).append(...shadowroot.childNodes),
cloned.append(...node.childNodes),
node.replaceWith(cloned),
readyPromise.then(() => {
cloned.shadowRoot && node.shadowRoot.append(...cloned.shadowRoot.childNodes),
node.append(...cloned.childNodes),
cloned.replaceWith(node)
})
)
}
}
constructor() {
super(),
(this.ready = new Promise((resolve) => {
this.readyResolve = resolve
}))
}
static getParents(el, stopAt = !1) {
let nodes = []
for (; el; ) {
if (el.matches && el.matches(Island.tagName)) {
if (stopAt && el === stopAt) break
Conditions.hasConditions(el) && nodes.push(el)
}
el = el.parentNode
}
return nodes
}
static async ready(el, parents) {
if ((parents || (parents = Island.getParents(el)), 0 === parents.length)) return
let imports = await Promise.all(parents.map((p) => p.wait()))
return imports.length ? imports[0] : void 0
}
forceFallback() {
window.Island && Object.assign(Island.fallback, window.Island.fallback)
for (let selector in Island.fallback) {
let components = Array.from(this.querySelectorAll(selector)).reverse()
for (let node of components) {
if (!node.isConnected) continue
let parents = Island.getParents(node)
if (1 === parents.length) {
let p = Island.ready(node, parents)
Island.fallback[selector](p, node, Island.prefix)
}
}
}
}
wait() {
return this.ready
}
async connectedCallback() {
Conditions.hasConditions(this) && this.forceFallback(), await this.hydrate()
}
getTemplates() {
return this.querySelectorAll(`template[${Island.attr.template}]`)
}
replaceTemplates(templates) {
for (let node of templates) {
if (Island.getParents(node, this).length > 0) continue
let value = node.getAttribute(Island.attr.template)
if ('replace' === value) {
let children = Array.from(this.childNodes)
for (let child of children) this.removeChild(child)
this.appendChild(node.content)
break
}
{
let html = node.innerHTML
if ('once' === value && html) {
if (Island.onceCache.has(html)) return void node.remove()
Island.onceCache.set(html, !0)
}
node.replaceWith(node.content)
}
}
}
async hydrate() {
let conditions = []
this.parentNode && conditions.push(Island.ready(this.parentNode))
let attrs = Conditions.getConditions(this)
for (let condition in attrs)
Conditions.map[condition] && conditions.push(Conditions.map[condition](attrs[condition], this))
await Promise.all(conditions), this.replaceTemplates(this.getTemplates())
for (let fn of Island.onReady.values()) await fn.call(this, Island)
this.readyResolve(),
this.setAttribute(Island.attr.ready, ''),
this.querySelectorAll(`[${Island.attr.defer}]`).forEach((node) => node.removeAttribute(Island.attr.defer))
}
}
class Conditions {
static map = {
visible: Conditions.visible,
idle: Conditions.idle,
interaction: Conditions.interaction,
media: Conditions.media,
'save-data': Conditions.saveData
}
static hasConditions(node) {
return Object.keys(Conditions.getConditions(node)).length > 0
}
static getConditions(node) {
let map = {}
for (let key of Object.keys(Conditions.map))
node.hasAttribute(`on:${key}`) && (map[key] = node.getAttribute(`on:${key}`))
return map
}
static visible(noop, el) {
if ('IntersectionObserver' in window)
return new Promise((resolve) => {
let observer = new IntersectionObserver((entries) => {
let [entry] = entries
entry.isIntersecting && (observer.unobserve(entry.target), resolve())
})
observer.observe(el)
})
}
static idle() {
let onload = new Promise((resolve) => {
'complete' !== document.readyState ? window.addEventListener('load', () => resolve(), { once: !0 }) : resolve()
})
return 'requestIdleCallback' in window
? Promise.all([
new Promise((resolve) => {
requestIdleCallback(() => {
resolve()
})
}),
onload
])
: onload
}
static interaction(eventOverrides, el) {
let events = ['click', 'touchstart']
return (
eventOverrides && (events = (eventOverrides || '').split(',').map((entry) => entry.trim())),
new Promise((resolve) => {
function resolveFn(e) {
resolve()
for (let name of events) el.removeEventListener(name, resolveFn)
}
for (let name of events) el.addEventListener(name, resolveFn, { once: !0 })
})
)
}
static media(query) {
let mm = { matches: !0 }
if ((query && 'matchMedia' in window && (mm = window.matchMedia(query)), !mm.matches))
return new Promise((resolve) => {
mm.addListener((e) => {
e.matches && resolve()
})
})
}
static saveData(expects) {
if ('connection' in navigator && navigator.connection.saveData !== ('false' !== expects))
return new Promise(() => {})
}
}
'customElements' in window && (window.customElements.define(Island.tagName, Island), (window.Island = Island))
export { Island, Island as component }
export const ready = Island.ready