Skip to content

Instantly share code, notes, and snippets.

@Neophen
Last active March 30, 2024 09:17
Show Gist options
  • Save Neophen/27a694155c430acfb8e9be3433887a47 to your computer and use it in GitHub Desktop.
Save Neophen/27a694155c430acfb8e9be3433887a47 to your computer and use it in GitHub Desktop.
LiveView Hooks vs WebComponents
export class MarkoNativeShare extends HTMLElement {
pictureElement?: HTMLPictureElement | null
images?: HTMLImageElement[] | null
loaded = false
connectedCallback() {
this.pictureElement = this.querySelector('picture')
if (!this.pictureElement) {
throw new Error('<x-image>: requires a <picture> element')
}
this.images = Array.from(this.pictureElement.querySelectorAll('img'))
if (this.images.length === 0) {
throw new Error('<x-image>: requires an <img> element inside <picture>')
}
const complete = this.images.some((image) => image.complete)
if (complete) {
this.onLoad()
} else {
this.images.forEach((image) => {
image.addEventListener('load', this.onLoad)
})
}
}
disconnectedCallback() {
this.onLoad()
}
onLoad = () => {
if (this.loaded || !this.pictureElement) return
this.loaded = true
this.images?.forEach((image) => image.removeEventListener('load', this.onLoad))
this.pictureElement?.style.setProperty('--preview-image', 'none')
this.pictureElement = null
this.images = null
}
}
customElements.define('marko-image', MarkoNativeShare)
export class XCounter extends HTMLElement {
connectedCallback() {
this.inputElement = this.querySelector('input') ?? this.querySelector('textarea')
if (!this.inputElement) {
throw new Error('<x-counter>: requires an <input> or a <textarea> element')
}
this.counterElement = this.querySelector('[x-counter-value]')
if (!this.counterElement) {
throw new Error('<x-counter>: requires an element with the [x-counter-value] attribute')
}
this.inputElement.addEventListener('input', this.onInput)
this.onInput()
}
disconnectedCallback() {
this.inputElement?.removeEventListener('input', this.onInput)
}
onInput = () => {
this.counterElement.textContent = this.inputElement?.value?.length ?? 0
}
}
customElements.define('x-counter', XCounter)
import { ViewHook } from 'phoenix_live_view'
const XImage: Partial<ViewHook> & {
pictureElement?: HTMLPictureElement
image?: HTMLImageElement | null
init: () => void
onLoad: () => void
} = {
mounted() {
this.init()
},
updated() {
this.destroyed?.()
this.init()
},
destroyed() {
if (!this.image) return
this.image.removeEventListener('load', this.onLoad)
this.image = undefined
this.pictureElement = undefined
},
// ___________________________________________________________________________________________________________________
init() {
this.pictureElement = this.el
if (!(this.pictureElement instanceof HTMLPictureElement)) return
this.onLoad = this.onLoad.bind(this)
this.image = this.pictureElement.querySelector('img')
if (!this.image) return
if (this.image.complete) {
this.onLoad()
return
}
this.image.addEventListener('load', this.onLoad)
},
onLoad() {
if (!this.pictureElement) return
if (this.image) {
this.image.removeEventListener('load', this.onLoad)
}
this.pictureElement.style.setProperty('--preview-image', 'none')
}
}
export default XImage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment