UNPKG

msc-any-pip

Version:

Imaging what if we can let anything Picture-in-Picture (not only <video />) ?! Here comes <msc-any-pip /> to let it dream comes true. <msc-any-pip /> apply Document Picture-in-Picture API to given elements have Picture-in-Picture feature.

67 lines (51 loc) 8.03 kB
import{_wcl}from"./common-lib.js";import{_wccss}from"./common-css.js";const defaults={winwidth:0,winheight:0,preferinitialwindowplacement:!1,disallowreturntoopener:!1,dismisswhenbacktoopener:!1},booleanAttrs=["disallowreturntoopener","preferinitialwindowplacement","dismisswhenbacktoopener"],objectAttrs=[],custumEvents={piping:"msc-any-pip-piping",pipingEnd:"msc-any-pip-pip-end"},template=document.createElement("template");if(template.innerHTML=` <style> ${_wccss} :host { --block-size: auto; position: relative; display: block; } :host(.align-container-size) { --block-size: 100%; block-size: 100%; } .main { --icon-pip: path('M2.4,11.4v-2H6L1.7,5.1l1.4-1.4L7.4,8V4.4h2v7H2.4z M4.4,20.4c-0.5,0-1-0.2-1.4-0.6c-0.4-0.4-0.6-0.9-0.6-1.4v-5h2v5h8v2 H4.4z M20.4,13.4v-7h-9v-2h9c0.5,0,1,0.2,1.4,0.6c0.4,0.4,0.6,0.9,0.6,1.4v7H20.4z M14.4,20.4v-5h8v5H14.4z'); --piping-text: var(--msc-any-pip-piping-text, 'Playing in Picture-in-Picture.'); --piping-color: var(--msc-any-pip-piping-color, #39e75f); --piping-font-size: var(--msc-any-pip-piping-font-size, 16px); --btn-size: 40px; --btn-icon-color: rgba(255 255 255); --btn-z-index: var(--msc-any-pip-button-z-index, 1); block-size: var(--block-size); } .btn-pip { --btn-scale-normal: 1; --btn-scale-active: 0; --btn-scale: var(--btn-scale-normal); --btn-opacity-normal: .5; --btn-opacity-active: .8; --btn-opacity: var(--btn-opacity-normal); --btn-background-color: rgba(0 0 0/var(--btn-opacity)); } .main--active{} .main--active::after{position:absolute;inset-inline-start:50%;inset-block-start:50%;transform:translate(-50%,-50%);inline-size:80%;text-align:center;text-wrap:balance;line-height:1.3;content:var(--piping-text);color:var(--piping-color);font-size:var(--piping-font-size);} :is(.main--active,.main--active--not-support) .btn-pip{--btn-scale:var(--btn-scale-active);} .main{position:relative;inline-size:100%;outline:0 none;} .btn-pip{position:absolute;inset-inline-end:8px;inset-block-start:8px;inline-size:var(--btn-size);aspect-ratio:1/1;font-size:0;color:transparent;background:var(--btn-background-color);border-radius:var(--btn-size);padding:0;margin:0;appearance:none;border:0 none;box-shadow:none;display:grid;place-content:center;outline:0 none;overflow:hidden;scale:var(--btn-scale);transition:background-color 200ms ease-in-out,scale 150ms linear;z-index:var(--btn-z-index);} .btn-pip::before{content:'';inline-size:24px;aspect-ratio:1/1;background-color:var(--btn-icon-color);display:block;clip-path:var(--icon-pip);} .btn-pip:active{scale:.8;transition-duration:0ms;} .btn-pip:focus-visible{--btn-opacity:var(--btn-opacity-active);} .main__slot{position:relative;inline-size:100%;block-size:var(--block-size);display:block;} .main__slot::slotted(*){max-inline-size:100%;} .main__slot::slotted(.msc-any-pip-cloned){visibility:hidden;opacity:0;pointer-events:none;} @media (hover: hover) { .btn-pip:hover{--btn-opacity:var(--btn-opacity-active);} } </style> <div class="main" ontouchstart="" tabindex="0"> <slot class="main__slot"></slot> <button class="btn-pip" type="button" title="Picture in Picture" aria-label="Picture in Picture">pip</button> </div> `,CSS?.registerProperty)try{CSS.registerProperty({name:"--msc-any-pip-piping-font-size",syntax:"<length>",inherits:!0,initialValue:"16px"}),CSS.registerProperty({name:"--msc-any-pip-piping-color",syntax:"<color>",inherits:!0,initialValue:"#39e75f"}),CSS.registerProperty({name:"--msc-any-pip-button-z-index",syntax:"<number>",inherits:!0,initialValue:"1"})}catch(t){console.warn("msc-any-pip: "+t.message)}class MscAnyPip extends HTMLElement{#data;#nodes;#config;constructor(t){super(),this.attachShadow({mode:"open",delegatesFocus:!0}),this.shadowRoot.appendChild(template.content.cloneNode(!0)),this.#data={controller:""},this.#nodes={styleSheet:this.shadowRoot.querySelector("style"),main:this.shadowRoot.querySelector(".main"),trigger:this.shadowRoot.querySelector(".btn-pip")},this.#config={...defaults,...t},this._onClick=this._onClick.bind(this)}async connectedCallback(){var{config:t,error:e}=await _wcl.getWCConfig(this),{main:i,trigger:n}=this.#nodes;e?(console.warn(_wcl.classToTagName(this.constructor.name)+": "+e),this.remove()):(this.#config={...this.#config,...t},Object.keys(defaults).forEach(t=>this.#upgradeProperty(t)),Array.from(this.querySelectorAll(':scope > script[type="application/json"]')).forEach(t=>t.remove()),window?.documentPictureInPicture||i.classList.add("main--active--not-support"),this.#data.controller=new AbortController,e=this.#data.controller.signal,n.addEventListener("click",this._onClick,{signal:e}))}disconnectedCallback(){this.#data?.controller&&this.#data.controller.abort()}#format(t,e,i){if(null!==i)switch(t){case"winwidth":case"winheight":this.#config[t]=_wcl.isNumeric(i)?parseFloat(i):defaults[t];break;case"disallowreturntoopener":case"preferinitialwindowplacement":case"dismisswhenbacktoopener":this.#config[t]=!0}else booleanAttrs.includes(t)?this.#config[t]=!1:this.#config[t]=defaults[t]}attributeChangedCallback(t,e,i){MscAnyPip.observedAttributes.includes(t)&&this.#format(t,e,i)}static get observedAttributes(){return Object.keys(defaults)}static get supportedEvents(){return Object.keys(custumEvents).map(t=>custumEvents[t])}#upgradeProperty(t){let e;MscAnyPip.observedAttributes.includes(t)&&(Object.prototype.hasOwnProperty.call(this,t)?(e=this[t],delete this[t]):e=booleanAttrs.includes(t)?!(!this.hasAttribute(t)&&!this.#config[t]):objectAttrs.includes(t)?this.hasAttribute(t)?this.getAttribute(t):JSON.stringify(this.#config[t]):this.hasAttribute(t)?this.getAttribute(t):this.#config[t],this[t]=e)}set winwidth(t){t?this.setAttribute("winwidth",t):this.removeAttribute("winwidth")}get winwidth(){var t=this.getBoundingClientRect()["width"];return 0!==this.#config.winwidth?this.#config.winwidth:t}set winheight(t){t?this.setAttribute("winheight",t):this.removeAttribute("winheight")}get winheight(){var t=this.getBoundingClientRect()["height"];return 0!==this.#config.winheight?this.#config.winheight:t}set preferinitialwindowplacement(t){this.toggleAttribute("preferinitialwindowplacement",Boolean(t))}get preferinitialwindowplacement(){return this.#config.preferinitialwindowplacement}set disallowreturntoopener(t){this.toggleAttribute("disallowreturntoopener",Boolean(t))}get disallowreturntoopener(){return this.#config.disallowreturntoopener}set dismisswhenbacktoopener(t){this.toggleAttribute("dismisswhenbacktoopener",Boolean(t))}get dismisswhenbacktoopener(){return this.#config.dismisswhenbacktoopener}#fireEvent(t,e){this.dispatchEvent(new CustomEvent(t,{bubbles:!0,composed:!0,...e&&{detail:e}}))}async _onClick(){const t=[...this.children];if(t.length&&window.documentPictureInPicture){const e=t.map(t=>{t=t.cloneNode(!0);return t.classList.add("msc-any-pip-cloned"),t}),i=await window.documentPictureInPicture.requestWindow({width:this.winwidth,height:this.winheight,disallowReturnToOpener:this.disallowreturntoopener,preferInitialWindowPlacement:this.preferinitialwindowplacement});_wcl.cloneStyleSheetsToDocument(i.document),t.forEach(t=>i.document.body.append(t)),e.forEach(t=>this.append(t)),this.#nodes.main.classList.toggle("main--active",!0),this.#fireEvent(custumEvents.piping),i.addEventListener("pagehide",()=>{e.forEach(t=>t.remove()),t.forEach(t=>this.append(t)),this.#nodes.main.classList.toggle("main--active",!1),this.#fireEvent(custumEvents.pipingEnd)},{once:!0})}}requestPictureInPicture(){this.#nodes.trigger.click()}}const S=_wcl.supports(),T=_wcl.classToTagName("MscAnyPip");S.customElements&&S.shadowDOM&&S.template&&!window.customElements.get(T)&&window.customElements.define(_wcl.classToTagName("MscAnyPip"),MscAnyPip),window.customElements.whenDefined("msc-any-pip").then(()=>{document.addEventListener("visibilitychange",()=>{document.hidden||document.querySelector("msc-any-pip[dismisswhenbacktoopener] .msc-any-pip-cloned")&&window.documentPictureInPicture?.window&&window.documentPictureInPicture.window.close()})});export{MscAnyPip};