{"version":3,"file":"video-cards-section.min.js","mappings":"wZAkBA,MAAMA,oBAAoBC,YAChBC,aAAeC,KAAKC,QAAQC,KAAKF,MACjCG,sBAAwBH,KAAKI,iBAAiBF,KAAKF,MACnDK,YAAcL,KAAKM,OAAOJ,KAAKF,MAC/BO,iBAAmBP,KAAKQ,YAAYN,KAAKF,MAE1C,iBAAAS,GAELT,KAAKU,uBAILV,KAAKW,iBAAiB,OAAQX,KAAKK,aAAa,GAChDL,KAAKW,iBAAiB,QAASX,KAAKD,cAAc,GAIlDa,SAASD,iBAAiB,wBAAyBX,KAAKG,uBAExD,MAAMU,EAASb,KAAKc,WAAWC,cAAc,UAC7CF,EAAOF,iBAAiB,QAASX,KAAKO,kBAEtC,MAAMS,EAAQhB,KAAKiB,QAAQC,WACvBF,GACFH,EAAOM,aAAa,aAAc,QAAQH,UAE9C,CAEO,oBAAAI,GACLpB,KAAKqB,oBAAoB,OAAQrB,KAAKK,aAAa,GACnDL,KAAKqB,oBAAoB,QAASrB,KAAKD,cAAc,GACrDa,SAASS,oBAAoB,wBAAyBrB,KAAKG,uBAE5CH,KAAKc,WAAWC,cAAc,UACtCM,oBAAoB,QAASrB,KAAKO,iBAC3C,CAEQ,oBAAAG,GACDV,KAAKc,aACRd,KAAKsB,aAAa,CAAEC,KAAM,SAC1BvB,KAAKc,WAAWU,UAAY,gzBAEhC,CAEQ,WAAAhB,CAAYiB,ICvCfC,eAAoBC,GACzB,OAAOA,EAAMC,MACf,EDuCIA,CADc5B,KAAKe,cAAc,UACrBc,MAAMC,QAAQC,MAC5B,CAUQ,MAAAzB,CAAOmB,GACb,MAAME,EAAQ3B,KAAKe,cAAc,SAC3BF,EAASb,KAAKc,WAAWC,cAAc,UAEvCiB,EAAUhC,KAAKc,WAAWmB,gBAAkBpB,EAClDc,EAAMO,UAAW,EACjBrB,EAAOsB,QAAS,EACZH,GACFL,EAAMS,QAGR,MAAMC,EAAQ,IAAIC,MAAM,wBAAyB,CAAEC,SAAS,IAC5DvC,KAAKwC,cAAcH,EACrB,CAMQ,OAAApC,CAAQwB,GACd,MAAME,EAAQ3B,KAAKe,cAAc,SAC3BF,EAASb,KAAKc,WAAWC,cAAc,UAEvCiB,EAAUpB,SAASqB,gBAAkBN,EAC3CA,EAAMO,UAAW,EACjBrB,EAAOsB,QAAS,EACZH,GACFnB,EAAOuB,OAEX,CAKQ,gBAAAhC,CAAiBiC,GACvB,GAAIA,EAAMI,SAAWzC,KAAM,CACXA,KAAKe,cAAc,SAC3B2B,OACR,CACF,EAgBGC,eAAeC,IAAI,iBACtBD,eAAeE,OAAO,eAAgBhD","sources":["webpack://lange/./sections/video-cards/video-player/index.ts","webpack://lange/./lib/video.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { play } from '../../../lib/video.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Wraps a video with sound that the visitor starts themselves. Before the first play, and again\n * once the video ends, it shows the play button from the design over the middle and no other\n * controls, and a click anywhere on the video starts it. From the first play on, the play button\n * hides and the browser's own controls take over for sound, volume, captions, and seeking. They\n * stay up while the video is paused, so the visitor can still seek or turn on captions.\n *\n * The video comes in as child content. The host's video title data attribute names the play button\n * for screen readers, so several players on one page can be told apart.\n *\n * Only one player plays at a time. A player that starts dispatches a video-pause-requested event,\n * and every other player pauses its own video when it hears one.\n */\nclass VideoPlayer extends HTMLElement implements CustomElement {\n  private onEndedBound = this.onEnded.bind(this);\n  private onPauseRequestedBound = this.onPauseRequested.bind(this);\n  private onPlayBound = this.onPlay.bind(this);\n  private onPlayClickBound = this.onPlayClick.bind(this);\n\n  public connectedCallback() {\n    // The bundle is deferred, so the video inside is parsed by the time the element upgrades.\n    this.initializeShadowRoot();\n\n    // Play and ended do not bubble, so we listen in the capture phase to hear them from the video\n    // inside the element.\n    this.addEventListener('play', this.onPlayBound, true);\n    this.addEventListener('ended', this.onEndedBound, true);\n\n    // The players sit in separate cards and know nothing about each other, so the document is the\n    // one place every one of them can hear the request from.\n    document.addEventListener('video-pause-requested', this.onPauseRequestedBound);\n\n    const button = this.shadowRoot.querySelector('button');\n    button.addEventListener('click', this.onPlayClickBound);\n\n    const title = this.dataset.videoTitle;\n    if (title) {\n      button.setAttribute('aria-label', `Play ${title} video`);\n    }\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('play', this.onPlayBound, true);\n    this.removeEventListener('ended', this.onEndedBound, true);\n    document.removeEventListener('video-pause-requested', this.onPauseRequestedBound);\n\n    const button = this.shadowRoot.querySelector('button');\n    button.removeEventListener('click', this.onPlayClickBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private onPlayClick(_event: PointerEvent) {\n    const video = this.querySelector('video');\n    play(video).catch(console.debug);\n  }\n\n  /**\n   * A video can start from somewhere other than our button, for example the browser's own controls\n   * or a media key, so we go by the play event rather than by the button.\n   *\n   * The play button hides here, so focus on it would drop back to the page. We move it to the video\n   * instead. We have not checked that every browser lets a video with controls take focus, and\n   * where one does not, that focus call does nothing.\n   */\n  private onPlay(_event: Event) {\n    const video = this.querySelector('video');\n    const button = this.shadowRoot.querySelector('button');\n\n    const focused = this.shadowRoot.activeElement === button;\n    video.controls = true;\n    button.hidden = true;\n    if (focused) {\n      video.focus();\n    }\n\n    const event = new Event('video-pause-requested', { bubbles: true });\n    this.dispatchEvent(event);\n  }\n\n  /**\n   * A finished video goes back to how it looked before the first play. The controls hide here, so\n   * focus on the video would drop back to the page, and we move it to the play button instead.\n   */\n  private onEnded(_event: Event) {\n    const video = this.querySelector('video');\n    const button = this.shadowRoot.querySelector('button');\n\n    const focused = document.activeElement === video;\n    video.controls = false;\n    button.hidden = false;\n    if (focused) {\n      button.focus();\n    }\n  }\n\n  /**\n   * The player that asked dispatches from itself, so it skips its own request.\n   */\n  private onPauseRequested(event: Event) {\n    if (event.target !== this) {\n      const video = this.querySelector('video');\n      video.pause();\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'video-player': VideoPlayer;\n  }\n\n  interface GlobalEventHandlersEventMap {\n    /**\n     * A video player started playing and asks every other player to pause.\n     */\n    'video-pause-requested': Event;\n  }\n}\n\nif (!customElements.get('video-player')) {\n  customElements.define('video-player', VideoPlayer);\n}\n","/**\n * Try to play a video, and always report the outcome as a promise.\n *\n * Do not inline this function. Playing a video is inconsistent across browsers in several ways, and\n * wrapping the call in an async function is what smooths all of them over at once.\n *\n * Some browsers have no `play` function on the element at all, so the call throws a TypeError. Some\n * have one that returns undefined instead of a promise, which is how `play` worked before the spec\n * gained a return value. Some return a promise that rejects later, which is the modern behavior and\n * is how a browser reports that it refused to autoplay. And some throw synchronously, right out of\n * the call, rather than rejecting.\n *\n * An async function turns all four into the same thing. Whatever happens inside it, the caller gets\n * a promise back, so a synchronous throw arrives as a rejection and a missing or non-promise `play`\n * still produces a promise that resolves. That means one `catch` on the call site covers every\n * failure mode, and the caller never has to check whether it got a promise before attaching a\n * handler.\n *\n * Nothing here is actionable, because a refusal is the browser enforcing its own autoplay policy.\n * Restrictive in-app browsers and low power mode refuse even after a user gesture, and interrupted\n * or unsupported sources reject too. The video simply stays paused, so callers should keep a debug\n * breadcrumb and never warn.\n */\nexport async function play(video: HTMLVideoElement) {\n  return video.play();\n}\n"],"names":["VideoPlayer","HTMLElement","onEndedBound","this","onEnded","bind","onPauseRequestedBound","onPauseRequested","onPlayBound","onPlay","onPlayClickBound","onPlayClick","connectedCallback","initializeShadowRoot","addEventListener","document","button","shadowRoot","querySelector","title","dataset","videoTitle","setAttribute","disconnectedCallback","removeEventListener","attachShadow","mode","innerHTML","_event","async","video","play","catch","console","debug","focused","activeElement","controls","hidden","focus","event","Event","bubbles","dispatchEvent","target","pause","customElements","get","define"],"sourceRoot":""}