import { VideoOverlay, LatLngBounds, LatLngBoundsExpression, LatLngExpression } from 'leaflet'; import { registerWithParent, buildOptions, parseAttributeValue, definePropAccessors, } from '../core/utils.js'; import { createChildRegisterHandler, type ChildEntry, LeafletRegisterEvent, } from '../core/register.js'; import type { PropDef, PropTypesFromTable } from '../types/props.js'; const PROPS = { url: { kind: 'str', attr: 'url', default: '' }, bounds: { kind: 'str', attr: 'bounds', default: '' }, opacity: { kind: 'num', attr: 'opacity', default: 1.0 }, alt: { kind: 'str', attr: 'alt', default: '' }, interactive: { kind: 'bool-on', attr: 'interactive' }, crossOrigin: { kind: 'str', attr: 'cross-origin', default: '' }, loop: { kind: 'bool-on', attr: 'loop' }, autoplay: { kind: 'bool-on', attr: 'autoplay' }, muted: { kind: 'bool-on', attr: 'muted' }, playsInline: { kind: 'bool-on', attr: 'playsinline' }, } satisfies Record; const PROP_BY_ATTR = new Map( Object.entries(PROPS).map(([name, spec]) => [spec.attr, name]), ); type PropTypes = PropTypesFromTable; const TypedBase = HTMLElement as unknown as new () => HTMLElement & PropTypes; export class LeafletVideoOverlay extends TypedBase { #obj?: VideoOverlay; #children = new Map(); #childHandler?: (e: LeafletRegisterEvent) => void; static get observedAttributes() { return Object.values(PROPS).map((s) => s.attr); } static { definePropAccessors(LeafletVideoOverlay.prototype, PROPS); } connectedCallback() { const url = this.getAttribute('url') || ''; this.#obj = new VideoOverlay( url, this.#parsedBounds(), buildOptions(this, PROPS, ['url', 'bounds']), ); this.#childHandler = createChildRegisterHandler(this.#obj, this.#children); this.addEventListener('leaflet-register', this.#childHandler as EventListener); registerWithParent(this, this.#obj); } disconnectedCallback() { if (this.#childHandler) this.removeEventListener('leaflet-register', this.#childHandler as EventListener); this.#children.clear(); this.#obj?.remove(); this.#obj = undefined; } attributeChangedCallback(name: string, _old: string | null, val: string | null) { if (!this.#obj) return; if (name === 'url') { if (val) this.#obj.setUrl(val); } else if (name === 'bounds') { this.#obj.setBounds(new LatLngBounds(this.#parsedBounds() as LatLngExpression[])); } else { const propName = PROP_BY_ATTR.get(name); if (!propName) return; const setter = `set${propName.charAt(0).toUpperCase()}${propName.slice(1)}` as keyof VideoOverlay; if (typeof this.#obj[setter] === 'function') { (this.#obj[setter] as (v: unknown) => void)(parseAttributeValue(val)); } } } #parsedBounds(): LatLngBoundsExpression { const raw = this.getAttribute('bounds'); return raw ? (JSON.parse(raw) as LatLngBoundsExpression) : []; } getElement(): HTMLVideoElement | undefined { return this.#obj?.getElement(); } } customElements.define('leaflet-video-overlay', LeafletVideoOverlay);